Feature: Open bill using bill number

This commit is contained in:
Amritanshu Agrawal 2020-12-24 12:58:46 +05:30
parent 98c75f66c9
commit 161896154d
17 changed files with 234 additions and 29 deletions

View File

@ -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)

View File

@ -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>

View File

@ -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();
});
});

View File

@ -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);
}
}
}

View File

@ -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 {

View File

@ -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');
}
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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}`,

View File

@ -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,

View File

@ -8,6 +8,11 @@
color: #000000;
}
.open-bill {
background-color: #8e44ad;
color: #000000;
}
.square-button {
min-width: 150px;
max-width: 150px;

View File

@ -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>

View File

@ -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),
);
}
}

View File

@ -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: [
{

View File

@ -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 {}

View File

@ -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 {