420 lines
12 KiB
TypeScript
420 lines
12 KiB
TypeScript
import { Component } from '@angular/core';
|
|
import { MatDialog } from '@angular/material/dialog';
|
|
import { ActivatedRoute, Router } from '@angular/router';
|
|
import { Observable, of as observableOf } from 'rxjs';
|
|
import { map, switchMap, tap } from 'rxjs/operators';
|
|
|
|
import { AuthService } from '../../auth/auth.service';
|
|
import { ReceivePaymentItem } from '../../core/receive-payment-item';
|
|
import { Table } from '../../core/table';
|
|
import { ToasterService } from '../../core/toaster.service';
|
|
import { SaleCategoryService } from '../../sale-category/sale-category.service';
|
|
import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
|
|
import { TableService } from '../../tables/table.service';
|
|
import { BillTypeComponent } from '../bill-type/bill-type.component';
|
|
import { BillService } from '../bill.service';
|
|
import { BillSelectionItem } from '../bills/bill-selection-item';
|
|
import { VoucherType } from '../bills/voucher-type';
|
|
import { CustomerDiscountsService } from '../discount/customer-discounts.service';
|
|
import { DiscountComponent } from '../discount/discount.component';
|
|
import { ReasonComponent } from '../reason/reason.component';
|
|
import { ReceivePaymentComponent } from '../receive-payment/receive-payment.component';
|
|
import { SplitBillComponent } from '../split-bill/split-bill.component';
|
|
import { TablesDialogComponent } from '../tables-dialog/tables-dialog.component';
|
|
|
|
@Component({
|
|
selector: 'app-sales-home',
|
|
templateUrl: './sales-home.component.html',
|
|
styleUrls: ['./sales-home.component.css'],
|
|
})
|
|
export class SalesHomeComponent {
|
|
constructor(
|
|
private route: ActivatedRoute,
|
|
private router: Router,
|
|
private dialog: MatDialog,
|
|
private auth: AuthService,
|
|
private toaster: ToasterService,
|
|
private saleCategoryService: SaleCategoryService,
|
|
private tableService: TableService,
|
|
private customerDiscountsService: CustomerDiscountsService,
|
|
private bs: BillService,
|
|
) {}
|
|
|
|
printKotAllowed(): boolean {
|
|
if (!this.auth.allowed('print-kot')) {
|
|
return false;
|
|
}
|
|
if (!this.bs.bill.id) {
|
|
return true;
|
|
}
|
|
if (this.bs.bill.voucherType !== VoucherType.Kot) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
printKot() {
|
|
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.toaster.show('Success', '');
|
|
this.router.navigate(['/sales']);
|
|
},
|
|
(result) => {
|
|
this.toaster.show('Error', result);
|
|
},
|
|
);
|
|
}
|
|
|
|
discountAllowed(): boolean {
|
|
return this.auth.allowed('discount');
|
|
}
|
|
|
|
showDiscount(): Observable<boolean | { id: string; name: string; discount: number }[]> {
|
|
if (this.discountAllowed()) {
|
|
const dialogRef = this.dialog.open(DiscountComponent, {
|
|
// width: '750px',
|
|
data: this.customerDiscountsService.list(this.bs.bill.customer?.id),
|
|
});
|
|
return dialogRef.afterClosed();
|
|
}
|
|
return observableOf(false);
|
|
}
|
|
|
|
discount(): void {
|
|
this.showDiscount().subscribe((result: boolean | { id: string; name: string; discount: number }[]) => {
|
|
if (result) {
|
|
this.bs.discount(result as { id: string; name: string; discount: number }[]);
|
|
}
|
|
});
|
|
}
|
|
|
|
billTypeDialog() {
|
|
if (this.bs.bill.voucherType !== VoucherType.Kot) {
|
|
return observableOf(this.bs.bill.voucherType);
|
|
}
|
|
return this.dialog.open(BillTypeComponent).afterClosed();
|
|
}
|
|
|
|
confirmTableDialog(table: Table): Observable<Table | boolean> {
|
|
return this.dialog
|
|
.open(ConfirmDialogComponent, {
|
|
width: '250px',
|
|
data: { title: 'Select Table?', content: 'Are you sure?' },
|
|
})
|
|
.afterClosed()
|
|
.pipe(map((x: boolean) => (x ? table : x)));
|
|
}
|
|
|
|
confirmCancelDialog(reason: string): Observable<string | boolean> {
|
|
return this.dialog
|
|
.open(ConfirmDialogComponent, {
|
|
width: '250px',
|
|
data: { title: 'Cancel Bill?', content: 'Are you sure?' },
|
|
})
|
|
.afterClosed()
|
|
.pipe(map((x: boolean) => (x ? reason : x)));
|
|
}
|
|
|
|
printBillAllowed(): boolean {
|
|
if (!this.auth.allowed('print-bill')) {
|
|
return false;
|
|
}
|
|
if (!this.bs.bill.id) {
|
|
return true;
|
|
}
|
|
if (this.bs.bill.voucherType !== VoucherType.Kot && !this.auth.allowed('edit-printed-bill')) {
|
|
return false;
|
|
}
|
|
if (this.bs.bill.voucherType === VoucherType.Void) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
printBill() {
|
|
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 }[]) => {
|
|
if (result) {
|
|
this.bs.discount(result as { id: string; name: string; discount: number }[]);
|
|
} else if (this.discountAllowed()) {
|
|
throw new Error('Discount Selection Cancelled');
|
|
}
|
|
}),
|
|
switchMap(() => this.billTypeDialog()),
|
|
switchMap((x) => {
|
|
if (x) {
|
|
return observableOf(x);
|
|
}
|
|
throw new Error('No Bill Type Chosen');
|
|
}),
|
|
switchMap((x: VoucherType) => this.bs.printBill(guestBookId, x as VoucherType)),
|
|
)
|
|
.subscribe(
|
|
() => {
|
|
this.toaster.show('Success', '');
|
|
this.router.navigate(['/sales']);
|
|
},
|
|
(result) => {
|
|
this.toaster.show('Error', result);
|
|
},
|
|
);
|
|
}
|
|
|
|
receivePaymentAllowed(): boolean {
|
|
if (!this.auth.allowed('settle-bill')) {
|
|
return false;
|
|
}
|
|
if (!this.bs.bill.id) {
|
|
return false;
|
|
}
|
|
if (this.bs.bill.voucherType === VoucherType.Kot) {
|
|
return false;
|
|
}
|
|
if (this.bs.bill.voucherType === VoucherType.Void) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
receivePayment() {
|
|
if (!this.receivePaymentAllowed()) {
|
|
return;
|
|
}
|
|
const amount = this.bs.amountVal;
|
|
const type = this.bs.bill.voucherType;
|
|
this.dialog
|
|
.open(ReceivePaymentComponent, {
|
|
data: {
|
|
type,
|
|
amount,
|
|
},
|
|
})
|
|
.afterClosed()
|
|
.pipe(
|
|
switchMap((value: undefined | { choices: ReceivePaymentItem[]; reason: string }) => {
|
|
if (value === undefined) {
|
|
throw new Error('Receive Payment Choices / Reason not selected');
|
|
}
|
|
return this.bs.receivePayment(value);
|
|
}),
|
|
)
|
|
.subscribe(
|
|
() => {
|
|
this.toaster.show('Success', '');
|
|
this.router.navigate(['/sales']);
|
|
},
|
|
(value) => {
|
|
this.toaster.show('Error', value);
|
|
},
|
|
);
|
|
}
|
|
|
|
moveTableAllowed(): boolean {
|
|
if (!this.auth.allowed('move-table') && !this.auth.allowed('merge-tables')) {
|
|
return false;
|
|
}
|
|
if (!this.bs.bill.id) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
moveTable() {
|
|
if (!this.moveTableAllowed()) {
|
|
return;
|
|
}
|
|
const canMergeTables = this.auth.allowed('merge-tables');
|
|
this.showChooseTableDialog(canMergeTables)
|
|
.pipe(
|
|
tap((x) => {
|
|
if (!x) {
|
|
throw new Error('Please choose a table');
|
|
}
|
|
}),
|
|
map((x) => x as Table),
|
|
switchMap((x: Table) => this.confirmTableDialog(x)),
|
|
tap((x) => {
|
|
if (!x) {
|
|
throw new Error('Move Table Cancelled');
|
|
}
|
|
}),
|
|
map((x) => x as Table),
|
|
switchMap((x: Table) => (x.status ? this.bs.mergeTable(x) : this.bs.moveTable(x))),
|
|
)
|
|
.subscribe(
|
|
() => {
|
|
this.toaster.show('Success', '');
|
|
this.router.navigate(['/sales']);
|
|
},
|
|
(x) => {
|
|
this.toaster.show('Error', x);
|
|
},
|
|
);
|
|
}
|
|
|
|
cancelBillAllowed(): boolean {
|
|
if (!this.auth.allowed('void-bill')) {
|
|
return false;
|
|
}
|
|
if (!this.bs.bill.id) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
cancelBill() {
|
|
if (!this.cancelBillAllowed()) {
|
|
return;
|
|
}
|
|
this.dialog
|
|
.open(ReasonComponent, {
|
|
// width: '750px'
|
|
data: {
|
|
title: 'Cancel Reason',
|
|
reasons: [
|
|
'Discount',
|
|
'Printing fault',
|
|
'Item changed',
|
|
'Quantity reduced',
|
|
'Costing bill for party',
|
|
'Cashier mistake',
|
|
'Management free sale',
|
|
],
|
|
},
|
|
})
|
|
.afterClosed()
|
|
.pipe(
|
|
switchMap((x: boolean | string) => {
|
|
if (x) {
|
|
return this.confirmCancelDialog(x as string);
|
|
}
|
|
throw new Error('Please choose a reason to cancel the bill');
|
|
}),
|
|
switchMap((x: boolean | string) => {
|
|
if (x) {
|
|
return this.bs.cancelBill(x as string);
|
|
}
|
|
throw new Error('You chose not to cancel the bill');
|
|
}),
|
|
)
|
|
.subscribe(
|
|
() => {
|
|
this.toaster.show('Success', '');
|
|
this.router.navigate(['/sales']);
|
|
},
|
|
(x) => {
|
|
this.toaster.show('Error', x);
|
|
},
|
|
);
|
|
}
|
|
|
|
splitBillAllowed(): boolean {
|
|
if (!this.auth.allowed('split-bill')) {
|
|
return false;
|
|
}
|
|
if (!this.bs.bill.id) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
showBillSplitChoices(): Observable<{ id: string; name: string; selected: boolean }[] | undefined | false> {
|
|
return this.dialog
|
|
.open(SplitBillComponent, {
|
|
data: this.saleCategoryService
|
|
.list()
|
|
.pipe(map((x) => x.map((y) => ({ id: y.id, name: y.name, selected: false })))),
|
|
})
|
|
.afterClosed();
|
|
}
|
|
|
|
showChooseTableDialog(canChooseRunning: boolean): Observable<Table | undefined | null> {
|
|
return this.dialog
|
|
.open(TablesDialogComponent, {
|
|
data: {
|
|
list: this.tableService.running(),
|
|
canChooseRunning,
|
|
},
|
|
})
|
|
.afterClosed();
|
|
}
|
|
|
|
splitBill() {
|
|
if (!this.splitBillAllowed()) {
|
|
return;
|
|
}
|
|
const obs = this.bs.selection.isEmpty() ? this.splitBillWithChoice() : this.splitBillWithSelection();
|
|
let inventories: string[];
|
|
obs
|
|
.pipe(
|
|
tap((x) => (inventories = x)),
|
|
switchMap(() => this.showChooseTableDialog(false)),
|
|
tap((x) => {
|
|
if (!x) {
|
|
throw new Error('Please choose a table');
|
|
}
|
|
}),
|
|
map((x) => x as Table),
|
|
switchMap((x: Table) => this.confirmTableDialog(x)),
|
|
tap((x) => {
|
|
if (!x) {
|
|
throw new Error('Bill Split Cancelled');
|
|
}
|
|
}),
|
|
map((x) => x as Table),
|
|
switchMap((x: Table) => this.bs.splitBill(inventories, x)),
|
|
)
|
|
.subscribe(
|
|
() => {
|
|
this.toaster.show('Success', '');
|
|
this.router.navigate(['/sales']);
|
|
},
|
|
(x) => {
|
|
this.toaster.show('Error', x);
|
|
},
|
|
);
|
|
}
|
|
|
|
splitBillWithChoice() {
|
|
let inventories: string[];
|
|
return this.showBillSplitChoices().pipe(
|
|
tap((x) => {
|
|
if (x === undefined || x === false) {
|
|
throw new Error('Cancelled');
|
|
}
|
|
}),
|
|
map((x) => (x as { id: string; name: string; selected: boolean }[]).filter((y) => y.selected).map((y) => y.id)),
|
|
tap((x: string[]) => {
|
|
const sel = this.bs.getInventories(x);
|
|
if (sel.keep.length === 0 || sel.move.length === 0) {
|
|
throw new Error('This will move either All or None of the items. Cancelled');
|
|
}
|
|
inventories = sel.move;
|
|
}),
|
|
map(() => inventories),
|
|
);
|
|
}
|
|
|
|
splitBillWithSelection() {
|
|
const inventories: string[] = this.bs.selection.selected.map(
|
|
(x: string) => (JSON.parse(x) as BillSelectionItem).inventoryId as string,
|
|
);
|
|
return observableOf(inventories);
|
|
}
|
|
}
|