Created a period table for date ranges and made the recipes dependent on it instead of having arbitrary date ranges. This will enable easy copying into new months.

This commit is contained in:
2022-07-28 22:16:28 +05:30
parent 492b80e116
commit 3eb715e2de
68 changed files with 996 additions and 353 deletions
+4
View File
@@ -120,6 +120,10 @@ const appRoutes: Routes = [
path: 'payment',
loadChildren: () => import('./payment/payment.module').then((mod) => mod.PaymentModule),
},
{
path: 'periods',
loadChildren: () => import('./period/period.module').then((mod) => mod.PeriodModule),
},
{
path: 'products',
loadChildren: () => import('./product/product.module').then((mod) => mod.ProductModule),
@@ -54,6 +54,7 @@
<a mat-menu-item routerLink="/products">Products</a>
<a mat-menu-item routerLink="/product-groups">Product Groups</a>
<a mat-menu-item routerLink="/recipes">Recipes</a>
<a mat-menu-item routerLink="/periods">Periods</a>
</mat-menu>
<button mat-button [matMenuTriggerFor]="masterMenu">Masters</button>
@@ -0,0 +1,41 @@
<mat-card>
<mat-card-title-group>
<mat-card-title>Period</mat-card-title>
</mat-card-title-group>
<mat-card-content>
<form [formGroup]="form" fxLayout="column">
<div fxLayout="row">
<mat-form-field fxFlex>
<input
matInput
[matDatepicker]="validFrom"
placeholder="Valid From"
formControlName="validFrom"
autocomplete="off"
/>
<mat-datepicker-toggle matSuffix [for]="validFrom"></mat-datepicker-toggle>
<mat-datepicker #validFrom></mat-datepicker>
</mat-form-field>
</div>
<div fxLayout="row">
<mat-form-field fxFlex>
<input
matInput
[matDatepicker]="validTill"
placeholder="Valid Till"
formControlName="validTill"
autocomplete="off"
/>
<mat-datepicker-toggle matSuffix [for]="validTill"></mat-datepicker-toggle>
<mat-datepicker #validTill></mat-datepicker>
</mat-form-field>
</div>
</form>
</mat-card-content>
<mat-card-actions>
<button mat-raised-button color="primary" (click)="save()">Save</button>
<button mat-raised-button color="warn" (click)="confirmDelete()" *ngIf="!!item.id">
Delete
</button>
</mat-card-actions>
</mat-card>
@@ -0,0 +1,28 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { MatDialogModule } from '@angular/material/dialog';
import { RouterTestingModule } from '@angular/router/testing';
import { PeriodDetailComponent } from './period-detail.component';
describe('PeriodDetailComponent', () => {
let component: PeriodDetailComponent;
let fixture: ComponentFixture<PeriodDetailComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [MatDialogModule, ReactiveFormsModule, RouterTestingModule],
declarations: [PeriodDetailComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PeriodDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,97 @@
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router';
import * as moment from 'moment';
import { ToasterService } from '../../core/toaster.service';
import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
import { Period } from '../period';
import { PeriodService } from '../period.service';
@Component({
selector: 'app-period-detail',
templateUrl: './period-detail.component.html',
styleUrls: ['./period-detail.component.css'],
})
export class PeriodDetailComponent implements OnInit {
form: FormGroup<{
validFrom: FormControl<Date>;
validTill: FormControl<Date>;
}>;
item: Period = new Period();
constructor(
private route: ActivatedRoute,
private router: Router,
private dialog: MatDialog,
private toaster: ToasterService,
private ser: PeriodService,
) {
this.form = new FormGroup({
validFrom: new FormControl(new Date(), { nonNullable: true }),
validTill: new FormControl(new Date(), { nonNullable: true }),
});
}
ngOnInit() {
this.route.data.subscribe((value) => {
const data = value as { item: Period };
this.showItem(data.item);
});
}
showItem(item: Period) {
this.item = item;
this.form.setValue({
validFrom: moment(this.item.validFrom, 'DD-MMM-YYYY').toDate(),
validTill: moment(this.item.validTill, 'DD-MMM-YYYY').toDate(),
});
}
save() {
this.ser.saveOrUpdate(this.getItem()).subscribe(
() => {
this.toaster.show('Success', '');
this.router.navigateByUrl('/periods');
},
(error) => {
this.toaster.show('Danger', error);
},
);
}
delete() {
this.ser.delete(this.item.id as string).subscribe(
() => {
this.toaster.show('Success', '');
this.router.navigateByUrl('/periods');
},
(error) => {
this.toaster.show('Danger', error);
},
);
}
confirmDelete(): void {
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
width: '250px',
data: { title: 'Delete Period?', content: 'Are you sure? This cannot be undone.' },
});
dialogRef.afterClosed().subscribe((result: boolean) => {
if (result) {
this.delete();
}
});
}
getItem(): Period {
const formValue = this.form.value;
this.item.validFrom = moment(formValue.validFrom).format('DD-MMM-YYYY');
this.item.validTill = moment(formValue.validTill).format('DD-MMM-YYYY');
return this.item;
}
}
@@ -0,0 +1,17 @@
import { HttpClientModule } from '@angular/common/http';
import { inject, TestBed } from '@angular/core/testing';
import { PeriodListResolver } from './period-list-resolver.service';
describe('PeriodListResolver', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule],
providers: [PeriodListResolver],
});
});
it('should be created', inject([PeriodListResolver], (service: PeriodListResolver) => {
expect(service).toBeTruthy();
}));
});
@@ -0,0 +1,17 @@
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
import { Observable } from 'rxjs/internal/Observable';
import { Period } from './period';
import { PeriodService } from './period.service';
@Injectable({
providedIn: 'root',
})
export class PeriodListResolver implements Resolve<Period[]> {
constructor(private ser: PeriodService) {}
resolve(): Observable<Period[]> {
return this.ser.list();
}
}
@@ -0,0 +1,16 @@
import { DataSource } from '@angular/cdk/collections';
import { Observable, of as observableOf } from 'rxjs';
import { Period } from '../period';
export class PeriodListDataSource extends DataSource<Period> {
constructor(public data: Period[]) {
super();
}
connect(): Observable<Period[]> {
return observableOf(this.data);
}
disconnect() {}
}
@@ -0,0 +1,31 @@
<mat-card>
<mat-card-title-group>
<mat-card-title>Periods</mat-card-title>
<a mat-button [routerLink]="['/periods', 'new']">
<mat-icon>add_box</mat-icon>
Add
</a>
</mat-card-title-group>
<mat-card-content>
<mat-table #table [dataSource]="dataSource" aria-label="Elements">
<!-- Valid From Column -->
<ng-container matColumnDef="validFrom">
<mat-header-cell *matHeaderCellDef>Valid From</mat-header-cell>
<mat-cell *matCellDef="let row">
<a [routerLink]="['/periods', row.id]">
{{ row.validFrom }}
</a>
</mat-cell>
</ng-container>
<!-- Valid Till Column -->
<ng-container matColumnDef="validTill">
<mat-header-cell *matHeaderCellDef>Valid Till</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.validTill }}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
</mat-table>
</mat-card-content>
</mat-card>
@@ -0,0 +1,25 @@
import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { PeriodListComponent } from './period-list.component';
describe('PeriodListComponent', () => {
let component: PeriodListComponent;
let fixture: ComponentFixture<PeriodListComponent>;
beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule, RouterTestingModule],
declarations: [PeriodListComponent],
}).compileComponents();
fixture = TestBed.createComponent(PeriodListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it('should compile', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,33 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Period } from '../period';
import { PeriodListDataSource } from './period-list-datasource';
@Component({
selector: 'app-period-list',
templateUrl: './period-list.component.html',
styleUrls: ['./period-list.component.css'],
})
export class PeriodListComponent implements OnInit {
dataSource: PeriodListDataSource;
list: Period[] = [];
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
displayedColumns = ['validFrom', 'validTill'];
constructor(private route: ActivatedRoute) {
this.dataSource = new PeriodListDataSource(this.list);
}
ngOnInit() {
this.route.data.subscribe((value) => {
const data = value as { list: Period[] };
this.list = data.list;
});
this.dataSource = new PeriodListDataSource(this.list);
}
}
@@ -0,0 +1,18 @@
import { HttpClientModule } from '@angular/common/http';
import { inject, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { PeriodResolver } from './period-resolver.service';
describe('PeriodResolver', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule, RouterTestingModule],
providers: [PeriodResolver],
});
});
it('should be created', inject([PeriodResolver], (service: PeriodResolver) => {
expect(service).toBeTruthy();
}));
});
@@ -0,0 +1,18 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router';
import { Observable } from 'rxjs/internal/Observable';
import { Period } from './period';
import { PeriodService } from './period.service';
@Injectable({
providedIn: 'root',
})
export class PeriodResolver implements Resolve<Period> {
constructor(private ser: PeriodService, private router: Router) {}
resolve(route: ActivatedRouteSnapshot): Observable<Period> {
const id = route.paramMap.get('id');
return this.ser.get(id);
}
}
@@ -0,0 +1,13 @@
import { PeriodRoutingModule } from './period-routing.module';
describe('PeriodRoutingModule', () => {
let periodRoutingModule: PeriodRoutingModule;
beforeEach(() => {
periodRoutingModule = new PeriodRoutingModule();
});
it('should create an instance', () => {
expect(periodRoutingModule).toBeTruthy();
});
});
@@ -0,0 +1,56 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '../auth/auth-guard.service';
import { CostCentreListResolver } from '../cost-centre/cost-centre-list-resolver.service';
import { PeriodDetailComponent } from './period-detail/period-detail.component';
import { PeriodListResolver } from './period-list-resolver.service';
import { PeriodListComponent } from './period-list/period-list.component';
import { PeriodResolver } from './period-resolver.service';
const periodRoutes: Routes = [
{
path: '',
component: PeriodListComponent,
canActivate: [AuthGuard],
data: {
permission: 'Recipes',
},
resolve: {
list: PeriodListResolver,
},
},
{
path: 'new',
component: PeriodDetailComponent,
canActivate: [AuthGuard],
data: {
permission: 'Recipes',
},
resolve: {
item: PeriodResolver,
costCentres: CostCentreListResolver,
},
},
{
path: ':id',
component: PeriodDetailComponent,
canActivate: [AuthGuard],
data: {
permission: 'Recipes',
},
resolve: {
item: PeriodResolver,
costCentres: CostCentreListResolver,
},
},
];
@NgModule({
imports: [CommonModule, RouterModule.forChild(periodRoutes)],
exports: [RouterModule],
providers: [PeriodListResolver, PeriodResolver],
})
export class PeriodRoutingModule {}
@@ -0,0 +1,13 @@
import { PeriodModule } from './period.module';
describe('PeriodModule', () => {
let periodModule: PeriodModule;
beforeEach(() => {
periodModule = new PeriodModule();
});
it('should create an instance', () => {
expect(periodModule).toBeTruthy();
});
});
+69
View File
@@ -0,0 +1,69 @@
import { CdkTableModule } from '@angular/cdk/table';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms';
import { MomentDateAdapter } from '@angular/material-moment-adapter';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import {
DateAdapter,
MAT_DATE_FORMATS,
MAT_DATE_LOCALE,
MatOptionModule,
} from '@angular/material/core';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { PeriodDetailComponent } from './period-detail/period-detail.component';
import { PeriodListComponent } from './period-list/period-list.component';
import { PeriodRoutingModule } from './period-routing.module';
export const MY_FORMATS = {
parse: {
dateInput: 'DD-MMM-YYYY',
},
display: {
dateInput: 'DD-MMM-YYYY',
monthYearLabel: 'MMM YYYY',
dateA11yLabel: 'DD-MMM-YYYY',
monthYearA11yLabel: 'MMM YYYY',
},
};
@NgModule({
imports: [
CommonModule,
CdkTableModule,
FlexLayoutModule,
MatTableModule,
MatPaginatorModule,
MatSortModule,
MatCardModule,
MatDatepickerModule,
MatDialogModule,
MatProgressSpinnerModule,
MatInputModule,
MatButtonModule,
MatIconModule,
MatOptionModule,
MatSelectModule,
MatCheckboxModule,
ReactiveFormsModule,
PeriodRoutingModule,
],
declarations: [PeriodListComponent, PeriodDetailComponent],
providers: [
{ provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] },
{ provide: MAT_DATE_FORMATS, useValue: MY_FORMATS },
],
})
export class PeriodModule {}
@@ -0,0 +1,17 @@
import { HttpClientModule } from '@angular/common/http';
import { inject, TestBed } from '@angular/core/testing';
import { PeriodService } from './period.service';
describe('PeriodService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule],
providers: [PeriodService],
});
});
it('should be created', inject([PeriodService], (service: PeriodService) => {
expect(service).toBeTruthy();
}));
});
+54
View File
@@ -0,0 +1,54 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/internal/Observable';
import { catchError } from 'rxjs/operators';
import { ErrorLoggerService } from '../core/error-logger.service';
import { Period } from './period';
const url = '/api/periods';
const serviceName = 'PeriodService';
@Injectable({ providedIn: 'root' })
export class PeriodService {
constructor(private http: HttpClient, private log: ErrorLoggerService) {}
get(id: string | null): Observable<Period> {
const getUrl: string = id === null ? `${url}` : `${url}/${id}`;
return this.http
.get<Period>(getUrl)
.pipe(catchError(this.log.handleError(serviceName, `get id=${id}`))) as Observable<Period>;
}
list(): Observable<Period[]> {
return this.http
.get<Period[]>(`${url}/list`)
.pipe(catchError(this.log.handleError(serviceName, 'list'))) as Observable<Period[]>;
}
save(period: Period): Observable<Period> {
return this.http
.post<Period>(`${url}`, period)
.pipe(catchError(this.log.handleError(serviceName, 'save'))) as Observable<Period>;
}
update(period: Period): Observable<Period> {
return this.http
.put<Period>(`${url}/${period.id}`, period)
.pipe(catchError(this.log.handleError(serviceName, 'update'))) as Observable<Period>;
}
saveOrUpdate(period: Period): Observable<Period> {
if (!period.id) {
return this.save(period);
}
return this.update(period);
}
delete(id: string): Observable<Period> {
return this.http
.delete<Period>(`${url}/${id}`)
.pipe(catchError(this.log.handleError(serviceName, 'delete'))) as Observable<Period>;
}
}
+11
View File
@@ -0,0 +1,11 @@
export class Period {
id: string | undefined;
validFrom: string;
validTill: string;
public constructor(init?: Partial<Period>) {
this.validFrom = '';
this.validTill = '';
Object.assign(this, init);
}
}
@@ -11,27 +11,12 @@
fxLayoutGap.lt-md="0px"
fxLayoutAlign="space-around start"
>
<mat-form-field fxFlex="50">
<input
matInput
[matDatepicker]="validFrom"
placeholder="Valid From"
formControlName="validFrom"
autocomplete="off"
/>
<mat-datepicker-toggle matSuffix [for]="validFrom"></mat-datepicker-toggle>
<mat-datepicker #validFrom></mat-datepicker>
</mat-form-field>
<mat-form-field fxFlex="50">
<input
matInput
[matDatepicker]="validTill"
placeholder="Valid Till"
formControlName="validTill"
autocomplete="off"
/>
<mat-datepicker-toggle matSuffix [for]="validTill"></mat-datepicker-toggle>
<mat-datepicker #validTill></mat-datepicker>
<mat-form-field fxFlex>
<mat-select formControlName="period">
<mat-option *ngFor="let p of periods" [value]="p">
{{ p.validFrom }} to {{ p.validTill }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div fxLayout="row" fxLayout.lt-md="column" fxLayoutGap="20px" fxLayoutGap.lt-md="0px">
@@ -4,9 +4,9 @@ import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router';
import { round } from 'mathjs';
import * as moment from 'moment';
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
import { debounceTime, distinctUntilChanged, map, switchMap } from 'rxjs/operators';
import { Period } from 'src/app/period/period';
import { Product } from '../../core/product';
import { ProductSku } from '../../core/product-sku';
@@ -31,8 +31,7 @@ export class RecipeDetailComponent implements OnInit, AfterViewInit {
public itemsObservable = new BehaviorSubject<RecipeItem[]>([]);
dataSource: RecipeDetailDatasource = new RecipeDetailDatasource(this.itemsObservable);
form: FormGroup<{
validFrom: FormControl<Date>;
validTill: FormControl<Date>;
period: FormControl<Period>;
recipeYield: FormControl<string | null>;
costPrice: FormControl<string | null>;
salePrice: FormControl<string | null>;
@@ -45,6 +44,7 @@ export class RecipeDetailComponent implements OnInit, AfterViewInit {
}>;
}>;
periods: Period[] = [];
product: ProductSku | null;
products: Observable<ProductSku[]>;
ingredient: ProductSku | null;
@@ -65,8 +65,7 @@ export class RecipeDetailComponent implements OnInit, AfterViewInit {
this.product = null;
this.ingredient = null;
this.form = new FormGroup({
validFrom: new FormControl(new Date(), { nonNullable: true }),
validTill: new FormControl(new Date(), { nonNullable: true }),
period: new FormControl(new Period(), { nonNullable: true }),
recipeYield: new FormControl<string | null>(null),
costPrice: new FormControl<string | null>(null),
salePrice: new FormControl<string | null>(null),
@@ -99,7 +98,8 @@ export class RecipeDetailComponent implements OnInit, AfterViewInit {
ngOnInit() {
this.route.data.subscribe((value) => {
const data = value as { item: Recipe };
const data = value as { item: Recipe; periods: Period[] };
this.periods = data.periods;
this.showItem(data.item);
this.updateView();
});
@@ -107,9 +107,9 @@ export class RecipeDetailComponent implements OnInit, AfterViewInit {
showItem(item: Recipe) {
this.item = item;
item.period = this.periods.find((x) => x.id == item.period.id) as Period;
this.form.setValue({
validFrom: moment(item.validFrom, 'DD-MMM-YYYY').toDate(),
validTill: moment(item.validTill, 'DD-MMM-YYYY').toDate(),
period: item.period,
recipeYield: `${item.recipeYield}`,
salePrice: `${item.salePrice}`,
costPrice: `${item.costPrice}`,
@@ -152,7 +152,7 @@ export class RecipeDetailComponent implements OnInit, AfterViewInit {
this.ingredient = ingredient;
const item = this.getItem();
this.ser
.getIngredientDetails(ingredient.id, item.validFrom, item.validTill)
.getIngredientDetails(ingredient.id, item.period.validFrom, item.period.validTill)
.subscribe((x) => this.form.controls.addRow.controls.rate.setValue('' + x.costPrice));
}
@@ -253,8 +253,9 @@ export class RecipeDetailComponent implements OnInit, AfterViewInit {
getItem(): Recipe {
const formModel = this.form.value;
this.item.validFrom = moment(formModel.validFrom).format('DD-MMM-YYYY');
this.item.validTill = moment(formModel.validTill).format('DD-MMM-YYYY');
if (formModel.period) {
this.item.period = formModel.period;
}
this.item.recipeYield = this.math.parseAmount(formModel.recipeYield ?? '1', 2);
this.item.salePrice = this.math.parseAmount(formModel.salePrice ?? '0', 2);
this.item.costPrice = this.math.parseAmount(formModel.costPrice ?? '0', 2);
@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
import { Observable } from 'rxjs/internal/Observable';
import { Recipe } from './recipe';
@@ -11,7 +11,8 @@ import { RecipeService } from './recipe.service';
export class RecipeListResolver implements Resolve<Recipe[]> {
constructor(private ser: RecipeService) {}
resolve(): Observable<Recipe[]> {
return this.ser.list();
resolve(route: ActivatedRouteSnapshot): Observable<Recipe[]> {
const period = route.queryParamMap.get('p') || null;
return this.ser.list(period);
}
}
@@ -11,13 +11,9 @@ import { Recipe } from '../recipe';
export class RecipeListDatasource extends DataSource<Recipe> {
public data: Recipe[];
public filteredData: Recipe[];
public validFrom: Date | null;
public validTill: Date | null;
public productGroup: string;
constructor(
private readonly validFromFilter: Observable<Date | null>,
private readonly validTillFilter: Observable<Date | null>,
private readonly productGroupFilter: Observable<string>,
private readonly dataObs: Observable<Recipe[]>,
@@ -27,15 +23,12 @@ export class RecipeListDatasource extends DataSource<Recipe> {
super();
this.data = [];
this.filteredData = [];
this.validFrom = null;
this.validTill = null;
this.productGroup = '';
}
connect(): Observable<Recipe[]> {
const dataMutations: (
| Observable<Recipe[]>
| Observable<Date | null>
| Observable<string>
| EventEmitter<PageEvent>
| EventEmitter<Sort>
@@ -45,16 +38,6 @@ export class RecipeListDatasource extends DataSource<Recipe> {
this.data = x;
}),
),
this.validFromFilter.pipe(
tap((x) => {
this.validFrom = x;
}),
),
this.validTillFilter.pipe(
tap((x) => {
this.validTill = x;
}),
),
this.productGroupFilter.pipe(
tap((x) => {
this.productGroup = x;
@@ -69,7 +52,7 @@ export class RecipeListDatasource extends DataSource<Recipe> {
}
return merge(...dataMutations).pipe(
map(() => this.getFilteredData(this.data, this.productGroup, this.validFrom, this.validTill)),
map(() => this.getFilteredData(this.data, this.productGroup)),
tap((x: Recipe[]) => {
if (this.paginator) {
this.paginator.length = x.length;
@@ -84,18 +67,8 @@ export class RecipeListDatasource extends DataSource<Recipe> {
disconnect() {}
private getFilteredData(
data: Recipe[],
productGroup: string,
validFrom: Date | null,
validTill: Date | null,
): Recipe[] {
return data
.filter((x: Recipe) => productGroup === '' || x.notes === productGroup)
.filter((x) => validFrom === null || validFrom <= moment(x.validFrom, 'DD-MMM-YYYY').toDate())
.filter(
(x) => validTill === null || validTill >= moment(x.validTill, 'DD-MMM-YYYY').toDate(),
);
private getFilteredData(data: Recipe[], productGroup: string): Recipe[] {
return data.filter((x: Recipe) => productGroup === '' || x.notes === productGroup);
}
private getPagedData(data: Recipe[]) {
@@ -120,12 +93,6 @@ export class RecipeListDatasource extends DataSource<Recipe> {
switch (sort.active) {
case 'name':
return compare(a.sku.name, b.sku.name, isAsc);
case 'validity':
if (isAsc) {
return compareDate(a.validFrom, b.validFrom, isAsc);
} else {
return compareDate(a.validTill, b.validTill, isAsc);
}
case 'salePrice':
return compare(a.salePrice, b.salePrice, isAsc);
case 'costPrice':
@@ -16,28 +16,11 @@
fxLayoutGap.lt-md="0px"
>
<mat-form-field fxFlex>
<input
matInput
[matDatepicker]="validFrom"
placeholder="Valid From"
formControlName="validFrom"
autocomplete="off"
(dateChange)="filterValidFrom($event.value)"
/>
<mat-datepicker-toggle matSuffix [for]="validFrom"></mat-datepicker-toggle>
<mat-datepicker #validFrom></mat-datepicker>
</mat-form-field>
<mat-form-field fxFlex>
<input
matInput
[matDatepicker]="validTill"
placeholder="Valid Till"
formControlName="validTill"
autocomplete="off"
(dateChange)="filterValidTill($event.value)"
/>
<mat-datepicker-toggle matSuffix [for]="validTill"></mat-datepicker-toggle>
<mat-datepicker #validTill></mat-datepicker>
<mat-select formControlName="period">
<mat-option *ngFor="let p of periods" [value]="p">
{{ p.validFrom }} to {{ p.validTill }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex>
<mat-label>Product Type</mat-label>
@@ -63,12 +46,6 @@
>
</ng-container>
<!-- Validity Column -->
<ng-container matColumnDef="validity">
<mat-header-cell *matHeaderCellDef mat-sort-header>Validity</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.validFrom }} - {{ row.validTill }}</mat-cell>
</ng-container>
<!-- Sale Price Column -->
<ng-container matColumnDef="salePrice">
<mat-header-cell *matHeaderCellDef mat-sort-header>Sale Price</mat-header-cell>
@@ -83,7 +60,7 @@
<!-- Cost Percentage Column -->
<ng-container matColumnDef="costPercentage">
<mat-header-cell *matHeaderCellDef mat-sort-header>Cost Price</mat-header-cell>
<mat-header-cell *matHeaderCellDef mat-sort-header>Cost %age</mat-header-cell>
<mat-cell *matCellDef="let row">{{
row.costPrice / row.salePrice | percent: '1.2-2'
}}</mat-cell>
@@ -2,9 +2,9 @@ import { Component, OnInit, ViewChild } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { ActivatedRoute } from '@angular/router';
import * as moment from 'moment';
import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject } from 'rxjs';
import { Period } from 'src/app/period/period';
import { ProductGroup } from '../../core/product-group';
import { Recipe } from '../recipe';
@@ -20,39 +20,38 @@ export class RecipeListComponent implements OnInit {
@ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator;
@ViewChild(MatSort, { static: true }) sort?: MatSort;
form: FormGroup<{
validFrom: FormControl<Date>;
validTill: FormControl<Date>;
period: FormControl<Period>;
productGroup: FormControl<ProductGroup | string | null>;
}>;
productGroups: ProductGroup[] = [];
validFromFilter: BehaviorSubject<Date | null> = new BehaviorSubject<Date | null>(null);
validTillFilter: BehaviorSubject<Date | null> = new BehaviorSubject<Date | null>(null);
periods: Period[] = [];
periodFilter: BehaviorSubject<Period> = new BehaviorSubject<Period>(new Period());
productGroupFilter: BehaviorSubject<string> = new BehaviorSubject('');
list: Recipe[] = [];
data: BehaviorSubject<Recipe[]> = new BehaviorSubject<Recipe[]>([]);
dataSource: RecipeListDatasource = new RecipeListDatasource(
this.validFromFilter,
this.validTillFilter,
this.productGroupFilter,
this.data,
);
dataSource: RecipeListDatasource = new RecipeListDatasource(this.productGroupFilter, this.data);
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
displayedColumns = ['name', 'validity', 'salePrice', 'costPrice', 'costPercentage'];
displayedColumns = ['name', 'salePrice', 'costPrice', 'costPercentage'];
constructor(private route: ActivatedRoute) {
constructor(private route: ActivatedRoute, private router: Router) {
this.form = new FormGroup({
validFrom: new FormControl(new Date(), { nonNullable: true }),
validTill: new FormControl(new Date(), { nonNullable: true }),
period: new FormControl(new Period(), { nonNullable: true }),
productGroup: new FormControl<ProductGroup | string | null>(null),
});
// Listen to Payment Account Change
this.form.controls.period.valueChanges.subscribe((x) =>
this.router.navigate([], {
relativeTo: this.route,
queryParams: { p: x.id },
replaceUrl: true,
}),
);
}
ngOnInit() {
this.dataSource = new RecipeListDatasource(
this.validFromFilter,
this.validTillFilter,
this.productGroupFilter,
this.data,
this.paginator,
@@ -60,25 +59,15 @@ export class RecipeListComponent implements OnInit {
);
// this.dataSource = new RecipeListDatasource(this.validFromFilter, this.validTillFilter, this.productGroupFilter, this.data);
this.route.data.subscribe((value) => {
const data = value as { list: Recipe[]; productGroups: ProductGroup[] };
const vf = data.list
.map((x) => x.validFrom)
.reduce((p, c) => {
const pdate = moment(p, 'DD-MMM-YYYY').toDate();
const cdate = moment(c, 'DD-MMM-YYYY').toDate();
return pdate < cdate ? p : c;
});
const vt = data.list
.map((x) => x.validTill)
.reduce((p, c) => {
const pdate = moment(p, 'DD-MMM-YYYY').toDate();
const cdate = moment(c, 'DD-MMM-YYYY').toDate();
return pdate > cdate ? p : c;
});
const data = value as { list: Recipe[]; productGroups: ProductGroup[]; periods: Period[] };
this.productGroups = data.productGroups;
this.periods = data.periods;
const period =
this.periods.find((x) => x.id === this.route.snapshot.queryParamMap.get('p')) ||
this.periods[0];
this.form.setValue({
validFrom: vf === null ? new Date() : moment(vf, 'DD-MMM-YYYY').toDate(),
validTill: vt === null ? new Date() : moment(vt, 'DD-MMM-YYYY').toDate(),
period: period,
productGroup: '',
});
this.data.next(data.list);
@@ -88,12 +77,4 @@ export class RecipeListComponent implements OnInit {
filterProductGroup(val: string) {
this.productGroupFilter.next(val || '');
}
filterValidFrom(val: Date) {
this.validFromFilter.next(val);
}
filterValidTill(val: Date) {
this.validTillFilter.next(val);
}
}
@@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '../auth/auth-guard.service';
import { PeriodListResolver } from '../period/period-list-resolver.service';
import { ProductGroupListResolver } from '../product-group/product-group-list-resolver.service';
import { RecipeDetailComponent } from './recipe-detail/recipe-detail.component';
@@ -21,7 +22,9 @@ const recipeRoutes: Routes = [
resolve: {
list: RecipeListResolver,
productGroups: ProductGroupListResolver,
periods: PeriodListResolver,
},
runGuardsAndResolvers: 'paramsOrQueryParamsChange',
},
{
path: 'new',
@@ -32,6 +35,7 @@ const recipeRoutes: Routes = [
},
resolve: {
item: RecipeResolver,
periods: PeriodListResolver,
},
},
{
@@ -43,6 +47,7 @@ const recipeRoutes: Routes = [
},
resolve: {
item: RecipeResolver,
periods: PeriodListResolver,
},
},
];
+6 -3
View File
@@ -1,6 +1,6 @@
import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/internal/Observable';
import { Observable, of as observableOf } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { ErrorLoggerService } from '../core/error-logger.service';
@@ -22,9 +22,12 @@ export class RecipeService {
.pipe(catchError(this.log.handleError(serviceName, `get id=${id}`))) as Observable<Recipe>;
}
list(): Observable<Recipe[]> {
list(periodId: string | null): Observable<Recipe[]> {
if (periodId === null) {
return observableOf([]);
}
return this.http
.get<Recipe[]>(`${url}/list`)
.get<Recipe[]>(`${url}/list`, { params: new HttpParams().set('p', periodId) })
.pipe(catchError(this.log.handleError(serviceName, 'getList'))) as Observable<Recipe[]>;
}
+3 -4
View File
@@ -1,4 +1,5 @@
import { ProductSku } from '../core/product-sku';
import { Period } from '../period/period';
import { RecipeItem } from './recipe-item';
@@ -10,8 +11,7 @@ export class Recipe {
salePrice: number;
notes: string;
items: RecipeItem[];
validFrom: string;
validTill: string;
period: Period;
public constructor(init?: Partial<Recipe>) {
this.sku = new ProductSku();
@@ -20,8 +20,7 @@ export class Recipe {
this.costPrice = 0;
this.notes = '';
this.items = [];
this.validFrom = '';
this.validTill = '';
this.period = new Period();
Object.assign(this, init);
}
}