Split bill working along with all checks.

Update bill ensures that the total number of happy hour punches of a product in a bill are less than or equal to the regular punches
This commit is contained in:
2020-12-18 13:24:05 +05:30
parent 608dde4619
commit f28cf1eea0
5 changed files with 158 additions and 61 deletions

View File

@ -1,5 +1,6 @@
import uuid import uuid
from decimal import Decimal
from typing import List, Optional from typing import List, Optional
import barker.schemas.voucher as schemas import barker.schemas.voucher as schemas
@ -120,15 +121,32 @@ def do_update_settlements(voucher: Voucher, others: List[SettleSchema], db: Sess
db.delete(i) db.delete(i)
def happy_hour_items_balanced(inventories: [schemas.Inventory]): def happy_hour_items_balanced(inventories: List[schemas.Inventory]) -> bool:
happy = set((i.product.id_, i.quantity) for i in inventories if i.is_happy_hour) happy = set((i.product.id_, i.quantity) for i in inventories if i.is_happy_hour)
other = set( products = set(i.product.id_ for i in inventories if i.is_happy_hour)
(i.product.id_, i.quantity) for i in inventories if not i.is_happy_hour and (i.product.id_, i.quantity) in happy other = set((i.product.id_, i.quantity) for i in inventories if not i.is_happy_hour and i.product.id_ in products)
)
return happy == other return happy == other
def happy_hour_has_discount(inventories: [schemas.Inventory]): def happy_hour_has_discount(inventories: List[schemas.Inventory]) -> bool:
happy = set(i.product.id_ for i in inventories if i.is_happy_hour) happy = set(i.product.id_ for i in inventories if i.is_happy_hour)
offenders = [i for i in inventories if i.product.id_ in happy and i.discount != 0] offenders = [i for i in inventories if i.product.id_ in happy and i.discount != 0]
return len(offenders) > 0 return len(offenders) > 0
# This is for the whole bill. eg. Kot 1 => Reg 2 + HH 2; Kot 2 => Reg 4; Kot 3 => Reg - 4
# This is pass okay in happy hours items balanced, but overall this is wrong. Hence this check
def happy_hour_items_more_than_regular(kots: List[schemas.Kot]) -> bool:
inventories = {}
for kot in kots:
for inventory in kot.inventories:
if inventory.product.id_ not in inventories:
inventories[inventory.product.id_] = {"normal": Decimal(0), "happy": Decimal(0)}
if inventory.is_happy_hour:
inventories[inventory.product.id_]["happy"] += inventory.quantity
else:
inventories[inventory.product.id_]["normal"] += inventory.quantity
for value in inventories.values():
if value["happy"] > value["normal"]:
return True
return False

View File

@ -1,6 +1,8 @@
import uuid import uuid
from collections import defaultdict
from datetime import datetime from datetime import datetime
from decimal import Decimal
from typing import List, Optional from typing import List, Optional
import barker.schemas.split as schemas import barker.schemas.split as schemas
@ -49,6 +51,7 @@ def split(
update_table = u update_table = u
item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first()
item.bill_id = None item.bill_id = None
original_voucher_type = item.voucher_type
item.voucher_type = VoucherType.VOID item.voucher_type = VoucherType.VOID
item.reason = "Bill Split" item.reason = "Bill Split"
do_update_settlements(item, [SettleSchema(id=SettleOption.VOID(), amount=round(item.amount))], db) do_update_settlements(item, [SettleSchema(id=SettleOption.VOID(), amount=round(item.amount))], db)
@ -62,7 +65,7 @@ def split(
one = save( one = save(
one_inventories, one_inventories,
now, now,
item.voucher_type, original_voucher_type,
0, 0,
data.table_id, data.table_id,
item.customer_id, item.customer_id,
@ -74,7 +77,7 @@ def split(
two = save( two = save(
two_inventories, two_inventories,
now, now,
item.voucher_type, original_voucher_type,
item.pax, item.pax,
item.food_table_id, item.food_table_id,
item.customer_id, item.customer_id,
@ -105,52 +108,97 @@ def save(
user_id: uuid.UUID, user_id: uuid.UUID,
db: Session, db: Session,
): ):
product_quantities = {} if not are_product_quantities_positive(inventories):
for i in inventories: raise HTTPException(
if (i.product_id, i.is_happy_hour) in product_quantities: status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
product_quantities[(i.product_id, i.is_happy_hour)][1] += i.quantity detail="Quantity of a product is negative",
else: )
product_quantities[(i.product_id, i.is_happy_hour)] = (i, i.quantity) if len(inventories) == 0:
for (product, happy_hour), (inventory, quantity) in product_quantities.items(): raise HTTPException(
if quantity < 0: status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
raise HTTPException( detail="No inventories selected",
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, )
detail="Quantity of a product is negative", if happy_hour_items_more_than_regular(inventories):
) raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="When product has happy hours\n"
"Minimum same number of regular items also needed in the whole bill.",
)
bill_id = get_bill_id(voucher_type, db) bill_id = get_bill_id(voucher_type, db)
kot_id = db.query(func.coalesce(func.max(Voucher.kot_id), 0) + 1).scalar() kot_id = db.query(func.coalesce(func.max(Voucher.kot_id), 0) + 1).scalar()
item: Voucher = Voucher(now, pax, bill_id, kot_id, table_id, customer_id, voucher_type, user_id) item: Voucher = Voucher(now, pax, bill_id, kot_id, table_id, customer_id, voucher_type, user_id)
db.add(item) db.add(item)
code = db.query(func.coalesce(func.max(Kot.code), 0) + 1).scalar() for split_inventories in split_into_kots(inventories):
kot = Kot(item.id, code, item.food_table_id, item.date, item.user_id) if not happy_hour_items_balanced(split_inventories):
item.kots.append(kot) raise HTTPException(
db.add(kot) # Multiple inventories of the same product give a key error, but combining messes with modifiers, this status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
# will get important when modifiers have a price. detail="Happy hour products are not balanced.",
for index, (old_inventory, q) in enumerate([i for i in product_quantities.values() if i[1] != 0]): )
inv = Inventory( code = db.query(func.coalesce(func.max(Kot.code), 0) + 1).scalar()
kot.id, kot = Kot(item.id, code, item.food_table_id, item.date, item.user_id)
old_inventory.product_id, item.kots.append(kot)
q, db.add(kot)
old_inventory.price, db.flush()
old_inventory.discount, for old_inventory in split_inventories:
old_inventory.is_happy_hour, inv = Inventory(
old_inventory.tax_id, kot.id,
old_inventory.tax_rate, old_inventory.product_id,
index, old_inventory.quantity,
) old_inventory.price,
kot.inventories.append(inv) old_inventory.discount,
db.add(inv) old_inventory.is_happy_hour,
for m in old_inventory.modifiers: old_inventory.tax_id,
mod = InventoryModifier(None, m.modifier_id, m.price) old_inventory.tax_rate,
inv.modifiers.append(mod) old_inventory.sort_order,
db.add(mod) )
kot.inventories.append(inv)
db.add(inv)
for m in old_inventory.modifiers:
mod = InventoryModifier(None, m.modifier_id, m.price)
inv.modifiers.append(mod)
db.add(mod)
db.flush()
do_update_settlements(item, [], db) do_update_settlements(item, [], db)
if len(kot.inventories) == 0:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="No inventories selected",
)
db.flush() db.flush()
return item return item
def split_into_kots(inventories: List[Inventory]) -> list:
kots = defaultdict(list)
for item in inventories:
kots[item.kot_id].append(item)
return [k for k in kots.values() if len(k) > 0]
def happy_hour_items_balanced(inventories: List[Inventory]) -> bool:
happy = set((i.product_id, i.quantity) for i in inventories if i.is_happy_hour)
products = set(i.product_id for i in inventories if i.is_happy_hour)
other = set((i.product_id, i.quantity) for i in inventories if not i.is_happy_hour and i.product_id in products)
return happy == other
def are_product_quantities_positive(inventories: List[Inventory]) -> bool:
quantities = defaultdict(Decimal)
for i in inventories:
key = (i.product_id, i.is_happy_hour)
quantities[key] += i.quantity
for i in quantities.values():
if i < 0:
return False
return True
def happy_hour_items_more_than_regular(invs: List[Inventory]) -> bool:
inventories = {}
for inventory in invs:
if inventory.product_id not in inventories:
inventories[inventory.product_id] = {"normal": Decimal(0), "happy": Decimal(0)}
if inventory.is_happy_hour:
inventories[inventory.product_id]["happy"] += inventory.quantity
else:
inventories[inventory.product_id]["normal"] += inventory.quantity
for value in inventories.values():
if value["happy"] > value["normal"]:
return True
return False

View File

@ -26,6 +26,7 @@ from ...routers.voucher import (
get_tax, get_tax,
happy_hour_has_discount, happy_hour_has_discount,
happy_hour_items_balanced, happy_hour_items_balanced,
happy_hour_items_more_than_regular,
) )
from ...schemas.auth import UserToken from ...schemas.auth import UserToken
@ -80,6 +81,12 @@ def update(
item.voucher_type = voucher_type item.voucher_type = voucher_type
item.user_id = user.id_ item.user_id = user.id_
item.last_edit_date = now item.last_edit_date = now
if happy_hour_items_more_than_regular(data.kots):
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="When product has happy hours\n"
"Minimum same number of regular items also needed in the whole bill.",
)
for k in item.kots: for k in item.kots:
for i in k.inventories: for i in k.inventories:
i.tax_rate = get_tax(i.tax_rate, voucher_type) i.tax_rate = get_tax(i.tax_rate, voucher_type)

View File

@ -230,7 +230,7 @@ export class BillService {
if (newKot.inventories.length === 0) { if (newKot.inventories.length === 0) {
return throwError('Cannot print a blank KOT\nPlease add some products!'); return throwError('Cannot print a blank KOT\nPlease add some products!');
} }
if (!this.happyHourItemsBalanced()) { if (!this.happyHourItemsBalanced() || this.happyHourItemsMoreThanRegular()) {
return throwError('Happy hour products are not balanced.'); 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, true);
@ -242,7 +242,7 @@ export class BillService {
if (item.kots.length === 1 && newKot.inventories.length === 0) { if (item.kots.length === 1 && newKot.inventories.length === 0) {
return throwError('Cannot print a blank Bill\nPlease add some products!'); return throwError('Cannot print a blank Bill\nPlease add some products!');
} }
if (!this.happyHourItemsBalanced()) { if (!this.happyHourItemsBalanced() || this.happyHourItemsMoreThanRegular()) {
return throwError('Happy hour products are not balanced.'); 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, true);
@ -334,18 +334,42 @@ export class BillService {
} }
private happyHourItemsBalanced(): boolean { private happyHourItemsBalanced(): boolean {
const newKot = this.bill.kots.find((k) => k.id === undefined) as Kot; for (const kot of this.bill.kots) {
const happyHourItems = newKot.inventories const happyHourItems = kot.inventories
.filter((x) => x.isHappyHour) .filter((x) => x.isHappyHour)
.map((x) => ({ id: x.product.id as string, quantity: x.quantity })); .map((x) => ({ id: x.product.id as string, quantity: x.quantity }));
for (const item of happyHourItems) { for (const item of happyHourItems) {
const q = newKot.inventories.find( const q = kot.inventories.find(
(x) => !x.isHappyHour && x.product.id === item.id && x.quantity === item.quantity, (x) => !x.isHappyHour && x.product.id === item.id && x.quantity === item.quantity,
); );
if (q === undefined) { if (q === undefined) {
return false; return false;
}
} }
} }
return true; return true;
} }
private happyHourItemsMoreThanRegular(): boolean {
const invs: { [id: string]: { normal: number; happy: number } } = {};
for (const kot of this.bill.kots) {
for (const inventory of kot.inventories) {
const pid = inventory.product.id as string;
if (invs[pid] === undefined) {
invs[pid] = { normal: 0, happy: 0 };
}
if (inventory.isHappyHour) {
invs[pid].happy += inventory.quantity;
} else {
invs[pid].normal += inventory.quantity;
}
}
}
for (const [, value] of Object.entries(invs)) {
if (value.happy > value.normal) {
return true;
}
}
return false;
}
} }

View File

@ -126,7 +126,7 @@
<button <button
mat-icon-button mat-icon-button
(click)="moveKot(row)" (click)="moveKot(row)"
[disabled]="row.isKot && !row.id" [disabled]="row.isKot && !row.kotId"
*ngIf="row.isKot" *ngIf="row.isKot"
> >
<mat-icon class="del">open_in_new</mat-icon> <mat-icon class="del">open_in_new</mat-icon>