fda
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import { DecimalPipe, PercentPipe } from '@angular/common';
|
import { DecimalPipe, PercentPipe } from '@angular/common';
|
||||||
import { Component, inject, computed, linkedSignal, input, afterNextRender } from '@angular/core';
|
import { Component, inject, computed, debounced, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||||
import { toObservable } from '@angular/core/rxjs-interop';
|
|
||||||
import { form, FormField } from '@angular/forms/signals';
|
import { form, FormField } from '@angular/forms/signals';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||||
@@ -11,13 +10,13 @@ import { MatPaginatorModule } from '@angular/material/paginator';
|
|||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { MatSortModule } from '@angular/material/sort';
|
import { MatSortModule } from '@angular/material/sort';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
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 moment from 'moment';
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { ToCsvService } from '../shared/to-csv.service';
|
import { ToCsvService } from '../shared/to-csv.service';
|
||||||
import { MenuEngineeringReport } from './menu-engineering-report';
|
import { MenuEngineeringReport } from './menu-engineering-report';
|
||||||
|
import { MenuEngineeringReportItem } from './menu-engineering-report-item';
|
||||||
import { MenuEngineeringReportService } from './menu-engineering-report.service';
|
import { MenuEngineeringReportService } from './menu-engineering-report.service';
|
||||||
|
|
||||||
export interface MenuEngineeringReportFormData {
|
export interface MenuEngineeringReportFormData {
|
||||||
@@ -45,7 +44,6 @@ export interface MenuEngineeringReportFormData {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class MenuEngineeringReportComponent {
|
export class MenuEngineeringReportComponent {
|
||||||
private route = inject(ActivatedRoute);
|
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private toCsv = inject(ToCsvService);
|
private toCsv = inject(ToCsvService);
|
||||||
private snackBar = inject(MatSnackBar);
|
private snackBar = inject(MatSnackBar);
|
||||||
@@ -56,8 +54,7 @@ export class MenuEngineeringReportComponent {
|
|||||||
infoResource = this.ser.get(this.startDate, this.finishDate);
|
infoResource = this.ser.get(this.startDate, this.finishDate);
|
||||||
|
|
||||||
info = computed(() => this.infoResource.value() ?? new MenuEngineeringReport());
|
info = computed(() => this.infoResource.value() ?? new MenuEngineeringReport());
|
||||||
filter: Observable<string>;
|
|
||||||
dataSource = computed(() => this.info().amounts);
|
|
||||||
formModel = linkedSignal({
|
formModel = linkedSignal({
|
||||||
source: this.info,
|
source: this.info,
|
||||||
computation: (infoVal): MenuEngineeringReportFormData => {
|
computation: (infoVal): MenuEngineeringReportFormData => {
|
||||||
@@ -78,14 +75,51 @@ export class MenuEngineeringReportComponent {
|
|||||||
|
|
||||||
form = form(this.formModel);
|
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. */
|
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
||||||
displayedColumns = ['name', 'price', 'saleCategory', 'menuCategory', 'quantity', 'sales'];
|
displayedColumns = ['name', 'price', 'saleCategory', 'menuCategory', 'quantity', 'sales'];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Create form
|
|
||||||
// Listen to Filter Change
|
|
||||||
const filterSignal = computed(() => this.formModel().filter);
|
|
||||||
this.filter = toObservable(filterSignal).pipe(debounceTime(150), distinctUntilChanged());
|
|
||||||
afterNextRender(() => {
|
afterNextRender(() => {
|
||||||
this.form().focusBoundControl();
|
this.form().focusBoundControl();
|
||||||
});
|
});
|
||||||
@@ -142,3 +176,39 @@ export class MenuEngineeringReportComponent {
|
|||||||
document.body.removeChild(link);
|
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 { MatCardModule } from '@angular/material/card';
|
||||||
import { MatRippleModule } from '@angular/material/core';
|
import { MatRippleModule } from '@angular/material/core';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
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 { Observable, of as observableOf } from 'rxjs';
|
||||||
import { map, switchMap, tap } from 'rxjs/operators';
|
import { map, switchMap, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
@@ -31,7 +31,6 @@ import { TablesDialogComponent } from '../tables-dialog/tables-dialog.component'
|
|||||||
imports: [MatRippleModule, RouterLink, MatCardModule],
|
imports: [MatRippleModule, RouterLink, MatCardModule],
|
||||||
})
|
})
|
||||||
export class SalesHomeComponent {
|
export class SalesHomeComponent {
|
||||||
private route = inject(ActivatedRoute);
|
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private dialog = inject(MatDialog);
|
private dialog = inject(MatDialog);
|
||||||
private auth = inject(AuthService);
|
private auth = inject(AuthService);
|
||||||
@@ -40,6 +39,7 @@ export class SalesHomeComponent {
|
|||||||
private tableService = inject(TableService);
|
private tableService = inject(TableService);
|
||||||
private customerDiscountsService = inject(CustomerDiscountsService);
|
private customerDiscountsService = inject(CustomerDiscountsService);
|
||||||
private bs = inject(BillService);
|
private bs = inject(BillService);
|
||||||
|
guest = input(null, { transform: (v: string | null | undefined) => v ?? null });
|
||||||
|
|
||||||
saleCategoriesResource = this.saleCategoryService.list();
|
saleCategoriesResource = this.saleCategoryService.list();
|
||||||
splitBillCategories = computed(() => {
|
splitBillCategories = computed(() => {
|
||||||
@@ -64,11 +64,7 @@ export class SalesHomeComponent {
|
|||||||
if (!this.printKotAllowed()) {
|
if (!this.printKotAllowed()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let guestBookId = null;
|
this.bs.printKot(this.guest()).subscribe({
|
||||||
if (this.route.snapshot.queryParamMap.has('guest')) {
|
|
||||||
guestBookId = this.route.snapshot.queryParamMap.get('guest');
|
|
||||||
}
|
|
||||||
this.bs.printKot(guestBookId).subscribe({
|
|
||||||
next: () => {
|
next: () => {
|
||||||
this.snackBar.open('', 'Success');
|
this.snackBar.open('', 'Success');
|
||||||
this.router.navigate(['/sales']);
|
this.router.navigate(['/sales']);
|
||||||
@@ -149,10 +145,6 @@ export class SalesHomeComponent {
|
|||||||
if (!this.printBillAllowed()) {
|
if (!this.printBillAllowed()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let guestBookId: string | null = null;
|
|
||||||
if (this.route.snapshot.queryParamMap.has('guest')) {
|
|
||||||
guestBookId = this.route.snapshot.queryParamMap.get('guest');
|
|
||||||
}
|
|
||||||
this.showDiscount()
|
this.showDiscount()
|
||||||
.pipe(
|
.pipe(
|
||||||
tap((result: boolean | { id: string; name: string; discount: number }[]) => {
|
tap((result: boolean | { id: string; name: string; discount: number }[]) => {
|
||||||
@@ -169,7 +161,7 @@ export class SalesHomeComponent {
|
|||||||
}
|
}
|
||||||
throw new Error('No Bill Type Chosen');
|
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({
|
.subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user