barker/barker/barker/routers/voucher/split.py

210 lines
6.8 KiB
Python

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
from fastapi import APIRouter, Depends, HTTPException, Security, status
from sqlalchemy import func
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from ...core.security import get_current_active_user as get_user
from ...db.session import SessionLocal
from ...models.inventory import Inventory
from ...models.inventory_modifier import InventoryModifier
from ...models.kot import Kot
from ...models.overview import Overview
from ...models.settle_option import SettleOption
from ...models.voucher import Voucher
from ...models.voucher_type import VoucherType
from ...routers.voucher import (
check_permissions,
do_update_settlements,
do_update_table,
get_bill_id,
)
from ...schemas.receive_payment import ReceivePaymentItem as SettleSchema
from ...schemas.user_token import UserToken
router = APIRouter()
# Dependency
def get_db():
try:
db = SessionLocal()
yield db
finally:
db.close()
@router.post("/split-bill/{id_}")
def split(
id_: uuid.UUID,
data: schemas.Split,
u: bool, # Update table?
db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["split-bill"]),
):
try:
now = datetime.utcnow()
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)
if update_table:
db.query(Overview).filter(Overview.voucher_id == item.id).delete()
check_permissions(None, item.voucher_type, user.permissions)
one_inventories = [i for k in item.kots for i in k.inventories if i.id in data.inventories]
two_inventories = [i for k in item.kots for i in k.inventories if i.id not in data.inventories]
one = save(
one_inventories,
now,
original_voucher_type,
0,
data.table_id,
item.customer_id,
user.id_,
db,
)
if update_table:
do_update_table(one, None, db)
two = save(
two_inventories,
now,
original_voucher_type,
item.pax,
item.food_table_id,
item.customer_id,
user.id_,
db,
)
if update_table:
do_update_table(two, None, db)
db.commit()
except SQLAlchemyError as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
)
except Exception:
db.rollback()
raise
def save(
inventories: List[Inventory],
now: datetime,
voucher_type: VoucherType,
pax: int,
table_id: uuid.UUID,
customer_id: Optional[uuid.UUID],
user_id: uuid.UUID,
db: Session,
):
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)
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)
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