barker/barker/barker/printing/bill.py

167 lines
6.2 KiB
Python

import asyncio
import locale
import re
import uuid
from datetime import timedelta
from decimal import Decimal
from typing import List, Tuple
from arq import ArqRedis, create_pool
from barker.core.config import settings
from sqlalchemy import and_, or_
from sqlalchemy.orm import Session
from ..core.arq import settings as redis_settings
from ..models.db_setting import DbSetting
from ..models.inventory import Inventory
from ..models.printer import Printer
from ..models.product_version import ProductVersion
from ..models.section_printer import SectionPrinter
from ..models.voucher import Voucher
from ..models.voucher_type import VoucherType
from . import currency_format, format_no_decimals
def print_bill(voucher_id: uuid.UUID, db: Session):
locale.setlocale(locale.LC_MONETARY, "en_IN")
voucher: Voucher = db.query(Voucher).filter(Voucher.id == voucher_id).first()
printer = (
db.query(Printer)
.join(SectionPrinter.printer)
.filter(SectionPrinter.section_id == voucher.food_table.section_id)
.filter(SectionPrinter.menu_category_id == None) # noqa: E711
.first()
)
items_dict = {}
tax = {}
for i in [i for k in voucher.kots for i in k.inventories]:
key = (
i.product_id,
i.is_happy_hour,
tuple(m.modifier_id for m in i.modifiers if m.modifier.show_in_bill),
)
if key in items_dict:
items_dict[key].quantity += i.quantity
else:
items_dict[key] = i
get_tax_item(i.tax.id, i.tax.name, i.tax_amount, tax)
data = design_bill(voucher, items_dict.values(), tax.values(), db)
redis: ArqRedis = asyncio.run(create_pool(redis_settings))
asyncio.run(
redis.enqueue_job(
"sent_to_printer", data, printer.address, "\x1dV\x41\x03", _queue_name=f"barker:print:{printer.name}"
)
)
def get_tax_item(id_: uuid.UUID, tax_name: str, amount: Decimal, tax_dict: {}) -> None:
items = tax_name.split(";")
if len(items) == 1:
key = (id_, 0)
old_amount = tax_dict[key][1] if key in tax_dict else 0
tax_dict[key] = tax_name, round(amount + old_amount, 2)
else:
for i, item in enumerate(it.strip() for it in items):
key = (id_, i)
old_amount = tax_dict[key][1] if key in tax_dict else 0
match = re.match(r"(^.*)\s+\((.*?)/(.*?)\)[^(]*$", item)
if not match or len(match.groups()) != 3:
raise Exception("Error in tax as it has multiple items, but the format is wrong.")
sub_amount = round(amount * Decimal(match.group(2)) / Decimal(match.group(3)), 2)
tax_dict[(id_, i)] = match.group(1), round(sub_amount + old_amount, 2)
def design_bill(
voucher: Voucher,
items: List[Tuple[Inventory, Decimal]],
tax: List[Tuple[str, Decimal]],
db: Session,
):
# Header
s = "\n\r" + db.query(DbSetting).filter(DbSetting.name == "Header").first().data["Text"]
if voucher.voucher_type == VoucherType.REGULAR_BILL:
s += "\n\r" + "Retail Invoice".center(42)
s += "\n\r"
elif voucher.voucher_type == VoucherType.NO_CHARGE:
s += "\n\r" + "NO CHARGE - THIS IS NOT A BILL - DON'T PAY".center(42)
s += "\n\r"
elif voucher.voucher_type == VoucherType.STAFF:
s += "\n\r" + "STAFF CONSUMPTION -- THIS IS NOT A BILL".center(42)
s += "\n\r"
# Products
date = voucher.date + timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES)
s += "\n\r" + f"Bill No: {voucher.full_bill_id:>12} {date:%d-%b-%Y %H:%M}"
s += "\n\r" + "Table No.: " + voucher.food_table.name
s += "\n\r" + "-" * 42
s += "\n\r" + "Qty. Particulars Price Amount"
s += "\n\r" + "-" * 42
for item in [i for i in items if i.quantity != 0]:
product: ProductVersion = (
db.query(ProductVersion)
.filter(
and_(
ProductVersion.product_id == item.product_id,
or_(
ProductVersion.valid_from == None, # noqa: E711
ProductVersion.valid_from
<= (voucher.date - timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES)).date(),
),
or_(
ProductVersion.valid_till == None, # noqa: E711
ProductVersion.valid_till
>= (voucher.date - timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES)).date(),
),
)
)
.first()
)
name = "H H " + product.full_name if item.is_happy_hour else product.full_name
s += (
f"\n\r"
f"{item.quantity: >5.2f} {name:<22.22} {format_no_decimals(item.price): >6}"
f" {format_no_decimals(item.price * item.quantity): >6}"
)
for m in [m for m in item.modifiers if m.modifier.show_in_bill]:
s += f"\n\r -- {m.modifier.name: <38.38}"
s += "\n\r" + "------------------------------------------"
# Totals
amount = sum(i.quantity * i.price for i in items)
s += f"\n\r{'Subtotal :': >32} {currency_format(amount): >9}"
amount = sum(i.quantity * i.price for i in items if i.is_happy_hour)
if amount != 0:
s += f"\n\r{'Happy Hour Discount :': >32} {currency_format(amount): >9}"
amount = sum(i.quantity * i.effective_price * i.discount for i in items)
if amount != 0:
s += f"\n\r{'Discount :': >32} {currency_format(amount): >9}"
for t in tax:
if t[1] != 0:
s += f"\n\r{t[0]: >30} : {currency_format(t[1]): >9}"
s += f"\n\r{'Total Amount :': >32} {currency_format(round(voucher.amount)): >9}"
s += "\n\r" + "-" * 42
if voucher.voucher_type != VoucherType.REGULAR_BILL:
s += "\n\r" + "THIS IS NOT A BILL - DON'T PAY".center(42)
s += "\n\r" + "-" * 42
if voucher.narration:
s += f"\n\r{voucher.narration: ^42}"
s += "\n\r" + "-" * 42
if voucher.customer:
s += f"\n\r{voucher.customer.name}\n\r{voucher.customer.phone}\n\r{voucher.customer.address}"
s += "\n\r" + "-" * 42
s += "\n\r" + "Cashier : " + voucher.user.name
s += "\n\r" + db.query(DbSetting).filter(DbSetting.name == "Footer").first().data["Text"]
return s