Void Bill Working
This commit is contained in:
parent
2fcff26e34
commit
40a958a935
|
@ -54,7 +54,6 @@ def receive_payment(request):
|
|||
Overview.voucher_id == item.id
|
||||
).delete()
|
||||
transaction.commit()
|
||||
item = request.dbsession.query(Voucher).filter(Voucher.id == item.id).first()
|
||||
return voucher_info(item)
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
@ -3,46 +3,55 @@ import uuid
|
|||
import transaction
|
||||
from pyramid.view import view_config
|
||||
|
||||
from barker.models import Voucher, SettleOption, Settlement
|
||||
from barker.views.voucher import get_settlements
|
||||
from barker.views.voucher.show import voucher_info
|
||||
from barker.models import Voucher, SettleOption, Settlement, Overview
|
||||
|
||||
|
||||
@view_config(
|
||||
request_method="POST",
|
||||
route_name="voucher_void",
|
||||
route_name="v1_vouchers_id",
|
||||
renderer="json",
|
||||
request_param="v",
|
||||
permission="Void Bill",
|
||||
trans=True,
|
||||
)
|
||||
def void_voucher(request):
|
||||
update_table = request.GET["u"]
|
||||
json = request.json_body
|
||||
id_ = uuid.UUID(request.matchdict["id"])
|
||||
reason = request.GET["v"]
|
||||
update_table = request.GET["u"]
|
||||
|
||||
item = request.dbsession.query(Voucher).filter(Voucher.id == id_).first()
|
||||
|
||||
item.void = True
|
||||
item.void_reason = json["Reason"]
|
||||
item.void_reason = reason
|
||||
|
||||
amount = round(-1 * sum(i.amount for k in item.kots for i in k.inventories), 5) * -1
|
||||
s = Settlement(item.id, SettleOption.VOID(), amount)
|
||||
item.settlements.append(s)
|
||||
request.dbsession.add(s)
|
||||
allowed = [
|
||||
SettleOption.AMOUNT(),
|
||||
SettleOption.ROUND_OFF(),
|
||||
SettleOption.UNSETTLED(),
|
||||
SettleOption.VOID(),
|
||||
]
|
||||
for i in (s for s in item.settlements if s.settled not in allowed):
|
||||
settlements = []
|
||||
total_amount = item.amount
|
||||
settlements.append({"id": SettleOption.AMOUNT(), "amount": -total_amount})
|
||||
round_off = round(total_amount) - total_amount
|
||||
if round_off != 0:
|
||||
settlements.append({"id": SettleOption.ROUND_OFF(), "amount": -round_off})
|
||||
settlements.append({"id": SettleOption.VOID(), "amount": round(total_amount)})
|
||||
|
||||
for i in settlements:
|
||||
amount = i["amount"]
|
||||
settlement_type_id = i["id"]
|
||||
old = [s for s in item.settlements if s.settled == settlement_type_id]
|
||||
if len(old) == 1:
|
||||
old[0].amount = amount
|
||||
else:
|
||||
s = Settlement(item.id, settlement_type_id, amount)
|
||||
item.settlements.append(s)
|
||||
request.dbsession.add(s)
|
||||
|
||||
for i in (i for i in item.settlements if i.settled not in [x["id"] for x in settlements]):
|
||||
item.settlements.remove(i)
|
||||
request.dbsession.delete(i)
|
||||
get_settlements(item, request.dbsession)
|
||||
|
||||
if update_table:
|
||||
request.dbsession.delete(item.status)
|
||||
request.dbsession.query(Overview).filter(
|
||||
Overview.voucher_id == item.id
|
||||
).delete()
|
||||
transaction.commit()
|
||||
item = request.dbsession.query(Voucher).filter(Voucher.id == item.id).first()
|
||||
return voucher_info(item)
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import { VoucherService } from './bills/voucher.service';
|
|||
import { ToasterService } from "../core/toaster.service";
|
||||
import { tap } from "rxjs/operators";
|
||||
import { Table } from "../core/table";
|
||||
import {observableToBeFn} from "rxjs/internal/testing/TestScheduler";
|
||||
|
||||
@Injectable()
|
||||
export class BillService {
|
||||
|
@ -192,7 +193,7 @@ export class BillService {
|
|||
return this.bill.voucherType;
|
||||
}
|
||||
|
||||
receivePayment(amounts: { id: string; name: string; amount: number }[]) {
|
||||
receivePayment(amounts: { id: string; name: string; amount: number }[]): Observable<boolean> {
|
||||
return this.ser.receivePayment(this.bill.id, amounts, true).pipe(
|
||||
tap(x => console.log(x))
|
||||
);
|
||||
|
@ -202,6 +203,10 @@ export class BillService {
|
|||
return this.ser.moveTable(this.bill.id, table);
|
||||
}
|
||||
|
||||
voidBill(reason: string): Observable<boolean> {
|
||||
return this.ser.voidBill(this.bill.id, reason, true);
|
||||
}
|
||||
|
||||
netAmount() {
|
||||
return math.round(this.bill.kots.reduce(
|
||||
(ka: number, k: Kot) => ka + k.inventories.reduce(
|
||||
|
|
|
@ -5,6 +5,7 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
|||
import { ErrorLoggerService } from '../../core/error-logger.service';
|
||||
import { Bill, PrintType } from './bill';
|
||||
import { Table } from "../../core/table";
|
||||
import {ObjectUnsubscribedError} from "rxjs";
|
||||
|
||||
const httpOptions = {
|
||||
headers: new HttpHeaders({'Content-Type': 'application/json'})
|
||||
|
@ -77,9 +78,9 @@ export class VoucherService {
|
|||
}
|
||||
}
|
||||
|
||||
receivePayment(id: string, amounts: { id: string; name: string; amount: number }[], updateTable: boolean): Observable<Bill> {
|
||||
receivePayment(id: string, amounts: { id: string; name: string; amount: number }[], updateTable: boolean): Observable<boolean> {
|
||||
const options = {params: new HttpParams().set('r', "").set('u', updateTable.toString())};
|
||||
return <Observable<Bill>>this.http.post<Bill>(`${url}/${id}`, amounts, options)
|
||||
return <Observable<boolean>>this.http.post<boolean>(`${url}/${id}`, amounts, options)
|
||||
.pipe(
|
||||
catchError(this.log.handleError(serviceName, 'receivePayment'))
|
||||
);
|
||||
|
@ -92,4 +93,12 @@ export class VoucherService {
|
|||
catchError(this.log.handleError(serviceName, 'moveTable'))
|
||||
);
|
||||
}
|
||||
|
||||
voidBill(id: string, reason: string, updateTable: boolean): Observable<boolean> {
|
||||
const options = {params: new HttpParams().set('v', reason).set('u', updateTable.toString())};
|
||||
return <Observable<boolean>>this.http.post<boolean>(`${url}/${id}`, {}, options)
|
||||
.pipe(
|
||||
catchError(this.log.handleError(serviceName, 'voidBill'))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<mat-card fxLayout="column" class="square-button" matRipple (click)="moveTable()">
|
||||
<h3 class="item-name">Move Table</h3>
|
||||
</mat-card>
|
||||
<mat-card fxLayout="column" class="square-button" matRipple [routerLink]="['../../tables']">
|
||||
<mat-card fxLayout="column" class="square-button" matRipple (click)="voidBill()">
|
||||
<h3 class="item-name">Void Bill</h3>
|
||||
</mat-card>
|
||||
<mat-card fxLayout="column" class="square-button" matRipple [routerLink]="['../../tables']">
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
|||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { MatDialog } from "@angular/material";
|
||||
import { concatMap, map, switchMap, tap } from "rxjs/operators";
|
||||
import { iif, Observable, of as observableOf, throwError} from "rxjs";
|
||||
import { iif, Observable, of as observableOf, throwError } from "rxjs";
|
||||
import { BillService } from '../bill.service';
|
||||
import { ToasterService } from "../../core/toaster.service";
|
||||
import { DiscountComponent } from "../discount/discount.component";
|
||||
|
@ -15,6 +15,7 @@ import { TableService } from "../../tables/table.service";
|
|||
import { Table } from "../../core/table";
|
||||
import { TablesDialogComponent } from "../tables-dialog/tables-dialog.component";
|
||||
import { ConfirmDialogComponent } from "../../shared/confirm-dialog/confirm-dialog.component";
|
||||
import { VoidReasonComponent } from "../void-reason/void-reason.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-sales-home',
|
||||
|
@ -97,6 +98,15 @@ export class SalesHomeComponent implements OnInit {
|
|||
);
|
||||
}
|
||||
|
||||
confirmVoidDialog(reason: string): Observable<boolean | string> {
|
||||
return this.dialog.open(ConfirmDialogComponent, {
|
||||
width: '250px',
|
||||
data: {title: 'Void Bill?', content: 'Are you sure?'}
|
||||
}).afterClosed().pipe(
|
||||
map((x: boolean) => x ? reason : x)
|
||||
);
|
||||
}
|
||||
|
||||
printBill() {
|
||||
const canGiveDiscount = this.auth.hasPermission("Discount");
|
||||
let guestBookId = null;
|
||||
|
@ -143,19 +153,18 @@ export class SalesHomeComponent implements OnInit {
|
|||
}
|
||||
|
||||
moveTable() {
|
||||
const dialogRef = this.dialog.open(TablesDialogComponent, {
|
||||
this.dialog.open(TablesDialogComponent, {
|
||||
// width: '750px',
|
||||
data: {
|
||||
list: this.tSer.running(),
|
||||
canChooseRunning: false
|
||||
}
|
||||
});
|
||||
dialogRef.afterClosed().pipe(
|
||||
}).afterClosed().pipe(
|
||||
switchMap((x: boolean | Table) => {
|
||||
if (!!x) {
|
||||
return this.confirmMoveTableDialog(x as Table)
|
||||
return this.confirmMoveTableDialog(x as Table);
|
||||
} else {
|
||||
return throwError("Please choose a table")
|
||||
return throwError("Please choose a table");
|
||||
}
|
||||
}),
|
||||
switchMap((value: { table: Table, confirmed: boolean }, index: number) => {
|
||||
|
@ -176,6 +185,29 @@ export class SalesHomeComponent implements OnInit {
|
|||
}
|
||||
|
||||
voidBill() {
|
||||
|
||||
this.dialog.open(VoidReasonComponent, {
|
||||
// width: '750px'
|
||||
}).afterClosed().pipe(
|
||||
switchMap((x: boolean | string) => {
|
||||
if (!!x) {
|
||||
return this.confirmVoidDialog(x as string);
|
||||
} else {
|
||||
return throwError("Please choose a reason to void the bill");
|
||||
}
|
||||
}),
|
||||
switchMap((x: boolean | string) => {
|
||||
if (!!x) {
|
||||
return this.bs.voidBill(x as string)
|
||||
} else {
|
||||
return throwError("You chose not to void the bill")
|
||||
}
|
||||
})
|
||||
).subscribe((x) => {
|
||||
this.toaster.show('Success', '');
|
||||
this.router.navigate(['/sales']);
|
||||
},
|
||||
x => {
|
||||
this.toaster.show('Error', x)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,3 +5,7 @@
|
|||
cursor: pointer;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: #dddddd;
|
||||
}
|
||||
|
|
|
@ -7,11 +7,12 @@
|
|||
<mat-chip *ngIf="!!item.maximum">{{item.minimum}} - {{item.maximum}}</mat-chip>
|
||||
</mat-chip-list>
|
||||
</ng-template>
|
||||
<mat-button-toggle-group multiple>
|
||||
<mat-button-toggle mat-raised-button class="square-button" *ngFor="let m of item.modifiers" (click)="select(m)" [checked]="selectedIds.indexOf(m.id) !== -1">
|
||||
{{m.name}}
|
||||
</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
<div fxLayout="row wrap" fxLayoutGap="grid 20px">
|
||||
<mat-card fxLayout="column" class="square-button" matRipple *ngFor="let m of item.modifiers" (click)="select(m)"
|
||||
[class.selected]="selectedIds.indexOf(m.id) !== -1">
|
||||
<h3 class="item-name">{{m.name}}</h3>
|
||||
</mat-card>
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
<mat-dialog-actions align="end">
|
||||
|
|
|
@ -15,6 +15,11 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.warn {
|
||||
background-color: red;
|
||||
.yellow300 {
|
||||
background-color: #fff176;
|
||||
}
|
||||
|
||||
.red700 {
|
||||
background-color: #d32f2f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<mat-card>
|
||||
<mat-card-content fxLayout="row wrap" fxLayoutGap="grid 20px">
|
||||
<mat-card fxLayout="column" class="square-button" matRipple *ngFor="let item of list" (click)="addProduct(item)">
|
||||
<mat-card fxLayout="column" class="square-button" matRipple *ngFor="let item of list" (click)="addProduct(item)" [class.yellow300]="item.hasHappyHour">
|
||||
<h3 class="item-name">{{item.name}}</h3>
|
||||
</mat-card>
|
||||
<mat-card fxLayout="column" class="square-button warn" matRipple [routerLink]="['../../menu-categories']"
|
||||
<mat-card fxLayout="column" class="square-button red700" matRipple [routerLink]="['../../menu-categories']"
|
||||
queryParamsHandling="preserve">
|
||||
<h3 class="item-name">Back</h3>
|
||||
</mat-card>
|
||||
|
|
|
@ -29,6 +29,7 @@ import { DiscountComponent } from "./discount/discount.component";
|
|||
import { BillTypeComponent } from "./bill-type/bill-type.component";
|
||||
import { ReceivePaymentComponent } from "./receive-payment/receive-payment.component";
|
||||
import { TablesDialogComponent } from "./tables-dialog/tables-dialog.component";
|
||||
import { VoidReasonComponent } from "./void-reason/void-reason.component";
|
||||
|
||||
@NgModule({
|
||||
providers: [
|
||||
|
@ -45,7 +46,8 @@ import { TablesDialogComponent } from "./tables-dialog/tables-dialog.component";
|
|||
ReceivePaymentComponent,
|
||||
RunningTablesComponent,
|
||||
SalesHomeComponent,
|
||||
TablesDialogComponent
|
||||
TablesDialogComponent,
|
||||
VoidReasonComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
@ -72,7 +74,8 @@ import { TablesDialogComponent } from "./tables-dialog/tables-dialog.component";
|
|||
ModifiersComponent,
|
||||
QuantityComponent,
|
||||
ReceivePaymentComponent,
|
||||
TablesDialogComponent
|
||||
TablesDialogComponent,
|
||||
VoidReasonComponent
|
||||
]
|
||||
})
|
||||
export class SalesModule { }
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: #dddddd;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import { DataSource } from '@angular/cdk/collections';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
|
||||
export class VoidReasonDatasource extends DataSource<string> {
|
||||
|
||||
constructor(private data: string[]) {
|
||||
super();
|
||||
}
|
||||
|
||||
connect(): Observable<string[]> {
|
||||
return observableOf(this.data);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
.selected {
|
||||
background-color: #dddddd;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<h2 mat-dialog-title>Reason</h2>
|
||||
<mat-dialog-content>
|
||||
<mat-table [dataSource]="dataSource">
|
||||
|
||||
<!-- Name Column -->
|
||||
<ng-container matColumnDef="reason">
|
||||
<mat-cell *matCellDef="let row" (click)="select(row)">{{row}}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns;" [class.selected]="row === selected"></mat-row>
|
||||
</mat-table>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button [mat-dialog-close]="false">Cancel</button>
|
||||
<button mat-button (click)="accept()" color="primary" [disabled]="!selected">Ok</button>
|
||||
</mat-dialog-actions>
|
|
@ -0,0 +1,25 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { VoidReasonComponent } from './void-reason.component';
|
||||
|
||||
describe('VoidReasonComponent', () => {
|
||||
let component: VoidReasonComponent;
|
||||
let fixture: ComponentFixture<VoidReasonComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ VoidReasonComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(VoidReasonComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { MatDialogRef } from '@angular/material';
|
||||
import { VoidReasonDatasource } from "./void-reason-datasource";
|
||||
|
||||
@Component({
|
||||
selector: 'app-void-reason',
|
||||
templateUrl: './void-reason.component.html',
|
||||
styleUrls: ['./void-reason.component.css']
|
||||
})
|
||||
export class VoidReasonComponent {
|
||||
dataSource: VoidReasonDatasource;
|
||||
selected: string;
|
||||
reasons = [
|
||||
"Discount",
|
||||
"Printing fault",
|
||||
"Item changed",
|
||||
"Quantity reduced",
|
||||
"Costing bill for party",
|
||||
"Cashier mistake",
|
||||
"Management free sale",
|
||||
"Other"
|
||||
];
|
||||
displayedColumns = ['reason'];
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<VoidReasonComponent>
|
||||
) {
|
||||
this.dataSource = new VoidReasonDatasource(this.reasons);
|
||||
}
|
||||
|
||||
select(reason: string) {
|
||||
this.selected = reason;
|
||||
}
|
||||
|
||||
accept(): void {
|
||||
this.dialogRef.close(this.selected);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue