Feature: Open bill using bill number
This commit is contained in:
parent
98c75f66c9
commit
161896154d
|
@ -46,22 +46,34 @@ def from_bill(
|
|||
item: Voucher = db.query(Voucher)
|
||||
if re.compile(r"^\d{2,}-\d{4}$").match(id_):
|
||||
item = item.filter(
|
||||
Voucher.bill_id == int(id_.replace("-", "")),
|
||||
Voucher.voucher_type.in_([1, 3]),
|
||||
Voucher.bill_id == int(id_.replace("-", "")), Voucher.voucher_type == VoucherType.REGULAR_BILL
|
||||
)
|
||||
elif re.compile(r"^K-\d+$").match(id_):
|
||||
item = item.filter(
|
||||
Voucher.kot_id == int(id_.replace("K-", "")),
|
||||
Voucher.voucher_type == VoucherType.KOT,
|
||||
)
|
||||
elif re.compile(r"^NC-\d+$").match(id_):
|
||||
item = item.filter(
|
||||
Voucher.bill_id == int(id_.replace("NC-", "")),
|
||||
Voucher.voucher_type == 2,
|
||||
Voucher.voucher_type == VoucherType.NO_CHARGE,
|
||||
)
|
||||
elif re.compile(r"^ST-\d+$").match(id_):
|
||||
item = item.filter(
|
||||
Voucher.bill_id == int(id_.replace("ST-", "")),
|
||||
Voucher.voucher_type == 4,
|
||||
Voucher.voucher_type == VoucherType.STAFF,
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Bill Number is invalid",
|
||||
)
|
||||
item = item.first()
|
||||
if item is None:
|
||||
return {}
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Bill not found",
|
||||
)
|
||||
return voucher_info(item, db)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<mat-dialog-content>
|
||||
<form [formGroup]="form">
|
||||
<div
|
||||
fxLayout="row"
|
||||
fxLayoutAlign="space-around start"
|
||||
fxLayout.lt-md="column"
|
||||
fxLayoutGap="20px"
|
||||
fxLayoutGap.lt-md="0px"
|
||||
>
|
||||
<mat-form-field fxFlex="15">
|
||||
<mat-select formControlName="billType">
|
||||
<mat-option value="0">KOT</mat-option>
|
||||
<mat-option value="1">Regular Bill</mat-option>
|
||||
<mat-option value="4">Staff</mat-option>
|
||||
<mat-option value="2">No Charge</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex>
|
||||
<mat-label>Bill Number</mat-label>
|
||||
<input
|
||||
type="text"
|
||||
matInput
|
||||
#billNumber
|
||||
placeholder="Bill Number"
|
||||
formControlName="billNumber"
|
||||
autocomplete="off"
|
||||
(focus)="billNumber.select()"
|
||||
cdkFocusInitial
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</form>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button [mat-dialog-close]="false">Cancel</button>
|
||||
<button mat-button (click)="accept()" color="primary">Ok</button>
|
||||
</mat-dialog-actions>
|
|
@ -0,0 +1,26 @@
|
|||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { BillNumberComponent } from './bill-number.component';
|
||||
|
||||
describe('QuantityComponent', () => {
|
||||
let component: BillNumberComponent;
|
||||
let fixture: ComponentFixture<BillNumberComponent>;
|
||||
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [BillNumberComponent],
|
||||
}).compileComponents();
|
||||
}),
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(BillNumberComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-bill-number',
|
||||
templateUrl: './bill-number.component.html',
|
||||
styleUrls: ['./bill-number.component.css'],
|
||||
})
|
||||
export class BillNumberComponent implements OnInit {
|
||||
form: FormGroup;
|
||||
|
||||
constructor(public dialogRef: MatDialogRef<BillNumberComponent>, private fb: FormBuilder) {
|
||||
// Create form
|
||||
this.form = this.fb.group({
|
||||
billType: '',
|
||||
billNumber: '',
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.form.setValue({
|
||||
billType: '1',
|
||||
billNumber: '',
|
||||
});
|
||||
}
|
||||
|
||||
accept(): void {
|
||||
const formValue = this.form.value;
|
||||
const billNumber = parseInt(formValue.billNumber.replace('-', ''), 10);
|
||||
if (isNaN(billNumber)) {
|
||||
this.dialogRef.close(undefined);
|
||||
} else {
|
||||
let billId: string;
|
||||
switch (formValue.billType) {
|
||||
case '0': // KOT
|
||||
billId = 'K-' + billNumber;
|
||||
break;
|
||||
case '1': // Regular Bill
|
||||
billId = Math.floor(billNumber / 10000) + '-' + (billNumber % 10000);
|
||||
break;
|
||||
case '4': // Staff
|
||||
billId = 'ST-' + billNumber;
|
||||
break;
|
||||
case '2': // No Charge
|
||||
billId = 'NC-' + billNumber;
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown Bill Type');
|
||||
}
|
||||
this.dialogRef.close(billId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,6 +34,7 @@ export class BillService {
|
|||
public amountVal: number;
|
||||
public selection = new SelectionModel<string>(true, []);
|
||||
private amountBs: BehaviorSubject<number>;
|
||||
private updateTable: boolean;
|
||||
|
||||
constructor(
|
||||
private dialog: MatDialog,
|
||||
|
@ -47,6 +48,7 @@ export class BillService {
|
|||
this.taxAmount = new BehaviorSubject(0);
|
||||
this.amountBs = new BehaviorSubject(0);
|
||||
this.amountVal = 0;
|
||||
this.updateTable = true;
|
||||
this.amount = this.amountBs.pipe(tap((x) => (this.amountVal = x)));
|
||||
}
|
||||
|
||||
|
@ -83,7 +85,8 @@ export class BillService {
|
|||
return view;
|
||||
}
|
||||
|
||||
loadData(bill: Bill): void {
|
||||
loadData(bill: Bill, updateTable: boolean): void {
|
||||
this.updateTable = updateTable;
|
||||
bill.kots.push(new Kot());
|
||||
this.bill = bill;
|
||||
this.displayBill();
|
||||
|
@ -234,7 +237,7 @@ export class BillService {
|
|||
if (!this.happyHourItemsBalanced() || this.happyHourItemsMoreThanRegular()) {
|
||||
return throwError('Happy hour products are not balanced.');
|
||||
}
|
||||
return this.ser.saveOrUpdate(item, VoucherType.Kot, guestBookId, true);
|
||||
return this.ser.saveOrUpdate(item, VoucherType.Kot, guestBookId, this.updateTable);
|
||||
}
|
||||
|
||||
printBill(guest_book_id: string | null, voucherType: VoucherType): Observable<boolean> {
|
||||
|
@ -246,11 +249,16 @@ export class BillService {
|
|||
if (!this.happyHourItemsBalanced() || this.happyHourItemsMoreThanRegular()) {
|
||||
return throwError('Happy hour products are not balanced.');
|
||||
}
|
||||
return this.ser.saveOrUpdate(item, voucherType, guest_book_id, true);
|
||||
return this.ser.saveOrUpdate(item, voucherType, guest_book_id, this.updateTable);
|
||||
}
|
||||
|
||||
receivePayment(value: { choices: ReceivePaymentItem[]; reason: string }): Observable<boolean> {
|
||||
return this.ser.receivePayment(this.bill.id as string, value.choices, value.reason, true);
|
||||
return this.ser.receivePayment(
|
||||
this.bill.id as string,
|
||||
value.choices,
|
||||
value.reason,
|
||||
this.updateTable,
|
||||
);
|
||||
}
|
||||
|
||||
moveTable(table: Table): Observable<boolean> {
|
||||
|
@ -270,7 +278,7 @@ export class BillService {
|
|||
}
|
||||
|
||||
voidBill(reason: string): Observable<boolean> {
|
||||
return this.ser.voidBill(this.bill.id as string, reason, true);
|
||||
return this.ser.voidBill(this.bill.id as string, reason, this.updateTable);
|
||||
}
|
||||
|
||||
updateAmounts() {
|
||||
|
@ -327,7 +335,7 @@ export class BillService {
|
|||
const inventoriesToMove: string[] = this.selection.selected.map(
|
||||
(x: string) => (JSON.parse(x) as BillSelectionItem).inventoryId as string,
|
||||
);
|
||||
return this.ser.splitBill(this.bill.id as string, inventoriesToMove, table);
|
||||
return this.ser.splitBill(this.bill.id as string, inventoriesToMove, table, this.updateTable);
|
||||
}
|
||||
|
||||
private happyHourItemsBalanced(): boolean {
|
||||
|
|
|
@ -12,9 +12,16 @@ export class BillResolver implements Resolve<Bill> {
|
|||
constructor(private ser: VoucherService) {}
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot): Observable<Bill> {
|
||||
const tableId = route.queryParamMap.get('table') as string;
|
||||
const tableId = route.queryParamMap.get('table');
|
||||
const guestId = route.queryParamMap.get('guest');
|
||||
const voucherId = route.queryParamMap.get('voucher');
|
||||
return this.ser.getFromTable(tableId, voucherId, guestId);
|
||||
const billId = route.queryParamMap.get('bill');
|
||||
if (billId !== null) {
|
||||
return this.ser.getFromBill(billId);
|
||||
}
|
||||
if (tableId !== null) {
|
||||
return this.ser.getFromTable(tableId as string, voucherId, guestId);
|
||||
}
|
||||
throw new Error('Unable to get bill');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,8 +44,8 @@ export class BillsComponent implements OnInit {
|
|||
|
||||
ngOnInit() {
|
||||
this.route.data.subscribe((value) => {
|
||||
const data = value as { item: Bill };
|
||||
this.bs.loadData(data.item);
|
||||
const data = value as { item: Bill; updateTable: boolean };
|
||||
this.bs.loadData(data.item, data.updateTable);
|
||||
});
|
||||
this.getPax();
|
||||
this.dataSource = new BillsDataSource(this.bs.dataObs);
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class UpdateTableResolver implements Resolve<boolean> {
|
||||
constructor() {}
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot): Observable<boolean> {
|
||||
return route.queryParamMap.get('bill') !== null ? observableOf(false) : observableOf(true);
|
||||
}
|
||||
}
|
|
@ -50,6 +50,14 @@ export class VoucherService {
|
|||
) as Observable<Bill>;
|
||||
}
|
||||
|
||||
getFromBill(billId: string): Observable<Bill> {
|
||||
return this.http
|
||||
.get<Bill>(`${url}/from-bill/${billId}`)
|
||||
.pipe(
|
||||
catchError(this.log.handleError(serviceName, `getFromBill billId=${billId}`)),
|
||||
) as Observable<Bill>;
|
||||
}
|
||||
|
||||
save(
|
||||
voucher: Bill,
|
||||
voucherType: VoucherType,
|
||||
|
@ -179,8 +187,8 @@ export class VoucherService {
|
|||
) as Observable<boolean>;
|
||||
}
|
||||
|
||||
splitBill(id: string, inventoriesToMove: string[], table: Table) {
|
||||
const options = { params: new HttpParams().set('u', 'true') };
|
||||
splitBill(id: string, inventoriesToMove: string[], table: Table, updateTable: boolean) {
|
||||
const options = { params: new HttpParams().set('u', updateTable.toString()) };
|
||||
return this.http
|
||||
.post<boolean>(
|
||||
`${urlSplitBill}/${id}`,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, Inject } from '@angular/core';
|
||||
import {FormArray, FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms';
|
||||
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { round } from 'mathjs';
|
||||
import { Observable } from 'rxjs';
|
||||
|
@ -46,13 +46,12 @@ export class DiscountComponent {
|
|||
|
||||
accept(): void {
|
||||
const array = this.form.get('discounts') as FormArray;
|
||||
for (let i = this.list.length - 1; i >=0 ; i--) {
|
||||
for (let i = this.list.length - 1; i >= 0; i--) {
|
||||
const item = this.list[i];
|
||||
const control = (array.controls[i] as FormGroup).controls.discount as FormControl;
|
||||
if (control.pristine || control.value === null) {
|
||||
this.list.splice(i, 1);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
item.discount = Math.max(
|
||||
Math.min(round(array.controls[i].value.discount / 100, 5), item.discountLimit),
|
||||
0,
|
||||
|
|
|
@ -8,6 +8,11 @@
|
|||
color: #000000;
|
||||
}
|
||||
|
||||
.open-bill {
|
||||
background-color: #8e44ad;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.square-button {
|
||||
min-width: 150px;
|
||||
max-width: 150px;
|
||||
|
|
|
@ -19,5 +19,8 @@
|
|||
<span class="center" *ngIf="table.date">{{ table.date }}</span>
|
||||
<span class="center" *ngIf="table.amount">{{ table.amount | currency: 'INR' }}</span>
|
||||
</mat-card>
|
||||
<mat-card fxLayout="column" class="square-button open-bill" matRipple (click)="openBill()">
|
||||
<h3 class="item-name">Open Bill</h3>
|
||||
</mat-card>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { Table } from '../../core/table';
|
||||
import { ToasterService } from '../../core/toaster.service';
|
||||
import { BillNumberComponent } from '../bill-number/bill-number.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-running-tables',
|
||||
|
@ -11,7 +15,12 @@ import { Table } from '../../core/table';
|
|||
export class RunningTablesComponent implements OnInit {
|
||||
list: Table[] = [];
|
||||
|
||||
constructor(private router: Router, private route: ActivatedRoute) {}
|
||||
constructor(
|
||||
private dialog: MatDialog,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private toaster: ToasterService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.data.subscribe((value) => {
|
||||
|
@ -33,4 +42,22 @@ export class RunningTablesComponent implements OnInit {
|
|||
};
|
||||
this.router.navigate(['/sales', 'bill'], navigationExtras);
|
||||
}
|
||||
|
||||
openBill() {
|
||||
return this.dialog
|
||||
.open(BillNumberComponent)
|
||||
.afterClosed()
|
||||
.pipe(
|
||||
map((x) => {
|
||||
if (x === undefined || x === false) {
|
||||
throw new Error('Invalid Bill Number');
|
||||
}
|
||||
return x;
|
||||
}),
|
||||
)
|
||||
.subscribe(
|
||||
(x) => this.router.navigate(['/sales', 'bill'], { queryParams: { bill: x } }),
|
||||
(x) => this.toaster.show('Error', x),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { AuthGuard } from '../auth/auth-guard.service';
|
|||
|
||||
import { BillResolver } from './bills/bill-resolver.service';
|
||||
import { BillsComponent } from './bills/bills.component';
|
||||
import { UpdateTableResolver } from './bills/update-table-resolver.service';
|
||||
import { SalesHomeComponent } from './home/sales-home.component';
|
||||
import { MenuCategoriesResolver } from './menu-categories/menu-categories-resolver.service';
|
||||
import { MenuCategoriesComponent } from './menu-categories/menu-categories.component';
|
||||
|
@ -46,6 +47,7 @@ const routes: Routes = [
|
|||
},
|
||||
resolve: {
|
||||
item: BillResolver,
|
||||
updateTable: UpdateTableResolver,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
|
|
|
@ -8,17 +8,19 @@ import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
|||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatChipsModule } from '@angular/material/chips';
|
||||
import { MatRippleModule } from '@angular/material/core';
|
||||
import { MatOptionModule, MatRippleModule } from '@angular/material/core';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
import { BillNumberComponent } from './bill-number/bill-number.component';
|
||||
import { BillTypeComponent } from './bill-type/bill-type.component';
|
||||
import { BillService } from './bill.service';
|
||||
import { BillsComponent } from './bills/bills.component';
|
||||
|
@ -39,6 +41,7 @@ import { TablesDialogComponent } from './tables-dialog/tables-dialog.component';
|
|||
providers: [BillService],
|
||||
declarations: [
|
||||
BillsComponent,
|
||||
BillNumberComponent,
|
||||
BillTypeComponent,
|
||||
DiscountComponent,
|
||||
MenuCategoriesComponent,
|
||||
|
@ -64,6 +67,7 @@ import { TablesDialogComponent } from './tables-dialog/tables-dialog.component';
|
|||
MatChipsModule,
|
||||
MatDialogModule,
|
||||
MatDividerModule,
|
||||
MatOptionModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatRippleModule,
|
||||
|
@ -72,6 +76,7 @@ import { TablesDialogComponent } from './tables-dialog/tables-dialog.component';
|
|||
MatTooltipModule,
|
||||
SharedModule,
|
||||
SalesRoutingModule,
|
||||
MatSelectModule,
|
||||
],
|
||||
})
|
||||
export class SalesModule {}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
.running {
|
||||
/* Red 900 */
|
||||
background-color: #b71c1c;
|
||||
color: #ffffff;
|
||||
background-color: #f8cbad;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.printed {
|
||||
/* Green 900 */
|
||||
background-color: #1b5e20;
|
||||
color: #ffffff;
|
||||
background-color: #c6e0b4;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.square-button {
|
||||
|
|
Loading…
Reference in New Issue