Merged PR 247: #264 Bearbeiten Detail Seite + Various Bug Fixes

Related work items: #264
This commit is contained in:
Sebastian Neumair
2020-09-02 15:34:03 +00:00
committed by Lorenz Hilpert
135 changed files with 4173 additions and 1255 deletions

View File

@@ -1,37 +1,22 @@
<div class="filter-wrapper">
<div class="filter-nav">
<button
*ngFor="let filter of value; trackBy: trackByKeyOrName; let first = first"
type="button"
(click)="selected = filter"
class="isa-btn isa-btn-block isa-btn-default isa-btn-l"
[class.customer]="module === 'Customer'"
[class.isa-mt-10]="!first"
[class.is-active]="filter === selected"
[class.isa-font-weight-emphasis]="filter !== selected"
[class.isa-font-weight-bold]="filter === selected"
>
<button *ngFor="let filter of value; trackBy: trackByKeyOrName; let first = first" type="button"
(click)="selected = filter" class="isa-btn isa-btn-block isa-btn-default-bg isa-btn-large"
[class.customer]="module === 'Customer'" [class.isa-mt-10]="!first" [class.is-active]="filter === selected"
[class.isa-font-weight-emphasis]="filter !== selected" [class.isa-font-weight-bold]="filter === selected">
<span>{{ filter.name }}</span>
<lib-icon
[name]="
<lib-icon [name]="
filter === selected
? 'Arrow_Next_Dark'
: module === 'Customer'
? 'Arrow_Next'
: 'Arrow_Next_Grey'
"
height="17px"
></lib-icon>
" height="17px"></lib-icon>
</button>
</div>
<div class="filter-content isa-ml-10" [ngSwitch]="selected?.type">
<app-select-filter
*ngSwitchCase="'select'"
[options]="selected.options"
[module]="module"
[max]="selected.max"
(optionsChanged)="emitOnChange($event)"
>
<app-select-filter *ngSwitchCase="'select'" [options]="selected.options" [module]="module" [max]="selected.max"
(optionsChanged)="emitOnChange($event)">
</app-select-filter>
</div>
</div>
</div>

View File

@@ -35,7 +35,7 @@
<button
*ngIf="canScroll"
class="scroll-btn isa-btn isa-btn-circle isa-btn-default"
class="scroll-btn isa-btn isa-btn-circle isa-btn-default-bg"
[class.up]="scrollPositionPersantage >= 20"
[class.down]="scrollPositionPersantage < 20"
[class.no-shadow]="module === 'Customer'"

View File

@@ -0,0 +1,5 @@
// start:ng42.barrel
export * from './shelf-edit-actions.component';
export * from './shelf-edit-actions.module';
// end:ng42.barrel

View File

@@ -0,0 +1,8 @@
<div class="actions">
<button (click)="cancel.emit()"
class="isa-btn isa-btn-pill isa-btn-secondary isa-btn-shadow isa-btn-red-border isa-btn-medium">
Abbrechen</button><button (click)="save.emit()"
class="isa-btn isa-btn-pill isa-btn-primary isa-btn-shadow isa-btn-medium" [disabled]="disabled">
Speichern
</button>
</div>

View File

@@ -0,0 +1,16 @@
:host {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
}
.actions {
display: flex;
justify-content: center;
align-items: center;
& > .isa-btn {
margin: 0 31px;
}
}

View File

@@ -0,0 +1,22 @@
import {
Component,
ChangeDetectionStrategy,
Output,
EventEmitter,
Input,
} from '@angular/core';
@Component({
selector: 'app-shelf-edit-actions',
templateUrl: 'shelf-edit-actions.component.html',
styleUrls: ['./shelf-edit-actions.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfEditActionsComponent {
@Input() disabled = false;
@Output() save = new EventEmitter<void>();
@Output() cancel = new EventEmitter<void>();
constructor() {}
}

View File

@@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ShelfEditActionsComponent } from './shelf-edit-actions.component';
@NgModule({
imports: [CommonModule],
exports: [ShelfEditActionsComponent],
declarations: [ShelfEditActionsComponent],
providers: [],
})
export class ShelfEditActionsModule {}

View File

@@ -0,0 +1,67 @@
<ng-container *ngIf="orderItemForm">
<app-shelf-edit-order-item-header
[src]="orderItemForm.get('ean')?.value | createImageSourceFromEan"
[title]="orderItemForm.get('title')?.value"
(expand)="expandGroup($event)"
[isOpen]="isGroupOpen"
>
</app-shelf-edit-order-item-header>
<hr class="isa-content-spacer last" />
<form [formGroup]="orderItemForm" *ngIf="isGroupOpen">
<app-ui-text-input
formControlName="quantity"
label="Menge"
suffix="x"
></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-text-input
formControlName="price"
label="Preis"
suffix="EUR"
></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-text-input
formControlName="ean"
label="ISBN/EAN"
></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-text-input
formControlName="targetBranch"
label="Zielfiliale"
></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-text-input
formControlName="supplier"
label="Lieferant"
></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-text-input
formControlName="ssc"
label="Meldenummer"
></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-select-input
*ngIf="vatLabelPipe"
formControlName="vat"
[options]="vatOptions"
[optionsPipe]="vatOptionsPipe"
[labelPipe]="{ transform: vatLabelTransform }"
[valuePipe]="valuePipe"
label="MwSt"
>
</app-ui-select-input>
<hr class="isa-content-spacer" />
<app-ui-text-input
formControlName="comment"
label="Anmerkung"
></app-ui-text-input>
</form>
</ng-container>

View File

@@ -0,0 +1,35 @@
import { ShelfEditOrderItemComponent } from '.';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CreateImageSourceFromEanPipe } from '../../pipes';
import { ShelfEditOrderItemHeaderComponent } from './header';
import { ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
fdescribe('ShelfEditOrderItemComponent', () => {
let fixture: ComponentFixture<ShelfEditOrderItemComponent>;
let component: ShelfEditOrderItemComponent;
beforeEach(async () => {
TestBed.configureTestingModule({
imports: [CommonModule, ReactiveFormsModule],
declarations: [
ShelfEditOrderItemComponent,
ShelfEditOrderItemHeaderComponent,
CreateImageSourceFromEanPipe,
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ShelfEditOrderItemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component instanceof ShelfEditOrderItemComponent).toBeTruthy();
});
});

View File

@@ -0,0 +1,53 @@
import {
Component,
ChangeDetectionStrategy,
Input,
OnInit,
SimpleChanges,
OnChanges,
ChangeDetectorRef,
SimpleChange,
} from '@angular/core';
import { FormGroup } from '@angular/forms';
import { VATDTO } from '@swagger/oms';
import { VatTypeToDisplayNamePipe } from '../../pipes/vat-type-to-display-name.pipe';
import { VatDtoToVatValuePipe } from '../../pipes/vat-dto-to-vat-value.pipe';
import { VatDtoToVatTypePipe } from '../../pipes/vat-dto-to-vat-type.pipe';
@Component({
selector: 'app-shelf-edit-order-item',
templateUrl: 'edit-order-item.component.html',
styleUrls: ['./edit-order-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfEditOrderItemComponent implements OnInit, OnChanges {
@Input() orderItemForm: FormGroup;
@Input() onlyChild: boolean;
@Input() firstChild: boolean;
@Input() vatOptions: VATDTO[];
vatLabelPipe: VatTypeToDisplayNamePipe;
vatOptionsPipe = new VatDtoToVatValuePipe();
valuePipe = new VatDtoToVatTypePipe();
isGroupOpen: boolean;
vatLabelTransform = (value) => {
return this.vatLabelPipe.transform(value, this.vatOptions);
};
constructor() {}
ngOnChanges({ vatOptions }: SimpleChanges) {
if (vatOptions.currentValue) {
this.vatLabelPipe = new VatTypeToDisplayNamePipe();
}
}
ngOnInit() {
this.isGroupOpen = this.onlyChild || this.firstChild;
}
expandGroup(shouldExpand: boolean) {
this.isGroupOpen = shouldExpand;
}
}

View File

@@ -0,0 +1,27 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { IconModule } from '@libs/ui';
import { UiTextInputModule } from '@isa-ui/text-input';
import { ShelfEditOrderItemComponent } from './edit-order-item.component';
import { ShelfEditOrderItemHeaderComponent } from './header';
import { ShelfPipesModule } from '../../pipes';
import { UiSelectInputModule } from '@isa-ui/select-input';
@NgModule({
imports: [
CommonModule,
ReactiveFormsModule,
IconModule,
UiTextInputModule,
UiSelectInputModule,
ShelfPipesModule,
],
exports: [ShelfEditOrderItemComponent, ShelfEditOrderItemHeaderComponent],
declarations: [
ShelfEditOrderItemComponent,
ShelfEditOrderItemHeaderComponent,
],
providers: [],
})
export class ShelfEditOrderItemModule {}

View File

@@ -0,0 +1,24 @@
<div class="header">
<div>
<img
*ngIf="src"
class="isa-thumbnail"
[src]="src"
alt="item.product.name"
/>
</div>
<div class="title isa-font-weight-bold">{{ title }}</div>
<div
class="collapse"
*ngIf="showCollapseArrow"
(click)="expand.emit(!isOpen)"
>
<lib-icon
class="isa-accordion-arrow"
[class.arrow-up]="!isOpen"
[class.arrow-down]="isOpen"
name="Arrow_right"
[height]="'16px'"
></lib-icon>
</div>
</div>

View File

@@ -0,0 +1,13 @@
.header {
display: flex;
align-items: center;
margin: 30px 0;
.title {
margin-left: 20px;
}
.collapse {
margin-left: auto;
}
}

View File

@@ -0,0 +1,25 @@
import {
Component,
ChangeDetectionStrategy,
Input,
Output,
EventEmitter,
} from '@angular/core';
@Component({
selector: 'app-shelf-edit-order-item-header',
templateUrl: 'edit-order-item-header.component.html',
styleUrls: ['./edit-order-item-header.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfEditOrderItemHeaderComponent {
@Input() showCollapseArrow = true;
@Input() isOpen = false;
@Input() src: string;
@Input() title: string;
@Output() expand = new EventEmitter<boolean>();
constructor() {}
}

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './edit-order-item-header.component';
// end:ng42.barrel

View File

@@ -0,0 +1,5 @@
// start:ng42.barrel
export * from './edit-order-item.component';
export * from './edit-order-item.module';
// end:ng42.barrel

View File

@@ -1,4 +1,7 @@
import { OrderItemProcessingStatusValue, EnvironmentChannel } from '@swagger/oms';
import {
OrderItemProcessingStatusValue,
EnvironmentChannel,
} from '@swagger/oms';
export interface OrderDetailsCardInput {
firstName?: string;
@@ -12,4 +15,5 @@ export interface OrderDetailsCardInput {
pickupDeadline?: Date;
compartmentCode?: string;
compartmentInfo?: string;
processingStatusDate?: Date;
}

View File

@@ -1,83 +1,173 @@
<h2 class="isa-flex isa-justify-content-space-between">
<span>{{ orderDetails?.firstName }} {{ orderDetails?.lastName }}</span>
<span>{{ orderDetails?.compartmentCode }} {{ orderDetails?.compartmentInfo }}</span>
<h2 class="isa-flex isa-justify-content-space-between">
<span>{{ orderDetails?.firstName }} {{ orderDetails?.lastName }}</span>
<span
>{{ orderDetails?.compartmentCode }}
{{ orderDetails?.compartmentInfo }}</span
>
</h2>
<div class="isa-flex isa-justify-content-space-between">
<div class="isa-flex isa-flex-direction-column">
<div class="detail">
<div class="name">Vorgang-ID</div>
<div class="value">{{ orderDetails?.orderNumber }}</div>
</div>
<div class="detail">
<div class="name">Bestelldatum</div>
<div class="value">{{ orderDetails?.orderDate | date:'dd.MM.yy | HH:mm' }} Uhr</div>
</div>
<div class="detail">
<div class="name">Bestellkanal</div>
<div class="value">{{ orderDetails?.orderChannel | environmentChannel }}</div>
</div>
<div class="isa-flex isa-flex-direction-column">
<div class="detail">
<div class="name">Vorgang-ID</div>
<div class="value">{{ orderDetails?.orderNumber }}</div>
</div>
<div class="isa-flex isa-flex-direction-column">
<div class="detail">
<div class="name">Kundennummer</div>
<div class="value isa-text-right">{{ orderDetails?.customerNumber }}</div>
</div>
<div class="detail">
<div class="name">Status</div>
<div class="value">
<button class="isa-btn isa-p-0 isa-btn-block isa-text-right" (click)="statusDropdown.toggle()">
<lib-icon class="isa-mr-8" [ngClass]="icon"
*ngIf="orderDetails?.processingStatus | processingStatus:'icon'; let icon" [name]="icon">
</lib-icon>
<strong>{{ orderDetails?.processingStatus | processingStatus }}</strong>
<lib-icon class="dp-button-icon" [class.up]="statusDropdown.visible"
[class.down]="!statusDropdown.visible" name="Arrow_right" [height]="'16px'"></lib-icon>
</button>
<app-ui-dropdown #statusDropdown [value]="orderDetails?.processingStatus"
(valueChange)="changeProcessingStatus.emit($event)">
<ng-container *ngFor="let status of processingKeys | keyvalue">
<button appUiDropdownItem class="isa-btn isa-text-left isa-p-16" [value]="status.key"
*ngIf="status.key !== orderDetails?.processingStatus">
{{ status.value }}
</button>
</ng-container>
</app-ui-dropdown>
</div>
</div>
<div class="detail" *ngIf="orderDetails?.processingStatus === 16 || orderDetails?.processingStatus === 8192">
<div class="name">vsl. Lieferdatum</div>
<div class="value">
<button class="isa-btn isa-p-0 isa-btn-block isa-text-right" (click)="uiDatepicker.toggle()">
<strong>
{{ orderDetails?.estimatedShippingDate | date:'dd.MM.yy' }}
</strong>
<lib-icon class="dp-button-icon" [class.up]="uiDatepicker.opened"
[class.down]="!uiDatepicker.opened" name="Arrow_right" [height]="'16px'"></lib-icon>
</button>
<app-ui-datepicker #uiDatepicker [right]="'-1rem'" [selected]="orderDetails?.estimatedShippingDate"
(save)="changeEstimatedDeliveryDate.emit($event); uiDatepicker.close()">
</app-ui-datepicker>
</div>
</div>
<div class="detail" *ngIf="orderDetails?.processingStatus === 128">
<div class="name">Abholfrist</div>
<div class="value">
<button class="isa-btn isa-p-0 isa-btn-block isa-text-right" (click)="deadlineDropdown.toggle()">
<strong>
{{ orderDetails?.pickupDeadline | date:'dd.MM.yy' }}
</strong>
<lib-icon class="dp-button-icon" [class.up]="deadlineDropdown.visible"
[class.down]="!deadlineDropdown.visible" name="Arrow_right" [height]="'16px'"></lib-icon>
</button>
<app-ui-dropdown #deadlineDropdown [value]="orderDetails?.pickupDeadline"
(valueChange)="changePickUpDeadline.emit($event)">
<ng-container *ngFor="let dl of pickupDeadlines | keyvalue">
<button appUiDropdownItem class="isa-btn isa-text-left isa-p-16" [value]="dl.value">
{{ dl.key }}
</button>
</ng-container>
</app-ui-dropdown>
</div>
</div>
<div class="detail">
<div class="name">Bestelldatum</div>
<div class="value">
{{ orderDetails?.orderDate | date: 'dd.MM.yy | HH:mm' }} Uhr
</div>
</div>
</div>
<div class="detail">
<div class="name">Bestellkanal</div>
<div class="value">
{{ orderDetails?.orderChannel | environmentChannel }}
</div>
</div>
</div>
<div class="isa-flex isa-flex-direction-column">
<div class="detail">
<div class="name">Kundennummer</div>
<div class="value isa-text-right">{{ orderDetails?.customerNumber }}</div>
</div>
<div class="detail">
<div class="name">Status</div>
<div class="value">
<button
class="isa-btn isa-p-0 isa-btn-block isa-text-right"
(click)="statusDropdown.toggle()"
>
<lib-icon
class="isa-mr-8"
[ngClass]="icon"
*ngIf="
orderDetails?.processingStatus | processingStatus: 'icon';
let icon
"
[name]="icon"
>
</lib-icon>
<strong>{{
orderDetails?.processingStatus | processingStatus
}}</strong>
<lib-icon
class="dp-button-icon"
[class.up]="statusDropdown.visible"
[class.down]="!statusDropdown.visible"
name="Arrow_right"
[height]="'16px'"
></lib-icon>
</button>
<app-ui-dropdown
#statusDropdown
[value]="orderDetails?.processingStatus"
(valueChange)="changeProcessingStatus.emit($event)"
>
<ng-container
*ngFor="
let status of processingKeys
| keyvalue
| processingStatusOptionsKeyValuePipe
"
>
<button
appUiDropdownItem
class="isa-btn isa-text-left isa-p-16"
[value]="status.key"
*ngIf="status.key !== orderDetails?.processingStatus"
>
{{ status?.value?.value }}
</button>
</ng-container>
</app-ui-dropdown>
</div>
</div>
<ng-container [ngSwitch]="orderDetails?.processingStatus">
<ng-container *ngSwitchCase="16">
<ng-container *ngTemplateOutlet="vslLieferdatum"></ng-container>
</ng-container>
<ng-container *ngSwitchCase="8192">
<ng-container *ngTemplateOutlet="vslLieferdatum"></ng-container>
</ng-container>
<ng-container *ngSwitchCase="128">
<ng-container *ngTemplateOutlet="abholfrist"></ng-container>
</ng-container>
<div class="detail" *ngSwitchDefault>
<div class="name">Geändert</div>
<div class="value isa-text-right">
{{ orderDetails?.processingStatusDate | date: 'dd.MM.yy | HH:mm' }}
Uhr
</div>
</div>
</ng-container>
</div>
</div>
<ng-template #vslLieferdatum>
<div class="detail">
<div class="name">vsl. Lieferdatum</div>
<div class="value">
<button
class="isa-btn isa-p-0 isa-btn-block isa-text-right"
(click)="uiDatepicker.toggle()"
>
<strong>
{{ orderDetails?.estimatedShippingDate | date: 'dd.MM.yy' }}
</strong>
<lib-icon
class="dp-button-icon"
[class.up]="uiDatepicker.opened"
[class.down]="!uiDatepicker.opened"
name="Arrow_right"
[height]="'16px'"
></lib-icon>
</button>
<app-ui-datepicker
#uiDatepicker
[right]="'-1rem'"
[min]="minDate"
[disabledDaysOfWeek]="[0]"
[selected]="orderDetails?.estimatedShippingDate"
(save)="changeEstimatedDeliveryDate.emit($event); uiDatepicker.close()"
>
</app-ui-datepicker>
</div>
</div>
</ng-template>
<ng-template #abholfrist>
<div class="detail">
<div class="name">Abholfrist</div>
<div class="value">
<button
class="isa-btn isa-p-0 isa-btn-block isa-text-right"
(click)="deadlineDropdown.toggle()"
>
<strong>
{{ orderDetails?.pickupDeadline | date: 'dd.MM.yy' }}
</strong>
<lib-icon
class="dp-button-icon"
[class.up]="deadlineDropdown.visible"
[class.down]="!deadlineDropdown.visible"
name="Arrow_right"
[height]="'16px'"
></lib-icon>
</button>
<app-ui-dropdown
#deadlineDropdown
[value]="orderDetails?.pickupDeadline"
(valueChange)="changePickUpDeadline.emit($event)"
>
<ng-container *ngFor="let dl of pickupDeadlines | keyvalue">
<button
appUiDropdownItem
class="isa-btn isa-text-left isa-p-16"
[value]="dl.value"
>
{{ dl.key }}
</button>
</ng-container>
</app-ui-dropdown>
</div>
</div>
</ng-template>

View File

@@ -2,8 +2,7 @@ import { Component, OnInit, ChangeDetectionStrategy, Input, Output, EventEmitter
import { OrderDetailsCardInput } from './order-details-card-input';
import { ProcessingStatusNameMap } from '../../constants';
import { OrderItemProcessingStatusValue } from '@swagger/oms';
import { DetailsFacade } from '@shelf-store/details';
import { map } from 'rxjs/operators';
import { DateAdapter } from '@isa-ui/core/date';
@Component({
selector: 'app-shelf-order-details-card',
@@ -12,6 +11,8 @@ import { map } from 'rxjs/operators';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfOrderDetailsCardComponent implements OnInit {
minDate = this.dateAdapter.addCalendarDays(new Date(), -1);
get processingKeys() {
const copy = new Map(ProcessingStatusNameMap);
@@ -19,6 +20,9 @@ export class ShelfOrderDetailsCardComponent implements OnInit {
if (this.orderDetails.processingStatus === 16 || this.orderDetails.processingStatus === 8192) {
copy.delete(128);
}
if (this.orderDetails.processingStatus >= 0) {
copy.delete(this.orderDetails.processingStatus);
}
}
return copy;
@@ -56,7 +60,7 @@ export class ShelfOrderDetailsCardComponent implements OnInit {
@Output()
changePickUpDeadline = new EventEmitter<Date>();
constructor() {}
constructor(private dateAdapter: DateAdapter<Date>) {}
ngOnInit() {}
}

View File

@@ -1,114 +1,114 @@
<div class="wrapper">
<div class="product-image">
<img class="thumbnail"
src="https://produktbilder.ihugendubel.de/{{orderItemListItem?.product.ean}}.jpg?showDummy=true"
alt="item.product.name">
</div>
<div class="product-details isa-flex-fill isa-flex isa-flex-row">
<div class="isa-flex-fill">
<h4 class="product-name">{{ orderItemListItem?.product?.name }}</h4>
<h4 class="isa-mb-6 isa-mt-12">Bestellung</h4>
<div class="detail">
<div class="name">
Format
</div>
<div class="value">
{{ orderItemListItem?.product?.format }} {{ orderItemListItem?.product?.formatDetail }}
</div>
</div>
<div class="detail">
<div class="name">
Menge
</div>
<div class="value">
<button class="isa-btn btn-quantity isa-text-left isa-p-0" [disabled]="!selectable"
(click)="quantityDropdown.toggle()">
{{ orderItemListItem?.quantity }}x
<lib-icon class="isa-ml-5" *ngIf="selectable" [class.up]="quantityDropdown.visible"
[class.down]="!quantityDropdown.visible" name="Arrow_right" [height]="'16px'"></lib-icon>
</button>
<app-ui-dropdown #quantityDropdown [(ngModel)]="orderItemListItem.quantity"
(ngModelChange)="quantityChange.emit($event);quantityDropdown.close()">
<button appUiDropdownItem class="isa-btn isa-text-left isa-p-16"
*ngFor="let val of dropdownValues" [value]="val">
{{ val }}x
</button>
</app-ui-dropdown>
</div>
</div>
<div class="detail">
<div class="name">
Preis
</div>
<div class="value">
{{ orderItemListItem?.price | currency:'EUR' }}
</div>
</div>
<div class="detail ">
<div class="name">
ISBN/EAN
</div>
<div class="value">
{{ orderItemListItem?.product?.ean }}
</div>
</div>
<ng-container *ngIf="more">
<div class="detail ">
<div class="name">
Zielfiliale
</div>
<div class="value">
{{ orderItemListItem?.targetBranch }}
</div>
</div>
<div class="detail ">
<div class="name">
Lieferant
</div>
<div class="value">
{{ orderItemListItem?.supplier }}
</div>
</div>
<div class="detail ">
<div class="name">
Meldenummer
</div>
<div class="value">
{{ orderItemListItem?.ssc }} - {{ orderItemListItem?.sscText }}
</div>
</div>
<div class="detail ">
<div class="name">
MwSt
</div>
<div class="value">
{{ orderItemListItem['vatType'] | vatType }}
</div>
</div>
<button class="isa-btn isa-btn-default isa-p-0 isa-flex isa-align-items-center" (click)="more = false">
<lib-icon name="Arrow_back" height="15px" class="isa-flex isa-mr-5"></lib-icon>
<strong>Weniger</strong>
</button>
</ng-container>
<div class="product-image">
<img class="thumbnail"
src="https://produktbilder.ihugendubel.de/{{orderItemListItem?.product.ean}}.jpg?showDummy=true"
alt="item.product.name">
</div>
<div class="product-details isa-flex-fill isa-flex isa-flex-row">
<div class="isa-flex-fill">
<h4 class="product-name">{{ orderItemListItem?.product?.name }}</h4>
<h4 class="isa-mb-6 isa-mt-12">Bestellung</h4>
<div class="detail">
<div class="name">
Format
</div>
<div class="isa-flex isa-flex-direction-column isa-justify-content-flex-end isa-text-right">
<div class="isa-flex-fill"></div>
<div *ngIf="selectable">
<label class="checkbox-wrapper">
<input type="checkbox" [ngModel]="selected" (ngModelChange)="selectedChange($event)">
<span class="checkmark"></span>
</label>
<div class="value">
{{ orderItemListItem?.product?.format }} {{ orderItemListItem?.product?.formatDetail }}
</div>
</div>
<div class="detail">
<div class="name">
Menge
</div>
<div class="value">
<button class="isa-btn btn-quantity isa-text-left isa-p-0" [disabled]="!selectable"
(click)="quantityDropdown.toggle()">
{{ orderItemListItem?.quantity }}x
</div>
<div class="isa-flex-fill"></div>
<button class="isa-btn isa-btn-default isa-p-0 isa-flex isa-align-items-center" (click)="more = true"
*ngIf="!more">
<strong>Mehr</strong>
<lib-icon name="Arrow_More" height="15px" class="isa-flex isa-ml-5"></lib-icon>
<lib-icon class="isa-ml-5" *ngIf="selectable" [class.up]="quantityDropdown.visible"
[class.down]="!quantityDropdown.visible" name="Arrow_right" [height]="'16px'"></lib-icon>
</button>
<app-ui-dropdown #quantityDropdown [(ngModel)]="orderItemListItem.quantity"
(ngModelChange)="quantityChange.emit($event);quantityDropdown.close()">
<button appUiDropdownItem class="isa-btn isa-text-left isa-p-16" *ngFor="let val of dropdownValues"
[value]="val">
{{ val }}x
</button>
</app-ui-dropdown>
</div>
</div>
<div class="detail">
<div class="name">
Preis
</div>
<div class="value">
{{ orderItemListItem?.price | currency:'EUR' }}
</div>
</div>
<div class="detail ">
<div class="name">
ISBN/EAN
</div>
<div class="value">
{{ orderItemListItem?.product?.ean }}
</div>
</div>
<ng-container *ngIf="more">
<div class="detail ">
<div class="name">
Zielfiliale
</div>
<div class="value">
{{ orderItemListItem?.targetBranch }}
</div>
</div>
<div class="detail ">
<div class="name">
Lieferant
</div>
<div class="value">
{{ orderItemListItem?.supplier }}
</div>
</div>
<div class="detail ">
<div class="name">
Meldenummer
</div>
<div class="value">
{{ orderItemListItem?.ssc }} - {{ orderItemListItem?.sscText }}
</div>
</div>
<div class="detail ">
<div class="name">
MwSt
</div>
<div class="value">
{{ orderItemListItem['vatType'] | vatType }}
</div>
</div>
<button class="isa-btn isa-btn-default isa-p-0 isa-flex isa-align-items-center" (click)="more = false">
<lib-icon name="Arrow_back" height="15px" class="isa-flex isa-mr-5"></lib-icon>
<strong>Weniger</strong>
</button>
</ng-container>
</div>
<div class="isa-flex isa-flex-direction-column isa-justify-content-flex-end isa-text-right">
<div class="isa-flex-fill"></div>
<div *ngIf="selectable">
<label class="checkbox-wrapper">
<input type="checkbox" [ngModel]="selected" (ngModelChange)="selectedChange($event)">
<span class="checkmark"></span>
</label>
</div>
<div class="isa-flex-fill"></div>
<button class="isa-btn isa-btn-default isa-p-0 isa-flex isa-align-items-center" (click)="more = true"
*ngIf="!more">
<strong>Mehr</strong>
<lib-icon name="Arrow_More" height="15px" class="isa-flex isa-ml-5"></lib-icon>
</button>
</div>
</div>
</div>

View File

@@ -1,49 +1,76 @@
<div class="tags" *ngIf="item.features?.prebooked">
<lib-icon name="tag_icon_preorder" width="30px"></lib-icon>
<div class="tags" *ngIf="item.features">
<lib-icon
*ngIf="item.features?.prebooked"
name="tag_icon_preorder"
height="48px"
></lib-icon>
</div>
<div class="isa-text-right isa-mb-9" *ngIf="item.compartmentCode">
<strong>{{ item.compartmentCode }} {{ item.compartmentInfo }} </strong>
<div class="feature-spacing" *ngIf="item.features"></div>
<div
class="isa-text-right isa-mb-9 isa-font-size-24"
*ngIf="item.compartmentCode && displayCompartmentCode"
>
<strong>{{ item.compartmentCode }} {{ item.compartmentInfo }} </strong>
</div>
<div class="paid isa-mt-9 isa-mb-9" *ngIf="item.features?.paid">
<lib-icon height="24px" width="24px" name="Check_green_circle" class="isa-mr-10"></lib-icon>
<strong> {{ item.features?.paid }} </strong>
<lib-icon
height="24px"
width="24px"
name="Check_green_circle"
class="isa-mr-10"
></lib-icon>
<strong> {{ item.features?.paid }} </strong>
</div>
<div class="grid-container">
<div class="cover">
<img class="thumbnail" src="https://produktbilder.ihugendubel.de/{{item.product.ean}}.jpg?showDummy=true"
alt="item.product.name">
<div class="cover">
<img
class="thumbnail"
src="https://produktbilder.ihugendubel.de/{{
item.product.ean
}}.jpg?showDummy=true"
alt="item.product.name"
/>
</div>
<div class="title">
<strong class="product-name">
{{ [item.product.contributors, item.product.name] | title }}</strong
>
<strong class="processing-status">
<lib-icon
class="isa-mr-9"
*ngIf="item.processingStatus | processingStatus: 'icon'; let icon"
[name]="icon"
[ngClass]="icon"
>
</lib-icon>
{{ item.processingStatus | processingStatus }}
</strong>
</div>
<div class="details">
<div class="item-type">
<lib-icon
class="isa-mr-9"
name="Icon_{{ item.product.format }}"
></lib-icon>
<strong>{{ item.product.formatDetail }}</strong>
</div>
<div class="title">
<strong class="product-name"> {{ [ item.product.contributors, item.product.name] | title }}</strong>
<strong class="processing-status">
<lib-icon class="isa-mr-9" *ngIf="item.processingStatus | processingStatus:'icon'; let icon" [name]="icon"
[ngClass]="icon">
</lib-icon>
{{ item.processingStatus | processingStatus }}
</strong>
<div class="order-date spec">
<span>Bestelldatum</span>
<strong>{{ item.orderDate | date }}</strong>
</div>
<div class="details">
<div class="item-type">
<lib-icon class="isa-mr-9" name="Icon_{{item.product.format}}"></lib-icon>
<strong>{{ item.product.formatDetail }}</strong>
</div>
<div class="order-date spec">
<span>Bestelldatum</span>
<strong>{{ item.orderDate | date }}</strong>
</div>
<div class="item-number">
<strong>{{ item.product.ean }}</strong>
</div>
<div class="quantity spec">
<span>Menge</span>
<strong>{{ item.quantity }}x</strong>
</div>
<div class="price">
<strong>{{ item.price | currency:'EUR':'code' }}</strong>
</div>
<div class="target-branch spec">
<span>Zielfiliale</span>
<strong>{{ item.targetBranch }}</strong>
</div>
<div class="item-number">
<strong>{{ item.product.ean }}</strong>
</div>
</div>
<div class="quantity spec">
<span>Menge</span>
<strong>{{ item.quantity }}x</strong>
</div>
<div class="price">
<strong>{{ item.price | currency: 'EUR':'code' }}</strong>
</div>
<div class="target-branch spec">
<span>Zielfiliale</span>
<strong>{{ item.targetBranch }}</strong>
</div>
</div>
</div>

View File

@@ -101,5 +101,9 @@
.tags {
position: absolute;
top: -16px;
right: 150px;
right: 0;
}
.feature-spacing {
height: 40px;
}

View File

@@ -1,4 +1,9 @@
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
import {
Component,
OnInit,
ChangeDetectionStrategy,
Input,
} from '@angular/core';
import { OrderItemListItemDTO } from '@swagger/oms';
@Component({
@@ -11,6 +16,9 @@ export class SearchResultGroupItemComponent implements OnInit {
@Input()
item: OrderItemListItemDTO;
@Input()
displayCompartmentCode = true;
constructor() {}
ngOnInit() {}

View File

@@ -2,23 +2,38 @@
<h3 class="heading">
{{ group.items[0].firstName }} {{ group.items[0].lastName }}
</h3>
<ng-container *ngFor="
<ng-container
*ngFor="
let byOrderNumber of group.items | groupBy: byOrderNumberFn;
let lastOrder = last
">
<ng-container *ngFor="
"
>
<ng-container
*ngFor="
let byProcessingStatus of byOrderNumber.items
| groupBy: byProcessingStatusFn;
let lastStatus = last
">
<ng-container *ngFor="
"
>
<ng-container
*ngFor="
let groupedBy of byProcessingStatus.items
| groupBy: byCompartmentCodeFn;
let lastCompartment = last
">
"
>
<div class="isa-mb-12 isa-mt-12">
<app-search-result-group-item *ngFor="let item of groupedBy.items; let lastItem = last" [item]="item"
[class.group-item-bottom-space]="!lastItem" (click)="selectOrderItemListItem.emit(item)">
<app-search-result-group-item
*ngFor="
let item of groupedBy.items;
let firstItem = first;
let lastItem = last
"
[item]="item"
[class.group-item-bottom-space]="!lastItem"
(click)="selectOrderItemListItem.emit(item)"
[displayCompartmentCode]="firstItem"
>
</app-search-result-group-item>
</div>
<div class="divider" *ngIf="!lastCompartment"></div>
@@ -27,4 +42,4 @@
</ng-container>
<div class="divider" *ngIf="!lastOrder"></div>
</ng-container>
</div>
</div>

View File

@@ -1,30 +1,30 @@
export const ProcessingStatusNameMap = new Map<number, string>([
// [1, 'bestellt'],
// [2, 'Placed'],
// [4, 'Accepted'],
// [8, 'Parked'],
[16, 'bestellt'],
// [32, 'Vorbereitung Versand'],
[64, 'versendet'],
[128, 'eingetroffen'],
[256, 'abgeholt'],
[512, 'storniert (Kunde)'],
[1024, 'storniert'],
[2048, 'storniert (Lieferant)'],
// [4096, 'NotAvailable'],
[8192, 'nachbestellt'],
// [16384, 'ReturnedByBuyer'],
// [32768, 'AvailableForDownload'],
// [65536, 'Downloaded'],
[131072, 'ans Lager (nicht abgeholt)'],
// [262144, 'Zurück zum Lager'],
[524288, 'angefragt'],
[1048576, 'weitergeleitet intern'],
// [2097152, 'Overdue'],
[4194304, 'zugestellt'],
[8388608, 'Lieferant wird ermittelt'],
[16777216, 'derzeit nicht lieferbar'],
[33554432, 'reserviert'],
// [67108864, 'Assembled'],
// [134217728, 'Packed'],
export const ProcessingStatusNameMap = new Map<number, { value: string; disabled: boolean }>([
[1, { value: 'neu', disabled: true }],
[2, { value: '', disabled: true }],
[4, { value: '', disabled: true }],
[8, { value: 'geparkt', disabled: true }],
[16, { value: 'bestellt', disabled: false }],
[32, { value: 'Vorbereitung Versand', disabled: true }],
[64, { value: 'versendet', disabled: false }],
[128, { value: 'eingetroffen', disabled: false }],
[256, { value: 'abgeholt', disabled: false }],
[512, { value: 'storniert (Kunde)', disabled: false }],
[1024, { value: 'storniert', disabled: false }],
[2048, { value: 'storniert (Lieferant)', disabled: false }],
[4096, { value: 'nicht lieferbar', disabled: false }],
[8192, { value: 'nachbestellt', disabled: false }],
[16384, { value: 'zurückgegeben', disabled: true }],
[32768, { value: 'steht zum Download zur Verfügung', disabled: true }],
[65536, { value: 'downloaded', disabled: true }],
[131072, { value: 'nicht abgeholt', disabled: true }],
[262144, { value: 'ans Lager (nicht abgeholt)', disabled: false }],
[524288, { value: 'angefragt', disabled: false }],
[1048576, { value: 'weitergeleitet intern', disabled: false }],
[2097152, { value: 'überfällig', disabled: true }],
[4194304, { value: 'zugestellt', disabled: false }],
[8388608, { value: 'Lieferant ermittelt', disabled: false }],
[16777216, { value: 'derzeit nicht lieferbar', disabled: false }],
[33554432, { value: 'reserviert', disabled: true }],
[67108864, { value: 'zusammengestellt', disabled: true }],
[134217728, { value: 'verpackt', disabled: true }],
]);

View File

@@ -0,0 +1,5 @@
// start:ng42.barrel
export * from './shelf-edit-compartment.component';
export * from './shelf-edit-compartment.module';
// end:ng42.barrel

View File

@@ -0,0 +1,105 @@
<div class="wrapper">
<div class="container layout-container">
<div class="isa-form-header sticky header">
{{ customerName?.firstName }} {{ customerName?.lastName }}
<hr class="isa-content-spacer" />
</div>
<div class="layout-content">
<ng-container *ngIf="form">
<form [formGroup]="form">
<app-ui-text-input
formControlName="compartmentCode"
label="Abholfachnummer"
></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-text-input
formControlName="orderNumber"
label="Vorgang-ID"
></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-text-input
formControlName="orderDate"
label="Bestelldatum"
></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-text-input
formControlName="clientChannel"
label="Bestellkanal"
></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-text-input
formControlName="buyerNumber"
label="Kundennummer"
></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-select-input
formControlName="processingStatus"
label="Status"
[options]="
formService.processingStatus | processingStatusOptionsPipe
"
[labelPipe]="processingStatusPipe"
[optionsPipe]="processingStatusPipe"
></app-ui-select-input>
<hr class="isa-content-spacer" />
<ng-container
*ngIf="
+form.get('processingStatus').value === 16 ||
+form.get('processingStatus').value === 8192
"
>
<app-ui-select-input
formControlName="estimatedShippingDate"
label="Vsl. Lieferdatum"
[options]="
form.get('estimatedShippingDate').value | getExtendPickUpOptions
"
[labelPipe]="datePipe"
[optionsPipe]="dateOptionsPipe"
></app-ui-select-input>
<hr class="isa-content-spacer" />
</ng-container>
<ng-container *ngIf="+form.get('processingStatus').value === 128">
<app-ui-select-input
formControlName="pickUpDeadline"
label="Abholfrist"
[options]="
form.get('pickUpDeadline').value | getExtendPickUpOptions
"
[labelPipe]="datePipe"
[optionsPipe]="dateOptionsPipe"
></app-ui-select-input>
<hr class="isa-content-spacer" />
</ng-container>
<div formArrayName="items">
<ng-container
*ngFor="let item of items.controls; let index = index"
>
<app-shelf-edit-order-item
[orderItemForm]="item"
[onlyChild]="items.controls.length === 1"
[firstChild]="index === 1"
[vatOptions]="formService.vats$ | async"
></app-shelf-edit-order-item>
</ng-container>
</div>
</form>
</ng-container>
</div>
<div class="spacing"></div>
</div>
<app-shelf-edit-actions
[disabled]="!form || !form.valid"
(cancel)="onAbortEdit()"
(save)="onSubmit()"
>
</app-shelf-edit-actions>
</div>

View File

@@ -0,0 +1,31 @@
@import 'variables';
.wrapper {
overflow: scroll;
height: 105%;
background: $isa-branch-bg;
overflow: hidden;
}
.container {
.layout-content {
margin-bottom: 0;
padding: 0 0.3rem 0;
}
.header {
//offset page layout padding
padding: 20px 0.3rem 0 4%;
position: sticky;
background: inherit;
top: -1px;
margin-bottom: 0;
z-index: 2;
}
}
.spacing {
display: flex;
height: 130px;
background: $isa-branch-bg;
}

View File

@@ -0,0 +1,143 @@
import {
Component,
OnInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable, from } from 'rxjs';
import {
filter,
map,
distinctUntilChanged,
shareReplay,
take,
withLatestFrom,
first,
} from 'rxjs/operators';
import { isNullOrUndefined } from 'util';
import { ShelfEditFormService } from '../../services';
import { FormGroup, FormArray } from '@angular/forms';
import {
ProcessingStatusPipe,
PickUpDateOptionsToDisplayValuesPipe,
ProcessingStatusOptionsPipe,
} from '../../pipes';
import { DatePipe } from '@angular/common';
import { ShelfNavigationService } from '../../shared/services';
import { OrderItemProcessingStatusValue } from '@swagger/oms/lib';
@Component({
selector: 'app-shelf-edit-compartment',
templateUrl: 'shelf-edit-compartment.component.html',
styleUrls: ['./shelf-edit-compartment.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfEditCompartmentComponent implements OnInit {
// Form Information
customerName: { firstName: string; lastName: string };
form: FormGroup;
items: FormArray;
// Pipes for Mapping Values to display names
processingStatusPipe = new ProcessingStatusPipe();
processingStatusOptionsPipe = new ProcessingStatusOptionsPipe();
dateOptionsPipe = new PickUpDateOptionsToDisplayValuesPipe();
datePipe = new DatePipe('de');
constructor(
private activatedRoute: ActivatedRoute,
protected formService: ShelfEditFormService,
private shelfNavigationService: ShelfNavigationService,
private cdr: ChangeDetectorRef
) {}
get compartmentCode$(): Observable<string> {
return this.activatedRoute.params.pipe(
filter((params) => !isNullOrUndefined(params)),
map((params) => params.compartmentCode),
distinctUntilChanged(),
shareReplay()
);
}
get processingStatus$() {
return this.activatedRoute.params.pipe(
map(
(params) =>
Number(params.processingStatus) as OrderItemProcessingStatusValue
),
distinctUntilChanged(),
shareReplay()
);
}
ngOnInit() {
this.loadCompartmentAndInitForm();
}
async loadCompartmentAndInitForm() {
const compartmentCode = await this.compartmentCode$
.pipe(take(1))
.toPromise();
const processingStatus = await this.processingStatus$
.pipe(take(1))
.toPromise();
this.populateFormData(compartmentCode, processingStatus);
}
async onSubmit() {
console.log(this.form.valid);
const compartmentCode = await this.compartmentCode$
.pipe(first())
.toPromise();
const processingStatus = await this.processingStatus$
.pipe(first())
.toPromise();
const submitResult = await this.formService.submit(
this.form,
processingStatus
);
let newValues;
if (submitResult) {
newValues = {
compartmentCode:
this.form.get('compartmentCode').value || compartmentCode,
processingStatus:
this.form.get('processingStatus').value || processingStatus,
};
this.shelfNavigationService.navigateBackToDetails(newValues, {
compartmentCode,
processingStatus,
});
}
}
onAbortEdit() {
return this.compartmentCode$
.pipe(take(1), withLatestFrom(this.processingStatus$))
.subscribe(([compartmentCode, processingStatus]) =>
this.shelfNavigationService.navigateToDetails({
compartmentCode,
processingStatus,
})
);
}
private async populateFormData(
compartmentCode: string,
processingStatus: OrderItemProcessingStatusValue
) {
this.form = await this.formService.createFormByCompartmentCode(
compartmentCode,
processingStatus
);
this.items = this.formService.getItemsForm(compartmentCode);
this.customerName = this.formService.getCustomerName(compartmentCode);
this.cdr.detectChanges();
}
}

View File

@@ -0,0 +1,27 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { ShelfEditCompartmentComponent } from './shelf-edit-compartment.component';
import { ShelfEditActionsModule } from '../../components/edit-actions';
import { UiDropdownModule } from '@isa-ui/dropdown';
import { UiTextInputModule } from '@isa-ui/text-input';
import { UiSelectInputModule } from '@isa-ui/select-input';
import { ShelfPipesModule } from '../../pipes';
import { ShelfEditOrderItemModule } from '../../components/edit-order-item';
@NgModule({
imports: [
CommonModule,
ShelfEditActionsModule,
ShelfEditOrderItemModule,
UiDropdownModule,
UiTextInputModule,
UiSelectInputModule,
ReactiveFormsModule,
ShelfPipesModule,
],
exports: [ShelfEditCompartmentComponent],
declarations: [ShelfEditCompartmentComponent],
providers: [],
})
export class ShelfEditCompartmentModule {}

View File

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

View File

@@ -1,21 +1,104 @@
<div class="container">
<div class="header">
<app-button (action)="done()" type="small">Fertig</app-button>
</div>
<div class="content" *ngIf="order && order.items">
<app-order-overview-edit
[_order]="order"
(statusChanged)="statusChanged($event)"
(compartmentCodeChanged)="compartmentCodeChanged($event)"
[compartmentCode]="type === 'c' ? compartmentCode : ''"
></app-order-overview-edit>
<app-order-item-edit
*ngFor="let item of order.items"
[orderItem]="item.data"
[logistician]="order.logistician"
[clientChannel]="order.clientChannel"
[targetBranch]="order.targetBranch"
[multi]="multi"
></app-order-item-edit>
<div class="wrapper">
<div class="container layout-container">
<div class="isa-form-header sticky header">
{{ customerName?.firstName }} {{ customerName?.lastName }}
<hr class="isa-content-spacer" />
</div>
<div class="layout-content">
<ng-container *ngIf="form">
<form [formGroup]="form">
<app-ui-text-input
formControlName="compartmentCode"
label="Abholfachnummer"
></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-text-input
formControlName="orderNumber"
label="Vorgang-ID"
></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-text-input
formControlName="orderDate"
label="Bestelldatum"
></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-text-input
formControlName="clientChannel"
label="Bestellkanal"
></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-text-input
formControlName="buyerNumber"
label="Kundennummer"
></app-ui-text-input>
<hr class="isa-content-spacer" />
<app-ui-select-input
formControlName="processingStatus"
label="Status"
[options]="
formService.processingStatus | processingStatusOptionsPipe
"
[labelPipe]="processingStatusPipe"
[optionsPipe]="processingStatusPipe"
></app-ui-select-input>
<hr class="isa-content-spacer" />
<ng-container
*ngIf="
+form.get('processingStatus').value === 16 ||
+form.get('processingStatus').value === 8192
"
>
<app-ui-select-input
formControlName="estimatedShippingDate"
label="Vsl. Lieferdatum"
[options]="
form.get('estimatedShippingDate').value | getExtendPickUpOptions
"
[labelPipe]="datePipe"
[optionsPipe]="dateOptionsPipe"
></app-ui-select-input>
<hr class="isa-content-spacer" />
</ng-container>
<ng-container *ngIf="+form.get('processingStatus').value === 128">
<app-ui-select-input
formControlName="pickUpDeadline"
label="Abholfrist"
[options]="
form.get('pickUpDeadline').value | getExtendPickUpOptions
"
[labelPipe]="datePipe"
[optionsPipe]="dateOptionsPipe"
></app-ui-select-input>
<hr class="isa-content-spacer" />
</ng-container>
<div formArrayName="items">
<ng-container
*ngFor="let item of items.controls; let index = index"
>
<app-shelf-edit-order-item
[orderItemForm]="item"
[onlyChild]="items.controls.length === 1"
[firstChild]="index === 0"
[vatOptions]="formService.vats$ | async"
></app-shelf-edit-order-item>
</ng-container>
</div>
</form>
</ng-container>
</div>
<div class="spacing"></div>
</div>
<app-shelf-edit-actions
[disabled]="!form || !form.valid"
(cancel)="onAbortEdit()"
(save)="onSubmit()"
>
</app-shelf-edit-actions>
</div>

View File

@@ -1,18 +1,31 @@
.container {
background-color: white;
background-color: white;
border-radius: 5px;
box-shadow: 0px 0px 10px 0px #dce2e9;
height: calc(100% - 70px);
margin-top: 10px;
overflow-y: scroll;
padding-top: 15px;
scroll-behavior: smooth;
@import 'variables';
.wrapper {
overflow: scroll;
height: 105%;
background: $isa-branch-bg;
overflow: hidden;
}
.container {
.layout-content {
margin-bottom: 0;
padding: 0 0.3rem 0;
}
.header {
margin-bottom: 10px;
margin-right: 10px;
display: flex;
justify-content: flex-end;
//offset page layout padding
padding: 20px 0.3rem 0 4%;
position: sticky;
background: inherit;
top: -1px;
margin-bottom: 0;
z-index: 2;
}
}
.spacing {
display: flex;
height: 130px;
background: $isa-branch-bg;
}

View File

@@ -1,107 +1,136 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { CollectingShelfSelectors } from 'apps/sales/src/app/core/store/selectors/collecting-shelf.selector';
import { Observable, Subject } from 'rxjs';
import { OrderDTO } from '@swagger/oms';
import { takeUntil, filter, take, map } from 'rxjs/operators';
import {
Component,
OnInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { FormGroup, FormArray } from '@angular/forms';
import { ShelfEditFormService } from '../../services';
import { ShelfNavigationService } from '../../shared/services';
import {
filter,
map,
distinctUntilChanged,
shareReplay,
take,
withLatestFrom,
first,
} from 'rxjs/operators';
import { isNullOrUndefined } from 'util';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { ChangeCurrentRoute } from 'apps/sales/src/app/core/store/actions/process.actions';
import { PopBreadcrumbsAfterCurrent } from 'apps/sales/src/app/core/store/actions/breadcrumb.actions';
import { CollectingShelfService } from 'apps/sales/src/app/core/services/collecting-shelf.service';
import { SetBranchProcessCurrentPath } from 'apps/sales/src/app/core/store/actions/branch-process.actions';
import {
ProcessingStatusPipe,
PickUpDateOptionsToDisplayValuesPipe,
ProcessingStatusOptionsPipe,
} from '../../pipes';
import { DatePipe } from '@angular/common';
import { OrderItemProcessingStatusValue } from '@swagger/oms/lib';
import { from } from 'rxjs';
@Component({
selector: 'app-shelf-edit-order',
templateUrl: './shelf-edit-order.component.html',
styleUrls: ['./shelf-edit-order.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfEditOrderComponent implements OnInit, OnDestroy {
@Select(CollectingShelfSelectors.getShelfEditOrder) order$: Observable<OrderDTO>;
destroy$ = new Subject();
order: OrderDTO;
compartmentCode: string;
type: string;
status: string;
orderId: number;
origin: string;
customerId: number;
itemId: number;
multi = false;
constructor(private store: Store, private router: Router, private route: ActivatedRoute, private shelfService: CollectingShelfService) {}
export class ShelfEditOrderComponent implements OnInit {
// Form Information
customerName: { firstName: string; lastName: string };
form: FormGroup;
items: FormArray;
// Pipes for Mapping Values to display names
processingStatusPipe = new ProcessingStatusPipe();
processingStatusOptionsPipe = new ProcessingStatusOptionsPipe();
dateOptionsPipe = new PickUpDateOptionsToDisplayValuesPipe();
datePipe = new DatePipe('de');
constructor(
private activatedRoute: ActivatedRoute,
protected formService: ShelfEditFormService,
private shelfNavigationService: ShelfNavigationService,
private cdr: ChangeDetectorRef
) {}
get orderNumber$() {
return this.activatedRoute.params.pipe(
filter((params) => !isNullOrUndefined(params)),
map((params) => params.orderNumber),
distinctUntilChanged(),
shareReplay()
);
}
get processingStatus$() {
return this.activatedRoute.params.pipe(
map(
(params) =>
Number(params.processingStatus) as OrderItemProcessingStatusValue
),
distinctUntilChanged(),
shareReplay()
);
}
ngOnInit() {
this.route.params.pipe(take(1)).subscribe(this.routeParamsSubscriber$);
this.loadOrderNumberAndInitForm();
}
ngOnDestroy() {
this.destroy$.next();
async loadOrderNumberAndInitForm() {
const orderNumber = await this.orderNumber$.pipe(take(1)).toPromise();
const processingStatus = await this.processingStatus$
.pipe(take(1))
.toPromise();
console.log('loadOrderNumberAndInitForm', {
orderNumber,
processingStatus,
});
this.populateFormData(orderNumber, processingStatus);
}
routeParamsSubscriber$ = (params: Params) => {
if (params['id'] && params['status'] && params['type'] && params['orderId'] && params['origin'] && params['customerId']) {
this.status = params['status'];
this.type = params['type'];
this.compartmentCode = params['id'];
this.orderId = params['orderId'];
this.origin = params['origin'];
this.customerId = params['customerId'];
this.itemId = +params['itemId'];
}
async onSubmit() {
const orderNumber = await this.orderNumber$.pipe(first()).toPromise();
const processingStatus = await this.processingStatus$
.pipe(first())
.toPromise();
this.order$
.pipe(
takeUntil(this.destroy$),
filter((data) => !isNullOrUndefined(data)),
map((order) => ({
...order,
items:
this.itemId === 0 || isNullOrUndefined(this.itemId)
? order.items
: order.items.filter((item) => item.data.subsetItems.some((subsetItem) => subsetItem.id === this.itemId)),
}))
)
.subscribe(this.orderSubscriber$);
};
const submitResult = await this.formService.submit(
this.form,
processingStatus
);
orderSubscriber$ = (order: OrderDTO) => {
this.order = order;
if (order.items && order.items && order.items.length > 1) {
this.multi = true;
} else {
this.multi = false;
}
};
done() {
if (this.compartmentCode || this.orderId) {
if (this.origin === 'shelf') {
const path = `/shelf/details/${this.compartmentCode ? this.compartmentCode : this.orderId}/${this.orderId}/${this.status}/${
this.type
}`;
this.store.dispatch(new PopBreadcrumbsAfterCurrent(path));
this.store.dispatch(new ChangeCurrentRoute(path, true));
this.router.navigate([path]);
}
if (this.origin === 'customer') {
const path = `/customer/${this.customerId}/order/${this.orderId}`;
this.store.dispatch(new PopBreadcrumbsAfterCurrent(path));
this.store.dispatch(new ChangeCurrentRoute(path, true));
this.router.navigate([path]);
}
if (this.origin === 'goodsin') {
const path = `/goodsin/details/${this.orderId}/${this.itemId}/${this.status}`;
this.store.dispatch(new SetBranchProcessCurrentPath(path, true));
this.router.navigate([path]);
}
if (submitResult) {
this.shelfNavigationService.navigateBackToDetails({
orderNumber,
processingStatus:
this.form.get('processingStatus').value || processingStatus,
});
}
}
statusChanged(processingStatus: string) {
this.status = processingStatus;
onAbortEdit() {
return this.orderNumber$
.pipe(take(1), withLatestFrom(this.processingStatus$))
.subscribe(([orderNumber, processingStatus]) =>
this.shelfNavigationService.navigateToDetails({
orderNumber,
processingStatus,
})
);
}
compartmentCodeChanged(compartmentCode: string) {
this.compartmentCode = compartmentCode;
private async populateFormData(
orderNumber: string,
processingStatus: OrderItemProcessingStatusValue
) {
this.form = await this.formService.createFormByOrderNumber(
orderNumber,
processingStatus
);
console.log({ form: this.form });
this.items = this.formService.getItemsForm(orderNumber);
this.customerName = this.formService.getCustomerName(orderNumber);
this.cdr.detectChanges();
}
}

View File

@@ -0,0 +1,27 @@
import { NgModule } from '@angular/core';
import { ShelfEditActionsModule } from '../../components/edit-actions';
import { ShelfEditOrderComponent } from './shelf-edit-order.component';
import { CommonModule } from '@angular/common';
import { ShelfEditOrderItemModule } from '../../components/edit-order-item';
import { UiDropdownModule } from '@isa-ui/dropdown';
import { UiTextInputModule } from '@isa-ui/text-input';
import { UiSelectInputModule } from '@isa-ui/select-input';
import { ReactiveFormsModule } from '@angular/forms';
import { ShelfPipesModule } from '../../pipes';
@NgModule({
imports: [
CommonModule,
ShelfEditActionsModule,
ShelfEditOrderItemModule,
UiDropdownModule,
UiTextInputModule,
UiSelectInputModule,
ReactiveFormsModule,
ShelfPipesModule,
],
exports: [ShelfEditOrderComponent],
declarations: [ShelfEditOrderComponent],
providers: [],
})
export class ShelfEditOrderModule {}

View File

@@ -1,17 +1,11 @@
<div class="header-container">
<h2 class="headline isa-text-center isa-font-weight-bold">Historie</h2>
<div class="content">
<ng-template
[ngTemplateOutlet]="headerTemplate || defaultHeaderTemplate"
[ngTemplateOutletContext]="{ $implicit: orderItemSubsetId }"
></ng-template>
<app-shelf-order-details-card
[orderDetails]="details$ | async"
(changeEstimatedDeliveryDate)="updateEstimatedShippingDate($event)"
(changePickUpDeadline)="changePickUpDate($event)"
(changeProcessingStatus)="changeProcessingStatus($event)"
></app-shelf-order-details-card>
</div>
</div>
<ng-template let-orderItemSubsetId #defaultHeaderTemplate>
<p class="default-template">
Es liegen keine Detailinformation zum Produkt (Bestell-Id:
<span>{{ orderItemSubsetId }}</span
>) vor.
</p>
</ng-template>

View File

@@ -16,3 +16,7 @@
.default-template {
margin-top: 0;
}
.content {
width: 100%;
}

View File

@@ -3,8 +3,17 @@ import {
ChangeDetectionStrategy,
Input,
TemplateRef,
OnInit,
} from '@angular/core';
import { HistoryStateFacade } from '@shelf-store/history';
import { DetailsFacade } from '@shelf-store/details';
import { OrderDetailsCardInput } from '../../../components/order-details-card';
import {
OrderItemListItemDTO,
OrderItemProcessingStatusValue,
} from '@swagger/oms/lib';
import { Observable } from 'rxjs';
import { map, filter, shareReplay, take } from 'rxjs/operators';
@Component({
selector: 'app-shelf-history-header',
@@ -12,13 +21,126 @@ import { HistoryStateFacade } from '@shelf-store/history';
styleUrls: ['./history-header.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfHistoryHeaderComponent {
export class ShelfHistoryHeaderComponent implements OnInit {
@Input() orderId: any;
@Input() compartmentCode: string;
@Input() orderItemId: number;
@Input() orderItemSubsetId: number;
@Input() headerTemplate: TemplateRef<any>;
constructor(private historyStateFacade: HistoryStateFacade) {}
details$: Observable<OrderDetailsCardInput & { orderId: number }>;
orderItems$: Observable<OrderItemListItemDTO[]>;
constructor(
private detailsFacade: DetailsFacade,
private historyStateFacade: HistoryStateFacade,
private detailsStoreFacade: DetailsFacade
) {}
ngOnInit() {
if (this.compartmentCode) {
this.orderItems$ = this.detailsFacade.getOrderItemsByCompartmentCode$(
this.compartmentCode
);
} else if (this.orderId) {
this.orderItems$ = this.detailsFacade.getOrderItemsByOrderNumber$(
this.orderId
);
}
this.details$ = this.orderItems$.pipe(
map((items) => {
if (items.length > 0) {
const item = items[0];
return {
firstName: item.firstName,
lastName: item.lastName,
customerNumber: item.buyerNumber,
estimatedShippingDate: item.estimatedShippingDate
? new Date(item.estimatedShippingDate)
: undefined,
orderDate: new Date(item.orderDate),
orderNumber: item.orderNumber,
processingStatus: item.processingStatus,
processingStatusDate: new Date(item.processingStatusDate),
orderChannel: item.clientChannel,
compartmentCode: item.compartmentCode,
compartmentInfo: item.compartmentInfo,
pickupDeadline: new Date(item.pickUpDeadline),
orderId: item.orderId,
} as OrderDetailsCardInput & { orderId: number };
}
return undefined;
}),
filter((details) => !!details),
shareReplay()
);
}
updateHistory() {
this.historyStateFacade.reloadHistory(this.orderItemSubsetId);
}
async updateEstimatedShippingDate(date: Date) {
const orderId = await this.getOrderId();
await this.detailsStoreFacade.setEstimatedShippingDate({
items: [
{
orderItemSubsetId: this.orderItemSubsetId,
orderItemId: this.orderItemId,
orderId: orderId,
},
],
estimatedShippingDate: date,
});
this.updateHistory();
}
async changePickUpDate(date: Date) {
const orderId = await this.getOrderId();
await this.detailsStoreFacade.setPickUpDeadline({
items: [
{
orderItemSubsetId: this.orderItemSubsetId,
orderItemId: this.orderItemId,
orderId: orderId,
},
],
pickUpDeadline: date,
});
this.updateHistory();
}
async changeProcessingStatus(
processingStatus: OrderItemProcessingStatusValue
) {
const orderId = await this.getOrderId();
await this.detailsStoreFacade.changeStatus([
{
orderItemSubsetId: this.orderItemSubsetId,
orderItemId: this.orderItemId,
orderId,
data: {
processingStatus,
},
},
]);
this.updateHistory();
}
private async getOrderId() {
return this.details$
.pipe(
map((details) => details.orderId),
take(1)
)
.toPromise();
}
}

View File

@@ -1,20 +1,14 @@
<div class="layout-container">
<div class="layout-content">
<app-shelf-history-header
[orderItemSubsetId]="orderItemSubsetId"
></app-shelf-history-header>
<app-shelf-history-header [orderItemSubsetId]="orderItemSubsetId" [orderId]="orderId" [orderItemId]="orderItemId"
[compartmentCode]="compartmentCode"></app-shelf-history-header>
<hr class="isa-content-spacer" />
<app-shelf-history-log
*ngFor="
<app-shelf-history-log *ngFor="
let history of history$ | async;
let first = first;
let last = last
"
[history]="history"
[first]="first"
[last]="last"
></app-shelf-history-log>
" [history]="history" [first]="first" [last]="last"></app-shelf-history-log>
</div>
</div>
</div>

View File

@@ -1,9 +1,4 @@
import {
Component,
OnInit,
ChangeDetectionStrategy,
Input,
} from '@angular/core';
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
import { Observable } from 'rxjs';
import { HistoryDTO } from '@cmf/trade-api';
import { HistoryStateFacade } from '@shelf-store/history';
@@ -15,6 +10,9 @@ import { HistoryStateFacade } from '@shelf-store/history';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfHistoryLogsComponent implements OnInit {
@Input() orderId: number;
@Input() compartmentCode: string;
@Input() orderItemId: number;
@Input() orderItemSubsetId: number;
history$: Observable<HistoryDTO[]>;

View File

@@ -1,8 +1,7 @@
<app-shelf-history-logs
*ngIf="(status$ | async) === 2; else loading"
[orderItemSubsetId]="orderItemSubsetId$ | async"
></app-shelf-history-logs>
<app-shelf-history-logs *ngIf="(status$ | async) === 2; else loading" [orderItemSubsetId]="orderItemSubsetId$ | async"
[orderId]="orderId$ | async" [compartmentCode]="compartmentCode$ | async" [orderItemId]="orderItemId$ | async">
</app-shelf-history-logs>
<ng-template #loading>
<div class="spinner"></div>
</ng-template>
</ng-template>

View File

@@ -10,7 +10,6 @@ import {
shareReplay,
} from 'rxjs/operators';
import { isNullOrUndefined } from 'util';
import { HistoryDTO } from '@cmf/trade-api';
import { HistoryStateFacade } from '@shelf-store/history';
import { OrderHistoryStatus } from '@shelf-store/defs';
@@ -22,6 +21,9 @@ import { OrderHistoryStatus } from '@shelf-store/defs';
})
export class ShelfHistoryComponent implements OnInit {
status$: Observable<OrderHistoryStatus>;
orderId$: Observable<number>;
compartmentCode$: Observable<string>;
orderItemId$: Observable<number>;
orderItemSubsetId$: Observable<number>;
constructor(
@@ -32,6 +34,9 @@ export class ShelfHistoryComponent implements OnInit {
ngOnInit() {
this.status$ = this.getStatus();
this.orderItemSubsetId$ = this.getOrderItemSubsetId$();
this.orderId$ = this.getOrderId$();
this.orderItemId$ = this.getOrderItemId$();
this.compartmentCode$ = this.getCompartmentCode$();
}
fetchHistory = (orderItemSubsetId: number) => {
@@ -46,6 +51,34 @@ export class ShelfHistoryComponent implements OnInit {
);
}
getOrderId$(): Observable<number> {
return this.activatedRoute.params.pipe(
filter(
(params) =>
!isNullOrUndefined(params) && !isNullOrUndefined(params.orderId)
),
map((params) => params.orderId)
);
}
getCompartmentCode$(): Observable<string> {
return this.activatedRoute.params.pipe(
filter(
(params) =>
!isNullOrUndefined(params) &&
!isNullOrUndefined(params.compartmentCode)
),
map((params) => params.compartmentCode)
);
}
getOrderItemId$(): Observable<number> {
return this.activatedRoute.params.pipe(
filter((params) => !isNullOrUndefined(params)),
map((params) => Number(params.orderItemId))
);
}
getOrderItemSubsetId$(): Observable<number> {
return this.activatedRoute.params.pipe(
filter((params) => !isNullOrUndefined(params)),

View File

@@ -5,21 +5,12 @@ import { ShelfHistoryComponent } from './shelf-history.component';
import { ShelfHistoryLogsComponent } from './history-logs';
import { FilterHistoriesWithStatusChangePipe } from './pipes';
import { ShelfHistoryHeaderComponent } from './header';
import { ShelfOrderDetailsCardModule } from '../../components/order-details-card';
@NgModule({
imports: [CommonModule, ShelfHistoryLogModule],
exports: [
ShelfHistoryComponent,
ShelfHistoryHeaderComponent,
ShelfHistoryLogsComponent,
FilterHistoriesWithStatusChangePipe,
],
declarations: [
ShelfHistoryComponent,
ShelfHistoryHeaderComponent,
ShelfHistoryLogsComponent,
FilterHistoriesWithStatusChangePipe,
],
imports: [CommonModule, ShelfHistoryLogModule, ShelfOrderDetailsCardModule],
exports: [ShelfHistoryComponent, ShelfHistoryHeaderComponent, ShelfHistoryLogsComponent, FilterHistoriesWithStatusChangePipe],
declarations: [ShelfHistoryComponent, ShelfHistoryHeaderComponent, ShelfHistoryLogsComponent, FilterHistoriesWithStatusChangePipe],
providers: [],
})
export class ShelfHistoryModule {}

View File

@@ -2,6 +2,7 @@ import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
import { CustomerService } from '@swagger/crm';
import { ReplaySubject, of, NEVER } from 'rxjs';
import { switchMap, catchError, map, flatMap, filter, throttleTime, distinctUntilChanged } from 'rxjs/operators';
import { isNullOrUndefined } from 'util';
@Component({
selector: 'app-shelf-details-customer-features',
@@ -12,7 +13,9 @@ import { switchMap, catchError, map, flatMap, filter, throttleTime, distinctUnti
export class CustomerFeaturesComponent {
@Input()
set customerNumber(value: string) {
this.customernumber$.next(value);
if (!isNullOrUndefined(value)) {
this.customernumber$.next(value);
}
}
customernumber$ = new ReplaySubject<string>();

View File

@@ -1,84 +1,156 @@
<div class="isa-card isa-border-bottom-radius-0">
<div class="isa-flex isa-align-items-center isa-justify-content-space-between">
<app-shelf-details-customer-features [customerNumber]="(orderDetailsCard$ | async)?.customerNumber">
</app-shelf-details-customer-features>
<button type="button" class="isa-btn isa-btn-secondary isa-mr-n10"
(click)="navigateToDetails()">Bearbeiten</button>
</div>
<app-shelf-order-details-card [orderDetails]="orderDetailsCard$ | async"
(changeEstimatedDeliveryDate)="changeEstimatedShippingDate($event)"
(changeProcessingStatus)="changeProcessingStatus($event)" (changePickUpDeadline)="changePickupDeadline($event)">
</app-shelf-order-details-card>
<div class="isa-flex isa-flex-direction-row-reverse isa-mb-n10" *ngIf="partialPickup">
<div class="isa-flex isa-flex-direction-column" *ngIf="orderItems$ | async; let orderItems">
<button type="button" class="isa-btn isa-btn-secondary isa-mr-n10 isa-mb-7" (click)="selectAllForPickUp()"
*ngIf="orderItems.length !== selectedForPartialPickup.size">Alle auswählen</button>
<button type="button" class="isa-btn isa-btn-secondary isa-mr-n10 isa-mb-7" (click)="unselectAllForPickUp()"
*ngIf="orderItems.length === selectedForPartialPickup.size">Alle entfernen</button>
<span class="isa-text-right">
{{ selectedForPartialPickup.size }} von {{ orderItems?.length }} Titel
</span>
</div>
<div
class="isa-flex isa-align-items-center isa-justify-content-space-between"
>
<app-shelf-details-customer-features
[customerNumber]="(orderDetailsCard$ | async)?.customerNumber"
>
</app-shelf-details-customer-features>
<button
type="button"
class="isa-btn isa-btn-secondary isa-mr-n10"
(click)="navigateToDetails()"
>
Bearbeiten
</button>
</div>
<app-shelf-order-details-card
[orderDetails]="orderDetailsCard$ | async"
(changeEstimatedDeliveryDate)="changeEstimatedShippingDate($event)"
(changeProcessingStatus)="changeProcessingStatus($event)"
(changePickUpDeadline)="changePickupDeadline($event)"
>
</app-shelf-order-details-card>
<div
class="isa-flex isa-flex-direction-row-reverse isa-mb-n10"
*ngIf="partialPickup"
>
<div
class="isa-flex isa-flex-direction-column"
*ngIf="orderItems$ | async; let orderItems"
>
<button
type="button"
class="isa-btn isa-btn-secondary isa-mr-n10 isa-mb-7"
(click)="selectAllForPickUp()"
*ngIf="orderItems.length !== selectedForPartialPickup.size"
>
Alle auswählen
</button>
<button
type="button"
class="isa-btn isa-btn-secondary isa-mr-n10 isa-mb-7"
(click)="unselectAllForPickUp()"
*ngIf="orderItems.length === selectedForPartialPickup.size"
>
Alle entfernen
</button>
<span class="isa-text-right">
{{ selectedForPartialPickup.size }} von {{ orderItems?.length }} Titel
</span>
</div>
</div>
</div>
<ng-container *ngFor="let orderItem of orderItems$ | async">
<div class="isa-card isa-mt-2 isa-border-radius-0">
<app-shelf-order-item-details [orderItemListItem]="orderItem" [selectable]="partialPickup"
[selected]="selectedForPartialPickup.has(orderItem.orderItemId)"
(select)="selectOrderItem(orderItem, $event)" (quantityChange)="setOrderItemQuanity(orderItem, $event)">
</app-shelf-order-item-details>
<div class="isa-flex isa-justify-content-flex-end isa-mt-7">
<button class="isa-btn isa-btn-secondary isa-p-0" (click)="navigateToHistory(orderItem)">
Historie
</button>
</div>
<div class="isa-card isa-mt-2 isa-border-radius-0">
<app-shelf-order-item-details
[orderItemListItem]="orderItem"
[selectable]="partialPickup"
[selected]="selectedForPartialPickup.has(orderItem.orderItemId)"
(select)="selectOrderItem(orderItem, $event)"
(quantityChange)="setOrderItemQuanity(orderItem, $event)"
>
</app-shelf-order-item-details>
<div class="isa-flex isa-justify-content-flex-end isa-mt-7">
<button
class="isa-btn isa-btn-secondary isa-p-0"
(click)="navigateToHistory(orderItem)"
>
Historie
</button>
</div>
</div>
<div class="isa-card isa-mt-2 isa-border-radius-0" [class.isa-border-radius-0]="showArrivedAndPrintCta$ | async"
[class.isa-border-top-radius-0]="(showArrivedAndPrintCta$ | async) === false">
<app-shelf-order-details-comment [comment]="orderItem.specialComment"
(saveComment)="saveComment(orderItem, $event)"></app-shelf-order-details-comment>
</div>
<div
class="isa-card isa-mt-2 isa-border-radius-0"
[class.isa-border-radius-0]="showArrivedAndPrintCta$ | async"
[class.isa-border-top-radius-0]="
(showArrivedAndPrintCta$ | async) === false
"
>
<app-shelf-order-details-comment
[comment]="orderItem.specialComment"
(saveComment)="saveComment(orderItem, $event)"
></app-shelf-order-details-comment>
</div>
</ng-container>
<!-- CTAs für Bestellt -->
<ng-container *ngIf="showArrivedAndPrintCta$ | async">
<div class="isa-card isa-mt-2 isa-border-top-radius-0">
<app-shelf-order-details-shelf-tags #shelfDetailsTag></app-shelf-order-details-shelf-tags>
</div>
<div class="isa-card isa-mt-2 isa-border-top-radius-0">
<app-shelf-order-details-shelf-tags
#shelfDetailsTag
></app-shelf-order-details-shelf-tags>
</div>
<div class="cta-sticky isa-my-28 isa-text-center">
<button type="button" class="isa-btn isa-btn-primary isa-btn-pill isa-btn-medium"
(click)="changeProcessingStatus(128, { compartmentInfo: shelfDetailsTag.selected })">
eingetroffen und Drucken
</button>
</div>
<div class="cta-sticky isa-my-28 isa-text-center">
<button
type="button"
class="isa-btn isa-btn-primary isa-btn-pill isa-btn-medium"
[disabled]="
shelfDetailsTag.selected === shelfDetailsTag.customValue.value &&
shelfDetailsTag.customValue.invalid
"
(click)="
changeProcessingStatus(128, {
compartmentInfo: shelfDetailsTag.selected
})
"
>
eingetroffen und Drucken
</button>
</div>
</ng-container>
<div class="cta-sticky isa-my-28 isa-text-center">
<button *ngIf="(showPickUpPartialCollectCta$ | async) && !partialPickup" type="button"
class="isa-btn isa-btn-outline-primary isa-btn-pill isa-btn-medium isa-mr-10" (click)="activatePartialPickup()">
Teilabholung
</button>
<button *ngIf="showPickUpCta$ | async" type="button" class="isa-btn isa-btn-primary isa-btn-pill isa-btn-medium"
(click)="changeProcessingStatus(256)">
abgeholt
</button>
<!-- <button *ngIf="showAddToRemissionListCta$ | async" type="button"
<button
*ngIf="(showPickUpPartialCollectCta$ | async) && !partialPickup"
type="button"
class="isa-btn isa-btn-outline-primary isa-btn-pill isa-btn-medium isa-mr-10"
(click)="activatePartialPickup()"
>
Teilabholung
</button>
<button
*ngIf="showPickUpCta$ | async"
type="button"
class="isa-btn isa-btn-primary isa-btn-pill isa-btn-medium"
(click)="changeProcessingStatus(256)"
>
abgeholt
</button>
<!-- <button *ngIf="showAddToRemissionListCta$ | async" type="button"
class="isa-btn isa-btn-outline-primary isa-btn-pill isa-btn-medium isa-mr-10" (click)="addToRemissionList()">
Remissionsliste hinzufügen
</button> -->
<button *ngIf="showBackToStoreCta$ | async" type="button"
class="isa-btn isa-btn-primary isa-btn-pill isa-btn-medium" (click)="changeProcessingStatus(131072)">
ans Lager
</button>
<button *ngIf="showReOrderCta$ | async" type="button" class="isa-btn isa-btn-primary isa-btn-pill isa-btn-medium"
(click)="changeProcessingStatus(8192)">
nachbestellen
</button>
<button
*ngIf="showBackToStoreCta$ | async"
type="button"
class="isa-btn isa-btn-primary isa-btn-pill isa-btn-medium"
(click)="changeProcessingStatus(262144)"
>
ans Lager
</button>
<button
*ngIf="showReOrderCta$ | async"
type="button"
class="isa-btn isa-btn-primary isa-btn-pill isa-btn-medium"
(click)="changeProcessingStatus(8192)"
>
nachbestellen
</button>
</div>
<div class="cta-spacer"></div>
<app-printer-selection></app-printer-selection>
<app-printer-selection></app-printer-selection>

View File

@@ -1,7 +1,18 @@
import { ChangeDetectionStrategy, Component, ViewChild } from '@angular/core';
import { DetailsFacade } from '@shelf-store/details';
import { ActivatedRoute } from '@angular/router';
import { map, switchMap, first, filter, flatMap, distinctUntilChanged, shareReplay, tap, take, takeUntil } from 'rxjs/operators';
import {
map,
switchMap,
first,
filter,
flatMap,
distinctUntilChanged,
shareReplay,
take,
takeUntil,
withLatestFrom,
} from 'rxjs/operators';
import { OrderDetailsCardInput } from '../../components/order-details-card';
import {
OrderItemProcessingStatusValue,
@@ -10,11 +21,9 @@ import {
ResponseArgsOfValueTupleOfOrderItemSubsetDTOAndOrderItemSubsetDTO,
} from '@swagger/oms';
import { ShelfNavigationService } from '../../shared/services';
import { race, combineLatest, Observable } from 'rxjs';
import { RemissionService } from '@isa/remission';
import { ProcessingStatusNameMap } from '../../constants';
import { race, combineLatest } from 'rxjs';
import { AppService } from '@sales/core-services';
import { OMSPrintService } from '@swagger/print';
import { OMSPrintService, PrintService } from '@swagger/print';
import { PrinterSelectionComponent } from 'apps/sales/src/app/components/printer-selection/printer-selection.component';
@Component({
@@ -24,10 +33,15 @@ import { PrinterSelectionComponent } from 'apps/sales/src/app/components/printer
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfOrderDetailsComponent {
@ViewChild(PrinterSelectionComponent, { read: PrinterSelectionComponent, static: true })
@ViewChild(PrinterSelectionComponent, {
read: PrinterSelectionComponent,
static: true,
})
printerSlectionComponent: PrinterSelectionComponent;
processingStatus$ = this.activatedRoute.params.pipe(map((params) => params['processingStatus']));
processingStatus$ = this.activatedRoute.params.pipe(
map((params) => params['processingStatus'])
);
partialPickup = false;
@@ -48,14 +62,24 @@ export class ShelfOrderDetailsComponent {
);
orderItems$ = race(
this.orderNumber$.pipe(switchMap((orderNumber) => this.detailsFacade.getOrderItemsByOrderNumber$(orderNumber))),
this.compartmentCode$.pipe(switchMap((compartmentCode) => this.detailsFacade.getOrderItemsByCompartmentCode$(compartmentCode)))
this.orderNumber$.pipe(
switchMap((orderNumber) =>
this.detailsFacade.getOrderItemsByOrderNumber$(orderNumber)
)
),
this.compartmentCode$.pipe(
switchMap((compartmentCode) =>
this.detailsFacade.getOrderItemsByCompartmentCode$(compartmentCode)
)
)
).pipe(
flatMap((items) =>
this.processingStatus$.pipe(
map((processingStatus) => {
if (!!processingStatus) {
return items.filter((item) => item.processingStatus === +processingStatus);
return items.filter(
(item) => item.processingStatus === +processingStatus
);
}
return items;
})
@@ -72,10 +96,13 @@ export class ShelfOrderDetailsComponent {
firstName: item.firstName,
lastName: item.lastName,
customerNumber: item.buyerNumber,
estimatedShippingDate: item.estimatedShippingDate ? new Date(item.estimatedShippingDate) : undefined,
estimatedShippingDate: item.estimatedShippingDate
? new Date(item.estimatedShippingDate)
: undefined,
orderDate: new Date(item.orderDate),
orderNumber: item.orderNumber,
processingStatus: item.processingStatus,
processingStatusDate: new Date(item.processingStatusDate),
orderChannel: item.clientChannel,
compartmentCode: item.compartmentCode,
compartmentInfo: item.compartmentInfo,
@@ -88,34 +115,52 @@ export class ShelfOrderDetailsComponent {
);
showArrivedAndPrintCta$ = this.orderDetailsCard$.pipe(
map((details) => details.processingStatus === 16 || details.processingStatus === 8192) // wenn bestellt(=16) oder nachbestellt(=8192)
map(
(details) =>
details.processingStatus === 16 || details.processingStatus === 8192
) // wenn bestellt(=16) oder nachbestellt(=8192)
);
showPickUpCta$ = this.orderDetailsCard$.pipe(
map((details) => details.processingStatus === 128) // wenn eingetroffen(=128)
);
showPickUpPartialCollectCta$ = combineLatest([this.orderDetailsCard$, this.orderItems$]).pipe(
showPickUpPartialCollectCta$ = combineLatest([
this.orderDetailsCard$,
this.orderItems$,
]).pipe(
map(([details, items]) => {
return details.processingStatus === 128 && (items.length > 1 || items.some((i) => i.quantity > 1));
return (
details.processingStatus === 128 &&
(items.length > 1 || items.some((i) => i.quantity > 1))
);
}) // wenn eingetroffen(=128) und mehr als 1 Artikel
);
showBackToStoreCta$ = this.orderDetailsCard$.pipe(
map((details) => details.processingStatus === 1024 || details.processingStatus === 512)
map(
(details) =>
details.processingStatus === 1024 || details.processingStatus === 512
)
);
showAddToRemissionListCta$ = this.orderDetailsCard$.pipe(
map((details) => details.processingStatus === 1024 || details.processingStatus === 512)
map(
(details) =>
details.processingStatus === 1024 || details.processingStatus === 512
)
);
showReOrderCta$ = this.orderDetailsCard$.pipe(map((details) => details.processingStatus === 2048));
showReOrderCta$ = this.orderDetailsCard$.pipe(
map((details) => details.processingStatus === 2048)
);
constructor(
private activatedRoute: ActivatedRoute,
private detailsFacade: DetailsFacade,
private shelfNavigationService: ShelfNavigationService,
private printService: OMSPrintService,
private omsPrintService: OMSPrintService,
private printerService: PrintService,
private appService: AppService
) {}
@@ -138,29 +183,43 @@ export class ShelfOrderDetailsComponent {
});
}
async changeProcessingStatus(status: OrderItemProcessingStatusValue, data: { compartmentInfo?: string } = {}) {
async changeProcessingStatus(
status: OrderItemProcessingStatusValue,
data: { compartmentInfo?: string } = {}
) {
let items = await this.orderItems$.pipe(first()).toPromise();
let results: ResponseArgsOfValueTupleOfOrderItemSubsetDTOAndOrderItemSubsetDTO[];
const navigate = this.partialPickup ? items.length === this.selectedForPartialPickup.size : true;
const navigate = this.partialPickup
? items.length === this.selectedForPartialPickup.size
: true;
if (status === 256) {
if (this.partialPickup) {
items = items.filter(({ orderItemId }) => this.selectedForPartialPickup.has(orderItemId));
items = items.filter(({ orderItemId }) =>
this.selectedForPartialPickup.has(orderItemId)
);
}
results = await this.detailsFacade.pickedUp(items, this.quantityForPartialPickup);
} else if (status === 131072) {
results = await this.detailsFacade.pickedUp(
items,
this.quantityForPartialPickup
);
} else if (status === 262144) {
results = await this.detailsFacade.backToStock(items);
} else if (status === 8192) {
results = await this.detailsFacade.reorder(items);
} else if (status === 128) {
results = await this.detailsFacade.arrived(items, data.compartmentInfo);
this.printAbholfachetikett(items.map(({ orderItemSubsetId }) => orderItemSubsetId));
this.printAbholfachetikett(
items.map(({ orderItemSubsetId }) => orderItemSubsetId)
);
} else {
const payloads = items.map(({ orderId, orderItemId, orderItemSubsetId }) => ({
orderId,
orderItemId,
orderItemSubsetId,
data: { processingStatus: status } as StatusValues,
}));
const payloads = items.map(
({ orderId, orderItemId, orderItemSubsetId }) => ({
orderId,
orderItemId,
orderItemSubsetId,
data: { processingStatus: status } as StatusValues,
})
);
results = await this.detailsFacade.changeStatus(payloads);
}
@@ -171,17 +230,27 @@ export class ShelfOrderDetailsComponent {
if (navigate) {
const firstItem = items[0];
const firstResult = results[0].result;
this.shelfNavigationService.updateDetailsNavigation({
compartmentCode: firstResult.item1.compartmentCode,
orderNumber: firstItem.orderNumber,
processingStatus: firstResult.item1.processingStatus,
});
this.shelfNavigationService.updateDetails(
{
compartmentCode: firstResult.item1.compartmentCode,
orderNumber: firstItem.orderNumber,
processingStatus: firstResult.item1.processingStatus,
},
{
compartmentCode: firstItem.compartmentCode,
orderNumber: firstItem.orderNumber,
processingStatus: firstItem.processingStatus,
}
);
}
}
changeEstimatedShippingDate(estimatedShippingDate: Date | string) {
this.orderItems$.pipe(first()).subscribe((items) => {
this.detailsFacade.setEstimatedShippingDate({ items, estimatedShippingDate });
this.detailsFacade.setEstimatedShippingDate({
items,
estimatedShippingDate,
});
});
}
@@ -191,31 +260,68 @@ export class ShelfOrderDetailsComponent {
});
}
printAbholfachetikett(data: number[]) {
async printAbholfachetikett(data: number[]) {
// tslint:disable-next-line: no-shadowed-variable
const print = (printer?: string) =>
this.printService
this.omsPrintService
.OMSPrintAbholfachetikettResponse({
data,
printer,
})
.toPromise();
let printer: string;
if (this.appService.isIPadEnv()) {
this.printerSlectionComponent.openDialog();
this.printerSlectionComponent.print.pipe(takeUntil(this.printerSlectionComponent.closed), take(1)).subscribe((printer) => {
print(printer);
this.printerSlectionComponent.closeModal();
});
} else {
print();
printer = await this.printerSlectionComponent.print
.pipe(takeUntil(this.printerSlectionComponent.closed), take(1))
.toPromise();
this.printerSlectionComponent.closeModal();
}
if (!printer) {
printer = await this.getDefaultPrinter();
}
try {
print(printer);
} catch (error) {
console.log('Printing Abholfachetikett Failed');
}
}
async getDefaultPrinter(): Promise<string> {
const printers = await this.printerService.PrintLabelPrinters().toPromise();
const selected = printers.result.find((f) => f.selected);
if (!!selected) {
return selected.key;
}
return undefined;
}
activatePartialPickup() {
this.partialPickup = true;
}
navigateToDetails() {}
async navigateToDetails() {
const data = await race(
this.orderNumber$.pipe(map((orderNumber) => ({ orderNumber }))),
this.compartmentCode$.pipe(
map((compartmentCode) => ({ compartmentCode }))
)
)
.pipe(
withLatestFrom(this.processingStatus$),
map(([d, processingStatus]) => ({ ...d, processingStatus })),
first()
)
.toPromise();
this.shelfNavigationService.navigateToEdit(data);
}
navigateToHistory(orderItem: OrderItemListItemDTO) {
this.shelfNavigationService.navigateToHistory(orderItem);

View File

@@ -1,5 +1,5 @@
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core';
import { FormControl } from '@angular/forms';
import { FormControl, Validators } from '@angular/forms';
import { Subscription } from 'rxjs';
@Component({
@@ -19,7 +19,7 @@ export class ShelfOrderDetailsShelfTagsComponent implements OnInit, OnDestroy {
@Input()
tags = ['Maxi', 'Mini', 'Kleinkram', 'Nonbook'];
customValue = new FormControl('');
customValue = new FormControl('', [Validators.required, Validators.maxLength(15)]);
subscription = new Subscription();

View File

@@ -1,6 +1,13 @@
import { Subject, fromEvent, combineLatest } from 'rxjs';
import { Component, OnDestroy, OnInit, ViewChild, ElementRef, ChangeDetectionStrategy } from '@angular/core';
import {
Component,
OnDestroy,
OnInit,
ViewChild,
ElementRef,
ChangeDetectionStrategy,
} from '@angular/core';
import { SearchStateFacade } from 'apps/sales/src/app/store/customer';
import { first, takeUntil, map } from 'rxjs/operators';
import { groupBy } from 'apps/sales/src/app/utils';
@@ -19,11 +26,16 @@ export class ShelfSearchResultsComponent implements OnInit, OnDestroy {
destroy$ = new Subject();
grouped$ = this.searchStateFacade.result$.pipe(map((results) => groupBy(results, (item) => (item ? item.buyerNumber : ''))));
grouped$ = this.searchStateFacade.result$.pipe(
map((results) => groupBy(results, (item) => (item ? item.buyerNumber : '')))
);
fetching$ = this.searchStateFacade.fetching$;
constructor(private searchStateFacade: SearchStateFacade, private shelfNavigationService: ShelfNavigationService) {}
constructor(
private searchStateFacade: SearchStateFacade,
private shelfNavigationService: ShelfNavigationService
) {}
ngOnInit() {
this.initScrollContainer();
@@ -43,16 +55,25 @@ export class ShelfSearchResultsComponent implements OnInit, OnDestroy {
reachedBottom() {
const scrollContainer: HTMLElement = this.scrollContainer.nativeElement;
return scrollContainer.scrollHeight - (scrollContainer.scrollTop + scrollContainer.clientHeight) - 100 <= 0;
return (
scrollContainer.scrollHeight -
(scrollContainer.scrollTop + scrollContainer.clientHeight) -
100 <=
0
);
}
async fetch(force = false) {
const [hits, result, fetching] = await combineLatest([this.searchStateFacade.hits$, this.searchStateFacade.result$, this.fetching$])
const [hits, result, fetching] = await combineLatest([
this.searchStateFacade.hits$,
this.searchStateFacade.result$,
this.fetching$,
])
.pipe(first())
.toPromise();
if (force || !hits || (!result.length && !fetching)) {
this.searchStateFacade.fetchResult();
this.searchStateFacade.fetchResult({});
}
}

View File

@@ -275,6 +275,11 @@ export class ShelfSearchInputComponent
take(1)
)
.subscribe(([_, numberOfHits, searchQuery, result]) => {
this.shelfNavigationService.updateResultPageBreadcrumb({
numberOfHits,
searchQuery,
});
if (this.shouldNavigateToResultList(numberOfHits)) {
this.resetSessionStorage();

View File

@@ -0,0 +1,19 @@
import { CreateImageSourceFromEanPipe } from './generate-image-src-from-ean.pipe';
describe('Pipe: CreateImageSourceFromEanPipe', () => {
let pipe: CreateImageSourceFromEanPipe;
const ean = '123456';
beforeEach(() => {
pipe = new CreateImageSourceFromEanPipe();
});
it('should return the image url for the provided ean', () => {
const result = pipe.transform(ean);
expect(result).toEqual(
`https://produktbilder.ihugendubel.de/${ean}.jpg?showDummy=true`
);
});
});

View File

@@ -0,0 +1,12 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'createImageSourceFromEan',
})
export class CreateImageSourceFromEanPipe implements PipeTransform {
transform(ean: string): string {
return `https://produktbilder.ihugendubel.de/
${ean}
.jpg?showDummy=true`;
}
}

View File

@@ -0,0 +1,17 @@
import { Pipe, PipeTransform } from '@angular/core';
import { calculateExtendedPickUpDate } from '../shared/utils';
@Pipe({
name: 'getExtendPickUpOptions',
})
export class GetExtendPickUpOptionsPipe implements PipeTransform {
transform(pickUpDate: string): string[] {
if (!pickUpDate) {
return [];
}
return [
calculateExtendedPickUpDate(pickUpDate, 2),
calculateExtendedPickUpDate(pickUpDate, 4),
];
}
}

View File

@@ -1,5 +1,11 @@
// start:ng42.barrel
export * from './environment-channel.pipe';
export * from './generate-image-src-from-ean.pipe';
export * from './get-extend-pick-up-date-options.pipe';
export * from './pick-up-date-option-to-display-value.pipe';
export * from './processing-status-options.pipe';
export * from './processing-status.pipe';
export * from './shelf-pipes.module';
export * from './vat-dto-to-vat-value.pipe';
export * from './vat-dto-to-vat-type.pipe';
// end:ng42.barrel

View File

@@ -0,0 +1,18 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'pickUpDateOptionsToDisplayValues',
})
export class PickUpDateOptionsToDisplayValuesPipe implements PipeTransform {
transform(pickUpDate: string, index: number): string {
if (!pickUpDate) {
return;
}
if (index === 0) {
return '2 Wochen verlängern';
} else if (index === 1) {
return '4 Wochen verlängern';
}
}
}

View File

@@ -0,0 +1,22 @@
import { ProcessingStatusOptionsPipe } from './processing-status-options.pipe';
fdescribe('ProcessingStatusOptionsPipe', () => {
let pipe: ProcessingStatusOptionsPipe;
// activated code
const bestelltStatuscode = 16;
// deactivated code
const nachbestelltStatusCode = 8192;
beforeEach(() => {
pipe = new ProcessingStatusOptionsPipe();
});
it('should return bestellt (1)', () => {
const result = pipe.transform([bestelltStatuscode, nachbestelltStatusCode]);
expect(result.length).toBe(1);
expect(result).toEqual([bestelltStatuscode]);
});
});

View File

@@ -0,0 +1,47 @@
import { Pipe, PipeTransform } from '@angular/core';
import { ProcessingStatusNameMap } from '../constants';
import { KeyValue } from '@angular/common';
@Pipe({
name: 'processingStatusOptionsPipe',
})
export class ProcessingStatusOptionsPipe implements PipeTransform {
transform(states: number[]): number[] {
return states
.filter((state) => {
const result = ProcessingStatusNameMap.get(state);
if (!result || result.disabled) {
return false;
}
return true;
})
.sort((a, b) => {
const aValue = ProcessingStatusNameMap.get(a);
const bValue = ProcessingStatusNameMap.get(b);
return aValue.value.toUpperCase() === bValue.value.toUpperCase()
? 0
: aValue.value.toUpperCase() > bValue.value.toUpperCase()
? 1
: -1;
});
}
}
@Pipe({
name: 'processingStatusOptionsKeyValuePipe',
})
export class ProcessingStatusOptionsKeyValuePipe implements PipeTransform {
transform(states: KeyValue<number, { value: string; disabled: boolean }>[]): KeyValue<number, { value: string; disabled: boolean }>[] {
return states
.filter((state) => !state.value.disabled)
.sort((a, b) => {
return a.value.value.toUpperCase() === b.value.value.toUpperCase()
? 0
: a.value.value.toUpperCase() > b.value.value.toUpperCase()
? 1
: -1;
});
}
}

View File

@@ -0,0 +1,26 @@
import { ProcessingStatusPipe } from './processing-status.pipe';
import { OrderItemProcessingStatusValue } from '@swagger/oms/lib';
fdescribe('ProcessingStatusPipe', () => {
let pipe: ProcessingStatusPipe;
beforeEach(() => {
pipe = new ProcessingStatusPipe();
});
it('should return the status name for status 16 (bestellt)', () => {
const bestelltStatusCode = 16;
const result = pipe.transform(bestelltStatusCode);
expect(result).toEqual('bestellt');
});
it('should return an empty string if the status code is not found', () => {
const notExistingStatusCode = 17;
const result = pipe.transform(
notExistingStatusCode as OrderItemProcessingStatusValue
);
expect(result).toEqual('');
});
});

View File

@@ -1,42 +1,11 @@
import { Pipe, PipeTransform } from '@angular/core';
import { OrderItemProcessingStatusValue } from '@swagger/oms';
import { ProcessingStatusNameMap } from '../constants';
@Pipe({
name: 'processingStatus',
})
export class ProcessingStatusPipe implements PipeTransform {
map = {
0: '',
1: 'bestellt',
2: 'Placed',
4: 'Accepted',
8: 'Parked',
16: 'bestellt',
32: 'Vorbereitung Versand',
64: 'versendet',
128: 'eingetroffen',
256: 'abgeholt',
512: 'storniert (Kunde)',
1024: 'storniert',
2048: 'storniert (Lieferant)',
4096: 'NotAvailable',
8192: 'nachbestellt ',
16384: 'ReturnedByBuyer',
32768: 'AvailableForDownload',
65536: 'Downloaded',
131072: 'NotFetched',
262144: 'Zurück zum Lager',
524288: 'angefragt',
1048576: 'RedirectedInternally',
2097152: 'Overdue',
4194304: 'Delivered',
8388608: 'DetermineSupplier',
16777216: 'SupplierTemporarilyOutOfStock',
33554432: 'Reserved',
67108864: 'Assembled',
134217728: 'Packed',
};
icon = {
16: 'Check',
128: 'Check',
@@ -45,7 +14,14 @@ export class ProcessingStatusPipe implements PipeTransform {
2048: 'close',
};
transform(value: OrderItemProcessingStatusValue, type: 'icon' | 'text' = 'text'): string {
return (type === 'icon' ? this.icon[value] : this.map[value]) || '';
transform(
value: OrderItemProcessingStatusValue,
type: 'icon' | 'text' = 'text'
): string {
return type === 'icon'
? this.icon[value]
: ProcessingStatusNameMap.get(value)
? ProcessingStatusNameMap.get(value).value
: '';
}
}

View File

@@ -3,12 +3,42 @@ import { NgModule } from '@angular/core';
import { ProcessingStatusPipe } from './processing-status.pipe';
import { CommonModule } from '@angular/common';
import { EnvironmentChannelPipe } from './environment-channel.pipe';
import { GetExtendPickUpOptionsPipe } from './get-extend-pick-up-date-options.pipe';
import { PickUpDateOptionsToDisplayValuesPipe } from './pick-up-date-option-to-display-value.pipe';
import { CreateImageSourceFromEanPipe } from './generate-image-src-from-ean.pipe';
import { VatDtoToVatValuePipe } from './vat-dto-to-vat-value.pipe';
import { VatTypeToDisplayNamePipe } from './vat-type-to-display-name.pipe';
import { VatDtoToVatTypePipe } from './vat-dto-to-vat-type.pipe';
import { VatTypePipe } from './vat-type.pipe';
import { ProcessingStatusOptionsPipe, ProcessingStatusOptionsKeyValuePipe } from './processing-status-options.pipe';
@NgModule({
imports: [CommonModule],
exports: [ProcessingStatusPipe, EnvironmentChannelPipe, VatTypePipe],
declarations: [ProcessingStatusPipe, EnvironmentChannelPipe, VatTypePipe],
providers: [],
exports: [
VatTypeToDisplayNamePipe,
ProcessingStatusPipe,
ProcessingStatusOptionsPipe,
EnvironmentChannelPipe,
GetExtendPickUpOptionsPipe,
PickUpDateOptionsToDisplayValuesPipe,
CreateImageSourceFromEanPipe,
VatDtoToVatValuePipe,
VatDtoToVatTypePipe,
VatTypePipe,
ProcessingStatusOptionsKeyValuePipe,
],
declarations: [
VatTypeToDisplayNamePipe,
ProcessingStatusPipe,
ProcessingStatusOptionsPipe,
EnvironmentChannelPipe,
GetExtendPickUpOptionsPipe,
PickUpDateOptionsToDisplayValuesPipe,
CreateImageSourceFromEanPipe,
VatDtoToVatValuePipe,
VatDtoToVatTypePipe,
VatTypePipe,
ProcessingStatusOptionsKeyValuePipe,
],
})
export class ShelfPipesModule {}

View File

@@ -0,0 +1,17 @@
import { VatDtoToVatTypePipe } from './vat-dto-to-vat-type.pipe';
import { vatDtosMock } from '../shared/mockdata';
fdescribe('Pipe: VatDtoToVatTypePipe', () => {
let pipe: VatDtoToVatTypePipe;
beforeEach(() => {
pipe = new VatDtoToVatTypePipe();
});
it('should return the vat type (8)', () => {
const mock = vatDtosMock.find((vat) => vat.vatType === 8);
const result = pipe.transform(mock);
expect(result).toBe(8);
});
});

View File

@@ -0,0 +1,11 @@
import { Pipe, PipeTransform } from '@angular/core';
import { VATDTO } from '@swagger/checkout/lib';
@Pipe({
name: 'vatDtoToVatType',
})
export class VatDtoToVatTypePipe implements PipeTransform {
transform(vatDTO: VATDTO): number {
return vatDTO.vatType;
}
}

View File

@@ -0,0 +1,19 @@
import { VATDTO } from '@swagger/oms';
import { VatDtoToVatValuePipe } from './vat-dto-to-vat-value.pipe';
import { vatDtosMock } from '../shared/mockdata';
fdescribe('Pipe: VatDtoToVatValuePipe', () => {
let pipe: VatDtoToVatValuePipe;
beforeEach(() => {
pipe = new VatDtoToVatValuePipe();
});
it('should return 5%', () => {
const mock: VATDTO = vatDtosMock.find((vat) => vat.name === '5');
const result = pipe.transform(mock);
const expected = '5%';
expect(result).toEqual(expected);
});
});

View File

@@ -0,0 +1,14 @@
import { Pipe, PipeTransform } from '@angular/core';
import { VATDTO } from '@swagger/oms';
@Pipe({
name: 'vatDtoToVatValue',
})
export class VatDtoToVatValuePipe implements PipeTransform {
transform(vatDTO: VATDTO): string {
if (!vatDTO) {
return;
}
return `${vatDTO.value.toFixed()}%`;
}
}

View File

@@ -0,0 +1,19 @@
import { VatTypeToDisplayNamePipe } from './vat-type-to-display-name.pipe';
import { vatDtosMock } from '../shared/mockdata';
fdescribe('Pipe: VatTypeToDisplayNamePipe', () => {
let pipe: VatTypeToDisplayNamePipe;
beforeEach(() => {
pipe = new VatTypeToDisplayNamePipe();
});
it('should return 5%', () => {
const mock = vatDtosMock.find((vat) => vat.name === '5').vatType;
const result = pipe.transform(mock, vatDtosMock);
const expected = '5%';
expect(result).toEqual(expected);
});
});

View File

@@ -0,0 +1,17 @@
import { Pipe, PipeTransform } from '@angular/core';
import { VatDtoToVatValuePipe } from './vat-dto-to-vat-value.pipe';
import { VATDTO } from '@swagger/oms';
@Pipe({
name: 'vatTypeToDisplayName',
})
export class VatTypeToDisplayNamePipe implements PipeTransform {
constructor() {}
transform(vatType: number, vatOptions: VATDTO[]) {
const match = vatOptions && vatOptions.find((vat) => vat.vatType === vatType);
if (match) {
return new VatDtoToVatValuePipe().transform(match);
}
}
}

View File

@@ -1,7 +1,7 @@
import { Pipe, PipeTransform, ChangeDetectorRef, OnDestroy } from '@angular/core';
import { Pipe, PipeTransform, ChangeDetectorRef, OnDestroy, Injectable } from '@angular/core';
import { VATType } from '@swagger/oms';
import { VatService } from '../../../core/services/vat.service';
import { first, take, takeUntil } from 'rxjs/operators';
import { take, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
@Pipe({

View File

@@ -1,5 +1,5 @@
// start:ng42.barrel
export * from './shelf-filter.service';
export * from './shelf-overlay.service';
export * from './shelf-edit-form.service';
// end:ng42.barrel

View File

@@ -0,0 +1,30 @@
import { TestBed } from '@angular/core/testing';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ShelfEditFormService } from './shelf-edit-form.service';
import { DetailsFacade } from '@shelf-store/details';
fdescribe('ShelfEditFormService', () => {
let service: ShelfEditFormService;
let facade: jasmine.SpyObj<DetailsFacade>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CommonModule, FormsModule, ReactiveFormsModule],
providers: [
ShelfEditFormService,
{
provide: DetailsFacade,
useValue: jasmine.createSpy('detailsFacade'),
},
],
});
service = TestBed.get(ShelfEditFormService);
facade = TestBed.get(DetailsFacade);
});
it('should be created', () => {
expect(service instanceof ShelfEditFormService).toBeTruthy();
});
});

View File

@@ -0,0 +1,399 @@
import { Injectable, ɵbypassSanitizationTrustResourceUrl } from '@angular/core';
import {
FormBuilder,
FormGroup,
Validators,
FormArray,
AbstractControl,
} from '@angular/forms';
import { Observable, of, from } from 'rxjs';
import {
OrderItemListItemDTO,
VATDTO,
OrderItemDTO,
OrderItemProcessingStatusValue,
StatusValues,
EnvironmentChannel,
VATType,
OrderItemSubsetDTO,
} from '@swagger/oms';
import { DetailsFacade } from '@shelf-store/details';
import { take, filter } from 'rxjs/operators';
import { isNullOrUndefined } from 'util';
import { EnvironmentChannelPipe } from '../pipes/environment-channel.pipe';
import { DatePipe } from '@angular/common';
import { ProcessingStatusNameMap } from '../constants';
import { Select } from '@ngxs/store';
import { VatState } from '../../../core/store/state/vat.state';
@Injectable({ providedIn: 'root' })
export class ShelfEditFormService {
@Select(VatState.getVats) vats$: Observable<VATDTO[]>;
forms = new Map<string, FormGroup>();
public get processingStatus(): number[] {
return Array.from(ProcessingStatusNameMap.keys());
}
public get availableProcessingStatus(): number[] {
return this.processingStatus.filter((status) => {
const result = ProcessingStatusNameMap.get(status);
if (!result || !result.disabled) {
return false;
}
return true;
});
}
public get vatOptions$(): Observable<VATDTO[]> {
return this.vats$.pipe(filter((vats) => !!vats.length));
}
constructor(
private formBuilder: FormBuilder,
private detailsStoreFacade: DetailsFacade
) {}
public getItemsForm(compartmentCode: string): FormArray {
if (!this.forms.has(compartmentCode)) {
return new FormArray([]);
}
return this.forms.get(compartmentCode).get('items') as FormArray;
}
public getCustomerName(
compartmentCode: string
): { firstName: string; lastName: string } {
if (!this.forms.has(compartmentCode)) {
return {
firstName: 'unbekannt',
lastName: '',
};
}
const form = this.forms.get(compartmentCode);
return {
firstName: form.get('firstName').value || '',
lastName: form.get('lastName').value || '',
};
}
async createFormByOrderNumber(
orderNumber: string,
processingStatus: OrderItemProcessingStatusValue
): Promise<FormGroup> {
const controlsConfig = await this.getControlsConfigForOrder(
orderNumber,
processingStatus
);
const form = this.formBuilder.group(controlsConfig);
this.forms.set(orderNumber, form);
return this.forms.get(orderNumber);
}
async createFormByCompartmentCode(
compartmentCode: string,
processingStatus: OrderItemProcessingStatusValue
): Promise<FormGroup> {
const controlsConfig = await this.getControlsConfigForCompartment(
compartmentCode,
processingStatus
);
const form = this.formBuilder.group(controlsConfig);
this.forms.set(compartmentCode, form);
return this.forms.get(compartmentCode);
}
async getControlsConfigForOrder(
orderNumber: string,
processingStatus: OrderItemProcessingStatusValue
): Promise<{ [key: string]: any }> {
const orderItems$: Observable<
OrderItemListItemDTO[]
> = this.detailsStoreFacade.getOrderItemsByOrderNumberAndProcessingStatus$(
orderNumber,
processingStatus
);
return this.createControlConfig(orderItems$);
}
async getControlsConfigForCompartment(
compartmentCode: string,
processingStatus: OrderItemProcessingStatusValue
): Promise<{ [key: string]: any }> {
const orderItems$: Observable<
OrderItemListItemDTO[]
> = this.detailsStoreFacade.getOrderItemsByCompartmentCodeAndProcessingStatus$(
compartmentCode,
processingStatus
);
return this.createControlConfig(orderItems$);
}
private async createControlConfig(
orderItems$: Observable<OrderItemListItemDTO[]>
): Promise<{ [key: string]: any }> {
const orderItems = await orderItems$
.pipe(
filter((items) => !isNullOrUndefined(items) && !!items.length),
take(1)
)
.toPromise();
const vats = await this.vats$.pipe(take(1)).toPromise();
return {
...this.getGeneralForm(orderItems),
items: this.formBuilder.array(
orderItems.map((orderItem) => this.getItemForm(orderItem, vats))
),
};
}
getGeneralForm(orderItems: OrderItemListItemDTO[]): { [key: string]: any } {
const baseOrderItem = orderItems[0];
return {
firstName: baseOrderItem.firstName,
lastName: baseOrderItem.lastName,
compartmentCode: [
{ value: baseOrderItem.compartmentCode, disabled: false },
],
orderId: [{ value: baseOrderItem.orderId, disabled: true }],
orderNumber: [
{
value: baseOrderItem.orderNumber,
disabled: true,
},
],
orderDate: [
{
value: new DatePipe('de').transform(baseOrderItem.orderDate),
disabled: true,
},
],
clientChannel: [
{
value: new EnvironmentChannelPipe().transform(
baseOrderItem.clientChannel
),
disabled: true,
},
],
buyerNumber: [{ value: baseOrderItem.buyerNumber, disabled: true }],
processingStatus: [
{ value: baseOrderItem.processingStatus, disabled: false },
],
estimatedShippingDate: [
{ value: baseOrderItem.estimatedShippingDate, disabled: false },
],
pickUpDeadline: [
{
value: baseOrderItem.pickUpDeadline,
disabled: false,
},
],
};
}
getDataFromGeneralForm(form: FormGroup) {
return {
firstName: form.get('firstName').value,
lastName: form.get('lastName').value,
compartmentCode: form.get('compartmentCode').value,
orderId: Number(form.get('orderId').value),
orderNumber: form.get('orderNumber').value,
orderDate: new Date(form.get('orderDate').value),
clientChannel: Number(
form.get('clientChannel').value
) as EnvironmentChannel,
buyerNumber: form.get('buyerNumber').value,
processingStatus: Number(
form.get('processingStatus').value
) as OrderItemProcessingStatusValue,
pickUpDeadline: String(form.get('pickUpDeadline').value || ''),
estimatedShippingDate: String(
form.get('estimatedShippingDate').value || ''
),
};
}
getItemForm(orderItem: OrderItemListItemDTO, vats: VATDTO[]): FormGroup {
return this.formBuilder.group({
orderId: [{ value: orderItem.orderId, disabled: true }],
orderItemId: [{ value: orderItem.orderItemId, disabled: true }],
orderItemSubsetId: [
{ value: orderItem.orderItemSubsetId, disabled: true },
],
quantity: [{ value: orderItem.quantity, disabled: true }],
price: [
{ value: String(orderItem.price).replace('.', ','), disabled: false },
[Validators.required, Validators.pattern(/^\d+(,\d{1,2})?$/)],
],
ean: [
{
value: orderItem.product.ean,
disabled: false,
},
],
supplier: [{ value: orderItem.supplier, disabled: true }],
title: [{ value: orderItem.product.name, disabled: true }],
ssc: [
{
value: orderItem.ssc
? orderItem.ssc + ' - ' + orderItem.sscText
: '-',
disabled: false,
},
],
clientChannel: [{ value: orderItem.clientChannel, disabled: true }],
targetBranch: [{ value: orderItem.targetBranch, disabled: true }],
vat: [
{
value: orderItem.vatType,
disabled: false,
},
],
comment: [{ value: orderItem.specialComment, disabled: false }],
});
}
getOrderItemFromItemForm(form: AbstractControl): OrderItemDTO {
return {
id: Number(form.get('orderItemId').value),
quantity: Number(form.get('quantity').value),
grossPrice: {
vat: { vatType: Number(form.get('vat').value) as VATType },
value: { value: Number(form.get('price').value.replace(',', '.')) },
},
product: { ean: String(form.get('ean').value) },
};
}
getOrderItemSubsetFromItemForm(
ctrl: AbstractControl,
form: FormGroup
): OrderItemSubsetDTO & { orderItemId: number } {
const sscFull: string = ctrl.get('ssc').value;
let ssc = sscFull.split('-')[0];
let sscText = sscFull.substr(ssc.length + 1);
ssc = ssc.trim();
sscText = sscText.trim();
return {
id: Number(ctrl.get('orderItemSubsetId').value),
orderItemId: Number(ctrl.get('orderItemId').value),
orderItemSubsetNumber: ctrl.get('orderItemSubsetId').value,
processingStatus: form.get('processingStatus').value,
estimatedShippingDate: String(form.get('estimatedShippingDate').value),
compartmentCode: !!form.get('compartmentCode').value
? String(form.get('compartmentCode').value)
: null,
specialComment: String(ctrl.get('comment').value || ''),
ssc,
sscText,
};
}
async submit(
form: FormGroup,
processingStatus: OrderItemProcessingStatusValue
): Promise<boolean> {
const formData = this.getDataFromGeneralForm(form);
console.log({ formData });
const orderItemsToPatch = this.prepareOrderItems(form);
const orderItemSubsetsToPatch = this.prepareOrderItemSubsets(form);
const newProcessingStatus = Number(
form.get('processingStatus').value
) as OrderItemProcessingStatusValue;
try {
await this.detailsStoreFacade.patchOrderItems(orderItemsToPatch);
await this.detailsStoreFacade.patchOrderItemSubsets(
orderItemSubsetsToPatch
);
if (newProcessingStatus !== processingStatus) {
const changeStatusData = this.prepareStatusChange(
form,
newProcessingStatus
);
if (newProcessingStatus === 128) {
await this.detailsStoreFacade.arrived(changeStatusData, undefined);
} else {
await this.detailsStoreFacade.changeStatus(changeStatusData);
}
}
} catch (error) {
console.log('%cSubmit Error: ', 'color: red; font-weight: bold', error);
return false;
}
return true;
}
private prepareOrderItems(form: FormGroup) {
const orderItems = (form.get(
'items'
) as FormArray).controls.map((control) =>
this.getOrderItemFromItemForm(control)
);
return orderItems.map((orderItem) => ({
orderId: Number(form.get('orderId').value),
orderItemId: orderItem.id,
orderItem,
}));
}
private prepareOrderItemSubsets(
form: FormGroup
): {
orderId: number;
orderItemId: number;
orderItemSubsetId: number;
orderItemSubset: Partial<OrderItemSubsetDTO>;
}[] {
const subsetItems = (form.get(
'items'
) as FormArray).controls.map((control) =>
this.getOrderItemSubsetFromItemForm(control, form)
);
return subsetItems.map((item) => ({
orderId: form.get('orderId').value,
orderItemId: item.orderItemId,
orderItemSubsetId: item.id,
orderItemSubset: { ...item },
}));
}
private prepareStatusChange(
form: FormGroup,
processingStatus: OrderItemProcessingStatusValue
): {
orderItemSubsetId: number;
orderItemId: number;
orderId: number;
data: StatusValues;
}[] {
const items = (form.get('items') as FormArray).controls.map((itemForm) =>
this.getOrderItemSubsetFromItemForm(itemForm, form)
);
return items.map((item) => ({
orderId: Number(form.get('orderId').value),
orderItemId: item.orderItemId,
orderItemSubsetId: Number(item.orderItemSubsetNumber),
data: { processingStatus } as StatusValues,
}));
}
}

View File

@@ -1,7 +1,14 @@
import { Injectable, OnDestroy } from '@angular/core';
import { Observable, BehaviorSubject, Subject, combineLatest } from 'rxjs';
import { SelectFilter, Filter, SelectFilterOption } from '../../filter';
import { tap, startWith, map, filter, takeUntil } from 'rxjs/operators';
import {
startWith,
map,
filter,
takeUntil,
take,
distinctUntilChanged,
} from 'rxjs/operators';
import { SearchStateFacade } from '../../../store/customer';
import { flatten } from '../../../shared/utils';
import { isNullOrUndefined } from 'util';
@@ -60,13 +67,11 @@ export class ShelfFilterService implements OnDestroy {
get pendingFiltersShouldUpdate$() {
return combineLatest([
this.filters$.pipe(filter((filters) => !isNullOrUndefined(filters))),
this.resetFilters$.pipe(
startWith(),
tap(() => {
this.pendingFilters = null;
})
this.filters$.pipe(
filter((filters) => !isNullOrUndefined(filters)),
distinctUntilChanged((a, b) => this.isJsonEqual(a, b))
),
this.resetFilters$.pipe(startWith()),
]);
}
@@ -81,12 +86,14 @@ export class ShelfFilterService implements OnDestroy {
}
initPendingFilters() {
this.setInitialPendingFilters();
this.pendingFiltersShouldUpdate$
.pipe(takeUntil(this.destroy$))
.subscribe(([filters]) => {
this.pendingFilters = filters.map(cloneFilter);
this.setInitialFilterGroupLastChanged(filters);
});
.pipe(
takeUntil(this.destroy$),
map(([filters]) => filters)
)
.subscribe(this.updatePendingFilters);
}
updateFilters(updatedFilters: SelectFilter[]) {
@@ -106,6 +113,21 @@ export class ShelfFilterService implements OnDestroy {
this.overlayClosed$.next();
}
private setInitialPendingFilters() {
this.filters$
.pipe(
filter((filters) => !isNullOrUndefined(filters)),
take(1),
takeUntil(this.destroy$)
)
.subscribe(this.updatePendingFilters);
}
private updatePendingFilters = (filters: SelectFilter[]) => {
this.pendingFilters = filters.map(cloneFilter);
this.setInitialFilterGroupLastChanged(filters);
};
private setInitialFilterGroupLastChanged(filters: SelectFilter[]) {
if (!this.lastFilterGroupChanged$.value) {
this.lastFilterGroupChanged$.next(filters[0]);
@@ -123,4 +145,8 @@ export class ShelfFilterService implements OnDestroy {
}
return false;
}
private isJsonEqual(oldValue: any, newValue: any): boolean {
return JSON.stringify(oldValue) === JSON.stringify(newValue);
}
}

View File

@@ -1,4 +1,3 @@
// start:ng42.barrel
export * from './focus.directive';
// end:ng42.barrel

View File

@@ -1,6 +1,8 @@
// start:ng42.barrel
export * from './historyDto.mock';
export * from './filters.mock';
export * from './historyDto.mock';
export * from './primary-filters.mock';
export * from './select-filter-option.mock';
export * from './vat-dtos.mock';
// end:ng42.barrel

View File

@@ -0,0 +1,43 @@
import { VATDTO } from '@swagger/oms';
export const vatDtosMock: VATDTO[] = [
{
name: '0',
value: 0.0,
country: { id: 1243, enabled: true },
vatType: 1,
start: '2020-06-01T00:00:00',
stop: '2021-01-01T00:00:00',
id: 1041,
created: '2020-06-18T07:19:07Z',
changed: '2020-06-18T07:19:07Z',
version: 1,
status: 1,
},
{
name: '5',
value: 5.0,
country: { id: 1243, enabled: true },
vatType: 8,
start: '2020-06-01T00:00:00',
stop: '2021-01-01T00:00:00',
id: 1042,
created: '2020-06-18T07:19:08Z',
changed: '2020-06-18T07:19:08Z',
version: 1,
status: 1,
},
{
name: '16',
value: 16.0,
country: { id: 1243, enabled: true },
vatType: 2,
start: '2020-06-01T00:00:00',
stop: '2021-01-01T00:00:00',
id: 1043,
created: '2020-06-18T07:19:08Z',
changed: '2020-06-18T07:19:08Z',
version: 1,
status: 1,
},
];

View File

@@ -1,8 +1,17 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Store } from '@ngxs/store';
import { AddBreadcrumb, UpdateCurrentBreadcrumbName } from 'apps/sales/src/app/core/store/actions/breadcrumb.actions';
import { ChangeCurrentRoute, AddProcess } from 'apps/sales/src/app/core/store/actions/process.actions';
import {
AddBreadcrumb,
UpdateCurrentBreadcrumbName,
PopLastBreadcrumbs,
ClearBreadcrumbs,
DeleteBreadcrumbsForProcess,
} from 'apps/sales/src/app/core/store/actions/breadcrumb.actions';
import {
ChangeCurrentRoute,
AddProcess,
} from 'apps/sales/src/app/core/store/actions/process.actions';
import { Breadcrumb } from 'apps/sales/src/app/core/models/breadcrumb.model';
import { OrderItemListItemDTO } from '@swagger/oms/lib';
import { ProcessSelectors } from 'apps/sales/src/app/core/store/selectors/process.selectors';
@@ -12,7 +21,11 @@ import { Process } from 'apps/sales/src/app/core/models/process.model';
export class ShelfNavigationService {
constructor(private store: Store, private router: Router) {}
navigateToDetails(order: { orderNumber?: string; compartmentCode?: string; processingStatus?: number }) {
navigateToDetails(order: {
orderNumber?: string;
compartmentCode?: string;
processingStatus?: number;
}) {
this.createTab();
const path = this.getDetailsPath(order);
const breadcrumb = this.getDetailsBreadcrumb(order);
@@ -20,7 +33,23 @@ export class ShelfNavigationService {
this.navigateToRoute(path, breadcrumb);
}
updateDetailsNavigation(order: { orderNumber?: string; compartmentCode?: string; processingStatus?: number }) {
navigateToEdit(order: {
orderNumber?: string;
compartmentCode?: string;
processingStatus?: number;
}) {
this.createTab();
const path = this.getDetailsPath({ ...order, edit: true });
const breadcrumb = this.getEditBreadCrumb();
this.navigateToRoute(path, breadcrumb);
}
updateDetailsNavigation(order: {
orderNumber?: string;
compartmentCode?: string;
processingStatus?: number;
}) {
const path = this.getDetailsPath(order);
const breadcrumb = this.getDetailsBreadcrumb(order);
this.replaceRoute(path, breadcrumb);
@@ -32,7 +61,13 @@ export class ShelfNavigationService {
this.replaceRoute(path, breadcrumb);
}
navigateToResultList({ searchQuery, numberOfHits }: { searchQuery: string; numberOfHits: number }) {
navigateToResultList({
searchQuery,
numberOfHits,
}: {
searchQuery: string;
numberOfHits: number;
}) {
this.createTab();
const path = '/shelf/results';
const breadcrumb = this.getResultListBreadcrumb(searchQuery, numberOfHits);
@@ -40,12 +75,56 @@ export class ShelfNavigationService {
}
navigateToHistory(orderitem: OrderItemListItemDTO) {
this.navigateToRoute(`shelf/history/${orderitem.orderItemSubsetId}`, `Historie ${orderitem.orderItemSubsetId}`);
this.navigateToRoute(
this.getHistoryPath(orderitem),
`Historie ${orderitem.orderItemSubsetId}`
);
}
getHistoryPath(data: {
orderId?: number;
compartmentCode?: string;
orderItemId?: number;
orderItemSubsetId?: number;
orderNumber?: string;
}) {
if (data.compartmentCode) {
return `/shelf/details/compartment/${data.compartmentCode}/item/${data.orderItemId}/subset/${data.orderItemSubsetId}/history`;
} else if (data.orderId) {
return `/shelf/details/order/${data.orderNumber}/item/${data.orderItemId}/subset/${data.orderItemSubsetId}/history`;
}
}
updateResultPageBreadcrumb(data: {
numberOfHits: number;
searchQuery: string;
}) {
this.store.dispatch(new DeleteBreadcrumbsForProcess(1));
this.store.dispatch(
new AddBreadcrumb(
{ name: 'Warenausgabe', path: '/shelf/search' },
'shelf'
)
);
const path = '/shelf/results';
this.store.dispatch(
new AddBreadcrumb(
{
name: `${data.searchQuery} (${data.numberOfHits} ${
data.numberOfHits > 1 ? 'Ergebnisse' : 'Ergebnis'
})`,
path,
},
'shelf'
)
);
}
private replaceRoute(route: string, breadcrumbName: string) {
this.router.navigate([route]);
this.store.dispatch(new UpdateCurrentBreadcrumbName(breadcrumbName, 'shelf'));
this.store.dispatch(
new UpdateCurrentBreadcrumbName(breadcrumbName, 'shelf')
);
}
private navigateToRoute(route: string, breadcrumbName: string) {
@@ -62,18 +141,75 @@ export class ShelfNavigationService {
this.router.navigate([route]);
}
private getResultListBreadcrumb(searchQuery: string, numberOfHits: number): string {
public navigateBackToDetails(
data: {
orderNumber?: string;
compartmentCode?: string;
processingStatus?: number;
},
previous?: {
orderNumber?: string;
compartmentCode?: string;
processingStatus?: number;
}
) {
this.store.dispatch(new PopLastBreadcrumbs(this.getEditBreadCrumb()));
if (previous) {
this.store.dispatch(
new PopLastBreadcrumbs(this.getDetailsBreadcrumb(previous))
);
}
this.navigateToDetails(data);
}
public updateDetails(
data: {
orderNumber?: string;
compartmentCode?: string;
processingStatus?: number;
},
previous?: {
orderNumber?: string;
compartmentCode?: string;
processingStatus?: number;
}
) {
if (previous) {
this.store.dispatch(
new PopLastBreadcrumbs(this.getDetailsBreadcrumb(previous))
);
}
this.navigateToDetails(data);
}
private getResultListBreadcrumb(
searchQuery: string,
numberOfHits: number
): string {
return `${searchQuery} (${numberOfHits} Ergebnisse)`;
}
getDetailsBreadcrumb(data: { orderNumber?: string; compartmentCode?: string }): string {
getDetailsBreadcrumb(data: {
orderNumber?: string;
compartmentCode?: string;
}): string {
if (data.compartmentCode) {
return `${data.compartmentCode}`;
}
return `${data.orderNumber}`;
}
private getDetailsPath(data: { orderNumber?: string; compartmentCode?: string; processingStatus?: number }): string {
getEditBreadCrumb(): string {
return 'Bearbeiten';
}
private getDetailsPath(data: {
orderNumber?: string;
compartmentCode?: string;
processingStatus?: number;
edit?: boolean;
}): string {
let url = '';
if (data.compartmentCode) {
url = `/shelf/details/compartment/${data.compartmentCode}/${data.processingStatus}`;
@@ -81,11 +217,16 @@ export class ShelfNavigationService {
url = `/shelf/details/order/${data.orderNumber}/${data.processingStatus}`;
}
if (data.edit) {
url += '/edit';
}
return url;
}
private createTab() {
const processExists = this.store.selectSnapshot(ProcessSelectors.getProcessesCount) > 0;
const processExists =
this.store.selectSnapshot(ProcessSelectors.getProcessesCount) > 0;
if (!processExists) {
this.createProcess();
}

View File

@@ -38,7 +38,7 @@ export class ShelfSearchFacadeService {
}
this.searchStateFacade.setInput(searchQuery);
return this.requestSearch();
return this.requestSearch(true);
}
searchWithBarcode(barcode: string) {
@@ -49,7 +49,7 @@ export class ShelfSearchFacadeService {
}
this.searchStateFacade.setInput(searchQuery);
return this.requestSearch();
return this.requestSearch(true);
}
searchForAutocomplete(
@@ -72,8 +72,8 @@ export class ShelfSearchFacadeService {
return this.requestAutocompleteSearch(autoCompleteQuery);
}
private requestSearch() {
return this.searchStateFacade.fetchResult();
private requestSearch(isNewSearch: boolean = false) {
return this.searchStateFacade.fetchResult({ isNewSearch });
}
private generateAutocompleteToken(params: {

View File

@@ -0,0 +1,18 @@
import { calculateExtendedPickUpDate } from './calculate-extended-pick-up-date';
fdescribe('calculateExtendedPickUpDate', () => {
const basePickUpDate = '2020-07-17T11:16:50.4404048Z';
it('should return the extended pick up date by 2 weeks', () => {
const result = calculateExtendedPickUpDate(basePickUpDate);
expect(result.slice(0, 10)).toBe('2020-07-31');
const result2 = calculateExtendedPickUpDate(basePickUpDate, 2);
expect(result2.slice(0, 10)).toBe('2020-07-31');
});
it('should return the extended pick up date by 4 weeks', () => {
const result = calculateExtendedPickUpDate(basePickUpDate, 4);
expect(result.slice(0, 10)).toBe('2020-08-14');
});
});

View File

@@ -0,0 +1,8 @@
export function calculateExtendedPickUpDate(
pickUpdate: string,
weeks: number = 2
): string {
const baseDate = new Date(pickUpdate);
baseDate.setDate(baseDate.getDate() + 7 * weeks);
return baseDate.toISOString();
}

View File

@@ -0,0 +1,8 @@
import { calculateExtendedPickUpDate } from '.';
export function extendPickUpDate(currentPickUpdate: string): string[] {
return [
calculateExtendedPickUpDate(currentPickUpdate, 2),
calculateExtendedPickUpDate(currentPickUpdate, 4),
];
}

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './calculate-extended-pick-up-date';
// end:ng42.barrel

View File

@@ -6,6 +6,7 @@ import { ShelfOrderDetailsComponent } from './pages/shelf-order-details/shelf-or
import { ShelfEditOrderComponent } from './pages/shelf-edit-order/shelf-edit-order.component';
import { ShelfHistoryComponent } from './pages/shelf-history';
import { ShelfOrderDetailsModule } from './pages/shelf-order-details';
import { ShelfEditCompartmentComponent } from './pages/shelf-edit-compartment';
export const routes: Routes = [
{
@@ -25,11 +26,19 @@ export const routes: Routes = [
component: ShelfOrderDetailsComponent,
},
{
path: 'edit/:id/:orderId/:status/:type/:origin/:customerId/:itemId',
path: 'details/order/:orderNumber/:processingStatus/edit',
component: ShelfEditOrderComponent,
},
{
path: 'history/:orderItemSubsetId',
path: 'details/compartment/:compartmentCode/:processingStatus/edit',
component: ShelfEditCompartmentComponent,
},
{
path: 'details/order/:orderId/item/:orderItemId/subset/:orderItemSubsetId/history',
component: ShelfHistoryComponent,
},
{
path: 'details/compartment/:compartmentCode/item/:orderItemId/subset/:orderItemSubsetId/history',
component: ShelfHistoryComponent,
},
];

View File

@@ -5,7 +5,15 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { ButtonModule, CardModule, IconModule, InputModule, LoadingModule, SearchInputModule, DropdownModule } from '@libs/ui';
import {
ButtonModule,
CardModule,
IconModule,
InputModule,
LoadingModule,
SearchInputModule,
DropdownModule,
} from '@libs/ui';
import { SharedModule } from '../../shared/shared.module';
import {
@@ -14,18 +22,19 @@ import {
ShelfCustomerOrderComponent,
ShelfOrderTagComponent,
ShelfPartialCollectionModalComponent,
OrderItemEditComponent,
OrderOverviewEditComponent,
} from './components';
import { ShelfRoutingModule } from './shelf-routing.module';
import { OrderStatusPipe } from '../../pipes/order-status.pipe';
import { OrderLoadingComponent } from './components/order-loading/order-loading.component';
import { ShelfEditOrderComponent } from './pages/shelf-edit-order/shelf-edit-order.component';
import { OrderItemEditComponent } from './components/order-item-edit/order-item-edit.component';
import { OrderOverviewEditComponent } from './components/order-overview-edit/order-overview-edit.component';
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
import { ShelfSearchResultsModule } from './pages/shelf-search-results/shelf-search-results.module';
import { ShelfSearchModule } from './pages/shelf-search';
import { ShelfHistoryModule } from './pages/shelf-history';
import { ShelfEditOrderModule } from './pages/shelf-edit-order/shelf-edit-order.module';
import { ShelfEditCompartmentModule } from './pages/shelf-edit-compartment';
@NgModule({
declarations: [
@@ -36,7 +45,6 @@ import { ShelfHistoryModule } from './pages/shelf-history';
ShelfPartialCollectionModalComponent,
OrderStatusPipe,
OrderLoadingComponent,
ShelfEditOrderComponent,
OrderItemEditComponent,
OrderOverviewEditComponent,
],
@@ -46,6 +54,8 @@ import { ShelfHistoryModule } from './pages/shelf-history';
ShelfRoutingModule,
ShelfSearchModule,
ShelfHistoryModule,
ShelfEditOrderModule,
ShelfEditCompartmentModule,
FormsModule,
ReactiveFormsModule,
RouterModule,

View File

@@ -33,6 +33,8 @@ export abstract class DateAdapter<TDate> {
abstract getDifferenceInDays(first: TDate, second: TDate): number;
abstract getDayOfWeek(date: TDate): number;
compareDate(first: TDate, second: TDate) {
if (isNullOrUndefined(first) || isNullOrUndefined(second)) {
return null;
@@ -49,6 +51,9 @@ export abstract class DateAdapter<TDate> {
if (isNullOrUndefined(first) || isNullOrUndefined(second)) {
return null;
}
return this.getYear(first) - this.getYear(second) || this.getMonth(first) - this.getMonth(second);
return (
this.getYear(first) - this.getYear(second) ||
this.getMonth(first) - this.getMonth(second)
);
}
}

View File

@@ -23,10 +23,18 @@ export class NativeDateAdapter extends DateAdapter<Date> {
return this.getDate(date).toString();
}
addCalendarMonths(date: Date, months: number): Date {
return this.createDate(this.getYear(date), this.getMonth(date) + months, this.getDate(date));
return this.createDate(
this.getYear(date),
this.getMonth(date) + months,
this.getDate(date)
);
}
addCalendarDays(date: Date, days: number): Date {
return this.createDate(this.getYear(date), this.getMonth(date), this.getDate(date) + days);
return this.createDate(
this.getYear(date),
this.getMonth(date),
this.getDate(date) + days
);
}
getFirstDateOfWeek(date: Date): Date {
@@ -55,6 +63,10 @@ export class NativeDateAdapter extends DateAdapter<Date> {
return (first.getTime() - second.getTime()) / (1000 * 3600 * 24);
}
getDayOfWeek(date: Date) {
return date.getDay();
}
createDate(year: number, month: number, date: number): Date {
const result = new Date(year, month, date);
// abbreviations for 19xx.

View File

@@ -18,7 +18,14 @@ th {
height: 41px;
}
.cell-not-in-range {
.cell-is-today {
border: 1px dashed $datepicker-selected-cell-bg-color;
border-radius: 50%;
font-weight: 700;
}
.cell-not-in-range,
.cell-is-disabled-day-of-week {
color: $datepicker-cell-not-in-range-color;
}
@@ -29,4 +36,5 @@ th {
.cell-selected {
background-color: $datepicker-selected-cell-bg-color;
border-radius: 22px;
font-weight: 700;
}

View File

@@ -1,7 +1,19 @@
import { Directive, Input, HostBinding, OnDestroy, OnInit, HostListener } from '@angular/core';
import {
Directive,
Input,
HostBinding,
OnDestroy,
OnInit,
HostListener,
} from '@angular/core';
import { Datepicker } from '@isa-ui/datepicker/datepicker';
import { DateAdapter } from '@isa-ui/core/date';
import { Subscription, ReplaySubject, combineLatest, BehaviorSubject } from 'rxjs';
import {
Subscription,
ReplaySubject,
combineLatest,
BehaviorSubject,
} from 'rxjs';
@Directive({ selector: '[appUiDatepickerCell]' })
export class UiDatepickerCellDirective<TDate> implements OnInit, OnDestroy {
@@ -30,19 +42,34 @@ export class UiDatepickerCellDirective<TDate> implements OnInit, OnDestroy {
@HostBinding('class.cell-not-in-range')
notInRange: boolean;
@HostBinding('class.cell-is-today')
isToday: boolean;
@HostBinding('class.cell-is-disabled-day-of-week')
isDisabledDayOfWeek: boolean;
private subscription = new Subscription();
constructor(private datepicker: Datepicker<TDate>, private dateAdapter: DateAdapter<TDate>) {}
constructor(
private datepicker: Datepicker<TDate>,
private dateAdapter: DateAdapter<TDate>
) {}
ngOnInit(): void {
this.initDisplayed$();
this.initSelected$();
this.initRange$();
this.initToday$();
this.initDisabledDayOfWeek$();
}
initDisplayed$() {
const sub = combineLatest([this.dateSubject, this.datepicker.displayed$]).subscribe(([date, displayed]) => {
this.inDisplayedMonth = this.dateAdapter.compareYearAndMonth(date, displayed) === 0;
const sub = combineLatest([
this.dateSubject,
this.datepicker.displayed$,
]).subscribe(([date, displayed]) => {
this.inDisplayedMonth =
this.dateAdapter.compareYearAndMonth(date, displayed) === 0;
this.notInDisplayedMonth = !this.inDisplayedMonth;
});
@@ -50,7 +77,10 @@ export class UiDatepickerCellDirective<TDate> implements OnInit, OnDestroy {
}
initSelected$() {
const sub = combineLatest([this.dateSubject, this.datepicker.selected$]).subscribe(([date, selected]) => {
const sub = combineLatest([
this.dateSubject,
this.datepicker.selected$,
]).subscribe(([date, selected]) => {
this.selected = this.dateAdapter.compareDate(date, selected) === 0;
});
@@ -58,9 +88,17 @@ export class UiDatepickerCellDirective<TDate> implements OnInit, OnDestroy {
}
initRange$() {
const sub = combineLatest([this.dateSubject, this.datepicker.min$, this.datepicker.max$]).subscribe(([date, min, max]) => {
const minInRange = !!min ? this.dateAdapter.compareDate(date, min) > 0 : true;
const maxInRange = !!max ? this.dateAdapter.compareDate(max, date) > 0 : true;
const sub = combineLatest([
this.dateSubject,
this.datepicker.min$,
this.datepicker.max$,
]).subscribe(([date, min, max]) => {
const minInRange = !!min
? this.dateAdapter.compareDate(date, min) > 0
: true;
const maxInRange = !!max
? this.dateAdapter.compareDate(max, date) > 0
: true;
this.inRange = minInRange && maxInRange;
this.notInRange = !this.inRange;
@@ -69,6 +107,27 @@ export class UiDatepickerCellDirective<TDate> implements OnInit, OnDestroy {
this.subscription.add(sub);
}
initToday$() {
const sub = this.dateSubject.subscribe((date) => {
this.isToday =
this.dateAdapter.compareDate(date, this.dateAdapter.today()) === 0;
});
this.subscription.add(sub);
}
initDisabledDayOfWeek$() {
const sub = combineLatest([
this.dateSubject,
this.datepicker.disabledDaysOfWeek$,
]).subscribe(([date, disabledDayOfWeek]) => {
this.isDisabledDayOfWeek =
disabledDayOfWeek &&
disabledDayOfWeek.includes(this.dateAdapter.getDayOfWeek(date));
});
this.subscription.add(sub);
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
this.dateSubject.complete();
@@ -76,7 +135,7 @@ export class UiDatepickerCellDirective<TDate> implements OnInit, OnDestroy {
@HostListener('click')
select() {
if (this.inRange && this.inDisplayedMonth) {
if (this.inRange && this.inDisplayedMonth && !this.isDisabledDayOfWeek) {
this.datepicker.setSelected(this.date);
}
}

View File

@@ -45,10 +45,22 @@ export abstract class Datepicker<TDate> {
this.setMax(val);
}
@Input()
get disabledDaysOfWeek() {
return this.disableDayOfWeekSubject.value;
}
set disabledDaysOfWeek(value: number[]) {
// 0 = Sunday, 1 = Monday, ... 6 = Saturday
this.disableDayOfWeekSubject.next(value);
}
protected selectedSubject = new BehaviorSubject<TDate>(undefined);
protected displayedSubject = new BehaviorSubject<TDate>(this.dateAdapter.today());
protected displayedSubject = new BehaviorSubject<TDate>(
this.dateAdapter.today()
);
protected minSubject = new BehaviorSubject<TDate>(undefined);
protected maxSubject = new BehaviorSubject<TDate>(undefined);
protected disableDayOfWeekSubject = new BehaviorSubject<number[]>(undefined);
get selected$() {
return this.selectedSubject.asObservable();
@@ -62,6 +74,9 @@ export abstract class Datepicker<TDate> {
get max$() {
return this.maxSubject.asObservable();
}
get disabledDaysOfWeek$() {
return this.disableDayOfWeekSubject.asObservable();
}
constructor(private dateAdapter: DateAdapter<TDate>) {}
@@ -73,7 +88,10 @@ export abstract class Datepicker<TDate> {
this.setDisplayed(this.dateAdapter.addCalendarMonths(this.displayed, -1));
}
setSelected(date: TDate, { emit: emitChanged }: SetPropertyOptions = { emit: true }) {
setSelected(
date: TDate,
{ emit: emitChanged }: SetPropertyOptions = { emit: true }
) {
if (!this.dateAdapter.isValid(date)) {
this.selectedSubject.next(undefined);
return;
@@ -87,7 +105,10 @@ export abstract class Datepicker<TDate> {
}
}
setDisplayed(date: TDate, { emit: emitChanged }: SetPropertyOptions = { emit: true }) {
setDisplayed(
date: TDate,
{ emit: emitChanged }: SetPropertyOptions = { emit: true }
) {
if (!this.dateAdapter.isValid(date)) {
this.displayedSubject.next(undefined);
return;

View File

@@ -16,25 +16,34 @@ import { trigger, transition, style, animate } from '@angular/animations';
import { UiDropdownItemDirective } from './dropdown-item.directive';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { fromEvent, Subscription } from 'rxjs';
import { throttleTime } from 'rxjs/operators';
@Component({
selector: 'app-ui-dropdown',
templateUrl: 'dropdown.component.html',
styleUrls: ['dropdown.component.scss'],
exportAs: 'uiDropdown',
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => UiDropdownComponent), multi: true }],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => UiDropdownComponent),
multi: true,
},
],
animations: [
trigger('dropdownAnimation', [
transition(':enter', [
style({ transform: 'translateY(-60px)', opacity: 0 }),
animate('200ms', style({ transform: 'translateY(0)', opacity: 1 })),
]),
transition(':leave', [style({ opacity: '*' }), animate('200ms', style({ opacity: 0 }))]),
transition(':leave', [
style({ opacity: '*' }),
animate('200ms', style({ opacity: 0 })),
]),
]),
],
})
export class UiDropdownComponent implements OnInit, OnDestroy, ControlValueAccessor, AfterContentInit {
export class UiDropdownComponent
implements OnInit, OnDestroy, ControlValueAccessor, AfterContentInit {
visible = false;
@ContentChildren(UiDropdownItemDirective)
@@ -70,11 +79,13 @@ export class UiDropdownComponent implements OnInit, OnDestroy, ControlValueAcces
option.registerOnSelect((value) => this.selectValue(value));
});
this.optionChangesSubscription = this.options.changes.subscribe((changes) => {
this.options.forEach((option) => {
option.registerOnSelect((value) => this.selectValue(value));
});
});
this.optionChangesSubscription = this.options.changes.subscribe(
(changes) => {
this.options.forEach((option) => {
option.registerOnSelect((value) => this.selectValue(value));
});
}
);
}
toggle(visible?: boolean) {
@@ -86,7 +97,10 @@ export class UiDropdownComponent implements OnInit, OnDestroy, ControlValueAcces
if (this.visible) {
this.subscribeKeyboardEvents();
this.options.first.focus();
if (!!this.options.first) {
this.options.first.focus();
}
} else {
this.unsubscribeKeyboardEvents();
}
@@ -96,26 +110,32 @@ export class UiDropdownComponent implements OnInit, OnDestroy, ControlValueAcces
if (this.windowKeyEventsSubscription) {
this.unsubscribeKeyboardEvents();
}
this.windowKeyEventsSubscription = this.windowKeyEvents$.subscribe((event: KeyboardEvent) => {
const activeElementIndex = this.options.toArray().findIndex((o) => o.focused);
this.windowKeyEventsSubscription = this.windowKeyEvents$.subscribe(
(event: KeyboardEvent) => {
const activeElementIndex = this.options
.toArray()
.findIndex((o) => o.focused);
if (event.code === 'ArrowUp' || event.code === 'ArrowDown') {
let nextElementIndex = activeElementIndex;
if (event.code === 'ArrowUp') {
if (activeElementIndex > 0) {
nextElementIndex--;
if (event.code === 'ArrowUp' || event.code === 'ArrowDown') {
let nextElementIndex = activeElementIndex;
if (event.code === 'ArrowUp') {
if (activeElementIndex > 0) {
nextElementIndex--;
}
} else if (event.code === 'ArrowDown') {
if (activeElementIndex + 1 < this.options.length) {
nextElementIndex++;
}
}
} else if (event.code === 'ArrowDown') {
if (activeElementIndex + 1 < this.options.length) {
nextElementIndex++;
const option = this.options.find(
(item, index) => nextElementIndex === index
);
if (option) {
option.focus();
}
}
const option = this.options.find((item, index) => nextElementIndex === index);
if (option) {
option.focus();
}
}
});
);
}
unsubscribeKeyboardEvents() {

View File

@@ -0,0 +1,5 @@
// start:ng42.barrel
export * from './select-input.component';
export * from './select-input.module';
// end:ng42.barrel

View File

@@ -0,0 +1,20 @@
<div class="isa-form-field">
<div class="d-flex" (click)="dropdown.toggle()">
<label>
{{ label }}
</label>
<span
class="selected isa-font-weight-bold isa-white-space-nowrap">{{ labelPipe ? labelPipe.transform(value) : value }}
</span>
<lib-icon class="isa-accordion-arrow" [class.arrow-up]="dropdown.visible" [class.arrow-down]="!dropdown.visible"
name="Arrow_right" [height]="'16px'"></lib-icon>
</div>
<app-ui-dropdown #dropdown>
<button (click)="handleSelect({ selectedOption: option, dropdown: dropdown })" (blur)="handleBlur()"
class="isa-btn isa-text-left isa-p-16" [class.selected]="value === option"
*ngFor="let option of options; let index = index">
{{ optionsPipe ? optionsPipe.transform(option, index) : option }}
</button>
</app-ui-dropdown>
</div>

View File

@@ -0,0 +1,31 @@
@import 'variables';
.isa-form-field {
.selected {
margin-right: 10px;
}
label {
width: max-content;
}
}
:host ::ng-deep {
app-ui-dropdown {
margin-left: 20px;
.dropdown {
left: 0;
width: fit-content;
max-height: 250px;
button {
padding: none;
margin-right: 0;
}
.selected {
background: $isa-light-blue-platinum;
}
}
}
}

View File

@@ -0,0 +1,56 @@
import { Component, ChangeDetectionStrategy, Input, forwardRef, Pipe, PipeTransform } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { UiDropdownComponent } from '@isa-ui/dropdown';
@Component({
selector: 'app-ui-select-input',
templateUrl: 'select-input.component.html',
styleUrls: ['./select-input.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => UiSelectInputComponent),
multi: true,
},
],
})
export class UiSelectInputComponent implements ControlValueAccessor {
@Input() public options: string[] = [];
@Input() label = '';
@Input() labelPipe: PipeTransform;
@Input() optionsPipe: PipeTransform;
@Input() valuePipe: PipeTransform;
constructor() {}
public value: string;
public editable = true;
private onChange: (value: string) => void;
private onBlur: () => void;
handleSelect({ selectedOption, dropdown }: { selectedOption: string; dropdown: UiDropdownComponent }) {
this.value = this.valuePipe ? this.valuePipe.transform(selectedOption) : selectedOption;
this.onChange(this.value);
dropdown.close();
}
handleBlur() {
this.onBlur();
}
writeValue(val: string): void {
this.value = val;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onBlur = fn;
}
setDisabledState(isDisabled: boolean): void {
this.editable = !isDisabled;
}
}

View File

@@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UiSelectInputComponent } from './select-input.component';
import { UiDropdownModule } from '@isa-ui/dropdown';
import { FormsModule } from '@angular/forms';
import { IconModule } from '@libs/ui';
@NgModule({
imports: [CommonModule, FormsModule, UiDropdownModule, IconModule],
exports: [UiSelectInputComponent],
declarations: [UiSelectInputComponent],
providers: [],
})
export class UiSelectInputModule {}

View File

@@ -0,0 +1,5 @@
// start:ng42.barrel
export * from './text-input.component.component';
export * from './text-input.component.module';
// end:ng42.barrel

View File

@@ -0,0 +1,73 @@
import {
Component,
ChangeDetectionStrategy,
forwardRef,
Input,
ChangeDetectorRef,
} from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
import { asyncScheduler } from 'rxjs';
@Component({
selector: 'app-ui-text-input',
templateUrl: './text-input.component.html',
styleUrls: ['./text-input.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => UiTextInputComponent),
multi: true,
},
],
})
export class UiTextInputComponent implements ControlValueAccessor {
@Input() label = '';
@Input() suffix: string;
@Input() placeholder = '';
constructor(private cdr: ChangeDetectorRef) {}
public value: string;
public editable = true;
public touched = false;
public focussed = false;
private onChange: (value: string) => void;
private onTouched: () => void;
handleInput() {
this.onChange(this.value);
}
handleBlur() {
asyncScheduler.schedule(() => this.handleFocus(false));
this.onTouched();
}
handleFocus(isFocussed: boolean) {
if (isFocussed) {
this.touched = true;
}
this.focussed = isFocussed;
// this.cdr.detectChanges();
}
writeValue(val: string): void {
this.value = val;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.editable = !isDisabled;
}
reset() {
this.writeValue('');
this.onChange('');
}
}

Some files were not shown because too many files have changed in this diff Show More