[HIMA-702][HIMA-701][HIMA-328] Working on shipping address document list and on page of started remission list

This commit is contained in:
Eraldo Hasanaj
2019-11-12 17:37:48 +01:00
parent da7e23e86f
commit 3c48db4fe1
31 changed files with 1101 additions and 43 deletions

View File

@@ -130,12 +130,18 @@
<div>
<lib-icon
class="menu-icon"
name="{{ router.url === '/remission/create' ? 'Icon_Remission' : 'Icon_Remission_inactive' }}"
name="{{
router.url === '/remission/create' || router.url === '/remission/started' ? 'Icon_Remission' : 'Icon_Remission_inactive'
}}"
width="34px"
height="24px"
></lib-icon>
</div>
<span *ngIf="router.url === '/remission/create'; else remissionLabelElse" class="branch-menu-item branch-selected">Remission</span>
<span
*ngIf="router.url === '/remission/create' || router.url === '/remission/started'; else remissionLabelElse"
class="branch-menu-item branch-selected"
>Remission</span
>
<ng-template #remissionLabelElse>
<span class="branch-menu-item">Remission</span>
</ng-template>

View File

@@ -1,12 +1,12 @@
<div class="filter" (click)="toggle()">
<div class="filter-title" [ngClass]="{ expanded: expanded }">
<div id="filter" class="filter-title" [ngClass]="{ expanded: expanded }">
<span>{{ filter.name }}</span>
<lib-icon class="icon" [rotateBackwards]="expanded" transition="all 0.2s linear" width="17px" name="Arrow_Down_2_branch"></lib-icon>
</div>
</div>
<div class="options" *ngIf="expanded" [@fadeIn]="expanded">
<div class="options" [style.top]="optionsTopPosition" *ngIf="expanded" [@fadeIn]="expanded">
<ng-container *ngFor="let option of filter.options">
<div class="option">
<div class="option" [ngClass]="{ selected: isFirstLevelItemSelected(hasGrouping ? option.name : option.id) }">
<span class="check-icon" (click)="toggleFirstLevelItem(hasGrouping ? option.name : option)">
<div class="check-box-wrapper">
<lib-icon class="check-box" height="22px" name="Check_box_b"></lib-icon>
@@ -29,7 +29,7 @@
<div class="sub-items-wrapper" @growShrink>
<hr class="branch" />
<ng-container *ngFor="let subOption of option.options">
<div class="option">
<div class="option" [ngClass]="{ selected: isOptionsSelected(subOption.id) }">
<span class="check-icon" (click)="toggleOption(subOption, true, option.name)">
<div class="check-box-wrapper">
<lib-icon class="check-box" height="22px" name="Check_box_b"></lib-icon>

View File

@@ -28,10 +28,9 @@
box-shadow: 0 0 20px 1px #dde5ec;
border-radius: 4px;
padding-bottom: 10px;
padding-top: 10px;
margin-top: 13px;
width: 307px;
overflow: scroll;
z-index: 10;
.option {
display: flex;
@@ -40,6 +39,10 @@
padding-left: 14px;
padding-right: 14px;
cursor: pointer;
&.selected {
background-color: #cfd4d8;
}
}
.check-icon {

View File

@@ -27,6 +27,17 @@ export class RemissionFilterItemComponent implements OnInit, OnDestroy {
selectedGroups: string[] = [];
expandedGroups: string[] = [];
selectedOptions: FilterOption[] = [];
get optionsTopPosition() {
let value = 680;
const filterEl = document.getElementById('filter');
if (filterEl) {
const topPosition = filterEl.getBoundingClientRect().top;
value = topPosition + 47;
}
return value + 'px';
}
constructor(private store: Store, private remissionHelper: RemissionHelperService) {}
ngOnInit() {
@@ -211,6 +222,9 @@ export class RemissionFilterItemComponent implements OnInit, OnDestroy {
this.unSelectGroupOfOptions(group);
});
} else {
(this.remissionResourceType === RemissionResourceType.Overflow).ifTrue(() => {
this.selectedOptions = [];
});
this.addOptionToSelected(option);
modifyGroup.ifTrue(() => {
this.selectGroupOfOptions(group);

View File

@@ -0,0 +1,62 @@
<div class="card-container">
<div class="card-wrapper">
<div class="card-header">
<span class="authors-title">{{ product.contributors + ' - ' + product.name }}</span>
<span class="group">{{ product.productGroupName }}</span>
</div>
<div class="card-details">
<div class="icon">
<img [src]="product.imageId | bookImageUrl | async" alt="book" class="thumbnail" />
</div>
<div class="details-info">
<div class="general">
<div class="format-edition item">
<span class="format-icon">
<lib-icon name="Icon_{{ product.format }}" mt="4px" height="17px"></lib-icon>
</span>
<span class="format-description">{{ product.format }}</span>
<span *ngIf="product.edition">|</span>
<span class="edition">{{ product.edition }}</span>
</div>
<div class="item">
<span>{{ product.ean }}</span>
</div>
<div class="price item">
<span class="value">{{ product.price }}</span>
<span class="currency">{{ product.currency }}</span>
</div>
<div class="feature-tooltip" [style.marginLeft.px]="tooltipMl" *ngIf="toolTipOpened">
{{ selectedFeatureDescription }}
<lib-icon (click)="closeTooltip()" height="11px" name="Delete-white"></lib-icon>
</div>
<div class="feature-tooltip-arrow-down" [style.marginLeft.px]="tooltipArrowMl" *ngIf="toolTipOpened"></div>
<div class="features item">
<span *ngFor="let feature of product.features; let index = index" (click)="openTooltip(feature.name, index)">{{
feature.key
}}</span>
</div>
</div>
<div class="quantity-placement">
<div class="labels">
<div class="label">aktueller Bestand</div>
<div class="label">Remi-Menge</div>
<div class="label">übriger Bestand</div>
<div class="label">Platz</div>
<div class="label" *ngIf="product.remissionReason">Remigrund</div>
</div>
<div class="datas">
<div class="data">{{ product.inStock }}</div>
<div class="data">{{ product.remissionQuantity }}</div>
<div class="data short">{{ product.remainingQuantity }}</div>
<div class="data short">{{ product.placementType }}</div>
<div class="data" *ngIf="product.remissionReason">{{ product.remissionReason }}</div>
</div>
</div>
</div>
</div>
<div class="actions">
<app-button>Remi-Menge / Platzierung ändern</app-button>
<app-button outline="true">Remittieren</app-button>
</div>
</div>
</div>

View File

@@ -0,0 +1,165 @@
.card-container {
min-height: 200px;
margin-bottom: 10px;
border-radius: 5px;
background-color: #ffffff;
font-family: 'Open Sans';
.card-wrapper {
padding-top: 22px;
padding-left: 24px;
padding-right: 24px;
padding-bottom: 16px;
.card-header {
display: flex;
flex-direction: row;
justify-content: space-between;
.authors-title {
font-size: 26px;
font-weight: bold;
color: #000000;
padding-right: 10px;
}
.group {
font-size: 16px;
color: #596470;
padding-top: 11px;
}
}
.card-details {
display: flex;
flex-direction: row;
padding-top: 20px;
.icon {
.thumbnail {
max-width: 59px;
max-height: 111px;
box-shadow: 0 0 18px 0 #b8b3b7;
border-radius: 6px;
}
}
.details-info {
padding-left: 15px;
display: grid;
grid-template-columns: 250px auto;
.general {
display: flex;
flex-direction: column;
font-size: 16px;
font-weight: bold;
color: #000000;
.format-edition {
display: flex;
flex-direction: row;
span {
padding-right: 3px;
}
}
.price {
display: flex;
flex-direction: row;
.currency {
padding-left: 3px;
}
}
.features {
display: flex;
flex-direction: row;
span {
color: #596470;
padding-right: 5px;
cursor: pointer;
}
}
.feature-tooltip {
position: absolute;
background-color: #5a728a;
margin-top: 30px;
margin-left: -10px;
color: #ffffff;
padding: 12px;
border-radius: 5px;
lib-icon {
margin-left: 5px;
cursor: pointer;
}
}
.feature-tooltip-arrow-down {
position: absolute;
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid #5a728a;
margin-top: 75px;
margin-left: 5px;
}
}
.quantity-placement {
display: flex;
flex-direction: row;
.labels,
.datas {
display: flex;
flex-direction: column;
}
.datas {
font-size: 16px;
font-weight: bold;
color: #000000;
padding-left: 27px;
.data {
padding-bottom: 4px;
&.short {
max-width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.labels {
font-size: 16px;
color: #000000;
.label {
padding-bottom: 4px;
}
}
}
.item {
padding-bottom: 4px;
}
}
}
.actions {
display: flex;
flex-direction: row;
justify-content: flex-end;
margin-top: 26px;
}
}
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RemissionListCardStartedComponent } from './remission-list-card-started.component';
describe('RemissionListCardStartedComponent', () => {
let component: RemissionListCardStartedComponent;
let fixture: ComponentFixture<RemissionListCardStartedComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ RemissionListCardStartedComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(RemissionListCardStartedComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,36 @@
import { Component, OnInit, Input } from '@angular/core';
import { RemissionProduct } from '@isa/remission';
@Component({
selector: 'app-remission-list-card-started',
templateUrl: './remission-list-card-started.component.html',
styleUrls: ['./remission-list-card-started.component.scss'],
})
export class RemissionListCardStartedComponent implements OnInit {
@Input() product: RemissionProduct;
selectedFeatureDescription: string;
toolTipOpened = false;
tooltipMl = -10;
tooltipArrowMl = 5;
constructor() {}
ngOnInit() {}
openTooltip(desc: string, index: number) {
if (index === 0) {
this.tooltipMl = -10;
this.tooltipArrowMl = 5;
} else {
const offsetConstant = 37;
const offset = offsetConstant * index;
this.tooltipMl = -10 + offset;
this.tooltipArrowMl = 5 + offset;
}
this.selectedFeatureDescription = desc;
this.toolTipOpened = true;
}
closeTooltip() {
this.toolTipOpened = false;
}
}

View File

@@ -25,8 +25,15 @@
<span class="value">{{ product.price }}</span>
<span class="currency">{{ product.currency }}</span>
</div>
<div class="feature-tooltip" [style.marginLeft.px]="tooltipMl" *ngIf="toolTipOpened">
{{ selectedFeatureDescription }}
<lib-icon (click)="closeTooltip()" height="11px" name="Delete-white"></lib-icon>
</div>
<div class="feature-tooltip-arrow-down" [style.marginLeft.px]="tooltipArrowMl" *ngIf="toolTipOpened"></div>
<div class="features item">
<span>{{ product.features.key }}</span>
<span *ngFor="let feature of product.features; let index = index" (click)="openTooltip(feature.name, index)">{{
feature.key
}}</span>
</div>
</div>
<div class="quantity-placement">

View File

@@ -79,9 +79,34 @@
span {
color: #596470;
padding-right: 3px;
padding-right: 5px;
cursor: pointer;
}
}
.feature-tooltip {
position: absolute;
background-color: #5a728a;
margin-top: 30px;
color: #ffffff;
padding: 12px;
border-radius: 5px;
lib-icon {
margin-left: 5px;
cursor: pointer;
}
}
.feature-tooltip-arrow-down {
position: absolute;
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid #5a728a;
margin-top: 75px;
}
}
.quantity-placement {

View File

@@ -9,7 +9,29 @@ import { mapToIterable } from 'apps/sales/src/app/core/utils/app.utils';
})
export class RemissionListCardComponent implements OnInit {
@Input() product: RemissionProduct;
selectedFeatureDescription: string;
toolTipOpened = false;
tooltipMl = -10;
tooltipArrowMl = 5;
constructor() {}
ngOnInit() {}
openTooltip(desc: string, index: number) {
if (index === 0) {
this.tooltipMl = -10;
this.tooltipArrowMl = 5;
} else {
const offsetConstant = 37;
const offset = offsetConstant * index;
this.tooltipMl = -10 + offset;
this.tooltipArrowMl = 5 + offset;
}
this.selectedFeatureDescription = desc;
this.toolTipOpened = true;
}
closeTooltip() {
this.toolTipOpened = false;
}
}

View File

@@ -1,13 +1,14 @@
<div class="result-container">
<ng-container>
<cdk-virtual-scroll-viewport itemSize="222" class="viewport" #scroller>
<cdk-virtual-scroll-viewport [itemSize]="started ? 270 : 260" class="viewport" #scroller>
<div *cdkVirtualFor="let product of ds; let first = first; let last = last; let index = index">
<ng-container *ngIf="product != null; else loadingComponent">
<app-remission-list-card [product]="product"></app-remission-list-card>
<app-remission-list-card *ngIf="!started" [product]="product"></app-remission-list-card>
<app-remission-list-card-started *ngIf="started" [product]="product"></app-remission-list-card-started>
</ng-container>
</div>
<ng-template #loadingComponent>
<div height="222">
<div height="260">
loading
</div>
</ng-template>

View File

@@ -3,7 +3,7 @@
}
.viewport {
padding-bottom: 10px;
height: calc(100% - 10px);
height: 810px;
width: 100%;
}

View File

@@ -1,4 +1,4 @@
import { Component, OnInit, Input, OnDestroy } from '@angular/core';
import { Component, OnInit, Input, OnDestroy, ViewChild, Output, EventEmitter } from '@angular/core';
import { RemissionListDataSource } from './remission-list.datasource';
import { RemissionService } from '@isa/remission';
import { Store } from '@ngxs/store';
@@ -6,6 +6,7 @@ import { RemissionHelperService } from '../../services/remission-helper.service'
import { Subject } from 'rxjs';
import { takeUntil, filter } from 'rxjs/operators';
import { isNullOrUndefined } from 'util';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
@Component({
selector: 'app-remission-list',
@@ -13,6 +14,9 @@ import { isNullOrUndefined } from 'util';
styleUrls: ['./remission-list.component.scss'],
})
export class RemissionListComponent implements OnInit, OnDestroy {
@ViewChild('scroller') scroller: CdkVirtualScrollViewport;
@Input() started = false;
@Output() scrolled = new EventEmitter();
remissionProcesessId: string;
ds: RemissionListDataSource;
destroy$ = new Subject();
@@ -20,13 +24,25 @@ export class RemissionListComponent implements OnInit, OnDestroy {
constructor(private remisssionService: RemissionService, private store: Store, private remissionHelper: RemissionHelperService) {}
ngOnInit() {}
ngOnInit() {
this.subscribeToListSrollEvents();
}
ngOnDestroy() {
this.destroy$.next();
}
subscribeToListSrollEvents() {
this.scroller
.elementScrolled()
.pipe(takeUntil(this.destroy$))
.subscribe(event => {
this.scrolled.emit();
});
}
public subscribeToProducts() {
console.log('subscription to products called');
this.remissionHelper.loadRemissionListProducts$
.pipe(
filter(data => !isNullOrUndefined(data)),

View File

@@ -96,7 +96,7 @@ export class RemissionListDataSource extends DataSource<RemissionProduct | undef
console.log('product results', result);
this.loading = false;
this.results = true;
const products = result && result.items ? result.items : [];
const products = result && result.items ? result.items.slice(0, 9) : [];
if (page === 0) {
this.cachedData = Array.from<RemissionProduct>({ length: result.hits });

View File

@@ -0,0 +1,3 @@
<p>
remission-shipping-document works!
</p>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RemissionShippingDocumentComponent } from './remission-shipping-document.component';
describe('RemissionShippingDocumentComponent', () => {
let component: RemissionShippingDocumentComponent;
let fixture: ComponentFixture<RemissionShippingDocumentComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ RemissionShippingDocumentComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(RemissionShippingDocumentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,12 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-remission-shipping-document',
templateUrl: './remission-shipping-document.component.html',
styleUrls: ['./remission-shipping-document.component.scss'],
})
export class RemissionShippingDocumentComponent implements OnInit {
constructor() {}
ngOnInit() {}
}

View File

@@ -34,7 +34,10 @@
<div class="filters">
<app-remission-filters [remissionResourceType]="selectedRemissionResourceType"></app-remission-filters>
</div>
<div class="actions">
<div class="note" *ngIf="!listLoaded">
<span class="align-center">Wählen Sie die Abteilung aus, die Sie remittieren möchten.</span>
</div>
<div class="actions" *ngIf="listLoaded">
<div class="start-remission">
<app-button [primary]="true" (action)="startRemission()">Remission starten</app-button>
</div>
@@ -47,6 +50,6 @@
<div class="hits" *ngIf="remissionListHits">
<span>{{ remissionListHits }} Titel</span>
</div>
<app-remission-list #remissionList></app-remission-list>
<app-remission-list id="remission-list" (scrolled)="remissionListScrolled()" #remissionList></app-remission-list>
</div>
</div>

View File

@@ -95,6 +95,20 @@
padding-top: 27px;
}
}
.note {
display: flex;
flex-direction: row;
justify-content: center;
span {
width: 350px;
font-family: 'Open Sans';
font-size: 16px;
font-weight: 600;
color: #89949e;
}
}
}
.list {

View File

@@ -1,21 +1,12 @@
import { Component, OnInit, OnDestroy, HostListener, AfterViewInit, ViewChild } from '@angular/core';
import { Subject, Observable } from 'rxjs';
import { takeUntil, filter, take, tap, switchMap, catchError, delay } from 'rxjs/operators';
import { DoubleChoiceSwitch, DoubleChoiceSwitchColor } from '@libs/ui';
import { DoubleChoiceSwitch } from '@libs/ui';
import { RemissionResourceType } from '../../models/remission-resource-type.model';
import { ResourceTypeSpecificModel } from '../../models/resource-type-specific-model.model';
import { CapacityTypeClientWrapper } from '../../models/capacity-type-client-wrapper.model';
import { Side } from '@libs/ui/lib/small-double-choice-switch';
import {
RemissionSupplier,
RemissionService,
RemissionSourceType,
RemissionProcess,
CapacityType,
Filter,
FilterOption,
RemissionFilter,
} from '@isa/remission';
import { RemissionSupplier, RemissionService, RemissionSourceType, RemissionProcess, CapacityType, RemissionFilter } from '@isa/remission';
import { Select, Store } from '@ngxs/store';
import { RemissionSelectors } from 'apps/sales/src/app/core/store/selectors/remission.selectors';
import { RemissionProcessStatuses } from 'apps/sales/src/app/core/models/remission-process-statuses.model';
@@ -24,13 +15,16 @@ import { isNullOrUndefined } from 'util';
import {
SetRemissionProcess,
SetRemissionCreated,
SetRemissionFilters,
SetRemissionSource,
SetRemissionTarget,
SetRemissionStarted,
} from 'apps/sales/src/app/core/store/actions/remission.actions';
import { RemissionHelperService } from '../../services/remission-helper.service';
import { UpdateFilter } from '../../models/update-filter.model';
import { RemissionListComponent } from '../../components/remission-list/remission-list.component';
import { AddBreadcrumb } from 'apps/sales/src/app/core/store/actions/breadcrumb.actions';
import { Router } from '@angular/router';
import { SetBranchProcessCurrentPath } from 'apps/sales/src/app/core/store/actions/branch-process.actions';
@Component({
selector: 'app-remission-list-create',
@@ -57,7 +51,13 @@ export class RemissionListCreateComponent implements OnInit, OnDestroy, AfterVie
remissionProcessStatuses: RemissionProcessStatuses;
processNeedsToBeRestoredFromCache = false;
remissionListHits: number;
constructor(private remissionService: RemissionService, private store: Store, private remissionHelper: RemissionHelperService) {}
listLoaded = false;
constructor(
private remissionService: RemissionService,
private store: Store,
private remissionHelper: RemissionHelperService,
private router: Router
) {}
ngOnInit() {
this.subscriptions();
@@ -89,7 +89,6 @@ export class RemissionListCreateComponent implements OnInit, OnDestroy, AfterVie
takeUntil(this.destroy$)
)
.subscribe(hits => {
console.log(hits);
this.remissionListHits = hits;
});
}
@@ -127,9 +126,13 @@ export class RemissionListCreateComponent implements OnInit, OnDestroy, AfterVie
remissionSubsribtionInitialisation() {
this.loadCurrentRemissionProcessStatuses().then(remissionProcessStatuses => {
// if (remissionProcessStatuses.started && !remissionProcessStatuses.completed) {
// this.navigateToStartedRemission();
// } else {
this.remissionProcessStatuses = remissionProcessStatuses;
this.remissionProcess$ = !this.remissionProcessStatuses.created ? this.createProcess() : this.continueProcess();
this.remissionProcess$.pipe(takeUntil(this.destroy$)).subscribe(this.remisionProcessSubscriptionHandler);
// }
});
}
@@ -151,7 +154,6 @@ export class RemissionListCreateComponent implements OnInit, OnDestroy, AfterVie
if (updates.target) {
changes.target = updates.target;
}
console.log(changes);
if (!this.remissionProcess) {
return;
}
@@ -232,6 +234,7 @@ export class RemissionListCreateComponent implements OnInit, OnDestroy, AfterVie
this.selectedSupplier = remissionProcess.filter.target.name === this.suppliers.leftSupplier.name ? Side.LEFT : Side.RIGHT;
this.store.dispatch(new SetRemissionProcess(remissionProcess));
this.remissionHelper.loadRemissionListProducts(remissionProcess.id);
this.remissionProductsSubscription();
// TODO: remove console logging
console.log('remission subscribed', remissionProcess);
};
@@ -275,6 +278,9 @@ export class RemissionListCreateComponent implements OnInit, OnDestroy, AfterVie
(this.selectedRemissionResourceType === RemissionResourceType.Overflow && options && options.length > 0))
) {
this.remissionList.subscribeToProducts();
setTimeout(() => {
this.listLoaded = true;
});
} else if (
this.remissionList.subscribedToProducts &&
!(
@@ -284,6 +290,7 @@ export class RemissionListCreateComponent implements OnInit, OnDestroy, AfterVie
) {
this.remissionList.unsubscribeToProducts();
this.remissionListHits = null;
this.listLoaded = false;
}
}
@@ -300,8 +307,32 @@ export class RemissionListCreateComponent implements OnInit, OnDestroy, AfterVie
}
startRemission() {
// TODO: Implement method
throw new Error('(startRemission) Method not implemented');
this.remissionService
.startRemission({ remissionProcessId: this.remissionProcess.id })
.pipe(
filter(data => !isNullOrUndefined(data)),
take(1)
)
.subscribe(remissionProcess => {
this.store.dispatch(new SetRemissionStarted(true));
this.store.dispatch(new SetRemissionProcess(remissionProcess));
this.navigateToStartedRemission();
});
}
navigateToStartedRemission() {
const path = '/remission/started';
this.store.dispatch(
new AddBreadcrumb(
{
path: path,
name: 'Warenbegleitschein',
},
'remission'
)
);
this.store.dispatch(new SetBranchProcessCurrentPath(path));
this.router.navigate([path]);
}
addArticleToRemissionList() {
@@ -310,8 +341,11 @@ export class RemissionListCreateComponent implements OnInit, OnDestroy, AfterVie
}
@HostListener('window:scroll', ['$event'])
scrollHandler(event) {
// this.remissionHelper.closeFilters();
scrollHandler(event) {}
remissionListScrolled() {
const remissionList = document.getElementById('remission-list');
remissionList.scrollIntoView({ behavior: 'smooth' });
}
ngOnDestroy() {

View File

@@ -0,0 +1,51 @@
<div class="remission-list-container" (scroll)="scrollHandler($event)">
<div class="headers" *ngIf="resourceTypeSwitch">
<div class="header-item resource-type">
<lib-double-choice-switch [model]="resourceTypeSwitch" (change)="resourceTypeChange($event)"></lib-double-choice-switch>
</div>
<div class="header-item title">{{ selectedResourceTypeSpecificModel.headerTitle }}</div>
<div class="header-item sub-title">
<span class="align-center" [ngClass]="{ central: selectedRemissionResourceType === 'zentral' }">{{
selectedResourceTypeSpecificModel.headerSubTitle
}}</span>
</div>
<div class="progress-wrapper" *ngIf="selectedRemissionResourceType === 'ueberlauf'">
<ng-container *ngFor="let item of capacities">
<div>
<lib-progress-bar [progress]="item.percentage"></lib-progress-bar>
</div>
<div>
<span>{{ item.capacity.name }}: {{ item.capacity.utilized }} von {{ item.capacity.available }} {{ item.capacity.label }}</span>
</div>
</ng-container>
</div>
<div class="supplier">
<span>{{ selectedSupplierName }}</span>
</div>
<div class="filters">
<app-remission-filters [remissionResourceType]="selectedRemissionResourceType"></app-remission-filters>
</div>
<div class="note" *ngIf="!listLoaded">
<span class="align-center">Wählen Sie die Abteilung aus, die Sie remittieren möchten.</span>
</div>
<div class="actions" *ngIf="listLoaded">
<div class="remit-all" (click)="remittAll()">
<span>Wanne befüllen</span>
</div>
<div class="complete-remission">
<app-button [primary]="true" (action)="completeRemissionList()">Wanne abschließen</app-button>
</div>
</div>
</div>
<div class="list">
<div class="hits" *ngIf="remissionListHits">
<span>{{ remissionListHits }} Titel</span>
</div>
<app-remission-list id="remission-list" (scrolled)="remissionListScrolled()" started="true" #remissionList></app-remission-list>
</div>
<div class="bottom-actions" *ngIf="listLoaded">
<div class="complete-remission">
<app-button [primary]="true" (action)="completeRemissionList()">Wanne abschließen</app-button>
</div>
</div>
</div>

View File

@@ -0,0 +1,150 @@
.remission-list-container {
height: calc(100% - 20px);
overflow: scroll;
padding-bottom: 10px;
.headers {
display: flex;
flex-direction: column;
.header-item {
width: 100%;
display: flex;
justify-content: center;
}
.resource-type {
margin-top: 31px;
}
.title {
margin-top: 31px;
font-family: 'Open Sans';
font-size: 26px;
font-weight: bold;
color: #000000;
}
.sub-title {
font-family: 'Open Sans';
font-size: 22px;
color: #000000;
margin-top: 3px;
span {
max-width: 338px;
&.central {
max-width: 339px;
}
}
}
.progress-wrapper {
display: flex;
flex-direction: column;
margin-top: 30px;
div {
width: 100%;
display: flex;
justify-content: center;
span {
font-family: 'Open Sans';
font-size: 16px;
font-weight: 500;
color: #596470;
}
}
}
.supplier {
display: flex;
width: 100%;
margin-top: 46px;
justify-content: center;
span {
font-family: 'Open Sans';
font-size: 16px;
font-weight: bold;
color: #596470;
}
}
.filters {
display: flex;
width: 100%;
justify-content: center;
margin-top: 22px;
}
.actions {
display: flex;
flex-direction: column;
justify-content: center;
.complete-remission,
.remit-all {
display: flex;
justify-content: center;
}
.complete-remission {
padding-top: 22px;
}
.remit-all {
padding-top: 37px;
font-family: 'Open Sans';
font-size: 26px;
font-weight: bold;
color: #000000;
}
}
.note {
display: flex;
flex-direction: row;
justify-content: center;
span {
width: 350px;
font-family: 'Open Sans';
font-size: 16px;
font-weight: 600;
color: #89949e;
}
}
}
.list {
.hits {
display: flex;
justify-content: flex-end;
font-size: 15px;
color: #596470;
font-weight: 500;
margin-bottom: 15px;
}
margin-top: 40px;
}
.bottom-actions {
display: flex;
flex-direction: column;
justify-content: center;
.complete-remission {
display: flex;
justify-content: center;
padding-top: 30px;
padding-bottom: 30px;
font-family: 'Open Sans';
font-size: 26px;
font-weight: bold;
color: #000000;
}
}
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RemissionListStartedComponent } from './remission-list-started.component';
describe('RemissionListStartedComponent', () => {
let component: RemissionListStartedComponent;
let fixture: ComponentFixture<RemissionListStartedComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ RemissionListStartedComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(RemissionListStartedComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,348 @@
import { Component, OnInit, OnDestroy, HostListener, AfterViewInit, ViewChild } from '@angular/core';
import { Subject, Observable } from 'rxjs';
import { takeUntil, filter, take, tap, switchMap, catchError, delay } from 'rxjs/operators';
import { DoubleChoiceSwitch } from '@libs/ui';
import { RemissionResourceType } from '../../models/remission-resource-type.model';
import { ResourceTypeSpecificModel } from '../../models/resource-type-specific-model.model';
import { CapacityTypeClientWrapper } from '../../models/capacity-type-client-wrapper.model';
import { Side } from '@libs/ui/lib/small-double-choice-switch';
import { RemissionSupplier, RemissionService, RemissionSourceType, RemissionProcess, CapacityType, RemissionFilter } from '@isa/remission';
import { Select, Store } from '@ngxs/store';
import { RemissionSelectors } from 'apps/sales/src/app/core/store/selectors/remission.selectors';
import { RemissionProcessStatuses } from 'apps/sales/src/app/core/models/remission-process-statuses.model';
import { RESOURCE_TYPE_SPECIFIC_MODEL_OPTIONS, RESOURCE_TYPE_SWITCH } from '../../constants/remission.constants';
import { isNullOrUndefined } from 'util';
import {
SetRemissionProcess,
SetRemissionCreated,
SetRemissionSource,
SetRemissionTarget,
SetRemissionStarted,
} from 'apps/sales/src/app/core/store/actions/remission.actions';
import { RemissionHelperService } from '../../services/remission-helper.service';
import { UpdateFilter } from '../../models/update-filter.model';
import { RemissionListComponent } from '../../components/remission-list/remission-list.component';
import { AddBreadcrumb } from 'apps/sales/src/app/core/store/actions/breadcrumb.actions';
import { Router } from '@angular/router';
@Component({
selector: 'app-remission-list-started',
templateUrl: './remission-list-started.component.html',
styleUrls: ['./remission-list-started.component.scss'],
})
export class RemissionListStartedComponent implements OnInit, OnDestroy, AfterViewInit {
@Select(RemissionSelectors.getRemissionProcessStatuses) remissionProcessStatuses$: Observable<RemissionProcessStatuses>;
@Select(RemissionSelectors.getRemissionProcess) currentRemissionProcess$: Observable<RemissionProcess>;
@Select(RemissionSelectors.getRemissionSource) currentRemissionSource$: Observable<RemissionResourceType>;
@Select(RemissionSelectors.getRemissiontarget) currentRemissionTarget$: Observable<Side>;
remissionProcess$: Observable<RemissionProcess>;
remissionSuppliers$ = this.remissionService.getRemissionTargets();
remissionSources$ = this.remissionService.getRemissionSources();
@ViewChild('remissionList') remissionList: RemissionListComponent;
destroy$ = new Subject();
selectedRemissionResourceType: RemissionResourceType = RemissionResourceType.Central;
selectedResourceTypeSpecificModel: ResourceTypeSpecificModel = RESOURCE_TYPE_SPECIFIC_MODEL_OPTIONS[this.selectedRemissionResourceType];
capacities: CapacityTypeClientWrapper[];
selectedSupplier: Side = Side.LEFT;
selectedSupplierName: string;
suppliers: { leftSupplier: RemissionSupplier; rightSupplier: RemissionSupplier };
remissionProcess: RemissionProcess;
resourceTypeSwitch = RESOURCE_TYPE_SWITCH;
remissionProcessStatuses: RemissionProcessStatuses;
processNeedsToBeRestoredFromCache = false;
remissionListHits: number;
listLoaded = false;
constructor(
private remissionService: RemissionService,
private store: Store,
private remissionHelper: RemissionHelperService,
private router: Router
) {}
ngOnInit() {
this.subscriptions();
}
ngAfterViewInit() {}
subscriptions() {
this.remissionSuppliers$
.pipe(
take(1),
filter(this.remissionSuppliersFilter)
)
.subscribe(this.remissionSuppliersSubscriptionHandler);
this.remissionSubsribtionInitialisation();
this.remissionHelper.filtersUpdated$
.pipe(
takeUntil(this.destroy$),
filter(data => !isNullOrUndefined(data))
)
.subscribe(this.filtersUpdatedHandler);
this.remissionHelper.remissionListHits$
.pipe(
delay(0),
filter(data => !isNullOrUndefined(data)),
takeUntil(this.destroy$)
)
.subscribe(hits => {
this.remissionListHits = hits;
});
}
loadCurrentRemissionSource() {
this.currentRemissionSource$
.pipe(
filter(data => !isNullOrUndefined(data)),
take(1)
)
.subscribe(this.currentRemissionSourcesSubscriptionHandler);
}
currentRemissionSourcesSubscriptionHandler = (source: RemissionResourceType) => {
this.selectedRemissionResourceType = source;
this.selectedResourceTypeSpecificModel = RESOURCE_TYPE_SPECIFIC_MODEL_OPTIONS[source];
this.resourceTypeSwitch.isFirstSwitchedOn = source === RemissionResourceType.Central;
this.remissionProductsSubscription();
this.remissionHelper.updateFilters({ source: source });
};
loadCurrentRemissionTarget() {
this.currentRemissionTarget$
.pipe(
filter(data => !isNullOrUndefined(data)),
take(1)
)
.subscribe(this.currentRemissionTargetSubscriptionHandler);
}
currentRemissionTargetSubscriptionHandler = (side: Side) => {
this.selectedSupplier = side;
const supplier = side === Side.LEFT ? this.suppliers.leftSupplier : this.suppliers.rightSupplier;
this.selectedSupplierName = supplier.name;
this.remissionHelper.updateFilters({ target: side === Side.LEFT ? this.suppliers.leftSupplier : this.suppliers.rightSupplier });
};
remissionSubsribtionInitialisation() {
this.loadCurrentRemissionProcessStatuses().then(remissionProcessStatuses => {
this.remissionProcessStatuses = remissionProcessStatuses;
this.remissionProcess$ = !this.remissionProcessStatuses.created ? this.createProcess() : this.continueProcess();
this.remissionProcess$.pipe(takeUntil(this.destroy$)).subscribe(this.remisionProcessSubscriptionHandler);
});
}
filtersUpdatedHandler = (updates: UpdateFilter) => {
const changes = <RemissionFilter>{
skip: 0,
take: 10,
};
if (updates.filterId && updates.options) {
changes.filter = { [updates.filterId]: [...updates.options.map(op => op.id)] };
this.remissionProductsSubscription();
}
if (updates.source) {
changes.source = updates.source;
}
if (updates.target) {
changes.target = updates.target;
}
if (!this.remissionProcess) {
return;
}
this.remissionService
.updateRemissionFilter({
remissionProcessId: this.remissionProcess.id,
changes: changes,
})
.toPromise();
};
remissionCreatedSuccesfullyHandler = (remissionProcess: RemissionProcess) => {
if (remissionProcess) {
this.remissionProcess = remissionProcess;
this.store.dispatch(new SetRemissionCreated(true));
this.store.dispatch(new SetRemissionProcess(remissionProcess));
}
};
loadCurrentRemissionProcessStatuses() {
return this.remissionProcessStatuses$
.pipe(
filter(data => !isNullOrUndefined(data)),
take(1)
)
.toPromise();
}
continueProcess() {
console.log('process continued');
return this.loadCurrentRemissionProcess().pipe(
take(1),
switchMap(remissionProcess => {
return this.remissionService.continueProcess(remissionProcess);
}),
catchError(this.continueProcessErrorHandler)
);
}
continueProcessErrorHandler = (error: any) => {
console.error(error);
this.processNeedsToBeRestoredFromCache = true;
return this.createProcess();
};
createProcess() {
console.log('process created');
return this.remissionService.createProcess().pipe(tap(this.remissionCreatedSuccesfullyHandler));
}
loadCurrentRemissionProcess() {
return this.currentRemissionProcess$.pipe(filter(data => !isNullOrUndefined(data), take(1)));
}
remissionSourcesFilter = (remissionSources: RemissionSourceType[]) => {
return remissionSources && Array.isArray(remissionSources) && remissionSources.length === 2;
};
remissionSuppliersFilter = (remissionSuppliers: RemissionSupplier[]) => {
return remissionSuppliers && Array.isArray(remissionSuppliers) && remissionSuppliers.length === 2;
};
remissionSuppliersSubscriptionHandler = (remissionSuppliers: RemissionSupplier[]) => {
this.suppliers = {
leftSupplier: remissionSuppliers[1],
rightSupplier: remissionSuppliers[0],
};
this.loadCurrentRemissionTarget();
};
remisionProcessSubscriptionHandler = (remissionProcess: RemissionProcess) => {
this.remissionProcess = remissionProcess;
(!isNullOrUndefined(remissionProcess.capacities)).ifTrue(() => this.capacitiesBinder(remissionProcess.capacities));
this.processNeedsToBeRestoredFromCache.ifTrue(() => this.restoreProcess());
this.selectedRemissionResourceType =
remissionProcess.filter.source === 'zentral' ? RemissionResourceType.Central : RemissionResourceType.Overflow;
this.resourceTypeSwitch.isFirstSwitchedOn = this.selectedRemissionResourceType === RemissionResourceType.Central;
this.selectedResourceTypeSpecificModel = RESOURCE_TYPE_SPECIFIC_MODEL_OPTIONS[this.selectedRemissionResourceType];
this.selectedSupplier = remissionProcess.filter.target.name === this.suppliers.leftSupplier.name ? Side.LEFT : Side.RIGHT;
this.store.dispatch(new SetRemissionProcess(remissionProcess));
this.remissionHelper.loadRemissionListProducts(remissionProcess.id);
this.remissionProductsSubscription();
// TODO: remove console logging
console.log('remission subscribed', remissionProcess);
};
restoreProcess = () => {
this.processNeedsToBeRestoredFromCache = false;
this.loadCurrentRemissionSource();
this.loadCurrentRemissionTarget();
this.remissionHelper.reApplyFilters(this.selectedRemissionResourceType);
};
capacitiesBinder = (capacities: CapacityType[]) => {
this.capacities = [];
capacities.forEach(capacity => {
if (capacity && capacity.utilized && capacity.available) {
const percentage = capacity.utilized === 0 || capacity.available === 0 ? 0 : (capacity.utilized / capacity.available) * 100;
this.capacities.push(<CapacityTypeClientWrapper>{
percentage: percentage,
capacity: capacity,
});
}
});
};
resourceTypeChange(model: DoubleChoiceSwitch) {
this.resourceTypeSwitch = model;
this.selectedRemissionResourceType = model.isFirstSwitchedOn ? RemissionResourceType.Central : RemissionResourceType.Overflow;
this.selectedResourceTypeSpecificModel = RESOURCE_TYPE_SPECIFIC_MODEL_OPTIONS[this.selectedRemissionResourceType];
this.store.dispatch(new SetRemissionSource(this.selectedRemissionResourceType));
this.remissionHelper.updateFilters({ source: this.selectedRemissionResourceType });
this.loadCurrentRemissionTarget();
this.remissionHelper.reApplyFilters(this.selectedRemissionResourceType);
this.remissionProductsSubscription();
}
remissionProductsSubscription() {
const options = this.store.selectSnapshot(RemissionSelectors.getRemissionSelectedOptions);
if (
!this.remissionList.subscribedToProducts &&
(this.selectedRemissionResourceType === RemissionResourceType.Central ||
(this.selectedRemissionResourceType === RemissionResourceType.Overflow && options && options.length > 0))
) {
this.remissionList.subscribeToProducts();
setTimeout(() => {
this.listLoaded = true;
});
} else if (
this.remissionList.subscribedToProducts &&
!(
this.selectedRemissionResourceType === RemissionResourceType.Central ||
(this.selectedRemissionResourceType === RemissionResourceType.Overflow && options && options.length > 0)
)
) {
this.remissionList.unsubscribeToProducts();
this.remissionListHits = null;
this.listLoaded = false;
}
}
supplierUpdated(side: Side) {
this.selectedSupplier = side;
this.store.dispatch(
new SetRemissionTarget(
side,
side === Side.LEFT ? this.suppliers.leftSupplier : this.suppliers.rightSupplier,
this.selectedRemissionResourceType
)
);
this.remissionHelper.updateFilters({ target: side === Side.LEFT ? this.suppliers.leftSupplier : this.suppliers.rightSupplier });
}
completeRemissionList() {
// TODO: implement completing remission list action
throw new Error('(completeRemissionList) method not implemented');
}
navigateToStartedRemission() {
const path = '/remission/started';
this.store.dispatch(
new AddBreadcrumb(
{
path: path,
name: 'Warenbegleitschein',
},
'remission'
)
);
this.router.navigate([path]);
}
remittAll() {
// TODO: Implement method
throw new Error('(remittAll) Method not implemented');
}
addArticleToRemissionList() {
// TODO: Implement method
throw new Error('(addArticleToRemissionList) Method not implemented');
}
@HostListener('window:scroll', ['$event'])
scrollHandler(event) {}
remissionListScrolled() {
const remissionList = document.getElementById('remission-list');
remissionList.scrollIntoView({ behavior: 'smooth' });
}
ngOnDestroy() {
this.destroy$.next();
}
}

View File

@@ -1,12 +1,17 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { RemissionListCreateComponent } from './pages/remission-list-create/remission-list-create.component';
import { RemissionListStartedComponent } from './pages/remission-list-started/remission-list-started.component';
const routes: Routes = [
{
path: 'create',
component: RemissionListCreateComponent,
},
{
path: 'started',
component: RemissionListStartedComponent,
},
];
@NgModule({

View File

@@ -14,6 +14,9 @@ import { RemissionListCardComponent } from './components/remission-list-card/rem
import { RemissionListCardLoadingComponent } from './components/remission-list-card-loading/remission-list-card-loading.component';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { SharedModule } from '../../shared/shared.module';
import { RemissionListStartedComponent } from './pages/remission-list-started/remission-list-started.component';
import { RemissionListCardStartedComponent } from './components/remission-list-card-started/remission-list-card-started.component';
import { RemissionShippingDocumentComponent } from './components/remission-shipping-document/remission-shipping-document.component';
@NgModule({
declarations: [
@@ -24,6 +27,9 @@ import { SharedModule } from '../../shared/shared.module';
RemissionListComponent,
RemissionListCardComponent,
RemissionListCardLoadingComponent,
RemissionListStartedComponent,
RemissionListCardStartedComponent,
RemissionShippingDocumentComponent,
],
imports: [
CommonModule,

View File

@@ -11,7 +11,7 @@ export class RemissionHelperService {
filteOptionsDeleteEvents$ = new Subject<FilterOption>();
filtersClearedEvent$ = new Subject();
closeFilters$ = new Subject();
loadRemissionListProducts$ = new Subject<string>();
loadRemissionListProducts$ = new BehaviorSubject<string>(null);
filtersUpdated$ = new Subject<UpdateFilter>();
reApplyFilters$ = new BehaviorSubject<RemissionResourceType>(null);
remissionListHits$ = new BehaviorSubject<number>(null);

6
package-lock.json generated
View File

@@ -1039,9 +1039,9 @@
}
},
"@isa/remission": {
"version": "0.1.2",
"resolved": "https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel/npm/registry/@isa/remission/-/remission-0.1.2.tgz",
"integrity": "sha1-+NzVCbHooC+NWxVLZAJ4hKm7Hys=",
"version": "0.1.3",
"resolved": "https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel/npm/registry/@isa/remission/-/remission-0.1.3.tgz",
"integrity": "sha1-MV/31xguLjI50s62R0/APgaMEQQ=",
"requires": {
"deep-equal": "^1.1.0",
"tslib": "^1.9.0",

View File

@@ -27,7 +27,7 @@
"@angular/pwa": "^0.13.4",
"@angular/router": "~7.2.12",
"@angular/service-worker": "~7.2.12",
"@isa/remission": "^0.1.2",
"@isa/remission": "^0.1.3",
"@ngxs/store": "^3.4.1",
"@types/faker": "^4.1.5",
"@zxing/ngx-scanner": "^1.3.0",