This commit is contained in:
2026-08-27 11:52:55 +00:00
parent ff537edb06
commit 730048f239
2 changed files with 87 additions and 25 deletions
@@ -1,6 +1,5 @@
import { DecimalPipe, PercentPipe } from '@angular/common';
import { Component, inject, computed, linkedSignal, input, afterNextRender } from '@angular/core';
import { toObservable } from '@angular/core/rxjs-interop';
import { Component, inject, computed, debounced, linkedSignal, input, afterNextRender } from '@angular/core';
import { form, FormField } from '@angular/forms/signals';
import { MatButtonModule } from '@angular/material/button';
import { MatDatepickerModule } from '@angular/material/datepicker';
@@ -11,13 +10,13 @@ import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSnackBar } from '@angular/material/snack-bar';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { ActivatedRoute, Router } from '@angular/router';
import { Router } from '@angular/router';
import { equal, larger, largerEq, smaller, smallerEq } from 'mathjs';
import moment from 'moment';
import { Observable } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { ToCsvService } from '../shared/to-csv.service';
import { MenuEngineeringReport } from './menu-engineering-report';
import { MenuEngineeringReportItem } from './menu-engineering-report-item';
import { MenuEngineeringReportService } from './menu-engineering-report.service';
export interface MenuEngineeringReportFormData {
@@ -45,7 +44,6 @@ export interface MenuEngineeringReportFormData {
],
})
export class MenuEngineeringReportComponent {
private route = inject(ActivatedRoute);
private router = inject(Router);
private toCsv = inject(ToCsvService);
private snackBar = inject(MatSnackBar);
@@ -56,8 +54,7 @@ export class MenuEngineeringReportComponent {
infoResource = this.ser.get(this.startDate, this.finishDate);
info = computed(() => this.infoResource.value() ?? new MenuEngineeringReport());
filter: Observable<string>;
dataSource = computed(() => this.info().amounts);
formModel = linkedSignal({
source: this.info,
computation: (infoVal): MenuEngineeringReportFormData => {
@@ -78,14 +75,51 @@ export class MenuEngineeringReportComponent {
form = form(this.formModel);
filterSignal = computed(() => this.formModel().filter ?? '');
debouncedFilter = debounced(this.filterSignal, 150);
dataSource = computed(() => {
const data = this.info().amounts ?? [];
const filterValue = (this.debouncedFilter.value() ?? '').trim().toLowerCase();
if (!filterValue) {
return data;
}
const filtered = filterValue.split(/\s+/).reduce(
(p: MenuEngineeringReportItem[], c: string) =>
p.filter((x) => {
if (c.startsWith('n:')) {
return x.name.toLowerCase().includes(c.substring(2));
}
if (c.startsWith('sc:')) {
return x.saleCategory.toLowerCase().includes(c.substring(3));
}
if (c.startsWith('mc:')) {
return x.menuCategory.toLowerCase().includes(c.substring(3));
}
if (c.startsWith('q:')) {
const result = c.match(/^q:(?<sign>=|<=|>=|<|>)(?<amount>\d+(\.\d+)?)$/);
if (result && result.groups) {
return math_it_up(result.groups['sign'])(x.quantity, +result.groups['amount']);
}
}
if (c.startsWith('s:')) {
const result = c.match(/^s:(?<sign>=|<=|>=|<|>)(?<amount>\d+(\.\d+)?)$/);
if (result && result.groups) {
return math_it_up(result.groups['sign'])(x.sales, +result.groups['amount']);
}
}
const itemString = `${x.name} ${x.saleCategory} ${x.menuCategory}`.toLowerCase();
return itemString.includes(c);
}),
[...data],
);
return recalculate(filtered);
});
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
displayedColumns = ['name', 'price', 'saleCategory', 'menuCategory', 'quantity', 'sales'];
constructor() {
// Create form
// Listen to Filter Change
const filterSignal = computed(() => this.formModel().filter);
this.filter = toObservable(filterSignal).pipe(debounceTime(150), distinctUntilChanged());
afterNextRender(() => {
this.form().focusBoundControl();
});
@@ -142,3 +176,39 @@ export class MenuEngineeringReportComponent {
document.body.removeChild(link);
}
}
function math_it_up(sign: string) {
switch (sign) {
case '=':
return (x: number, y: number) => Boolean(equal(x, y));
case '>':
return (x: number, y: number) => Boolean(larger(x, y));
case '<':
return (x: number, y: number) => Boolean(smaller(x, y));
case '>=':
return (x: number, y: number) => Boolean(largerEq(x, y));
case '<=':
return (x: number, y: number) => Boolean(smallerEq(x, y));
default:
return (x: number, y: number) => Boolean(equal(x, y));
}
}
function recalculate(data: MenuEngineeringReportItem[]): MenuEngineeringReportItem[] {
const gr = data.reduce((res: Record<string, { q: number; s: number }>, value: MenuEngineeringReportItem) => {
if (!res[value.saleCategory]) {
res[value.saleCategory] = { q: 0, s: 0 };
}
res[value.saleCategory].q += value.quantity;
res[value.saleCategory].s += value.sales;
return res;
}, {});
return data.map(
(v) =>
new MenuEngineeringReportItem({
...v,
quantityPercent: gr[v.saleCategory]?.q ? v.quantity / gr[v.saleCategory].q : 0,
salesPercent: gr[v.saleCategory]?.s ? v.sales / gr[v.saleCategory].s : 0,
}),
);
}
@@ -1,9 +1,9 @@
import { Component, inject, computed, signal } from '@angular/core';
import { Component, inject, computed, signal, input } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { MatRippleModule } from '@angular/material/core';
import { MatDialog } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { Router, RouterLink } from '@angular/router';
import { Observable, of as observableOf } from 'rxjs';
import { map, switchMap, tap } from 'rxjs/operators';
@@ -31,7 +31,6 @@ import { TablesDialogComponent } from '../tables-dialog/tables-dialog.component'
imports: [MatRippleModule, RouterLink, MatCardModule],
})
export class SalesHomeComponent {
private route = inject(ActivatedRoute);
private router = inject(Router);
private dialog = inject(MatDialog);
private auth = inject(AuthService);
@@ -40,6 +39,7 @@ export class SalesHomeComponent {
private tableService = inject(TableService);
private customerDiscountsService = inject(CustomerDiscountsService);
private bs = inject(BillService);
guest = input(null, { transform: (v: string | null | undefined) => v ?? null });
saleCategoriesResource = this.saleCategoryService.list();
splitBillCategories = computed(() => {
@@ -64,11 +64,7 @@ export class SalesHomeComponent {
if (!this.printKotAllowed()) {
return;
}
let guestBookId = null;
if (this.route.snapshot.queryParamMap.has('guest')) {
guestBookId = this.route.snapshot.queryParamMap.get('guest');
}
this.bs.printKot(guestBookId).subscribe({
this.bs.printKot(this.guest()).subscribe({
next: () => {
this.snackBar.open('', 'Success');
this.router.navigate(['/sales']);
@@ -149,10 +145,6 @@ export class SalesHomeComponent {
if (!this.printBillAllowed()) {
return;
}
let guestBookId: string | null = null;
if (this.route.snapshot.queryParamMap.has('guest')) {
guestBookId = this.route.snapshot.queryParamMap.get('guest');
}
this.showDiscount()
.pipe(
tap((result: boolean | { id: string; name: string; discount: number }[]) => {
@@ -169,7 +161,7 @@ export class SalesHomeComponent {
}
throw new Error('No Bill Type Chosen');
}),
switchMap((x: VoucherType) => this.bs.printBill(guestBookId, x as VoucherType)),
switchMap((x: VoucherType) => this.bs.printBill(this.guest(), x as VoucherType)),
)
.subscribe({
next: () => {