Merge branch 'feature/189-Warenausgabe/264-Bearbeiten/main' into feature/189-Warenausgabe/main

This commit is contained in:
Lorenz Hilpert
2020-08-11 13:51:21 +02:00
64 changed files with 1524 additions and 52 deletions

View File

@@ -0,0 +1,14 @@
<div class="log-entry mt-40" [class.last]="last" [class.first]="first">
<span class="timeline-dot"></span>
<app-shelf-history-log-header
[infoText]="history | historyDtoToInfoText"
[statusText]="history | historyDtoToStatusText"
></app-shelf-history-log-header>
<app-shelf-history-log-body
*ngFor="let changes of history.values"
[oldStatus]="changes | getStatusFromHistory: 'old'"
[newStatus]="changes | getStatusFromHistory: 'new'"
[changedBy]="history.changedBy"
[caption]="changes.caption"
></app-shelf-history-log-body>
</div>

View File

@@ -0,0 +1,61 @@
@import 'variables';
$offset: 10px;
$line-top-offset: 3px;
$dot-size: 16px;
$dot-size-lg: 20px;
$border-size: 1px;
$border-size-cover: 2px;
.log-entry {
position: relative;
// calculate offset relative to container offset (22px)
padding-left: calc(70px - 22px);
&:before {
content: '';
position: absolute;
height: 130%;
width: 1px;
top: $line-top-offset;
left: $offset;
border: $border-size solid $isa-light-blue-platinum;
background: $isa-light-blue-platinum;
}
&.last:before {
height: 30%;
border: $border-size-cover solid #fff;
background: #fff;
left: calc(#{$offset} - #{$border-size-cover});
}
.timeline-dot {
position: absolute;
top: 0;
left: $offset;
background: #fff;
width: 2px;
&:before {
content: '';
position: absolute;
background: $isa-light-blue-platinum;
top: $line-top-offset;
left: calc((#{$dot-size} - #{$line-top-offset}) / -2);
border-radius: 50%;
width: $dot-size;
height: $dot-size;
}
}
&.first {
.timeline-dot:before {
width: $dot-size-lg;
height: $dot-size-lg;
background: $isa-neutral-info;
left: calc(#{$dot-size} / -2);
}
}
}

View File

@@ -0,0 +1,72 @@
import { DebugElement, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { ShelfHistoryLogComponent } from './history-log.component';
import { ShelfHistoryLogHeaderComponent } from './log-header';
import { ShelfHistoryLogBodyComponent } from './log-body';
import { By } from '@angular/platform-browser';
import { historiesDtoMock, historyDtoMock } from '../../shared/mockdata';
import {
HistoryDtoToInfoTextPipe,
HistoryDtoToStatusTextPipe,
GetStatusFromHistoryPipe,
} from '../../pages/shelf-history/pipes';
import { HistoryDTO } from '@cmf/trade-api';
fdescribe('HistoryLogComponent', () => {
let fixture: ComponentFixture<ShelfHistoryLogComponent>;
let component: ShelfHistoryLogComponent;
let debugElement: DebugElement;
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [
ShelfHistoryLogComponent,
ShelfHistoryLogHeaderComponent,
ShelfHistoryLogBodyComponent,
HistoryDtoToInfoTextPipe,
HistoryDtoToStatusTextPipe,
GetStatusFromHistoryPipe,
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ShelfHistoryLogComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement;
const historyMockWithMultipleLogEntries: HistoryDTO = historiesDtoMock.find(
(history) => history.values && history.values.length > 2
);
component.history = historyMockWithMultipleLogEntries;
fixture.detectChanges();
});
it('should be created', () => {
expect(component instanceof ShelfHistoryLogComponent).toBeTruthy();
});
it('should show a header for all history values (log entries)', () => {
const header = debugElement.queryAll(
By.css('app-shelf-history-log-header')
);
expect(header.length).toBe(1);
});
it('should show an entry for each history value (log entry)', () => {
const historyMockWithMultipleLogEntries: HistoryDTO = historiesDtoMock.find(
(history) => history.values && history.values.length > 2
);
fixture.detectChanges();
const entries = debugElement.queryAll(By.css('app-shelf-history-log-body'));
expect(entries.length).toBe(
historyMockWithMultipleLogEntries.values.length
);
});
});

View File

@@ -0,0 +1,16 @@
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
import { HistoryDTO } from '@cmf/trade-api';
@Component({
selector: 'app-shelf-history-log',
templateUrl: './history-log.component.html',
styleUrls: ['./history-log.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfHistoryLogComponent {
@Input() first = false;
@Input() last = false;
@Input() history: HistoryDTO;
constructor() {}
}

View File

@@ -0,0 +1,31 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
HistoryDtoToInfoTextPipe,
HistoryDtoToStatusTextPipe,
GetStatusFromHistoryPipe,
} from '../../pages/shelf-history/pipes';
import { IconModule } from '@libs/ui';
import { ShelfHistoryLogComponent } from './history-log.component';
import { ShelfHistoryLogHeaderComponent } from './log-header';
import { ShelfHistoryLogBodyComponent } from './log-body';
@NgModule({
imports: [CommonModule, IconModule],
declarations: [
ShelfHistoryLogComponent,
ShelfHistoryLogHeaderComponent,
ShelfHistoryLogBodyComponent,
HistoryDtoToInfoTextPipe,
HistoryDtoToStatusTextPipe,
GetStatusFromHistoryPipe,
],
exports: [
ShelfHistoryLogComponent,
ShelfHistoryLogHeaderComponent,
ShelfHistoryLogBodyComponent,
],
providers: [],
})
export class ShelfHistoryLogModule {}

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
<div class="container isa-mb-20">
<ul class="mt-8">
<li class="mb-5">
{{ caption }} alt:
<span class="isa-font-weight-bold ml-5">{{ oldStatus }}</span>
</li>
<li class="mb-5">
{{ caption }} neu:
<span class="isa-font-weight-bold ml-5">{{ newStatus }}</span>
</li>
<li>
Geändert von:
<span class="isa-font-weight-bold ml-5">{{ changedBy }}</span>
</li>
</ul>
</div>

View File

@@ -0,0 +1,15 @@
@import 'variables';
.container {
font-family: $font-family;
ul {
margin: 0;
padding: 0;
list-style: none;
}
li {
font-size: 16px;
line-height: $font-line-height;
}
}

View File

@@ -0,0 +1,121 @@
import { ShelfHistoryLogBodyComponent } from './log-body.component';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
fdescribe('ShelfHistoryLogBodyComponent', () => {
let fixture: ComponentFixture<ShelfHistoryLogBodyComponent>;
let component: ShelfHistoryLogBodyComponent;
let debugElement: DebugElement;
let oldStatusElement: HTMLSpanElement;
let newStatusElement: HTMLSpanElement;
let changedByElement: HTMLSpanElement;
let captionElementOldStatus: HTMLLIElement;
let captionElementNewStatus: HTMLLIElement;
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [ShelfHistoryLogBodyComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ShelfHistoryLogBodyComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement;
});
it('should be created', () => {
expect(component instanceof ShelfHistoryLogBodyComponent).toBeTruthy();
});
describe('Case: No Inputs provided', () => {
beforeEach(() => {
fixture.detectChanges();
});
it('should show a dash as old status', () => {
oldStatusElement = debugElement
.queryAll(By.css('li'))[0]
.nativeElement.querySelector('span');
expect(oldStatusElement.textContent).toBe('-');
});
it('should show a dash new status', () => {
newStatusElement = debugElement
.queryAll(By.css('li'))[1]
.nativeElement.querySelector('span');
expect(newStatusElement.textContent).toBe('-');
});
it('should show a dash as new changedBy', () => {
changedByElement = debugElement
.queryAll(By.css('li'))[2]
.nativeElement.querySelector('span');
expect(newStatusElement.textContent).toBe('-');
});
it('should show an empty string as caption for old status', () => {
captionElementOldStatus = debugElement.queryAll(By.css('li'))[0]
.nativeElement;
expect(captionElementOldStatus.textContent).toContain('alt:');
});
it('should show an empty string as caption for new status', () => {
captionElementNewStatus = debugElement.queryAll(By.css('li'))[1]
.nativeElement;
expect(captionElementNewStatus.textContent).toContain('neu:');
});
});
describe('Case: With Inputs provided', () => {
beforeEach(() => {
component.oldStatus = 'Mock Old Status';
component.newStatus = 'Mock New Status';
component.changedBy = 'Mock ChangedBy';
component.caption = 'Mock Caption';
fixture.detectChanges();
});
it('should show old status', () => {
oldStatusElement = debugElement
.queryAll(By.css('li'))[0]
.nativeElement.querySelector('span');
expect(oldStatusElement.textContent).toBe(component.oldStatus);
});
it('should show new status', () => {
newStatusElement = debugElement
.queryAll(By.css('li'))[1]
.nativeElement.querySelector('span');
expect(newStatusElement.textContent).toBe(component.newStatus);
});
it('should show new changedBy', () => {
changedByElement = debugElement
.queryAll(By.css('li'))[2]
.nativeElement.querySelector('span');
expect(changedByElement.textContent).toBe(component.changedBy);
});
it('should show caption for old status', () => {
captionElementOldStatus = debugElement.queryAll(By.css('li'))[0]
.nativeElement;
expect(captionElementOldStatus.textContent).toContain(
`alt: ${component.oldStatus}`
);
});
it('should show caption for new status', () => {
captionElementNewStatus = debugElement.queryAll(By.css('li'))[1]
.nativeElement;
expect(captionElementNewStatus.textContent).toContain(
`neu: ${component.newStatus}`
);
});
});
});

View File

@@ -0,0 +1,16 @@
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
@Component({
selector: 'app-shelf-history-log-body',
templateUrl: 'log-body.component.html',
styleUrls: ['./log-body.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfHistoryLogBodyComponent {
@Input() oldStatus = '-';
@Input() newStatus = '-';
@Input() changedBy = '-';
@Input() caption = '';
constructor() {}
}

View File

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

View File

@@ -0,0 +1,13 @@
<div class="container mb-20">
<div class="d-flex flex-column">
<h4 class="info-text isa-font-weight-default">{{ infoText }}</h4>
<h2 class="status-text isa-font-weight-bold">{{ statusText }}</h2>
</div>
<div class="notification-type">
<lib-icon name="Icon_Abholfach" width="17" height="18"></lib-icon>
<span class="ml-10 isa-font-weight-emphasis isa-font-color-customer">{{
type
}}</span>
</div>
</div>

View File

@@ -0,0 +1,36 @@
@import 'variables';
:host {
width: 100%;
}
.container {
display: flex;
h2,
h4 {
margin: 0;
}
.info-text,
.status-text {
line-height: $font-line-height;
font-family: $font-family;
}
.info-text {
margin-bottom: 4px;
}
.status-text {
font-size: $headline-font-size-xs;
}
.notification-type {
display: flex;
align-items: center;
font-size: 15px;
margin-left: auto;
}
}

View File

@@ -0,0 +1,48 @@
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { ShelfHistoryLogHeaderComponent } from './log-header.component';
import { DebugElement, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
fdescribe('ShelfHistoryLogHeaderComponent', () => {
let fixture: ComponentFixture<ShelfHistoryLogHeaderComponent>;
let component: ShelfHistoryLogHeaderComponent;
let debugElement: DebugElement;
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [ShelfHistoryLogHeaderComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ShelfHistoryLogHeaderComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement;
component.infoText = 'Mock Info Text';
component.statusText = 'Mock Status Text';
fixture.detectChanges();
});
it('should be created', () => {
expect(component instanceof ShelfHistoryLogHeaderComponent).toBeTruthy();
});
it('should show info text', () => {
const infoTextElement: HTMLHeadingElement = debugElement.query(
By.css('.info-text')
).nativeElement;
expect(infoTextElement.textContent).toBe(component.infoText);
});
it('should show status text', () => {
const statusTextElement: HTMLHeadingElement = debugElement.query(
By.css('.status-text')
).nativeElement;
expect(statusTextElement.textContent).toBe(component.statusText);
});
});

View File

@@ -0,0 +1,16 @@
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
import { HistoryNotificationType } from '../../../defs';
@Component({
selector: 'app-shelf-history-log-header',
templateUrl: 'log-header.component.html',
styleUrls: ['./log-header.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfHistoryLogHeaderComponent {
@Input() infoText: string;
@Input() statusText: string;
@Input() type: HistoryNotificationType = 'Abholfach';
constructor() {}
}

View File

@@ -0,0 +1 @@
export type HistoryNotificationType = 'Abholfach' | 'Benachrichtigung';

View File

@@ -1,4 +1,6 @@
// start:ng42.barrel
export * from './autocomplete-options.model';
export * from './history-notification.type';
export * from './shelf-primary-filter-options';
export * from './shelf-history-log-details.model';
// end:ng42.barrel

View File

@@ -0,0 +1,9 @@
export interface ShelfHistoryLogDetails {
caption?: string;
property?: string;
value?: string;
previousValue?: string;
}

View File

@@ -0,0 +1,17 @@
<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>
</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

@@ -0,0 +1,18 @@
@import 'variables';
.header-container {
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
.headline {
font-size: $headline-font-size-m;
// Calculate margin to fulfill 20px margin offset to top
margin-top: calc(20px - 37px);
}
}
.default-template {
margin-top: 0;
}

View File

@@ -0,0 +1,50 @@
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { HistoryStateFacade } from '@shelf-store/history';
import { ShelfHistoryHeaderComponent } from './history-header.component';
fdescribe('ShelfHistoryHeaderComponent', () => {
let fixture: ComponentFixture<ShelfHistoryHeaderComponent>;
let component: ShelfHistoryHeaderComponent;
let facade: HistoryStateFacade;
const id = 123;
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [ShelfHistoryHeaderComponent],
providers: [
{
provide: HistoryStateFacade,
useValue: jasmine.createSpyObj('historyStateFacade', {
reloadHistory: () => {},
}),
},
],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ShelfHistoryHeaderComponent);
component = fixture.componentInstance;
facade = TestBed.get(HistoryStateFacade);
component.orderItemSubsetId = id;
fixture.detectChanges();
});
it('should be created', () => {
expect(component instanceof ShelfHistoryHeaderComponent).toBeTruthy();
});
describe('updateHistory', () => {
beforeEach(() => {
spyOn(component, 'updateHistory').and.callThrough();
});
it('should call reloadHistory on the state facade', () => {
component.updateHistory();
expect(facade.reloadHistory).toHaveBeenCalledWith(id);
});
});
});

View File

@@ -0,0 +1,24 @@
import {
Component,
ChangeDetectionStrategy,
Input,
TemplateRef,
} from '@angular/core';
import { HistoryStateFacade } from '@shelf-store/history';
@Component({
selector: 'app-shelf-history-header',
templateUrl: 'history-header.component.html',
styleUrls: ['./history-header.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfHistoryHeaderComponent {
@Input() orderItemSubsetId: number;
@Input() headerTemplate: TemplateRef<any>;
constructor(private historyStateFacade: HistoryStateFacade) {}
updateHistory() {
this.historyStateFacade.reloadHistory(this.orderItemSubsetId);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,20 @@
<div class="layout-container">
<div class="layout-content">
<app-shelf-history-header
[orderItemSubsetId]="orderItemSubsetId"
></app-shelf-history-header>
<hr class="isa-content-spacer" />
<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>
</div>
</div>

View File

@@ -0,0 +1,56 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HistoryStateFacade } from '@shelf-store/history';
import { ShelfHistoryLogsComponent } from './shelf-history-logs.component';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ShelfHistoryLogModule } from '../../../components/history-log';
import { CommonModule } from '@angular/common';
import { FilterHistoriesWithStatusChangePipe } from '../pipes';
fdescribe('ShelfHistoryLogComponent', () => {
let fixture: ComponentFixture<ShelfHistoryLogsComponent>;
let component: ShelfHistoryLogsComponent;
let facade: jasmine.SpyObj<HistoryStateFacade>;
const id = 123;
beforeEach(async () => {
TestBed.configureTestingModule({
imports: [CommonModule, ShelfHistoryLogModule],
declarations: [
ShelfHistoryLogsComponent,
FilterHistoriesWithStatusChangePipe,
],
providers: [
{
provide: HistoryStateFacade,
useValue: jasmine.createSpyObj('historyStateFacade', {
getHistory$: () => {},
}),
},
],
schemas: [NO_ERRORS_SCHEMA],
})
.overrideTemplate(ShelfHistoryLogsComponent, '<div></div>')
.compileComponents();
facade = TestBed.get(HistoryStateFacade);
fixture = TestBed.createComponent(ShelfHistoryLogsComponent);
component = fixture.componentInstance;
component.orderItemSubsetId = id;
fixture.detectChanges();
});
it('should be created', () => {
expect(component instanceof ShelfHistoryLogsComponent).toBeTruthy();
});
describe('getHistory', () => {
it('should call the facade to get the histories', () => {
spyOn(component, 'getHistory$').and.callThrough();
component.getHistory$();
expect(facade.getHistory$).toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,30 @@
import {
Component,
OnInit,
ChangeDetectionStrategy,
Input,
} from '@angular/core';
import { Observable } from 'rxjs';
import { HistoryDTO } from '@cmf/trade-api';
import { HistoryStateFacade } from '@shelf-store/history';
@Component({
selector: 'app-shelf-history-logs',
templateUrl: 'shelf-history-logs.component.html',
styleUrls: ['./shelf-history-logs.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfHistoryLogsComponent implements OnInit {
@Input() orderItemSubsetId: number;
history$: Observable<HistoryDTO[]>;
constructor(private historyStateFacade: HistoryStateFacade) {}
ngOnInit() {
this.history$ = this.getHistory$();
}
getHistory$(): Observable<HistoryDTO[]> {
return this.historyStateFacade.getHistory$(this.orderItemSubsetId);
}
}

View File

@@ -0,0 +1,23 @@
import { historyDtoMock } from '../../../shared/mockdata';
import { HistoryDTO } from '@cmf/trade-api';
import { historyDtoToInfoText } from './history-dto-to-info-text.mapping';
fdescribe('Mapping: historyDtoToInfoText', () => {
const historyMock: HistoryDTO = historyDtoMock;
it('should return a string containing the date, time and location', () => {
const result = historyDtoToInfoText(historyMock);
const expected = `02.07.2020 | 10:56 Uhr | München Neuhausen`;
expect(result).toEqual(expected);
});
it('should only return a string containing date and tiem if no location is present', () => {
const { location, ...historyMockWithoutLocation } = historyMock;
const result = historyDtoToInfoText(historyMockWithoutLocation);
const expected = `02.07.2020 | 10:56 Uhr`;
expect(result).toEqual(expected);
});
});

View File

@@ -0,0 +1,18 @@
import { HistoryDTO } from '@cmf/trade-api';
export function historyDtoToInfoText(history: HistoryDTO): string {
const baseDate = new Date(history.changed);
const format: Intl.DateTimeFormatOptions = {
month: '2-digit',
day: '2-digit',
year: 'numeric',
};
const date = baseDate.toLocaleDateString('de', format);
const time = `${baseDate.getHours()}:${('0' + baseDate.getMinutes()).slice(
-2
)} Uhr`;
const location = history.location;
return `${date} | ${time}${location ? ' | ' + location : ''}`;
}

View File

@@ -0,0 +1,22 @@
import { historyDtoToStatusText } from './history-dto-to-status-text.mapping';
import { historyDtoMock } from '../../../shared/mockdata';
fdescribe('Mapping: historyDtoToStatusText', () => {
it('should return the description as status text', () => {
const details = historyDtoMock;
const result = historyDtoToStatusText(details);
const expected = 'Test Description';
expect(result).toEqual(expected);
});
it('should return a default status if no description is set', () => {
const { description, ...details } = historyDtoMock;
const result = historyDtoToStatusText(details);
const expected = 'Status der Bestellung wurde geändert';
expect(result).toEqual(expected);
});
});

View File

@@ -0,0 +1,5 @@
import { HistoryDTO } from '@cmf/trade-api';
export function historyDtoToStatusText(history: HistoryDTO): string {
return history.description || 'Status der Bestellung wurde geändert';
}

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './history-dto-to-status-text.mapping';
export * from './history-dto-to-info-text.mapping';
// end:ng42.barrel

View File

@@ -0,0 +1,86 @@
import { historiesDtoMock } from '../../../shared/mockdata';
import { FilterHistoriesWithStatusChangePipe } from './filter-histories-with-status-change.pipe';
import { HistoryDTO } from '@cmf/trade-api';
fdescribe('Pipe: HistoryDtoToInfoTextPipe', () => {
let pipe: FilterHistoriesWithStatusChangePipe;
const mockHistories: HistoryDTO[] = historiesDtoMock;
beforeEach(() => {
pipe = new FilterHistoriesWithStatusChangePipe();
});
it('should only return histories with a status change', () => {
const result = pipe.transform(mockHistories);
expect(result.length).toBe(2);
expect(result).toEqual([
{
location: 'München Neuhausen',
id: 114392259,
version: 2,
changed: '2020-02-24T14:53:58.9359',
changedBy: 'Lorenz Hilpert',
changeset: 68359312,
historyset: 10896418,
values: [
{
caption: 'Eingetroffen/Versendet',
value: '',
previousValue: '02.07.2020 10:55:23',
},
{
caption: 'Status',
value: 'bestellt',
previousValue: 'eingetroffen',
},
{
caption: 'Status Datum',
value: '24.02.2020 14:53:58',
previousValue: '02.07.2020 10:55:23',
},
{ caption: 'Abholfach-Nr', value: '', previousValue: '130_0207_1' },
{
caption: 'Im Abholfach seit',
value: '',
previousValue: '02.07.2020 10:55:23',
},
{
caption: 'Im Abholfach bis',
value: '',
previousValue: '16.07.2020 10:55:23',
},
],
},
{
location: 'München Neuhausen',
id: 114392259,
version: 1,
changed: '2020-02-24T14:53:57.6544',
changedBy: 'Lorenz Hilpert',
changeset: 68359310,
historyset: 9520697,
values: [
{ caption: 'Meldenummer', value: '', previousValue: '999' },
{
caption: 'bestellt bei Lieferant am',
value: '',
previousValue: '24.02.2020 14:53:58',
},
{
caption: 'vsl. Lieferdatum',
value: '25.02.2020 15:00:00',
previousValue: '25.02.2020 14:00:00',
},
{ caption: 'Status', value: 'neu', previousValue: 'bestellt' },
{
caption: 'Status Datum',
value: '24.02.2020 14:53:57',
previousValue: '24.02.2020 14:53:58',
},
],
},
]);
});
});

View File

@@ -0,0 +1,15 @@
import { Pipe, PipeTransform } from '@angular/core';
import { HistoryDTO } from '@cmf/trade-api';
@Pipe({
name: 'filterHistoriesWithStatusChange',
})
export class FilterHistoriesWithStatusChangePipe implements PipeTransform {
transform(histories: HistoryDTO[]): HistoryDTO[] {
return histories.filter(
(history) =>
history.values &&
history.values.some((value) => value.caption === 'Status')
);
}
}

View File

@@ -0,0 +1,51 @@
import { GetStatusFromHistoryPipe } from './get-status-from-history.pipe';
import { historyDtoMock, historiesDtoMock } from '../../../shared/mockdata';
import { ShelfHistoryLogDetails } from '../../../defs';
fdescribe('Pipe: GetNewStatusFromHistoryPipe', () => {
let pipe: GetStatusFromHistoryPipe;
beforeEach(() => {
pipe = new GetStatusFromHistoryPipe();
});
it('should return the new status value', () => {
const historyWithState: ShelfHistoryLogDetails = historiesDtoMock
.find((h) => h.values.find((v) => v.caption === 'Status'))
.values.find((value) => value && value.caption === 'Status');
const result = pipe.transform(historyWithState);
const expected = 'bestellt';
expect(result).toBe(expected);
const historiesWithOtherChanges = historiesDtoMock
.find((h) => h.values.find((v) => v.caption === 'vorgemerkt'))
.values.find((value) => value && value.caption === 'vorgemerkt');
const result2 = pipe.transform(historiesWithOtherChanges, 'new');
const expected2 = 'ja';
expect(result2).toBe(expected2);
});
it('should return the old status value', () => {
const historyWithState: ShelfHistoryLogDetails = historiesDtoMock
.find((h) => h.values.find((v) => v.caption === 'Status'))
.values.find((value) => value && value.caption === 'Status');
const result = pipe.transform(historyWithState, 'old');
const expected = 'eingetroffen';
expect(result).toBe(expected);
});
it('should return a default value if the status string is empty', () => {
const historyWithEmptyState: ShelfHistoryLogDetails = historiesDtoMock
.find((h) => h.values.find((v) => v.value === ''))
.values.find((value) => value && value.value === '');
const result = pipe.transform(historyWithEmptyState, 'new');
expect(result).toBe('-');
});
});

View File

@@ -0,0 +1,17 @@
import { Pipe, PipeTransform } from '@angular/core';
import { ShelfHistoryLogDetails } from '../../../defs';
@Pipe({
name: 'getStatusFromHistory',
})
export class GetStatusFromHistoryPipe implements PipeTransform {
transform(
details: ShelfHistoryLogDetails,
statusType: 'new' | 'old' = 'new'
): string {
const defaultValue = '-';
return statusType === 'old'
? details.previousValue || defaultValue
: details.value || defaultValue;
}
}

View File

@@ -0,0 +1,29 @@
import { HistoryDtoToStatusTextPipe } from './historyDtoToStatusText.pipe';
import * as mapping from '../mappings';
import { HistoryDTO } from '@cmf/trade-api';
import { historyDtoMock } from '../../../shared/mockdata';
fdescribe('Pipe: HistoryDtoToStatusTextPipe', () => {
let pipe: HistoryDtoToStatusTextPipe;
const mockHistory: HistoryDTO = historyDtoMock;
const fakeReturnValue = 'mapped result';
const historyDtoToStatusTextSpy: jasmine.Spy = jasmine.createSpy(
'historyDtoToStatusTextSpy'
);
beforeEach(() => {
pipe = new HistoryDtoToStatusTextPipe();
spyOnProperty(mapping, 'historyDtoToStatusText').and.returnValue(
historyDtoToStatusTextSpy
);
historyDtoToStatusTextSpy.and.returnValue(fakeReturnValue);
});
it('should call historyDtoToStatusText and return its value', () => {
const result = pipe.transform(mockHistory);
expect(mapping.historyDtoToStatusText).toHaveBeenCalledWith(mockHistory);
expect(result).toEqual(fakeReturnValue);
});
});

View File

@@ -0,0 +1,12 @@
import { Pipe, PipeTransform } from '@angular/core';
import { HistoryDTO } from '@cmf/trade-api';
import { historyDtoToStatusText } from '../mappings';
@Pipe({
name: 'historyDtoToStatusText',
})
export class HistoryDtoToStatusTextPipe implements PipeTransform {
transform(history: HistoryDTO): string {
return historyDtoToStatusText(history);
}
}

View File

@@ -0,0 +1,29 @@
import * as mappings from '../mappings';
import { HistoryDtoToInfoTextPipe } from './historyDtoToinfoText.pipe';
import { HistoryDTO } from '@cmf/trade-api';
import { historyDtoMock } from '../../../shared/mockdata';
fdescribe('Pipe: HistoryDtoToInfoTextPipe', () => {
let pipe: HistoryDtoToInfoTextPipe;
const mockHistory: HistoryDTO = historyDtoMock;
const fakeReturnValue = 'mapped result';
const historyDtoToInfoTextSpy: jasmine.Spy = jasmine.createSpy(
'historyDtoToInfoTextSpy'
);
beforeEach(() => {
pipe = new HistoryDtoToInfoTextPipe();
spyOnProperty(mappings, 'historyDtoToInfoText', 'get').and.returnValue(
historyDtoToInfoTextSpy
);
historyDtoToInfoTextSpy.and.returnValue(fakeReturnValue);
});
it('should call historyDtoToInfoText and return its mapped value', () => {
const result = pipe.transform(mockHistory);
expect(mappings.historyDtoToInfoText).toHaveBeenCalledWith(mockHistory);
expect(result).toEqual(fakeReturnValue);
});
});

View File

@@ -0,0 +1,12 @@
import { Pipe, PipeTransform } from '@angular/core';
import { HistoryDTO } from '@cmf/trade-api';
import { historyDtoToInfoText } from '../mappings';
@Pipe({
name: 'historyDtoToInfoText',
})
export class HistoryDtoToInfoTextPipe implements PipeTransform {
transform(history: HistoryDTO): string {
return historyDtoToInfoText(history);
}
}

View File

@@ -0,0 +1,6 @@
// start:ng42.barrel
export * from './historyDtoToinfoText.pipe';
export * from './historyDtoToStatusText.pipe';
export * from './filter-histories-with-status-change.pipe';
export * from './get-status-from-history.pipe';
// end:ng42.barrel

View File

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

View File

@@ -1,7 +1,14 @@
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable, BehaviorSubject } from 'rxjs';
import { filter, map, switchMap } from 'rxjs/operators';
import { Observable } from 'rxjs';
import {
filter,
map,
switchMap,
distinctUntilChanged,
tap,
shareReplay,
} from 'rxjs/operators';
import { isNullOrUndefined } from 'util';
import { HistoryDTO } from '@cmf/trade-api';
import { HistoryStateFacade } from '@shelf-store/history';
@@ -14,8 +21,8 @@ import { OrderHistoryStatus } from '@shelf-store/defs';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShelfHistoryComponent implements OnInit {
history$: Observable<HistoryDTO>;
status$: Observable<OrderHistoryStatus>;
orderItemSubsetId$: Observable<number>;
constructor(
private activatedRoute: ActivatedRoute,
@@ -23,30 +30,29 @@ export class ShelfHistoryComponent implements OnInit {
) {}
ngOnInit() {
this.history$ = this.getHistory();
this.status$ = this.getStatus();
this.orderItemSubsetId$ = this.getOrderItemSubsetId$();
}
getHistory() {
return this.getOrderItemId$().pipe(
switchMap((orderItemId) =>
this.historyStateFacade.getHistory$(orderItemId)
)
);
}
fetchHistory = (orderItemSubsetId: number) => {
this.historyStateFacade.fetchHistory(orderItemSubsetId);
};
getStatus() {
return this.getOrderItemId$().pipe(
switchMap((orderItemId) =>
this.historyStateFacade.getStatus$(orderItemId)
return this.getOrderItemSubsetId$().pipe(
switchMap((orderItemSubsetId) =>
this.historyStateFacade.getStatus$(orderItemSubsetId)
)
);
}
private getOrderItemId$(): Observable<number> {
getOrderItemSubsetId$(): Observable<number> {
return this.activatedRoute.params.pipe(
filter((params) => !isNullOrUndefined(params)),
map((params) => Number(params.orderItemId))
map((params) => Number(params.orderItemSubsetId)),
distinctUntilChanged(),
tap(this.fetchHistory),
shareReplay()
);
}
}

View File

@@ -1,11 +1,25 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ShelfHistoryLogModule } from '../../components/history-log';
import { ShelfHistoryComponent } from './shelf-history.component';
import { ShelfHistoryLogsComponent } from './history-logs';
import { FilterHistoriesWithStatusChangePipe } from './pipes';
import { ShelfHistoryHeaderComponent } from './header';
@NgModule({
imports: [CommonModule],
exports: [ShelfHistoryComponent],
declarations: [ShelfHistoryComponent],
imports: [CommonModule, ShelfHistoryLogModule],
exports: [
ShelfHistoryComponent,
ShelfHistoryHeaderComponent,
ShelfHistoryLogsComponent,
FilterHistoriesWithStatusChangePipe,
],
declarations: [
ShelfHistoryComponent,
ShelfHistoryHeaderComponent,
ShelfHistoryLogsComponent,
FilterHistoriesWithStatusChangePipe,
],
providers: [],
})
export class ShelfHistoryModule {}

View File

@@ -0,0 +1,97 @@
import { HistoryDTO } from '@cmf/trade-api';
export const historyDtoMock: HistoryDTO = {
location: 'München Neuhausen',
id: 114392259,
version: 5,
changed: '2020-07-02T10:56:54.7541',
changedBy: 'Günther Schmidlehner',
changeset: 69134345,
description: 'Test Description',
values: [{ caption: 'vorgemerkt', value: 'ja', previousValue: 'nein' }],
};
export const historiesDtoMock: HistoryDTO[] = [
{
location: 'München Neuhausen',
id: 114392259,
version: 5,
changed: '2020-07-02T10:56:54.7541',
changedBy: 'Günther Schmidlehner',
changeset: 69134345,
values: [{ caption: 'vorgemerkt', value: 'ja', previousValue: 'nein' }],
},
{
location: 'München Neuhausen',
id: 114392259,
version: 3,
changed: '2020-07-02T10:55:23.5543',
changedBy: 'Günther Schmidlehner',
changeset: 69134341,
historyset: 10896420,
values: [
{ caption: 'Abholfach-Zusatz', value: '', previousValue: 'Extra' },
],
},
{
location: 'München Neuhausen',
id: 114392259,
version: 2,
changed: '2020-02-24T14:53:58.9359',
changedBy: 'Lorenz Hilpert',
changeset: 68359312,
historyset: 10896418,
values: [
{
caption: 'Eingetroffen/Versendet',
value: '',
previousValue: '02.07.2020 10:55:23',
},
{ caption: 'Status', value: 'bestellt', previousValue: 'eingetroffen' },
{
caption: 'Status Datum',
value: '24.02.2020 14:53:58',
previousValue: '02.07.2020 10:55:23',
},
{ caption: 'Abholfach-Nr', value: '', previousValue: '130_0207_1' },
{
caption: 'Im Abholfach seit',
value: '',
previousValue: '02.07.2020 10:55:23',
},
{
caption: 'Im Abholfach bis',
value: '',
previousValue: '16.07.2020 10:55:23',
},
],
},
{
location: 'München Neuhausen',
id: 114392259,
version: 1,
changed: '2020-02-24T14:53:57.6544',
changedBy: 'Lorenz Hilpert',
changeset: 68359310,
historyset: 9520697,
values: [
{ caption: 'Meldenummer', value: '', previousValue: '999' },
{
caption: 'bestellt bei Lieferant am',
value: '',
previousValue: '24.02.2020 14:53:58',
},
{
caption: 'vsl. Lieferdatum',
value: '25.02.2020 15:00:00',
previousValue: '25.02.2020 14:00:00',
},
{ caption: 'Status', value: 'neu', previousValue: 'bestellt' },
{
caption: 'Status Datum',
value: '24.02.2020 14:53:57',
previousValue: '24.02.2020 14:53:58',
},
],
},
];

View File

@@ -1,4 +1,5 @@
// start:ng42.barrel
export * from './historyDto.mock';
export * from './filters.mock';
export * from './primary-filters.mock';
export * from './select-filter-option.mock';

View File

@@ -34,7 +34,7 @@ export class ShelfNavigationService {
}
navigateToHistory(orderitem: OrderItemListItemDTO) {
this.navigateToRoute(`/shelf/history/${orderitem.orderItemId}`, 'Historie');
this.navigateToRoute(`shelf/history/${orderitem.orderItemSubsetId}`, `Historie ${orderitem.orderItemSubsetId}`);
}
private replaceRoute(route: string, breadcrumbName: string) {

View File

@@ -29,7 +29,7 @@ export const routes: Routes = [
component: ShelfEditOrderComponent,
},
{
path: 'history/:orderItemId',
path: 'history/:orderItemSubsetId',
component: ShelfHistoryComponent,
},
];

View File

@@ -1,6 +1,8 @@
import { HistoryDTO } from '@cmf/trade-api';
import { OrderHistoryStatus } from './order-history-status';
export interface OrderHistory extends HistoryDTO {
export interface OrderHistory {
id: number;
status: OrderHistoryStatus;
histories: HistoryDTO[];
}

View File

@@ -1,6 +1,7 @@
import { OrderHistory, OrderHistoryStatus } from '../defs';
import { props, createAction } from '@ngrx/store';
import { StrictHttpResponse, ResponseArgsOfHistoryDTO } from '@swagger/oms';
import { HistoryDTO } from '@cmf/trade-api';
const prefix = '[CUSTOMER] [SHELF] [HISTORY]';
@@ -11,7 +12,7 @@ export const initOrderHistory = createAction(
export const addOrderHistory = createAction(
`${prefix} Add Order History`,
props<{ id: number; history: OrderHistory }>()
props<{ id: number; histories: HistoryDTO[] }>()
);
export const setStatus = createAction(

View File

@@ -0,0 +1,173 @@
import { TestBed } from '@angular/core/testing';
import { Observable, of } from 'rxjs';
import { provideMockActions } from '@ngrx/effects/testing';
import { provideMockStore } from '@ngrx/store/testing';
import {
OrderService,
ResponseArgsOfHistoryDTO,
StrictHttpResponse,
} from '@swagger/oms';
import { hot, cold } from 'jasmine-marbles';
import * as actions from './history.actions';
import { HistoryEffects } from './history.effects';
import { HttpHeaders } from '@angular/common/http';
import { TypedAction } from '@ngrx/store/src/models';
import { HistoryDTO } from '@cmf/trade-api';
fdescribe('HistoryEffects', () => {
let historyEffects: HistoryEffects;
let actions$: Observable<any>;
let orderService: jasmine.SpyObj<OrderService>;
const id = 123;
const httpResponse: StrictHttpResponse<ResponseArgsOfHistoryDTO> = {
body: ({
result: [
{
id: 456,
name: 'HistoryDTO Name',
values: [],
},
],
error: false,
} as unknown) as ResponseArgsOfHistoryDTO,
ok: true,
type: null,
clone: null,
headers: new HttpHeaders(),
status: 200,
statusText: 'Okay',
url: 'someRandomUrl.de',
};
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
HistoryEffects,
provideMockStore({}),
{
provide: OrderService,
useValue: jasmine.createSpyObj('orderService', [
'OrderGetOrderItemStatusHistoryResponse',
]),
},
provideMockActions(() => actions$),
],
});
historyEffects = TestBed.get(HistoryEffects);
orderService = TestBed.get(OrderService);
});
describe('initHistory$', () => {
it('should dispatch fetchHistory', () => {
const initHistoryAction = actions.initOrderHistory({ id });
const initHistory$ = hot('-a', { a: initHistoryAction });
actions$ = initHistory$;
const fetchHistory$ = actions.fetchHistory({ id });
const expected$ = cold('-b', { b: fetchHistory$ });
expect(historyEffects.initHistory$).toBeObservable(expected$);
});
});
describe('fetchHistory$', () => {
let fetchHistoryAction: {
id: number;
} & TypedAction<string>;
let fetchHistoryDoneAction: {
id: number;
response: StrictHttpResponse<ResponseArgsOfHistoryDTO>;
} & TypedAction<any>;
beforeEach(() => {
orderService.OrderGetOrderItemStatusHistoryResponse.and.returnValue(
of(httpResponse)
);
fetchHistoryAction = actions.fetchHistory({ id });
fetchHistoryDoneAction = actions.fetchHistoryDone({
id,
response: httpResponse,
});
});
it('should call the orderService OrderGetOrderItemStatusHistoryResponse', () => {
const fetchHistory$ = hot('a', { a: fetchHistoryAction });
actions$ = fetchHistory$;
const expected$ = cold('b', { b: fetchHistoryDoneAction });
expect(historyEffects.fetchHistory$).toBeObservable(expected$);
expect(
orderService.OrderGetOrderItemStatusHistoryResponse
).toHaveBeenCalledWith({ orderItemSubsetId: id });
});
it('should dispatch fetchHistoryDone', () => {
const fetchHistory$ = hot('-a', { a: fetchHistoryAction });
actions$ = fetchHistory$;
const expected$ = cold('-b', { b: fetchHistoryDoneAction });
expect(historyEffects.fetchHistory$).toBeObservable(expected$);
expect(
orderService.OrderGetOrderItemStatusHistoryResponse
).toHaveBeenCalled();
});
});
describe('fetchHistoryDone$', () => {
let fetchHistoryDoneAction: {
id: number;
response: StrictHttpResponse<ResponseArgsOfHistoryDTO>;
} & TypedAction<string>;
let addOrderHistoryAction: {
id: number;
histories: HistoryDTO[];
} & TypedAction<string>;
beforeEach(() => {
addOrderHistoryAction = actions.addOrderHistory({
id,
histories: (httpResponse.body.result as unknown) as HistoryDTO[],
});
});
it('should dispatch addOrderHistory if the response is ok', () => {
fetchHistoryDoneAction = actions.fetchHistoryDone({
id,
response: httpResponse,
});
const fetchHistoryDone$ = hot('-a', { a: fetchHistoryDoneAction });
actions$ = fetchHistoryDone$;
const expected$ = cold('-b', { b: addOrderHistoryAction });
expect(historyEffects.fetchHistoryDone$).toBeObservable(expected$);
});
it('should set an error and dispatch nothing', () => {
fetchHistoryDoneAction = actions.fetchHistoryDone({
id,
response: { ...httpResponse, ok: false } as StrictHttpResponse<
ResponseArgsOfHistoryDTO
>,
});
const fetchHistoryDone$ = hot('-a', { a: fetchHistoryDoneAction });
actions$ = fetchHistoryDone$;
const expected$ = cold('--');
expect(historyEffects.fetchHistoryDone$).toBeObservable(expected$);
});
});
});

View File

@@ -5,10 +5,9 @@ import { switchMap, map, catchError, flatMap } from 'rxjs/operators';
import {
OrderService,
StrictHttpResponse,
ResponseArgs,
ResponseArgsOfHistoryDTO,
HistoryDTO,
} from '@swagger/oms';
import { HistoryDTO } from '@cmf/trade-api';
import { of, NEVER } from 'rxjs';
import { OrderHistoryStatus } from '../defs';
@@ -28,7 +27,9 @@ export class HistoryEffects {
ofType(actions.fetchHistory),
switchMap((action) =>
this.orderService
.OrderGetOrderItemHistoryResponse({ orderItemId: action.id })
.OrderGetOrderItemStatusHistoryResponse({
orderItemSubsetId: action.id,
})
.pipe(
catchError((err) =>
of<StrictHttpResponse<ResponseArgsOfHistoryDTO>>(err)
@@ -49,11 +50,12 @@ export class HistoryEffects {
ofType(actions.fetchHistoryDone),
flatMap((action) => {
if (action.response.ok) {
const history = action.response.body.result[0];
const histories = (action.response.body
.result as unknown) as HistoryDTO[];
return [
actions.addOrderHistory({
id: action.id,
history,
histories,
}),
];
}

View File

@@ -0,0 +1,56 @@
import { TestBed } from '@angular/core/testing';
import { Store } from '@ngrx/store';
import { HistoryStateFacade } from './history.facade';
import * as actions from './history.actions';
fdescribe('HistoryStateFacade', () => {
let facade: HistoryStateFacade;
let store: jasmine.SpyObj<Store<any>>;
const orderItemSubsetId = 123;
beforeEach(async () => {
TestBed.configureTestingModule({
providers: [
HistoryStateFacade,
{
provide: Store,
useValue: jasmine.createSpyObj('store', {
dispatch: () => {},
}),
},
],
});
});
beforeEach(() => {
facade = TestBed.get(HistoryStateFacade);
store = TestBed.get(Store);
});
it('should be created', () => {
expect(facade instanceof HistoryStateFacade).toBeTruthy();
});
describe('fetchHistory', () => {
it('should dispatch initOrderHistory to get the history for the provided orderItemSubsetId', () => {
const initOrderHistoryAction = actions.initOrderHistory({
id: orderItemSubsetId,
});
facade.fetchHistory(orderItemSubsetId);
expect(store.dispatch).toHaveBeenCalledWith(initOrderHistoryAction);
});
});
describe('reloadHistory', () => {
it('should dispatch fetchHistory', () => {
const fetchHistoryAction = actions.fetchHistory({
id: orderItemSubsetId,
});
facade.reloadHistory(orderItemSubsetId);
expect(store.dispatch).toHaveBeenCalledWith(fetchHistoryAction);
});
});
});

View File

@@ -5,6 +5,7 @@ import { Dictionary } from '@ngrx/entity';
import { Observable } from 'rxjs';
import * as selectors from './history.selectors';
import * as actions from './history.actions';
import { HistoryDTO } from '@cmf/trade-api';
@Injectable({ providedIn: 'root' })
export class HistoryStateFacade {
@@ -14,14 +15,20 @@ export class HistoryStateFacade {
constructor(private store: Store<any>) {}
public getHistory$(orderItemId: number): Observable<OrderHistory> {
this.store.dispatch(actions.initOrderHistory({ id: orderItemId }));
return this.store.select(selectors.selectHistory, orderItemId);
public fetchHistory(orderItemSubsetId: number): void {
this.store.dispatch(actions.initOrderHistory({ id: orderItemSubsetId }));
}
public getStatus$(orderItemId: number): Observable<OrderHistoryStatus> {
return this.store.select(selectors.selectStatus, orderItemId);
public reloadHistory(orderItemSubsetId: number): void {
this.store.dispatch(actions.fetchHistory({ id: orderItemSubsetId }));
}
public getHistory$(orderItemSubsetId: number): Observable<HistoryDTO[]> {
return this.store.select(selectors.selectHistory, orderItemSubsetId);
}
public getStatus$(orderItemSubsetId: number): Observable<OrderHistoryStatus> {
return this.store.select(selectors.selectStatus, orderItemSubsetId);
}
private getHistories$(): Observable<Dictionary<OrderHistory>> {

View File

@@ -5,16 +5,14 @@ import {
} from './history.state';
import { historyReducer } from './history.reducer';
import * as actions from './history.actions';
import { OrderHistory } from '@shelf-store/defs';
import { HistoryDTO } from '@cmf/trade-api';
import { OrderHistoryStatus } from '@shelf-store/defs';
fdescribe('#HistoryStateReducer', () => {
const id = 123;
const mockOrderHistory: OrderHistory = {
...INITIAL_ORDER_HISTORY,
name: 'Fake History',
id,
values: [],
};
const mockHistories: HistoryDTO[] = [
{ name: 'Fake History', id, values: [] },
];
it('should return the initial state if on Init Order action is dispatched', () => {
const initialState = INITIAL_HISTORY_STATE;
@@ -24,12 +22,24 @@ fdescribe('#HistoryStateReducer', () => {
const entity = state.entities[id];
expect(entity).toEqual({
...initialState.entities[id],
...INITIAL_ORDER_HISTORY,
id,
});
});
it('should set status to fetching if fetchHistory action is dispatched (1)', () => {
let state: HistoryState;
const initialState = INITIAL_HISTORY_STATE;
const initAction = actions.initOrderHistory({ id });
state = historyReducer(initialState, initAction);
const action = actions.fetchHistory({ id });
state = historyReducer(state, action);
expect(state.entities[id].status).toBe(OrderHistoryStatus.FETCHING);
});
it('should add history and set status to available (2)', () => {
let state: HistoryState;
const initialState = INITIAL_HISTORY_STATE;
@@ -37,12 +47,13 @@ fdescribe('#HistoryStateReducer', () => {
const initAction = actions.initOrderHistory({ id });
state = historyReducer(initialState, initAction);
const action = actions.addOrderHistory({ id, history: mockOrderHistory });
const action = actions.addOrderHistory({ id, histories: mockHistories });
state = historyReducer(state, action);
const entity = state.entities[id];
expect(entity.status).toBe(2);
expect(entity).toEqual({ ...mockOrderHistory, status: 2 });
expect(entity.histories).toEqual(mockHistories);
expect(entity.id).toEqual(id);
});
});

View File

@@ -31,15 +31,19 @@ export const _historyReducer = createReducer(
s
)
),
on(actions.addOrderHistory, (s, a) => {
return historyStateAdapter.updateOne(
on(actions.addOrderHistory, (s, a) =>
historyStateAdapter.updateOne(
{
id: a.id,
changes: { ...a.history, status: OrderHistoryStatus.AVAILABLE },
changes: {
...s.entities[a.id],
histories: a.histories,
status: OrderHistoryStatus.AVAILABLE,
},
},
s
);
}),
)
),
on(actions.setStatus, (s, a) =>
historyStateAdapter.updateOne(
{
@@ -48,6 +52,12 @@ export const _historyReducer = createReducer(
},
s
)
),
on(actions.fetchHistory, (s, a) =>
historyStateAdapter.updateOne(
{ id: a.id, changes: { status: OrderHistoryStatus.FETCHING } },
s
)
)
);

View File

@@ -0,0 +1,5 @@
describe('HistoryStateSelectors', () => {
describe('selectHistory', () => {
it('should return all histories for the provided id', () => {});
});
});

View File

@@ -20,7 +20,8 @@ export const selectHistories = createSelector(
export const selectHistory = createSelector(
selectEntities,
(entities: Dictionary<OrderHistory>, id: number) => entities[id]
(entities: Dictionary<OrderHistory>, id: number) =>
entities[id] && entities[id].histories
);
export const selectStatus = createSelector(

View File

@@ -10,5 +10,7 @@ export const INITIAL_HISTORY_STATE: HistoryState = {
};
export const INITIAL_ORDER_HISTORY: OrderHistory = {
id: undefined,
status: OrderHistoryStatus.INIT,
histories: [],
};

View File

@@ -5,6 +5,7 @@ $font-weight: normal;
$font-weight-bold: bold;
$font-color-gray: #89949e;
$font-weight-emphasis: 600;
$font-line-height: 21px;
$max-content-width: 916px;
@@ -21,6 +22,7 @@ $isa-customer-light: #a3b4c8;
$isa-customer-inactive: #9cb1c6;
$isa-customer-active: #1f466c;
$isa-neutral-info: #be8100;
$isa-light-blue-platinum: #e1ebf5;
/* LAYOUT */
$layout-border-radius: 5px;
@@ -55,6 +57,7 @@ $chip-height: 53px;
$headline-font-size-l: 26px;
$headline-font-size-m: 22px;
$headline-font-size-s: 20px;
$headline-font-size-xs: 18px;
/* INPUT */
$input-height-size-l: 60px;

View File

@@ -4,3 +4,10 @@
background-color: $content-background-color;
border-radius: $content-border-radius;
}
.isa-content-spacer {
margin-top: 15px;
margin-bottom: 15px;
margin-left: -10%;
width: 120%;
}

View File

@@ -170,10 +170,26 @@ body {
margin-left: 8px;
}
.ml-10 {
margin-left: 10px;
}
.mb-5 {
margin-bottom: 5px;
}
.mb-12 {
margin-bottom: 12px;
}
.mb-20 {
margin-bottom: 20px;
}
.mb-25 {
margin-bottom: 25px;
}
.mb-40 {
margin-bottom: 40px;
}