Fix: Product ledger was not totalling. This is because for some reason, pydantic was sending the data as string when the field was nullable
250 lines
8.0 KiB
TypeScript
250 lines
8.0 KiB
TypeScript
import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
|
|
import { FormControl, FormGroup } from '@angular/forms';
|
|
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
|
import { MatDialog } from '@angular/material/dialog';
|
|
import { ActivatedRoute, Router } from '@angular/router';
|
|
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';
|
|
import { ToasterService } from '../../core/toaster.service';
|
|
import { ProductService } from '../../product/product.service';
|
|
import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
|
|
import { MathService } from '../../shared/math.service';
|
|
import { Recipe } from '../recipe';
|
|
import { RecipeItem } from '../recipe-item';
|
|
import { RecipeService } from '../recipe.service';
|
|
|
|
import { RecipeDetailDatasource } from './recipe-detail-datasource';
|
|
|
|
@Component({
|
|
selector: 'app-product-group-detail',
|
|
templateUrl: './recipe-detail.component.html',
|
|
styleUrls: ['./recipe-detail.component.css'],
|
|
})
|
|
export class RecipeDetailComponent implements OnInit, AfterViewInit {
|
|
@ViewChild('productElement', { static: true }) productElement?: ElementRef;
|
|
@ViewChild('ingredientElement', { static: true }) ingredientElement?: ElementRef;
|
|
public itemsObservable = new BehaviorSubject<RecipeItem[]>([]);
|
|
dataSource: RecipeDetailDatasource = new RecipeDetailDatasource(this.itemsObservable);
|
|
form: FormGroup<{
|
|
date: FormControl<Date>;
|
|
source: FormControl<string>;
|
|
recipeYield: FormControl<string | null>;
|
|
product: FormControl<ProductSku | string | null>;
|
|
addRow: FormGroup<{
|
|
ingredient: FormControl<string | null>;
|
|
quantity: FormControl<string>;
|
|
description: FormControl<string>;
|
|
rate: FormControl<string>;
|
|
}>;
|
|
instructions: FormControl<string>;
|
|
garnishing: FormControl<string>;
|
|
plating: FormControl<string>;
|
|
}>;
|
|
|
|
periods: Period[] = [];
|
|
product: ProductSku | null;
|
|
products: Observable<ProductSku[]>;
|
|
ingredient: ProductSku | null;
|
|
ingredients: Observable<ProductSku[]>;
|
|
item: Recipe = new Recipe();
|
|
|
|
displayedColumns = ['product', 'quantity', 'action'];
|
|
|
|
constructor(
|
|
private route: ActivatedRoute,
|
|
private router: Router,
|
|
private dialog: MatDialog,
|
|
private toaster: ToasterService,
|
|
private math: MathService,
|
|
private ser: RecipeService,
|
|
private productSer: ProductService,
|
|
) {
|
|
this.product = null;
|
|
this.ingredient = null;
|
|
this.form = new FormGroup({
|
|
date: new FormControl(new Date(), { nonNullable: true }),
|
|
source: new FormControl('', { nonNullable: true }),
|
|
recipeYield: new FormControl<string | null>(null),
|
|
product: new FormControl<ProductSku | string | null>(new ProductSku()),
|
|
addRow: new FormGroup({
|
|
ingredient: new FormControl(''),
|
|
quantity: new FormControl('', { nonNullable: true }),
|
|
description: new FormControl('', { nonNullable: true }),
|
|
rate: new FormControl('', { nonNullable: true }),
|
|
}),
|
|
instructions: new FormControl('', { nonNullable: true }),
|
|
garnishing: new FormControl('', { nonNullable: true }),
|
|
plating: new FormControl('', { nonNullable: true }),
|
|
});
|
|
// Setup Product Autocomplete
|
|
this.products = this.form.controls.product.valueChanges.pipe(
|
|
map((x) => ((x as ProductSku).name !== undefined ? (x as ProductSku).name : (x as string))),
|
|
map((x) => (x !== null && x.length >= 1 ? x : null)),
|
|
debounceTime(150),
|
|
distinctUntilChanged(),
|
|
switchMap((x) => (x === null ? observableOf([]) : this.productSer.autocompleteSku(x, false))),
|
|
);
|
|
// Setup Product Autocomplete
|
|
this.ingredients = this.form.controls.addRow.controls.ingredient.valueChanges.pipe(
|
|
map((x) => (x !== null && x.length >= 1 ? x : null)),
|
|
debounceTime(150),
|
|
distinctUntilChanged(),
|
|
switchMap((x) => (x === null ? observableOf([]) : this.productSer.autocompleteProduct(x, null))),
|
|
);
|
|
}
|
|
|
|
ngOnInit() {
|
|
this.route.data.subscribe((value) => {
|
|
const data = value as { item: Recipe; periods: Period[] };
|
|
this.periods = data.periods;
|
|
this.showItem(data.item);
|
|
this.updateView();
|
|
});
|
|
}
|
|
|
|
showItem(item: Recipe) {
|
|
this.item = item;
|
|
this.form.setValue({
|
|
date: moment(item.date, 'DD-MMM-YYYY').toDate(),
|
|
source: item.source,
|
|
recipeYield: `${item.recipeYield}`,
|
|
product: item.sku,
|
|
addRow: {
|
|
ingredient: null,
|
|
quantity: '',
|
|
description: '',
|
|
rate: '',
|
|
},
|
|
instructions: item.instructions,
|
|
garnishing: item.garnishing,
|
|
plating: item.plating,
|
|
});
|
|
this.dataSource = new RecipeDetailDatasource(this.itemsObservable);
|
|
}
|
|
|
|
ngAfterViewInit() {
|
|
setTimeout(() => {
|
|
if (this.productElement) {
|
|
this.productElement.nativeElement.focus();
|
|
}
|
|
}, 0);
|
|
}
|
|
|
|
displayIngredient(product?: Product): string {
|
|
return product ? `${product.name} (${product.fractionUnits})` : '';
|
|
}
|
|
|
|
displayProduct(product?: Product): string {
|
|
return product?.name || '';
|
|
}
|
|
|
|
productSelected(event: MatAutocompleteSelectedEvent): void {
|
|
const product: ProductSku = event.option.value;
|
|
this.item.sku = product;
|
|
this.form.controls.product.setValue(product);
|
|
}
|
|
|
|
ingredientSelected(event: MatAutocompleteSelectedEvent): void {
|
|
const ingredient: ProductSku = event.option.value;
|
|
this.ingredient = ingredient;
|
|
}
|
|
|
|
addRow() {
|
|
const formValue = this.form.value.addRow;
|
|
if (formValue === undefined) {
|
|
return;
|
|
}
|
|
const quantity = this.math.parseAmount(formValue.quantity, 2);
|
|
const rate = this.math.parseAmount(formValue.rate, 2);
|
|
if (this.ingredient === null || quantity <= 0 || rate <= 0) {
|
|
return;
|
|
}
|
|
const oldFiltered = this.item.items.filter((x) => x.product.id === (this.ingredient as ProductSku).id);
|
|
if (oldFiltered.length) {
|
|
this.toaster.show('Danger', 'Product already added');
|
|
return;
|
|
}
|
|
this.item.items.push(
|
|
new RecipeItem({
|
|
product: this.ingredient,
|
|
quantity,
|
|
description: formValue.description ?? '',
|
|
}),
|
|
);
|
|
this.resetAddRow();
|
|
this.updateView();
|
|
}
|
|
|
|
resetAddRow() {
|
|
this.form.controls.addRow.reset();
|
|
this.ingredient = null;
|
|
setTimeout(() => {
|
|
if (this.ingredientElement) {
|
|
this.ingredientElement.nativeElement.focus();
|
|
}
|
|
}, 0);
|
|
}
|
|
|
|
updateView() {
|
|
this.itemsObservable.next(this.item.items);
|
|
}
|
|
|
|
deleteRow(row: RecipeItem) {
|
|
this.item.items.splice(this.item.items.indexOf(row), 1);
|
|
this.updateView();
|
|
}
|
|
|
|
save() {
|
|
this.ser.saveOrUpdate(this.getItem()).subscribe(
|
|
() => {
|
|
this.toaster.show('Success', '');
|
|
this.router.navigateByUrl('/recipes');
|
|
},
|
|
(error) => {
|
|
this.toaster.show('Danger', error);
|
|
},
|
|
);
|
|
}
|
|
|
|
delete() {
|
|
this.ser.delete(this.item.id as string).subscribe(
|
|
() => {
|
|
this.toaster.show('Success', '');
|
|
this.router.navigate(['/recipes']);
|
|
},
|
|
(error) => {
|
|
this.toaster.show('Danger', error);
|
|
},
|
|
);
|
|
}
|
|
|
|
confirmDelete(): void {
|
|
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
|
width: '250px',
|
|
data: { title: 'Delete Recipe?', content: 'Are you sure? This cannot be undone.' },
|
|
});
|
|
|
|
dialogRef.afterClosed().subscribe((result: boolean) => {
|
|
if (result) {
|
|
this.delete();
|
|
}
|
|
});
|
|
}
|
|
|
|
getItem(): Recipe {
|
|
const formModel = this.form.value;
|
|
this.item.date = moment(formModel.date).format('DD-MMM-YYYY');
|
|
this.item.source = formModel.source ?? '';
|
|
this.item.recipeYield = this.math.parseAmount(formModel.recipeYield ?? '1', 2);
|
|
this.item.instructions = formModel.instructions ?? '';
|
|
this.item.garnishing = formModel.garnishing ?? '';
|
|
this.item.plating = formModel.plating ?? '';
|
|
return this.item;
|
|
}
|
|
}
|