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
@@ -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);
}
}