From f28cf1eea0717fd8977ef113983c7de68d921939 Mon Sep 17 00:00:00 2001 From: tanshu Date: Fri, 18 Dec 2020 13:24:05 +0530 Subject: [PATCH] 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 --- barker/barker/routers/voucher/__init__.py | 28 +++- barker/barker/routers/voucher/split.py | 134 ++++++++++++------ barker/barker/routers/voucher/update.py | 7 + bookie/src/app/sales/bill.service.ts | 48 +++++-- .../src/app/sales/bills/bills.component.html | 2 +- 5 files changed, 158 insertions(+), 61 deletions(-) diff --git a/barker/barker/routers/voucher/__init__.py b/barker/barker/routers/voucher/__init__.py index 3379d69..847babc 100644 --- a/barker/barker/routers/voucher/__init__.py +++ b/barker/barker/routers/voucher/__init__.py @@ -1,5 +1,6 @@ import uuid +from decimal import Decimal from typing import List, Optional import barker.schemas.voucher as schemas @@ -120,15 +121,32 @@ def do_update_settlements(voucher: Voucher, others: List[SettleSchema], db: Sess 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) - other = set( - (i.product.id_, i.quantity) for i in inventories if not i.is_happy_hour and (i.product.id_, i.quantity) in happy - ) + 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 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) offenders = [i for i in inventories if i.product.id_ in happy and i.discount != 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 diff --git a/barker/barker/routers/voucher/split.py b/barker/barker/routers/voucher/split.py index 60c92d8..cb14d9f 100644 --- a/barker/barker/routers/voucher/split.py +++ b/barker/barker/routers/voucher/split.py @@ -1,6 +1,8 @@ import uuid +from collections import defaultdict from datetime import datetime +from decimal import Decimal from typing import List, Optional import barker.schemas.split as schemas @@ -49,6 +51,7 @@ def split( update_table = u item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() item.bill_id = None + original_voucher_type = item.voucher_type item.voucher_type = VoucherType.VOID item.reason = "Bill Split" do_update_settlements(item, [SettleSchema(id=SettleOption.VOID(), amount=round(item.amount))], db) @@ -62,7 +65,7 @@ def split( one = save( one_inventories, now, - item.voucher_type, + original_voucher_type, 0, data.table_id, item.customer_id, @@ -74,7 +77,7 @@ def split( two = save( two_inventories, now, - item.voucher_type, + original_voucher_type, item.pax, item.food_table_id, item.customer_id, @@ -105,52 +108,97 @@ def save( user_id: uuid.UUID, db: Session, ): - product_quantities = {} - for i in inventories: - if (i.product_id, i.is_happy_hour) in product_quantities: - product_quantities[(i.product_id, i.is_happy_hour)][1] += i.quantity - else: - product_quantities[(i.product_id, i.is_happy_hour)] = (i, i.quantity) - for (product, happy_hour), (inventory, quantity) in product_quantities.items(): - if quantity < 0: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="Quantity of a product is negative", - ) + if not are_product_quantities_positive(inventories): + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="Quantity of a product is negative", + ) + if len(inventories) == 0: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="No inventories selected", + ) + 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) 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) db.add(item) - code = db.query(func.coalesce(func.max(Kot.code), 0) + 1).scalar() - kot = Kot(item.id, code, item.food_table_id, item.date, item.user_id) - item.kots.append(kot) - db.add(kot) # Multiple inventories of the same product give a key error, but combining messes with modifiers, this - # will get important when modifiers have a price. - for index, (old_inventory, q) in enumerate([i for i in product_quantities.values() if i[1] != 0]): - inv = Inventory( - kot.id, - old_inventory.product_id, - q, - old_inventory.price, - old_inventory.discount, - old_inventory.is_happy_hour, - old_inventory.tax_id, - old_inventory.tax_rate, - index, - ) - 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) + for split_inventories in split_into_kots(inventories): + if not happy_hour_items_balanced(split_inventories): + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="Happy hour products are not balanced.", + ) + code = db.query(func.coalesce(func.max(Kot.code), 0) + 1).scalar() + kot = Kot(item.id, code, item.food_table_id, item.date, item.user_id) + item.kots.append(kot) + db.add(kot) + db.flush() + for old_inventory in split_inventories: + inv = Inventory( + kot.id, + old_inventory.product_id, + old_inventory.quantity, + old_inventory.price, + old_inventory.discount, + old_inventory.is_happy_hour, + old_inventory.tax_id, + old_inventory.tax_rate, + old_inventory.sort_order, + ) + 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) - - if len(kot.inventories) == 0: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="No inventories selected", - ) db.flush() 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 diff --git a/barker/barker/routers/voucher/update.py b/barker/barker/routers/voucher/update.py index 0e90d85..f422f97 100644 --- a/barker/barker/routers/voucher/update.py +++ b/barker/barker/routers/voucher/update.py @@ -26,6 +26,7 @@ from ...routers.voucher import ( get_tax, happy_hour_has_discount, happy_hour_items_balanced, + happy_hour_items_more_than_regular, ) from ...schemas.auth import UserToken @@ -80,6 +81,12 @@ def update( item.voucher_type = voucher_type item.user_id = user.id_ 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 i in k.inventories: i.tax_rate = get_tax(i.tax_rate, voucher_type) diff --git a/bookie/src/app/sales/bill.service.ts b/bookie/src/app/sales/bill.service.ts index 717516a..cbc474f 100644 --- a/bookie/src/app/sales/bill.service.ts +++ b/bookie/src/app/sales/bill.service.ts @@ -230,7 +230,7 @@ export class BillService { if (newKot.inventories.length === 0) { 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 this.ser.saveOrUpdate(item, VoucherType.Kot, guestBookId, true); @@ -242,7 +242,7 @@ export class BillService { if (item.kots.length === 1 && newKot.inventories.length === 0) { 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 this.ser.saveOrUpdate(item, voucherType, guest_book_id, true); @@ -334,18 +334,42 @@ export class BillService { } private happyHourItemsBalanced(): boolean { - const newKot = this.bill.kots.find((k) => k.id === undefined) as Kot; - const happyHourItems = newKot.inventories - .filter((x) => x.isHappyHour) - .map((x) => ({ id: x.product.id as string, quantity: x.quantity })); - for (const item of happyHourItems) { - const q = newKot.inventories.find( - (x) => !x.isHappyHour && x.product.id === item.id && x.quantity === item.quantity, - ); - if (q === undefined) { - return false; + for (const kot of this.bill.kots) { + const happyHourItems = kot.inventories + .filter((x) => x.isHappyHour) + .map((x) => ({ id: x.product.id as string, quantity: x.quantity })); + for (const item of happyHourItems) { + const q = kot.inventories.find( + (x) => !x.isHappyHour && x.product.id === item.id && x.quantity === item.quantity, + ); + if (q === undefined) { + return false; + } } } 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; + } } diff --git a/bookie/src/app/sales/bills/bills.component.html b/bookie/src/app/sales/bills/bills.component.html index 713998f..d9d0988 100644 --- a/bookie/src/app/sales/bills/bills.component.html +++ b/bookie/src/app/sales/bills/bills.component.html @@ -126,7 +126,7 @@