import { SelectionModel } from '@angular/cdk/collections'; import { Injectable } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import * as math from 'mathjs'; import { BehaviorSubject } from 'rxjs'; import { Observable } from 'rxjs/internal/Observable'; import { BillViewItem } from '../core/bill-view-item'; import { ModifierCategory } from '../core/modifier-category'; import { Product } from '../core/product'; import { Table } from '../core/table'; import { ToasterService } from '../core/toaster.service'; import { ModifierCategoryService } from '../modifier-categories/modifier-category.service'; import { Bill } from './bills/bill'; import { Inventory } from './bills/inventory'; import { Kot } from './bills/kot'; import { VoucherType } from './bills/voucher-type'; import { VoucherService } from './bills/voucher.service'; import { ModifiersComponent } from './modifiers/modifiers.component'; @Injectable() export class BillService { public dataObs: BehaviorSubject; public data: BillViewItem[]; public bill: Bill = new Bill(); public netAmount: BehaviorSubject; public discountAmount: BehaviorSubject; public taxAmount: BehaviorSubject; public amount: BehaviorSubject; public selection = new SelectionModel(true, []); constructor( private dialog: MatDialog, private toaster: ToasterService, private ser: VoucherService, private modifierCategoryService: ModifierCategoryService, ) { this.data = []; this.dataObs = new BehaviorSubject(this.data); this.netAmount = new BehaviorSubject(0); this.discountAmount = new BehaviorSubject(0); this.taxAmount = new BehaviorSubject(0); this.amount = new BehaviorSubject(0); } loadData(bill: Bill): void { this.bill = bill; const view: BillViewItem[][] = this.bill.kots.map((k: Kot) => [ new BillViewItem({ id: k.id, isKot: true, info: `Kot: ${k.code} / ${k.date} (${k.user.name}) `, }), ...k.inventories.map( (i) => new BillViewItem({ id: i.id, kotId: k.id, isKot: false, product: i.product, productId: i.product.id, isHappyHour: i.isHappyHour, isPrinted: true, info: `${i.product.name} @ ${i.price} - ${math.round(i.discount * 100, 2)}%`, price: i.price, quantity: i.quantity, discount: i.discount, taxRate: i.taxRate, tax: i.tax, modifiers: i.modifiers, }), ), ]); this.data = view.reduce((a, c) => a.concat(c), []); this.data.push(new BillViewItem({ isKot: true, info: '== New Kot ==' })); this.dataObs.next(this.data); this.updateAmounts(); } minimum(productId: string, happyHour: boolean): number { return this.data.reduce( (a, c) => (c.productId === productId && c.isHappyHour === happyHour ? a + c.quantity : a), 0, ); } addProduct(product: Product, quantity: number, discount: number): void { const old = this.data.find( (x) => !x.isKot && !x.id && x.productId === product.id && x.isHappyHour === product.hasHappyHour, ); if (quantity < 0) { const minimum = this.minimum(product.id as string, product.hasHappyHour) + quantity; if (minimum + quantity < 0) { this.toaster.show('Error', 'Total quantity cannot be negative!'); return; } } if (old !== undefined) { old.quantity += quantity; } else { const item = new BillViewItem({ isKot: false, product, productId: product.id, isHappyHour: product.hasHappyHour, info: `${product.name} @ ${product.price} - ${0}%`, price: product.price, quantity, discount, taxRate: product.tax.rate, tax: product.tax, modifiers: [], }); this.data.push(item); this.modifierCategoryService.listForProduct(product.id as string).subscribe((result) => { if ( result.reduce((a: number, c: ModifierCategory) => { return a + c.minimum; }, 0) ) { this.showModifier(item); } }); } this.dataObs.next(this.data); this.updateAmounts(); } showModifier(item: BillViewItem): void { // [routerLink]="['/sales', 'modifiers', item.id]" const dialogRef = this.dialog.open(ModifiersComponent, { position: { top: '10vh', }, data: { list: this.modifierCategoryService.listForProduct(item.productId as string), selected: item.modifiers, }, }); dialogRef.afterClosed().subscribe((result) => { if (result !== undefined) { item.modifiers = result; } }); } addOne(item: BillViewItem): void { item.quantity += 1; this.dataObs.next(this.data); this.updateAmounts(); } quantity(item: BillViewItem, quantity: number): void { item.quantity = quantity; this.dataObs.next(this.data); this.updateAmounts(); } subtractOne(item: BillViewItem, canEdit: boolean): void { if ( item.quantity > 1 || (canEdit && this.minimum(item.productId as string, item.isHappyHour) >= 1) ) { item.quantity -= 1; this.dataObs.next(this.data); this.updateAmounts(); } else if (item.quantity === 0) { this.removeItem(item); } } removeItem(item: BillViewItem): void { this.data.splice(this.data.indexOf(item), 1); this.dataObs.next(this.data); this.updateAmounts(); } modifier(item: BillViewItem): void { this.showModifier(item); } discount(discounts: { id: string; name: string; discount: number }[]): void { this.data.forEach((x) => { if (!x.isKot) { x.discount = (discounts.find((d) => d.id === x.product.saleCategory.id) as { id: string; name: string; discount: number; }).discount / 100; x.info = `${x.product.name} @ ${x.price} - ${math.round(x.discount * 100, 2)}%`; } }); this.dataObs.next(this.data); this.updateAmounts(); } private getKot(): Kot { return new Kot({ inventories: this.data .filter((x) => !x.isKot && !x.isPrinted) .map( (y) => new Inventory({ product: y.product, quantity: y.quantity, price: y.price, isHappyHour: y.isHappyHour, discount: y.discount, modifiers: y.modifiers, taxRate: y.taxRate, tax: y.tax, }), ), }); } printKot(guestBookId: string | null): Observable { const item = JSON.parse(JSON.stringify(this.bill)); const newKot = this.getKot(); if (newKot.inventories.length === 0) { this.toaster.show('Error', 'Cannot print a blank KOT\nPlease add some products!'); } item.kots.push(newKot); return this.ser.saveOrUpdate(item, VoucherType.Kot, guestBookId, true); } printBill(guest_book_id: string | null, voucherType: VoucherType): Observable { const item = JSON.parse(JSON.stringify(this.bill)); item.kots.forEach((k: Kot) => { k.inventories.forEach((i: Inventory) => { i.discount = (this.data.find((x) => !x.isKot && x.id === i.id) as BillViewItem).discount; }); }); item.kots.push(this.getKot()); return this.ser.saveOrUpdate(item, voucherType, guest_book_id, true); } type() { return this.bill.voucherType; } receivePayment( amounts: { id: number; name: string; amount: number }[], name: string, ): Observable { return this.ser.receivePayment(this.bill.id as string, amounts, name, true); } moveTable(table: Table): Observable { return this.ser.moveTable(this.bill.id as string, table); } mergeTable(table: Table): Observable { return this.ser.mergeTable(this.bill.id as string, table); } moveKot(kotId: string, table: Table): Observable { return this.ser.moveKotToNewTable(this.bill.id as string, kotId, table); } mergeKot(kotId: string, table: Table): Observable { return this.ser.mergeKotWithOldBill(this.bill.id as string, kotId, table); } voidBill(reason: string): Observable { return this.ser.voidBill(this.bill.id as string, reason, true); } updateAmounts() { this.netAmount.next( math.round( this.data .filter((x) => !x.isKot) .reduce( (ca: number, c: BillViewItem) => ca + (c.isHappyHour ? 0 : c.price) * c.quantity, 0, ), ), ); this.discountAmount.next( math.round( this.data .filter((x) => !x.isKot) .reduce( (ca: number, c: BillViewItem) => ca + (c.isHappyHour ? 0 : c.price) * c.quantity * c.discount, 0, ), ), ); this.taxAmount.next( math.round( this.data .filter((x) => !x.isKot) .reduce( (ca: number, c: BillViewItem) => ca + (c.isHappyHour ? 0 : c.price) * c.quantity * (1 - c.discount) * c.taxRate, 0, ), ), ); this.amount.next( math.round( this.data .filter((x) => !x.isKot) .reduce( (ca: number, c: BillViewItem) => ca + (c.isHappyHour ? 0 : c.price) * c.quantity * (1 - c.discount) * (1 + c.taxRate), 0, ), ), ); } amountVal(): number { return math.round( this.bill.kots.reduce( (ka: number, k: Kot) => ka + k.inventories.reduce( (ca: number, c: Inventory) => ca + (c.isHappyHour ? 0 : c.price) * c.quantity * (1 - c.discount) * (1 + c.taxRate), 0, ), 0, ), ); } splitBill(table: Table): Observable { const inventoriesToMove: string[] = this.selection.selected.map( (x: BillViewItem) => x.id as string, ); return this.ser.splitBill(this.bill.id as string, inventoriesToMove, table); } }