diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..6324d401 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.14 diff --git a/README.md b/README.md index a87f3a2a..7d891a5c 100644 --- a/README.md +++ b/README.md @@ -46,13 +46,13 @@ libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-d ## 2. Install Python 3.14 using pyenv ```zsh -pyenv install 3.14.0 +pyenv install 3.14 ``` Set the local Python version for this project: ```zsh -pyenv local 3.14.0 +pyenv local 3.14 ``` Verify: @@ -79,7 +79,16 @@ export PATH="$HOME/.local/bin:$PATH" Enable tab completion for Zsh ```zsh -poetry completions zsh > ~/.zfunc/_poetry +mkdir $ZSH_CUSTOM/plugins/poetry +poetry completions zsh > $ZSH_CUSTOM/plugins/poetry/_poetry +``` + +You must then add poetry to your plugins array in ~/.zshrc: +```zsh +plugins( + poetry + ... + ) ``` Verify: diff --git a/barker/alembic/versions/32c508eed4df_skus.py b/barker/alembic/versions/32c508eed4df_skus.py index d00ea762..1c5c7ba6 100644 --- a/barker/alembic/versions/32c508eed4df_skus.py +++ b/barker/alembic/versions/32c508eed4df_skus.py @@ -75,7 +75,7 @@ def upgrade(): ["sku_id"], ["stock_keeping_units.id"], name=op.f("fk_sku_versions_sku_id_stock_keeping_units") ), sa.PrimaryKeyConstraint("id", name=op.f("pk_sku_versions")), - sa.CheckConstraint("length(trim(units)) >= 1", name="ck_sku_versions_units_not_blank"), + sa.CheckConstraint("length(trim(units)) >= 1", name="units_not_blank"), ) # --------------------------------------------------------------------- @@ -223,7 +223,7 @@ def upgrade(): op.alter_column("product_versions", "units", new_column_name="fraction_units") op.drop_constraint(op.f("fk_products_menu_category_id_menu_categories"), "product_versions", type_="foreignkey") op.create_check_constraint( - "ck_product_versions_name_not_blank", + "name_not_blank", "product_versions", "length(trim(name)) >= 1", ) diff --git a/barker/alembic/versions/367ecf7b898f_bundles.py b/barker/alembic/versions/367ecf7b898f_bundles.py index fc767238..affe8fab 100644 --- a/barker/alembic/versions/367ecf7b898f_bundles.py +++ b/barker/alembic/versions/367ecf7b898f_bundles.py @@ -28,6 +28,7 @@ def upgrade(): sa.Column("item_id", sa.Uuid(), nullable=False), sa.Column("quantity", sa.Numeric(precision=15, scale=2), nullable=False), sa.Column("sale_price", sa.Numeric(precision=15, scale=2), nullable=False), + sa.Column("print_in_bill", sa.Boolean(), server_default=sa.text("true"), nullable=False), sa.Column("valid_from", sa.Date(), nullable=True), sa.Column("valid_till", sa.Date(), nullable=True), postgresql.ExcludeConstraint( @@ -47,7 +48,7 @@ def upgrade(): ) inventory_type = sa.Enum("regular", "bundle", "bundle_item", name="inventorytype") - # inventory_type.create(op.get_bind()) + inventory_type.create(op.get_bind()) op.add_column("inventories", sa.Column("type", inventory_type, server_default=sa.text("'regular'"), nullable=False)) op.add_column("inventories", sa.Column("parent_id", sa.Uuid(), nullable=True)) op.drop_constraint(op.f("uq_inventories_kot_id"), "inventories", type_="unique") @@ -82,6 +83,45 @@ def upgrade(): ) ) + customers = sa.table( + "customers", + sa.column("id", sa.Uuid), + sa.column("name", sa.Text), + sa.column("phone", sa.Text), + ) + + guest_book = sa.table( + "guest_book", + sa.column("customer_id", sa.Uuid), + ) + + voucher = sa.table( + "vouchers", + sa.column("customer_id", sa.Uuid), + ) + + bad_customer_ids = sa.select(customers.c.id).where( + sa.func.length(sa.func.trim(customers.c.phone)) == 0, + ) + + op.execute(guest_book.delete().where(guest_book.c.customer_id.in_(bad_customer_ids))) + + op.execute(voucher.update().where(voucher.c.customer_id.in_(bad_customer_ids)).values(customer_id=None)) + + op.execute(customers.delete().where(customers.c.id.in_(bad_customer_ids))) + + op.execute( + customers.update() + .where( + sa.func.length(sa.func.trim(customers.c.name)) == 0, + ) + .values(name=customers.c.phone) + ) + + with op.batch_alter_table("customers") as batch_op: + batch_op.create_check_constraint("name_not_blank", sa.text("length(trim(both from name)) >= 1")) + batch_op.create_check_constraint("phone_not_blank", sa.text("length(trim(both from phone)) >= 1")) + def downgrade(): op.drop_table("bundle_items") @@ -101,3 +141,7 @@ def downgrade(): sa.column("id", sa.Uuid()), ) op.execute(mc.delete().where(mc.c.id == "6752ed9d-6f1a-4941-940a-17759dcc6720")) + + with op.batch_alter_table("customers") as batch_op: + batch_op.drop_constraint("name_not_blank", type_="check") + batch_op.drop_constraint("phone_not_blank", type_="check") diff --git a/barker/alembic/versions/7e9944b430d6_booking.py b/barker/alembic/versions/7e9944b430d6_booking.py index edd9a096..ea15aaec 100644 --- a/barker/alembic/versions/7e9944b430d6_booking.py +++ b/barker/alembic/versions/7e9944b430d6_booking.py @@ -106,7 +106,7 @@ def upgrade(): op.execute(gbt.update().values(type="walk_in")) with op.batch_alter_table("guest_book") as batch_op: batch_op.alter_column("type", existing_type=guestbooktype, nullable=False) - batch_op.create_check_constraint("ck_guest_book_nulls", sa.text("num_nonnulls(booking_date, arrival_date) > 0")) + batch_op.create_check_constraint("nulls", sa.text("num_nonnulls(booking_date, arrival_date) > 0")) # ### end Alembic commands ### diff --git a/barker/barker/models/bundle_item.py b/barker/barker/models/bundle_item.py index a66cba84..a2911bc4 100644 --- a/barker/barker/models/bundle_item.py +++ b/barker/barker/models/bundle_item.py @@ -6,7 +6,7 @@ from datetime import date from decimal import Decimal from typing import TYPE_CHECKING -from sqlalchemy import Date, ForeignKey, Numeric, Uuid, func, text +from sqlalchemy import Boolean, Date, ForeignKey, Numeric, Uuid, func, text from sqlalchemy.dialects import postgresql from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -28,6 +28,7 @@ class BundleItem: item_id: Mapped[uuid.UUID] = mapped_column(Uuid, ForeignKey("stock_keeping_units.id"), nullable=False) quantity: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=2), nullable=False) sale_price: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=2), nullable=False) + print_in_bill: Mapped[bool] = mapped_column(Boolean, server_default=text("true"), nullable=False) valid_from: Mapped[date | None] = mapped_column(Date(), nullable=True) valid_till: Mapped[date | None] = mapped_column(Date(), nullable=True) @@ -49,6 +50,7 @@ class BundleItem: self, quantity: Decimal, sale_price: Decimal, + print_in_bill: bool, bundle_id: uuid.UUID, item_id: uuid.UUID, bundle: StockKeepingUnit | None = None, @@ -59,6 +61,7 @@ class BundleItem: ): self.quantity = quantity self.sale_price = sale_price + self.print_in_bill = print_in_bill self.valid_from = valid_from self.valid_till = valid_till if bundle_id is not None: diff --git a/barker/barker/models/customer.py b/barker/barker/models/customer.py index e17d9a6b..c0c9781f 100644 --- a/barker/barker/models/customer.py +++ b/barker/barker/models/customer.py @@ -4,7 +4,7 @@ import uuid from typing import TYPE_CHECKING -from sqlalchemy import Boolean, Text, Uuid, text +from sqlalchemy import Boolean, CheckConstraint, Text, Uuid, text from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.sql import expression @@ -18,7 +18,16 @@ if TYPE_CHECKING: @reg.mapped_as_dataclass(unsafe_hash=True) class Customer: __tablename__ = "customers" - + __table_args__ = ( + CheckConstraint( + "length(trim(both from name)) >= 1", + name="name_not_blank", + ), + CheckConstraint( + "length(trim(both from phone)) >= 1", + name="phone_not_blank", + ), + ) id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, server_default=text("gen_random_uuid()")) name: Mapped[str] = mapped_column(Text, nullable=False) phone: Mapped[str] = mapped_column(Text, nullable=False, unique=True) diff --git a/barker/barker/models/guest_book.py b/barker/barker/models/guest_book.py index 154c0f48..ba29832b 100644 --- a/barker/barker/models/guest_book.py +++ b/barker/barker/models/guest_book.py @@ -29,7 +29,7 @@ if TYPE_CHECKING: @reg.mapped_as_dataclass(unsafe_hash=True) class GuestBook: __tablename__ = "guest_book" - __table_args__ = (CheckConstraint("num_nonnulls(booking_date, arrival_date) > 0", name="ck_guest_book_nulls"),) + __table_args__ = (CheckConstraint("num_nonnulls(booking_date, arrival_date) > 0", name="nulls"),) id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, server_default=text("gen_random_uuid()")) customer_id: Mapped[uuid.UUID] = mapped_column(Uuid, ForeignKey("customers.id"), nullable=False) diff --git a/barker/barker/models/inventory.py b/barker/barker/models/inventory.py index d5d9e999..bdd74b58 100644 --- a/barker/barker/models/inventory.py +++ b/barker/barker/models/inventory.py @@ -36,7 +36,9 @@ if TYPE_CHECKING: @reg.mapped_as_dataclass(unsafe_hash=True) class Inventory: __tablename__ = "inventories" - __table_args__ = (UniqueConstraint("kot_id", "sku_id", "is_happy_hour", "type", "parent_id"),) + __table_args__ = ( + UniqueConstraint("kot_id", "sku_id", "is_happy_hour", "type", "parent_id", postgresql_nulls_not_distinct=True), + ) id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, server_default=text("gen_random_uuid()")) kot_id: Mapped[uuid.UUID] = mapped_column(Uuid, ForeignKey("kots.id"), nullable=False, index=True) diff --git a/barker/barker/printing/__init__.py b/barker/barker/printing/__init__.py index 68a7cb14..bebb388c 100644 --- a/barker/barker/printing/__init__.py +++ b/barker/barker/printing/__init__.py @@ -1,59 +1,20 @@ -import locale -import uuid - -from datetime import date from decimal import Decimal -from sqlalchemy import and_, or_, select -from sqlalchemy.orm import Session, contains_eager - -from ..models.product import Product -from ..models.product_version import ProductVersion -from ..models.sku_version import SkuVersion -from ..models.stock_keeping_unit import StockKeepingUnit - def currency_format(amount: Decimal | int) -> str: - return locale.currency(amount, symbol=False, grouping=True) + return format_indian(amount) def format_no_decimals(amount: Decimal | int) -> str: - return locale.format_string("%d", amount, grouping=True, monetary=True) + return format_indian(amount).split(".")[0] -def get_product_name(sku_id: uuid.UUID, date_: date, db: Session) -> str: - item = get_product(sku_id, date_, db) - return f"{item.sku.product.versions[0].name} ({item.units})" - - -def get_product(sku_id: uuid.UUID, date_: date, db: Session) -> SkuVersion: - product_version_onclause = and_( - ProductVersion.product_id == Product.id, - or_( - ProductVersion.valid_from == None, # noqa: E711 - ProductVersion.valid_from <= date_, - ), - or_( - ProductVersion.valid_till == None, # noqa: E711 - ProductVersion.valid_till >= date_, - ), - ) - sku_version_onclause = and_( - SkuVersion.sku_id == StockKeepingUnit.id, - or_(SkuVersion.valid_from == None, SkuVersion.valid_from <= date_), # noqa: E711 - or_(SkuVersion.valid_till == None, SkuVersion.valid_till >= date_), # noqa: E711 - ) - return ( - db.execute( - select(SkuVersion) - .join(StockKeepingUnit, onclause=sku_version_onclause) - .join(StockKeepingUnit.product) - .join(ProductVersion, onclause=product_version_onclause) - .where(SkuVersion.sku_id == sku_id) - .options( - contains_eager(SkuVersion.sku).contains_eager(StockKeepingUnit.product).contains_eager(Product.versions) - ) - ) - .unique() - .scalar_one() - ) +def format_indian(amount: Decimal | int) -> str: + s = f"{abs(amount):.2f}" + integer, fraction = s.split(".") + remainder = integer[-3:] + integer = integer[:-3] + while integer: + remainder = integer[-2:] + "," + remainder + integer = integer[:-2] + return ("-" if amount < 0 else "") + remainder + "." + fraction diff --git a/barker/barker/printing/bill.py b/barker/barker/printing/bill.py index 1ede7b1a..dad83a2e 100644 --- a/barker/barker/printing/bill.py +++ b/barker/barker/printing/bill.py @@ -1,5 +1,4 @@ import asyncio -import locale import re import uuid @@ -7,7 +6,7 @@ from datetime import timedelta from decimal import Decimal from arq import ArqRedis, create_pool -from sqlalchemy import select +from sqlalchemy import Date, func, select from sqlalchemy.orm import Session from ..core.arq import settings as redis_settings @@ -15,16 +14,22 @@ from ..core.config import settings from ..models.bill import Bill from ..models.db_setting import DbSetting from ..models.inventory import Inventory +from ..models.inventory_type import InventoryType +from ..models.kot import Kot from ..models.printer import Printer from ..models.section_printer import SectionPrinter from ..models.voucher import Voucher from ..models.voucher_type import VoucherType -from . import currency_format, format_no_decimals, get_product_name +from ..routers import _pv_onclause, _sv_onclause +from ..routers.voucher import get_voucher +from . import currency_format, format_no_decimals def print_bill(voucher_id: uuid.UUID, db: Session) -> None: - locale.setlocale(locale.LC_MONETARY, "en_IN") - voucher: Voucher = db.execute(select(Voucher).where(Voucher.id == voucher_id)).scalar_one() + day = func.cast( + Kot.date + timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES - settings.NEW_DAY_OFFSET_MINUTES), Date + ).label("day") + voucher: Voucher = get_voucher(voucher_id, db, _pv_onclause(day), _sv_onclause(day)) printer = db.execute( select(Printer) @@ -38,7 +43,8 @@ def print_bill(voucher_id: uuid.UUID, db: Session) -> None: for regime_id, inventories in regimes: taxes_dict: dict[str, Decimal] = {} for i in inventories: - get_tax_item(i.tax.id, i.tax.name, i.tax_amount, taxes_dict) + if i.type_ != InventoryType.bundle_item: + get_tax_item(i.tax.id, i.tax.name, i.tax_amount, taxes_dict) taxes_list = [(t, a) for t, a in taxes_dict.items() if a != 0] bill_text = design_bill(voucher, regime_id, inventories, taxes_list, db) bills.append(bill_text) @@ -62,12 +68,16 @@ def print_bill(voucher_id: uuid.UUID, db: Session) -> None: def get_inventories(voucher: Voucher) -> list[Inventory]: - items_dict: dict[tuple[uuid.UUID, bool, Decimal, frozenset[uuid.UUID]], Inventory] = {} + items_dict: dict[ + tuple[uuid.UUID, bool, Decimal, InventoryType, uuid.UUID | None, frozenset[uuid.UUID]], Inventory + ] = {} for i in [i for k in voucher.kots for i in k.inventories]: key = ( i.sku_id, i.is_happy_hour, i.discount, + i.type_, + None if i.parent is None else i.parent.sku_id, frozenset(m.modifier_id for m in i.modifiers if m.modifier.show_in_bill), ) if key in items_dict: @@ -85,6 +95,8 @@ def get_inventories(voucher: Voucher) -> list[Inventory]: kot_id=i.kot_id, tax_id=i.tax_id, sort_order=i.sort_order, + parent_id=i.parent_id, + type_=i.type_, ) return list([item for item in items_dict.values() if item.quantity != 0]) @@ -98,9 +110,10 @@ def get_regimes( ] items_dict: dict[int, list[Inventory]] = {} - + previous_regime_id: int = inventories[0].tax.regime_id for inv in inventories: - key = inv.tax.regime_id + key = previous_regime_id if inv.type_ == InventoryType.bundle_item else inv.tax.regime_id + previous_regime_id = key if key not in items_dict: items_dict[key] = [] items_dict[key].append(inv) @@ -142,7 +155,6 @@ def design_bill( s += "\n\r" + bill_object.regime.header # Products voucher_date = voucher.date + timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES) - product_date = (voucher_date - timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES)).date() bill_number = f"{bill_object.regime.prefix}-{bill_object.bill_number}" s += "\n\r" + f"Bill No: {bill_number:>12} {voucher_date:%d-%b-%Y %H:%M}" @@ -151,33 +163,44 @@ def design_bill( s += "\n\r" + "Qty. Particulars Price Amount" s += "\n\r" + "-" * 42 for inv in inventories: - name = get_product_name(inv.sku_id, product_date, db) - name = "H H " + name if inv.is_happy_hour else name - s += ( - f"\n\r" - f"{inv.quantity: >5.2f} {name:<22.22} {format_no_decimals(inv.price): >6}" - f" {format_no_decimals(inv.price * inv.quantity): >6}" - ) + name = ( + "H H " if inv.is_happy_hour else "" + ) + f"{inv.sku.product.versions[0].name} ({inv.sku.versions[0].units})" + if inv.parent_id: + info = f" ({inv.quantity:f}) {name}" + s += "\n\r" + info[:42] + else: + s += ( + f"\n\r" + f"{inv.quantity: >5.2f} {name:<22.22} {format_no_decimals(inv.price): >6}" + f" {format_no_decimals(inv.price * inv.quantity): >6}" + ) for m in [m for m in inv.modifiers if m.modifier.show_in_bill]: s += f"\n\r -- {m.modifier.name: <38.38}" s += "\n\r" + "------------------------------------------" # Totals - amount = sum(round(i.quantity * i.price, 2) for i in inventories) + amount = sum(round(i.quantity * i.price, 2) for i in inventories if i.type_ != InventoryType.bundle_item) s += f"\n\r{'Subtotal :': >32} {currency_format(amount): >9}" - amount = sum(round(i.quantity * i.price, 2) for i in inventories if i.is_happy_hour) + amount = sum( + round(i.quantity * i.price, 2) for i in inventories if i.is_happy_hour and i.type_ != InventoryType.bundle_item + ) if amount != 0: s += f"\n\r{'Happy Hour Discount :': >32} {currency_format(amount): >9}" - amount = sum(round(i.quantity * i.effective_price * i.discount, 2) for i in inventories) + amount = sum( + round(i.quantity * i.effective_price * i.discount, 2) + for i in inventories + if i.type_ != InventoryType.bundle_item + ) if amount != 0: s += f"\n\r{'Discount :': >32} {currency_format(amount): >9}" for t in taxes: s += f"\n\r{t[0]: >30} : {currency_format(t[1]): >9}" - s += f"\n\r{'Total Amount :': >32} {currency_format(round(sum([i.amount for i in inventories]))): >9}" + s += f"\n\r{'Total Amount :': >32} {currency_format(round(sum([i.amount for i in inventories if i.type_ != InventoryType.bundle_item]))): >9}" s += "\n\r" + "-" * 42 if voucher.narration: diff --git a/barker/barker/printing/cashier_report.py b/barker/barker/printing/cashier_report.py index 4060da55..27a259a4 100644 --- a/barker/barker/printing/cashier_report.py +++ b/barker/barker/printing/cashier_report.py @@ -1,5 +1,4 @@ import asyncio -import locale import uuid from datetime import datetime, timedelta @@ -18,7 +17,6 @@ from . import currency_format def print_cashier_report(report: CashierReport, device_id: uuid.UUID, db: Session) -> None: - locale.setlocale(locale.LC_MONETARY, "en_IN") summary = design_cashier_report_summary(report) details = design_cashier_report_details(report) section_id = db.execute(select(Device.section_id).where(Device.id == device_id)).scalar_one() diff --git a/barker/barker/printing/discount_report.py b/barker/barker/printing/discount_report.py index 0e5e2f34..24bf4ea5 100644 --- a/barker/barker/printing/discount_report.py +++ b/barker/barker/printing/discount_report.py @@ -1,5 +1,4 @@ import asyncio -import locale import uuid from arq import ArqRedis, create_pool @@ -15,7 +14,6 @@ from . import currency_format def print_discount_report(report: DiscountReport, device_id: uuid.UUID, db: Session) -> None: - locale.setlocale(locale.LC_MONETARY, "en_IN") data = design_discount_report(report) section_id = db.execute(select(Device.section_id).where(Device.id == device_id)).scalar_one() printer = db.execute( diff --git a/barker/barker/printing/kot.py b/barker/barker/printing/kot.py index 7f64d672..e38f8a07 100644 --- a/barker/barker/printing/kot.py +++ b/barker/barker/printing/kot.py @@ -5,7 +5,7 @@ from datetime import timedelta from typing import Any from arq import ArqRedis, create_pool -from sqlalchemy import or_, select +from sqlalchemy import Date, func, or_, select from sqlalchemy.orm import Session from ..core.arq import settings as redis_settings @@ -15,14 +15,12 @@ from ..models.kot import Kot from ..models.printer import Printer from ..models.section_printer import SectionPrinter from ..models.voucher import Voucher -from . import get_product, get_product_name +from ..routers import _pv_onclause, _sv_onclause +from ..routers.voucher import get_voucher def design_kot(voucher: Voucher, kot: Kot, items: list[Inventory], copy_number: int, db: Session) -> str: date_ = kot.date + timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES) - product_date = ( - voucher.date + timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES - settings.NEW_DAY_OFFSET_MINUTES) - ).date() s = ( "KOT / BOT".center(42) + "\n\r" @@ -41,9 +39,13 @@ def design_kot(voucher: Voucher, kot: Kot, items: list[Inventory], copy_number: + "".ljust(42, "-") ) for item in items: - name = get_product_name(item.sku_id, product_date, db) - name = "H H " + name if item.is_happy_hour else name - s += "\n\r" + f"{item.quantity:6.2} x {name:<33}" + name = ( + "H H " if item.is_happy_hour else "" + ) + f"{item.sku.product.versions[0].name} ({item.sku.versions[0].units})" + if item.parent_id: + s += "\n\r" + f"{item.quantity:6.2f} #= {name:<32}" + else: + s += "\n\r" + f"{item.quantity:6.2f} x {name:<33}" for m in item.modifiers: s += "\n\r" + f" --- {m.modifier.name:<32}" s += "\n\r" + "".ljust(42, "-") @@ -51,14 +53,14 @@ def design_kot(voucher: Voucher, kot: Kot, items: list[Inventory], copy_number: def print_kot(voucher_id: uuid.UUID, db: Session) -> None: - voucher: Voucher = db.execute(select(Voucher).where(Voucher.id == voucher_id)).scalar_one() + day = func.cast( + Kot.date + timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES - settings.NEW_DAY_OFFSET_MINUTES), Date + ).label("day") + voucher: Voucher = get_voucher(voucher_id, db, _pv_onclause(day), _sv_onclause(day)) my_hash: dict[tuple[uuid.UUID, int], tuple[Printer, list[Any]]] = {} kot: Kot = voucher.kots[-1] - product_date = ( - voucher.date + timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES - settings.NEW_DAY_OFFSET_MINUTES) - ).date() for item in kot.inventories: - product = get_product(item.sku_id, product_date, db) + sku = item.sku row = db.execute( select(Printer, SectionPrinter.copies) @@ -66,7 +68,7 @@ def print_kot(voucher_id: uuid.UUID, db: Session) -> None: .where(SectionPrinter.section_id == voucher.food_table.section_id) .where( or_( - SectionPrinter.sale_category_id == product.sku.product.versions[0].sale_category_id, + SectionPrinter.sale_category_id == sku.product.versions[0].sale_category_id, SectionPrinter.sale_category_id == None, # noqa: E711 ) ) diff --git a/barker/barker/printing/product_sale_report.py b/barker/barker/printing/product_sale_report.py index adb1dc62..0a43bdeb 100644 --- a/barker/barker/printing/product_sale_report.py +++ b/barker/barker/printing/product_sale_report.py @@ -1,5 +1,4 @@ import asyncio -import locale import uuid from datetime import datetime, timedelta @@ -17,7 +16,6 @@ from ..schemas.product_sale_report import ProductSaleReport def print_product_sale_report(report: ProductSaleReport, device_id: uuid.UUID, db: Session) -> None: - locale.setlocale(locale.LC_MONETARY, "en_IN") data = design_product_sale_report(report) section_id = db.execute(select(Device.section_id).where(Device.id == device_id)).scalar_one() printer = db.execute( diff --git a/barker/barker/printing/sale_report.py b/barker/barker/printing/sale_report.py index 40ef881b..91a5b42c 100644 --- a/barker/barker/printing/sale_report.py +++ b/barker/barker/printing/sale_report.py @@ -1,5 +1,4 @@ import asyncio -import locale import uuid from datetime import datetime, timedelta @@ -18,7 +17,6 @@ from . import currency_format def print_sale_report(report: SaleReport, device_id: uuid.UUID, db: Session) -> None: - locale.setlocale(locale.LC_MONETARY, "en_IN") data = design_sale_report(report) section_id = db.execute(select(Device.section_id).where(Device.id == device_id)).scalar_one() printer = db.execute( diff --git a/barker/barker/routers/bundle.py b/barker/barker/routers/bundle.py index 574e1751..7c90c9a0 100644 --- a/barker/barker/routers/bundle.py +++ b/barker/barker/routers/bundle.py @@ -22,6 +22,7 @@ from ..models.stock_keeping_unit import StockKeepingUnit from ..models.voucher import Voucher from ..schemas import bundle as schemas from ..schemas.menu_category import MenuCategoryLink +from ..schemas.sale_category import SaleCategoryLink from ..schemas.user_token import UserToken from . import _pv_onclause, _sv_onclause, effective_date @@ -41,6 +42,7 @@ def bundle_blank() -> schemas.BundleBlank: name="", units="", sale_price=Decimal("0.00"), + sale_category=None, has_happy_hour=False, is_not_available=False, sort_order=0, @@ -56,6 +58,7 @@ def _bundle_info(pv: ProductVersion, sv: SkuVersion, items: Sequence[BundleItemM name=pv.name, units=pv.fraction_units, sale_price=sv.sale_price, + sale_category=SaleCategoryLink(id_=pv.sale_category_id, name=pv.sale_category.name), has_happy_hour=sv.has_happy_hour, is_not_available=sku.is_not_available, sort_order=sku.sort_order, @@ -73,6 +76,7 @@ def _bundle_info(pv: ProductVersion, sv: SkuVersion, items: Sequence[BundleItemM item_id=bi.item_id, sale_price=bi.sale_price, quantity=bi.quantity, + print_in_bill=bi.print_in_bill, ) for bi in items ], @@ -97,13 +101,6 @@ def save( ) sale_price = round(sum(round(it.sale_price, 2) * round(it.quantity, 5) for it in data.items), 2) - sale_category_id = db.execute( - select(ProductVersion.sale_category_id) - .join(Product, onclause=_pv_onclause(date_)) - .join(Product.skus) - .where(StockKeepingUnit.id == data.items[0].item_id) - ).scalar_one() - product = Product(sort_order=data.sort_order) db.add(product) db.flush() @@ -113,7 +110,7 @@ def save( product=product, name=data.name, fraction_units=data.units, # per your rule - sale_category_id=sale_category_id, + sale_category_id=data.sale_category.id_, sale_category=None, valid_from=date_, valid_till=None, @@ -151,6 +148,7 @@ def save( item_id=it.item_id, quantity=round(it.quantity, 5), sale_price=round(it.sale_price, 2), + print_in_bill=it.print_in_bill, valid_from=date_, valid_till=None, ) @@ -207,7 +205,7 @@ def update_route( sku = sv.sku product = sku.product pv = product.versions[0] - pv_changed = pv.name != data.name + pv_changed = pv.name != data.name or pv.sale_category_id != data.sale_category.id_ sku.is_not_available = data.is_not_available sku.sort_order = data.sort_order @@ -216,6 +214,7 @@ def update_route( if pv_changed: if pv.valid_from == date_: pv.name = data.name + pv.sale_category_id = data.sale_category.id_ else: pv.valid_till = date_ - timedelta(days=1) db.add( @@ -224,7 +223,7 @@ def update_route( product=product, name=data.name, fraction_units=pv.fraction_units, - sale_category_id=pv.sale_category_id, + sale_category_id=data.sale_category.id_, valid_from=date_, valid_till=None, ) @@ -290,6 +289,7 @@ def update_route( item_id=it.item_id, sale_price=round(it.sale_price, 2), quantity=round(it.quantity, 5), + print_in_bill=it.print_in_bill, valid_from=date_, valid_till=None, ) @@ -305,6 +305,7 @@ def update_route( item_id=it.item_id, sale_price=round(it.sale_price, 2), quantity=round(it.quantity, 5), + print_in_bill=it.print_in_bill, valid_from=date_, valid_till=None, ) @@ -362,6 +363,7 @@ def show_list( .join(SkuVersion, onclause=sv_onclause) .join(StockKeepingUnit.product) .join(ProductVersion, onclause=pv_onclause) + .join(ProductVersion.sale_category) .where( BundleItemModel.bundle_id == sv.sku_id, or_(BundleItemModel.valid_from == None, BundleItemModel.valid_from <= date_), # noqa: E711 @@ -371,7 +373,8 @@ def show_list( contains_eager(BundleItemModel.item).contains_eager(StockKeepingUnit.versions), contains_eager(BundleItemModel.item) .contains_eager(StockKeepingUnit.product) - .contains_eager(Product.versions), + .contains_eager(Product.versions) + .contains_eager(ProductVersion.sale_category), ) ) .unique() @@ -424,6 +427,7 @@ def show_id( .join(SkuVersion, onclause=sv_onclause) .join(StockKeepingUnit.product) .join(ProductVersion, onclause=pv_onclause) + .join(ProductVersion.sale_category) .where( BundleItemModel.bundle_id == sv.sku_id, or_(BundleItemModel.valid_from == None, BundleItemModel.valid_from <= date_), # noqa: E711 @@ -433,7 +437,8 @@ def show_id( contains_eager(BundleItemModel.item).contains_eager(StockKeepingUnit.versions), contains_eager(BundleItemModel.item) .contains_eager(StockKeepingUnit.product) - .contains_eager(Product.versions), + .contains_eager(Product.versions) + .contains_eager(ProductVersion.sale_category), ) ) .unique() diff --git a/barker/barker/routers/reports/tax_report.py b/barker/barker/routers/reports/tax_report.py index 184a3cb4..957a373f 100644 --- a/barker/barker/routers/reports/tax_report.py +++ b/barker/barker/routers/reports/tax_report.py @@ -61,7 +61,7 @@ def get_tax(s: date, f: date, id_: uuid.UUID | None, db: Session) -> list[TaxRep .join(Inventory.tax) .join(Voucher.food_table) .where( - Inventory.type_ != InventoryType.bundle, + Inventory.type_ != InventoryType.bundle_item, Voucher.date >= start_date, Voucher.date <= finish_date, Voucher.voucher_type == VoucherType.REGULAR_BILL, diff --git a/barker/barker/routers/voucher/change.py b/barker/barker/routers/voucher/change.py index 73febeae..da2c7665 100644 --- a/barker/barker/routers/voucher/change.py +++ b/barker/barker/routers/voucher/change.py @@ -49,7 +49,7 @@ def change( ) bill_changed: bool = ( len( - set((i.id, i.amount) for k in old.kots for i in k.inventories) + set((i.id, i.amount) for k in old.kots for i in k.inventories if not i.parent_id) ^ set((i.id_, i.amount) for k in data.kots for i in k.inventories) ) != 0 diff --git a/barker/barker/routers/voucher/save.py b/barker/barker/routers/voucher/save.py index 8bae387c..755bf06c 100644 --- a/barker/barker/routers/voucher/save.py +++ b/barker/barker/routers/voucher/save.py @@ -170,7 +170,9 @@ def do_save( inv.modifiers.append(mod) db.add(mod) if data_inv.type_ == InventoryType.bundle: - if data_inv.price != sum(child.price * child.quantity for child in data_inv.children): + if (data_inv.price * data_inv.quantity) != sum( + child.price * child.quantity for child in data_inv.children + ): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=f"Bundle price for {data_inv.sku.name} must be equal to sum of child item prices.", diff --git a/barker/barker/routers/voucher/update.py b/barker/barker/routers/voucher/update.py index 76d8de9f..2a5361de 100644 --- a/barker/barker/routers/voucher/update.py +++ b/barker/barker/routers/voucher/update.py @@ -4,7 +4,7 @@ from datetime import UTC, datetime, timedelta from decimal import Decimal from fastapi import APIRouter, HTTPException, Security, status -from sqlalchemy import func, select +from sqlalchemy import Date, func, select from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session, contains_eager @@ -75,6 +75,9 @@ def update_route( try: with SessionFuture() as db: now = datetime.now(UTC).replace(tzinfo=None) + day = func.cast( + Kot.date + timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES - settings.NEW_DAY_OFFSET_MINUTES), Date + ).label("day") product_date = ( now + timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES - settings.NEW_DAY_OFFSET_MINUTES) ).date() @@ -84,8 +87,7 @@ def update_route( voucher_type = VoucherType(p) guest_book = get_guest_book(g, db) need_to_print_kot = False - # item: Voucher = db.execute(select(Voucher).where(Voucher.id == id_)).scalar_one() - item = get_voucher(id_, db, product_version_onclause, sku_version_onclause) + item = get_voucher(id_, db, _pv_onclause(day), _sv_onclause(day)) check_permissions(item, voucher_type, user.permissions) if item.voucher_type != VoucherType.KOT: @@ -210,7 +212,9 @@ def update_route( inv.modifiers.append(mod) db.add(mod) if data_inv.type_ == InventoryType.bundle: - if data_inv.price != sum(child.price * child.quantity for child in data_inv.children): + if (data_inv.price * data_inv.quantity) != sum( + child.price * child.quantity for child in data_inv.children + ): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=f"Bundle price for {data_inv.sku.name} must be equal to sum of child item prices.", diff --git a/barker/barker/schemas/bundle.py b/barker/barker/schemas/bundle.py index 7d214425..2c7c9f68 100644 --- a/barker/barker/schemas/bundle.py +++ b/barker/barker/schemas/bundle.py @@ -7,6 +7,7 @@ from pydantic import BaseModel, ConfigDict, Field from . import Daf, to_camel from .menu_category import MenuCategoryLink +from .sale_category import SaleCategoryLink class BundleItem(BaseModel): @@ -15,6 +16,7 @@ class BundleItem(BaseModel): item_id: Annotated[uuid.UUID, Field(description="StockKeepingUnit ID of the item")] sale_price: Annotated[Daf, Field(ge=Decimal(0))] quantity: Annotated[Daf, Field(gt=Decimal(0))] + print_in_bill: bool model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True) @@ -23,6 +25,7 @@ class BundleIn(BaseModel): name: Annotated[str, Field(min_length=1)] units: Annotated[str, Field(min_length=1)] sale_price: Annotated[Daf, Field(ge=Decimal(0))] + sale_category: SaleCategoryLink has_happy_hour: bool is_not_available: bool sort_order: int @@ -46,6 +49,7 @@ class BundleBlank(BaseModel): name: str units: str sale_price: Daf + sale_category: SaleCategoryLink | None has_happy_hour: bool is_not_available: bool sort_order: int diff --git a/barker/pyproject.toml b/barker/pyproject.toml index d46f78a0..2556101f 100644 --- a/barker/pyproject.toml +++ b/barker/pyproject.toml @@ -5,7 +5,7 @@ description = "Point of Sale for a restaurant" authors = ["tanshu "] [tool.poetry.dependencies] -python = "^3.13" +python = "^3.14" uvicorn = {extras = ["standard"], version = "^0.35.0"} fastapi = {extras = ["all"], version = "^0.115.14"} passlib = {extras = ["bcrypt"], version = "^1.7.4"} diff --git a/bookie/src/app/bundles/bundle-detail/bundle-detail-dialog.component.html b/bookie/src/app/bundles/bundle-detail/bundle-detail-dialog.component.html index 54943ae4..f056aa16 100644 --- a/bookie/src/app/bundles/bundle-detail/bundle-detail-dialog.component.html +++ b/bookie/src/app/bundles/bundle-detail/bundle-detail-dialog.component.html @@ -22,6 +22,7 @@ Sale Price + Print in Bill? diff --git a/bookie/src/app/bundles/bundle-detail/bundle-detail-dialog.component.ts b/bookie/src/app/bundles/bundle-detail/bundle-detail-dialog.component.ts index a1855707..3c078c3f 100644 --- a/bookie/src/app/bundles/bundle-detail/bundle-detail-dialog.component.ts +++ b/bookie/src/app/bundles/bundle-detail/bundle-detail-dialog.component.ts @@ -1,8 +1,9 @@ -import { AsyncPipe } from '@angular/common'; +import { CommonModule } from '@angular/common'; import { Component, OnInit, inject } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { MatAutocompleteModule, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; @@ -18,9 +19,10 @@ import { BundleItem } from '../bundle'; styleUrls: ['./bundle-detail-dialog.component.css'], standalone: true, imports: [ - AsyncPipe, + CommonModule, MatAutocompleteModule, MatButtonModule, + MatCheckboxModule, MatDialogModule, MatFormFieldModule, MatInputModule, @@ -40,6 +42,7 @@ export class BundleDetailDialogComponent implements OnInit { product: FormControl; quantity: FormControl; salePrice: FormControl; + printInBill: FormControl; }>; constructor() { @@ -47,6 +50,7 @@ export class BundleDetailDialogComponent implements OnInit { product: new FormControl('', { nonNullable: true }), quantity: new FormControl(1, { nonNullable: true }), salePrice: new FormControl(0, { nonNullable: true }), + printInBill: new FormControl(true, { nonNullable: true }), }); this.products$ = this.form.controls.product.valueChanges.pipe( @@ -70,6 +74,7 @@ export class BundleDetailDialogComponent implements OnInit { product: this.data.item.name ?? '', quantity: Number(this.data.item.quantity ?? 1), salePrice: Number(this.data.item.salePrice ?? 0), + printInBill: this.data.item.printInBill, }); this.selectedProduct = null; @@ -104,6 +109,7 @@ export class BundleDetailDialogComponent implements OnInit { this.data.item.quantity = quantity; this.data.item.salePrice = salePrice; + this.data.item.printInBill = v.printInBill || false; this.dialogRef.close(this.data.item); } diff --git a/bookie/src/app/bundles/bundle-detail/bundle-detail.component.html b/bookie/src/app/bundles/bundle-detail/bundle-detail.component.html index 98fbb188..edfad6a5 100644 --- a/bookie/src/app/bundles/bundle-detail/bundle-detail.component.html +++ b/bookie/src/app/bundles/bundle-detail/bundle-detail.component.html @@ -14,13 +14,13 @@
- + Sale Price Computed from bundle items - + Menu Category @for (mc of menuCategories; track mc) { @@ -31,8 +31,26 @@ - Has Happy Hour? - Not Available? + + Sale Category + + @for (sc of saleCategories; track sc) { + + {{ sc.name }} + + } + + +
+
+ Sale Category is used for Tax Report and displaying in the right bill regimes. For the Sale Report, Discount + Report and Beer Sale Report, the Sale Category from the Bundle Items is used. +
+
+ Has Happy Hour? + Not Available?

Items

@@ -63,6 +81,7 @@ Sale Price + Print in Bill? @@ -94,6 +113,16 @@ + + + Print in Bill + + + {{ row.printInBill ? 'visibility' : 'visibility_off' }} + + + + Action diff --git a/bookie/src/app/bundles/bundle-detail/bundle-detail.component.ts b/bookie/src/app/bundles/bundle-detail/bundle-detail.component.ts index 4d58a353..6b5a60d4 100644 --- a/bookie/src/app/bundles/bundle-detail/bundle-detail.component.ts +++ b/bookie/src/app/bundles/bundle-detail/bundle-detail.component.ts @@ -17,6 +17,7 @@ import { of as observableOf, BehaviorSubject, debounceTime, distinctUntilChanged import { MenuCategory } from '../../core/menu-category'; import { ProductQuery } from '../../core/product-query'; +import { SaleCategory } from '../../core/sale-category'; import { ProductService } from '../../product/product.service'; import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component'; import { Bundle, BundleItem } from '../bundle'; @@ -59,6 +60,7 @@ export class BundleDetailComponent implements OnInit, AfterViewInit { units: FormControl; salePrice: FormControl; menuCategory: FormControl; + saleCategory: FormControl; hasHappyHour: FormControl; isNotAvailable: FormControl; @@ -67,10 +69,12 @@ export class BundleDetailComponent implements OnInit, AfterViewInit { itemId: FormControl; quantity: FormControl; salePrice: FormControl; + printInBill: FormControl; }>; }>; menuCategories: MenuCategory[] = []; + saleCategories: SaleCategory[] = []; public items$ = new BehaviorSubject([]); dataSource: BundleDetailDatasource = new BundleDetailDatasource(this.items$); @@ -79,7 +83,7 @@ export class BundleDetailComponent implements OnInit, AfterViewInit { itemProduct: ProductQuery | null = null; itemProducts: Observable; - displayedColumns = ['name', 'quantity', 'salePrice', 'action']; + displayedColumns = ['name', 'quantity', 'salePrice', 'printInBill', 'action']; constructor() { this.form = new FormGroup({ @@ -87,6 +91,7 @@ export class BundleDetailComponent implements OnInit, AfterViewInit { units: new FormControl('', { nonNullable: true }), salePrice: new FormControl(0, { nonNullable: true }), menuCategory: new FormControl('', { nonNullable: true }), + saleCategory: new FormControl('', { nonNullable: true }), hasHappyHour: new FormControl(false, { nonNullable: true }), isNotAvailable: new FormControl(false, { nonNullable: true }), @@ -94,6 +99,7 @@ export class BundleDetailComponent implements OnInit, AfterViewInit { itemId: new FormControl('', { nonNullable: true }), quantity: new FormControl(1, { nonNullable: true }), salePrice: new FormControl(0, { nonNullable: true }), + printInBill: new FormControl(true, { nonNullable: true }), }), }); this.itemProducts = this.form.controls.addRow.controls.itemId.valueChanges.pipe( @@ -119,8 +125,10 @@ export class BundleDetailComponent implements OnInit, AfterViewInit { const data = value as { item: Bundle; menuCategories: MenuCategory[]; + saleCategories: SaleCategory[]; }; this.menuCategories = data.menuCategories; + this.saleCategories = data.saleCategories; this.showItem(data.item); }); } @@ -158,6 +166,7 @@ export class BundleDetailComponent implements OnInit, AfterViewInit { units: this.item.units ?? '', salePrice: Number(this.item.salePrice ?? 0), menuCategory: this.item.menuCategory?.id ?? '', + saleCategory: this.item.saleCategory?.id ?? '', hasHappyHour: this.item.hasHappyHour ?? false, isNotAvailable: this.item.isNotAvailable ?? false, @@ -165,6 +174,7 @@ export class BundleDetailComponent implements OnInit, AfterViewInit { itemId: '', quantity: 1, salePrice: 0, + printInBill: true, }, }); this.itemProduct = null; @@ -217,6 +227,7 @@ export class BundleDetailComponent implements OnInit, AfterViewInit { name: this.itemProduct?.name ?? '', quantity, salePrice, + printInBill: v.printInBill || false, }); this.item.items.push(bi); @@ -315,6 +326,19 @@ export class BundleDetailComponent implements OnInit, AfterViewInit { } this.item.menuCategory.id = menuCategoryId; + // sale category + const saleCategoryId = v.saleCategory ?? ''; + if (!saleCategoryId) { + // keep it as-is; backend will 422 anyway, but we can show UI error too + this.snackBar.open('Menu Category is required', 'Error'); + return this.item; + } + + if (this.item.saleCategory === null || this.item.saleCategory === undefined) { + this.item.saleCategory = new SaleCategory(); + } + this.item.saleCategory.id = saleCategoryId; + // ensure items array exists if (!this.item.items) { this.item.items = []; diff --git a/bookie/src/app/bundles/bundle-list/bundle-list.component.html b/bookie/src/app/bundles/bundle-list/bundle-list.component.html index e7fd27ee..8eceec2b 100644 --- a/bookie/src/app/bundles/bundle-list/bundle-list.component.html +++ b/bookie/src/app/bundles/bundle-list/bundle-list.component.html @@ -55,12 +55,23 @@ + + + Sale Category + + {{ row.saleCategory?.name }} + + + Items
@for (item of row.items; track item) { + + {{ item.printInBill ? 'visibility' : 'visibility_off' }} +
{{ item.name }} x {{ item.quantity }} @ {{ item.salePrice | currency: 'INR' }}
}
diff --git a/bookie/src/app/bundles/bundle-list/bundle-list.component.ts b/bookie/src/app/bundles/bundle-list/bundle-list.component.ts index 17cd7c96..a09ff960 100644 --- a/bookie/src/app/bundles/bundle-list/bundle-list.component.ts +++ b/bookie/src/app/bundles/bundle-list/bundle-list.component.ts @@ -51,7 +51,7 @@ export class BundleListComponent implements OnInit { list: Bundle[] = []; menuCategories: MenuCategory[] = []; - displayedColumns: string[] = ['name', 'price', 'menuCategory', 'info', 'items']; + displayedColumns: string[] = ['name', 'price', 'menuCategory', 'saleCategory', 'info', 'items']; constructor() { this.form = new FormGroup({ @@ -91,6 +91,7 @@ export class BundleListComponent implements OnInit { Units: 'units', Price: 'salePrice', MenuCategory: 'menuCategory', + SaleCategory: 'saleCategory', Items: 'items', }; diff --git a/bookie/src/app/bundles/bundle.ts b/bookie/src/app/bundles/bundle.ts index 380c531f..5f8cb251 100644 --- a/bookie/src/app/bundles/bundle.ts +++ b/bookie/src/app/bundles/bundle.ts @@ -1,4 +1,5 @@ import { MenuCategory } from '../core/menu-category'; +import { SaleCategory } from '../core/sale-category'; export class BundleItem { id?: string; @@ -6,12 +7,14 @@ export class BundleItem { name: string; salePrice: number; quantity: number; + printInBill: boolean; public constructor(init?: Partial) { this.itemId = ''; this.name = ''; this.salePrice = 0; this.quantity = 0; + this.printInBill = true; Object.assign(this, init); } } @@ -30,6 +33,7 @@ export class Bundle { sortOrder: number; menuCategory?: MenuCategory | null; + saleCategory?: SaleCategory | null; items: BundleItem[]; public constructor(init?: Partial) { diff --git a/bookie/src/app/bundles/bundles.routes.ts b/bookie/src/app/bundles/bundles.routes.ts index a20c3eb2..3e8c3161 100644 --- a/bookie/src/app/bundles/bundles.routes.ts +++ b/bookie/src/app/bundles/bundles.routes.ts @@ -2,6 +2,7 @@ import { Routes } from '@angular/router'; import { authGuard } from '../auth/auth-guard.service'; import { menuCategoryListResolver } from '../menu-category/menu-category-list.resolver'; +import { saleCategoryListResolver } from '../sale-category/sale-category-list.resolver'; import { BundleDetailComponent } from './bundle-detail/bundle-detail.component'; import { bundleListResolver } from './bundle-list.resolver'; import { BundleListComponent } from './bundle-list/bundle-list.component'; @@ -26,6 +27,7 @@ export const routes: Routes = [ resolve: { item: bundleResolver, // returns new Bundle() because id === null (no param here) menuCategories: menuCategoryListResolver, + saleCategories: saleCategoryListResolver, }, }, { @@ -36,6 +38,7 @@ export const routes: Routes = [ resolve: { item: bundleResolver, menuCategories: menuCategoryListResolver, + saleCategories: saleCategoryListResolver, }, }, ]; diff --git a/bookie/src/app/customers/customer-detail/customer-detail.component.ts b/bookie/src/app/customers/customer-detail/customer-detail.component.ts index b658da92..79b87022 100644 --- a/bookie/src/app/customers/customer-detail/customer-detail.component.ts +++ b/bookie/src/app/customers/customer-detail/customer-detail.component.ts @@ -21,7 +21,6 @@ import { CustomerService } from '../customer.service'; styleUrls: ['./customer-detail.component.sass'], imports: [ MatButtonModule, - MatCheckboxModule, MatDividerModule, MatFormFieldModule, diff --git a/frank/config.py b/frank/config.py index 08cb2fe3..fac92083 100644 --- a/frank/config.py +++ b/frank/config.py @@ -1,5 +1,5 @@ from dotenv import load_dotenv -from pydantic import BaseSettings +from pydantic_settings import BaseSettings class Settings(BaseSettings): diff --git a/frank/pyproject.toml b/frank/pyproject.toml index ddf0e302..79a9e331 100644 --- a/frank/pyproject.toml +++ b/frank/pyproject.toml @@ -5,10 +5,11 @@ description = "Point of Sale for a restaurant" authors = ["tanshu "] [tool.poetry.dependencies] -python = "^3.10" -pydantic = {extras = ["dotenv"], version = "^1.9.1"} -arq = "^0.23a1" -aiohttp = "^3.8.1" +python = "^3.14" +pydantic = {extras = ["dotenv"], version = "^2.11.7"} +pydantic-settings = "^2.12.0" +arq = "^0.26.3" +aiohttp = "^3.12.13" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/frank/run_worker.py b/frank/run_worker.py new file mode 100644 index 00000000..b32deb96 --- /dev/null +++ b/frank/run_worker.py @@ -0,0 +1,9 @@ +import asyncio +from arq.worker import run_worker +from worker import WorkerSettings + +# Create & set a loop BEFORE arq touches asyncio.get_event_loop() +asyncio.set_event_loop(asyncio.new_event_loop()) + + +run_worker(WorkerSettings) diff --git a/frank/worker-start.sh b/frank/worker-start.sh index c4572cb6..d9c21c39 100755 --- a/frank/worker-start.sh +++ b/frank/worker-start.sh @@ -1,5 +1,5 @@ #! /usr/bin/env bash set -e -echo arq worker.WorkerSettings -arq worker.WorkerSettings +echo python run_worker.py +python run_worker.py