From e46fe7f90e8e4b5b3358de926f97c77e971ad4cb Mon Sep 17 00:00:00 2001 From: Amritanshu Date: Sun, 5 Mar 2023 23:50:41 +0530 Subject: [PATCH] Feature: Tax Regimes are added so that different bills with different series can be printed for Different regimes such as VAT and GST Chore: Model relationships updated to make them simpler Chore: Bill printing majorly refactored for it Due to the sheer depth of the changes. There can be showstoppers. Please test it carefully --- .../versions/5967e6603c3e_tax_regimes.py | 200 ++++++++++++++++++ barker/barker/db/base.py | 2 + barker/barker/main.py | 2 + barker/barker/models/bill.py | 41 ++++ barker/barker/models/customer.py | 2 +- barker/barker/models/customer_discount.py | 2 +- barker/barker/models/device.py | 4 +- barker/barker/models/food_table.py | 2 +- barker/barker/models/guest_book.py | 2 +- barker/barker/models/inventory.py | 24 +-- barker/barker/models/inventory_modifier.py | 8 +- barker/barker/models/kot.py | 8 +- barker/barker/models/login_history.py | 5 +- barker/barker/models/menu_category.py | 2 +- barker/barker/models/modifier.py | 2 +- barker/barker/models/modifier_category.py | 5 +- barker/barker/models/overview.py | 6 +- barker/barker/models/permission.py | 4 +- barker/barker/models/product.py | 2 +- barker/barker/models/product_version.py | 8 +- barker/barker/models/regime.py | 53 +++++ barker/barker/models/reprint.py | 4 +- barker/barker/models/role.py | 2 +- barker/barker/models/sale_category.py | 4 +- barker/barker/models/section.py | 10 +- barker/barker/models/section_printer.py | 6 +- barker/barker/models/settlement.py | 2 + barker/barker/models/tax.py | 12 +- barker/barker/models/user.py | 6 +- barker/barker/models/voucher.py | 58 ++--- barker/barker/printing/bill.py | 190 +++++++++++------ barker/barker/printing/kot.py | 4 +- barker/barker/routers/customer.py | 6 +- barker/barker/routers/regime.py | 126 +++++++++++ .../routers/reports/bill_settlement_report.py | 5 +- .../barker/routers/reports/cashier_report.py | 3 +- barker/barker/routers/tax.py | 4 +- barker/barker/routers/voucher/__init__.py | 46 ++-- barker/barker/routers/voucher/change.py | 12 +- barker/barker/routers/voucher/merge_move.py | 9 +- barker/barker/routers/voucher/save.py | 5 +- barker/barker/routers/voucher/show.py | 42 ++-- barker/barker/routers/voucher/split.py | 8 +- barker/barker/routers/voucher/update.py | 20 +- barker/barker/routers/voucher/void.py | 6 +- barker/barker/schemas/customer.py | 4 +- barker/barker/schemas/regime.py | 40 ++++ barker/barker/schemas/tax.py | 6 +- barker/barker/worker.py | 3 +- bookie/src/app/app-routing.module.ts | 4 + bookie/src/app/auth/login/login.component.ts | 22 +- .../beer-sale-report-resolver.service.ts | 4 +- .../beer-sale-report.component.ts | 32 +-- ...bill-settlement-report-resolver.service.ts | 4 +- .../bill-settlement-report.component.ts | 20 +- .../active-cashiers-resolver.service.ts | 4 +- .../cashier-report-resolver.service.ts | 4 +- .../cashier-report.component.ts | 21 +- bookie/src/app/core/customer-discount.ts | 4 +- bookie/src/app/core/customer.ts | 4 +- bookie/src/app/core/http-auth-interceptor.ts | 8 +- bookie/src/app/core/modifier-category.ts | 4 +- bookie/src/app/core/regime.ts | 16 ++ bookie/src/app/core/tax.ts | 4 + .../customer-detail.component.ts | 82 +++---- .../device-detail/device-detail.component.ts | 26 ++- .../discount-report-resolver.service.ts | 4 +- .../discount-report.component.ts | 14 +- .../guest-book-detail.component.ts | 43 ++-- .../guest-book-list.component.ts | 14 +- bookie/src/app/guest-book/guest-book.ts | 4 +- .../header-footer/header-footer.component.ts | 17 +- bookie/src/app/home/home.component.html | 8 + .../menu-category-detail.component.ts | 19 +- .../modifier-category-detail.component.ts | 35 +-- .../modifier-detail.component.ts | 36 ++-- .../modifier-list/modifier-list.component.ts | 17 +- .../printer-detail.component.ts | 24 ++- .../product-sale-report-resolver.service.ts | 4 +- .../product-sale-report.component.ts | 14 +- ...product-updates-report-resolver.service.ts | 4 +- .../product-updates-report.component.ts | 20 +- .../product-detail.component.ts | 57 ++--- .../product-list/product-list.component.ts | 20 +- .../regime-detail/regime-detail.component.css | 3 + .../regime-detail.component.html | 47 ++++ .../regime-detail.component.spec.ts | 24 +++ .../regime-detail/regime-detail.component.ts | 109 ++++++++++ .../regime-list-resolver.service.spec.ts | 15 ++ .../regimes/regime-list-resolver.service.ts | 18 ++ .../regime-list/regime-list-datasource.ts | 16 ++ .../regime-list/regime-list.component.css | 0 .../regime-list/regime-list.component.html | 43 ++++ .../regime-list/regime-list.component.spec.ts | 22 ++ .../regime-list/regime-list.component.ts | 28 +++ .../regimes/regime-resolver.service.spec.ts | 15 ++ .../app/regimes/regime-resolver.service.ts | 19 ++ bookie/src/app/regimes/regime.service.spec.ts | 15 ++ bookie/src/app/regimes/regime.service.ts | 58 +++++ .../regimes/regimes-routing.module.spec.ts | 13 ++ .../src/app/regimes/regimes-routing.module.ts | 53 +++++ bookie/src/app/regimes/regimes.module.spec.ts | 13 ++ bookie/src/app/regimes/regimes.module.ts | 31 +++ .../sale-category-detail.component.ts | 24 ++- .../sale-report-resolver.service.ts | 4 +- .../app/sale-report/sale-report.component.ts | 14 +- .../bill-number/bill-number.component.html | 11 +- .../bill-number/bill-number.component.ts | 59 +++--- .../src/app/sales/bills/bills.component.html | 2 +- .../choose-customer.component.ts | 87 ++++---- .../app/sales/discount/discount.component.ts | 66 +++--- bookie/src/app/sales/pax/pax.component.ts | 13 +- .../src/app/sales/reason/reason.component.ts | 14 +- .../receive-payment.component.ts | 59 +++--- .../running-tables.component.html | 2 +- .../running-tables.component.ts | 7 +- bookie/src/app/sales/sales-routing.module.ts | 3 + .../sales/split-bill/split-bill.component.ts | 38 ++-- .../tables-dialog.component.html | 2 +- .../section-printer.component.html | 2 +- .../section-printer.component.ts | 58 ++--- .../section-detail.component.ts | 16 +- bookie/src/app/settings/settings.component.ts | 43 +++- bookie/src/app/settings/settings.service.ts | 16 ++ .../settle-option-detail.component.ts | 29 +-- .../table-detail/table-detail.component.ts | 29 +-- .../tax-report/tax-report-resolver.service.ts | 4 +- .../app/tax-report/tax-report.component.ts | 20 +- .../tax-detail/tax-detail.component.html | 10 + .../taxes/tax-detail/tax-detail.component.ts | 30 ++- .../taxes/tax-list/tax-list.component.html | 8 +- .../app/taxes/tax-list/tax-list.component.ts | 2 +- bookie/src/app/taxes/taxes-routing.module.ts | 3 + bookie/src/app/taxes/taxes.module.ts | 2 + .../temporal-product-detail.component.ts | 64 +++--- .../temporal-product-list.component.ts | 24 ++- .../update-product-prices-item.ts | 4 +- .../update-product-prices-resolver.service.ts | 2 +- .../update-product-prices.component.ts | 60 +++--- .../user-detail/user-detail.component.ts | 66 +++--- lint.sh | 6 +- 141 files changed, 2197 insertions(+), 892 deletions(-) create mode 100644 barker/alembic/versions/5967e6603c3e_tax_regimes.py create mode 100644 barker/barker/models/bill.py create mode 100644 barker/barker/models/regime.py create mode 100644 barker/barker/routers/regime.py create mode 100644 barker/barker/schemas/regime.py create mode 100644 bookie/src/app/core/regime.ts create mode 100644 bookie/src/app/regimes/regime-detail/regime-detail.component.css create mode 100644 bookie/src/app/regimes/regime-detail/regime-detail.component.html create mode 100644 bookie/src/app/regimes/regime-detail/regime-detail.component.spec.ts create mode 100644 bookie/src/app/regimes/regime-detail/regime-detail.component.ts create mode 100644 bookie/src/app/regimes/regime-list-resolver.service.spec.ts create mode 100644 bookie/src/app/regimes/regime-list-resolver.service.ts create mode 100644 bookie/src/app/regimes/regime-list/regime-list-datasource.ts create mode 100644 bookie/src/app/regimes/regime-list/regime-list.component.css create mode 100644 bookie/src/app/regimes/regime-list/regime-list.component.html create mode 100644 bookie/src/app/regimes/regime-list/regime-list.component.spec.ts create mode 100644 bookie/src/app/regimes/regime-list/regime-list.component.ts create mode 100644 bookie/src/app/regimes/regime-resolver.service.spec.ts create mode 100644 bookie/src/app/regimes/regime-resolver.service.ts create mode 100644 bookie/src/app/regimes/regime.service.spec.ts create mode 100644 bookie/src/app/regimes/regime.service.ts create mode 100644 bookie/src/app/regimes/regimes-routing.module.spec.ts create mode 100644 bookie/src/app/regimes/regimes-routing.module.ts create mode 100644 bookie/src/app/regimes/regimes.module.spec.ts create mode 100644 bookie/src/app/regimes/regimes.module.ts diff --git a/barker/alembic/versions/5967e6603c3e_tax_regimes.py b/barker/alembic/versions/5967e6603c3e_tax_regimes.py new file mode 100644 index 0000000..0e28c9d --- /dev/null +++ b/barker/alembic/versions/5967e6603c3e_tax_regimes.py @@ -0,0 +1,200 @@ +"""tax regimes + +Revision ID: 5967e6603c3e +Revises: f135019a4ebf +Create Date: 2023-02-22 03:55:58.764092 + +""" +import sqlalchemy as sa + +from alembic import op +from sqlalchemy.dialects import postgresql +from sqlalchemy.dialects.postgresql import ENUM + + +# revision identifiers, used by Alembic. +revision = "5967e6603c3e" +down_revision = "f135019a4ebf" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + create_regime_table() + create_bills_table() + update_taxes() + op.drop_index("ix_vouchers_date", table_name="vouchers") + op.drop_constraint("uq_vouchers_bill_id", "vouchers", type_="unique") + op.drop_column("vouchers", "bill_id") + p = sa.table( + "permissions", + sa.column("id", postgresql.UUID(as_uuid=True)), + sa.column("name", sa.Unicode(length=255)), + ) + op.execute(p.insert().values(id="28e2194d-b830-4282-b8c5-3b8522b48894", name="Regimes")) + # ### end Alembic commands ### + + +def create_regime_table(): + op.create_table( + "regimes", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sa.Unicode(length=255), nullable=False), + sa.Column("header", sa.Unicode(length=255), nullable=False), + sa.Column("prefix", sa.Unicode(length=255), nullable=False), + sa.Column("is_fixture", sa.Boolean(), nullable=False), + sa.PrimaryKeyConstraint("id", name=op.f("pk_regimes")), + sa.UniqueConstraint("name", name=op.f("uq_regimes_name")), + sa.UniqueConstraint("prefix", name=op.f("uq_regimes_prefix")), + ) + re = sa.table( + "regimes", + sa.column("id", sa.Integer()), + sa.column("name", sa.Unicode(length=255)), + sa.column("header", sa.Unicode(length=255)), + sa.column("prefix", sa.Unicode(length=255)), + sa.column("is_fixture", sa.Boolean()), + ) + op.execute( + re.insert().values( + id=0, + name="KOT", + header="", + prefix="K", + is_fixture=True, + ) + ) + op.execute( + re.insert().values( + id=1, + name="Combined", + header="", + prefix="INV", + is_fixture=True, + ) + ) + op.execute( + re.insert().values( + id=2, + name="No Charge", + header="NO CHARGE - THIS IS NOT A BILL - DON'T PAY", + prefix="NC", + is_fixture=True, + ) + ) + op.execute( + re.insert().values( + id=4, + name="Staff", + header=" STAFF CONSUMPTION -- THIS IS NOT A BILL ", + prefix="ST", + is_fixture=True, + ) + ) + op.execute( + re.insert().values( + id=8, + name="Void", + header="", + prefix="V", + is_fixture=True, + ) + ) + op.execute( + re.insert().values( + id=16, + name="Goods and Service Tax", + header=" GSTIN: XXXXXXXXXXXXXXX \n Retail Invoice ", + prefix="GST", + is_fixture=False, + ) + ) + op.execute( + re.insert().values( + id=32, + name="Value Added Tax", + header=" TIN: XXXXXXXXXXX \n Retail Invoice ", + prefix="VAT", + is_fixture=False, + ) + ) + + +def create_bills_table(): + voucher_type = ENUM("KOT", "REGULAR_BILL", "NO_CHARGE", "STAFF", "VOID", name="voucher_type", create_type=False) + op.create_table( + "bills", + sa.Column("id", sa.UUID(), server_default=sa.text("gen_random_uuid()"), nullable=False), + sa.Column("regime_id", sa.Integer(), nullable=False), + sa.Column("voucher_id", sa.UUID(), nullable=False), + sa.Column("is_valid", sa.Boolean(), nullable=False), + sa.Column("bill_number", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(["regime_id"], ["regimes.id"], name=op.f("fk_bills_regime_id_regimes")), + sa.ForeignKeyConstraint(["voucher_id"], ["vouchers.id"], name=op.f("fk_bills_voucher_id_vouchers")), + sa.PrimaryKeyConstraint("id", name=op.f("pk_bills")), + sa.UniqueConstraint("regime_id", "bill_number", name=op.f("uq_bills_regime_id")), + sa.UniqueConstraint("voucher_id", "regime_id", name=op.f("uq_bills_voucher_id")), + ) + + b = sa.table( + "bills", + sa.column("regime_id", sa.Integer()), + sa.column("voucher_id", sa.UUID()), + sa.column("is_valid", sa.Boolean()), + sa.column("bill_number", sa.Integer()), + ) + + v = sa.table( + "vouchers", + sa.column("id", sa.UUID()), + sa.column("voucher_type", voucher_type), + sa.column("bill_id", sa.Integer()), + sa.column("kot_id", sa.Integer()), + ) + + op.execute( + b.insert().from_select( + [b.c.regime_id, b.c.voucher_id, b.c.is_valid, b.c.bill_number], + sa.select( + sa.case( + (v.c.voucher_type == "KOT", sa.text("0")), + (v.c.voucher_type == "REGULAR_BILL", sa.text("1")), + (v.c.voucher_type == "NO_CHARGE", sa.text("2")), + (v.c.voucher_type == "STAFF", sa.text("4")), + (v.c.voucher_type == "VOID", sa.text("8")), + ), + v.c.id, + sa.text("True"), + sa.case((v.c.bill_id == None, v.c.kot_id), else_=v.c.bill_id), # noqa: E711 + ).where( + v.c.bill_id != None # noqa: E711 + ), + ) + ) + + +def update_taxes(): + op.add_column("taxes", sa.Column("regime_id", sa.Integer(), nullable=True)) + ta = sa.table( + "taxes", + sa.column("regime_id", sa.Integer()), + ) + op.execute(ta.update().values(regime_id=1)) + op.alter_column("taxes", "regime_id", existing_type=sa.Integer(), nullable=False) + op.create_foreign_key(op.f("fk_taxes_regime_id_regimes"), "taxes", "regimes", ["regime_id"], ["id"]) + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("vouchers", sa.Column("bill_id", sa.INTEGER(), autoincrement=False, nullable=True)) + op.add_column("vouchers", sa.Column("date", postgresql.TIMESTAMP(), autoincrement=False, nullable=False)) + op.drop_index(op.f("ix_vouchers_creation_date"), table_name="vouchers") + op.create_unique_constraint("uq_vouchers_bill_id", "vouchers", ["bill_id", "voucher_type"]) + op.create_index("ix_vouchers_date", "vouchers", ["date"], unique=False) + op.drop_constraint(op.f("fk_taxes_regime_id_regimes"), "taxes", type_="foreignkey") + op.drop_column("taxes", "regime_id") + op.drop_index(op.f("ix_bills_date"), table_name="bills") + op.drop_table("bills") + op.drop_table("regimes") + # ### end Alembic commands ### diff --git a/barker/barker/db/base.py b/barker/barker/db/base.py index bb7f6bf..fe0cde3 100644 --- a/barker/barker/db/base.py +++ b/barker/barker/db/base.py @@ -1,5 +1,6 @@ # Import all the models, so that Base has them before being # imported by Alembic +from ..models.bill import Bill # noqa: F401 from ..models.customer import Customer # noqa: F401 from ..models.customer_discount import CustomerDiscount # noqa: F401 from ..models.db_setting import DbSetting # noqa: F401 @@ -19,6 +20,7 @@ from ..models.permission import Permission # noqa: F401 from ..models.printer import Printer # noqa: F401 from ..models.product import Product # noqa: F401 from ..models.product_version import ProductVersion # noqa: F401 +from ..models.regime import Regime # noqa: F401 from ..models.reporting_level import ReportingLevel # noqa: F401 from ..models.reprint import Reprint # noqa: F401 from ..models.role import Role # noqa: F401 diff --git a/barker/barker/main.py b/barker/barker/main.py index 96d4cbc..49683cb 100644 --- a/barker/barker/main.py +++ b/barker/barker/main.py @@ -18,6 +18,7 @@ from .routers import ( modifier_category, printer, product, + regime, role, sale_category, section, @@ -81,6 +82,7 @@ app.include_router(settle_option.router, prefix="/api/settle-options", tags=["se app.include_router(db_settings.router, prefix="/api/settings", tags=["settings"]) app.include_router(tax.router, prefix="/api/taxes", tags=["taxes"]) +app.include_router(regime.router, prefix="/api/regimes", tags=["regimes"]) app.include_router(table.router, prefix="/api/tables", tags=["sections"]) diff --git a/barker/barker/models/bill.py b/barker/barker/models/bill.py new file mode 100644 index 0000000..bbe5c7f --- /dev/null +++ b/barker/barker/models/bill.py @@ -0,0 +1,41 @@ +import uuid + +from typing import TYPE_CHECKING + +from sqlalchemy import Boolean, ForeignKey, Integer, UniqueConstraint, text +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from ..db.base_class import reg + + +if TYPE_CHECKING: + from .regime import Regime + from .voucher import Voucher + + +@reg.mapped_as_dataclass(unsafe_hash=True) +class Bill: + __tablename__ = "bills" + __table_args__ = ( + UniqueConstraint("voucher_id", "regime_id"), + UniqueConstraint("regime_id", "bill_number"), + ) + + id: Mapped[uuid.UUID] = mapped_column( + "id", UUID(as_uuid=True), primary_key=True, server_default=text("gen_random_uuid()"), insert_default=uuid.uuid4 + ) + regime_id: Mapped[int] = mapped_column("regime_id", Integer, ForeignKey("regimes.id"), nullable=False) + voucher_id: Mapped[uuid.UUID] = mapped_column( + "voucher_id", UUID(as_uuid=True), ForeignKey("vouchers.id"), nullable=False + ) + is_valid: Mapped[bool] = mapped_column("is_valid", Boolean, nullable=False) + bill_number: Mapped[int] = mapped_column("bill_number", Integer) + + voucher: Mapped["Voucher"] = relationship(back_populates="bills") + regime: Mapped["Regime"] = relationship(back_populates="bills") + + def __init__(self, regime_id, is_valid, bill_number): + self.regime_id = regime_id + self.is_valid = is_valid + self.bill_number = bill_number diff --git a/barker/barker/models/customer.py b/barker/barker/models/customer.py index 525de14..7488f1a 100644 --- a/barker/barker/models/customer.py +++ b/barker/barker/models/customer.py @@ -29,7 +29,7 @@ class Customer: "print_in_bill", Boolean, server_default=expression.false(), insert_default=False, nullable=False ) - discounts: Mapped[List["CustomerDiscount"]] = relationship("CustomerDiscount", back_populates="customer") + discounts: Mapped[List["CustomerDiscount"]] = relationship(back_populates="customer") @property def __name__(self): diff --git a/barker/barker/models/customer_discount.py b/barker/barker/models/customer_discount.py index df6735c..325be73 100644 --- a/barker/barker/models/customer_discount.py +++ b/barker/barker/models/customer_discount.py @@ -32,7 +32,7 @@ class CustomerDiscount: ) discount: Mapped[Decimal] = mapped_column("discount", Numeric(precision=15, scale=5), nullable=False) - customer: Mapped["Customer"] = relationship("Customer", back_populates="discounts") + customer: Mapped["Customer"] = relationship(back_populates="discounts") sale_category: Mapped["SaleCategory"] = relationship("SaleCategory") def __init__(self, sale_category_id=None, discount=None, id_=None, customer_id=None, customer=None): diff --git a/barker/barker/models/device.py b/barker/barker/models/device.py index f2f7710..f2be1b5 100644 --- a/barker/barker/models/device.py +++ b/barker/barker/models/device.py @@ -31,9 +31,9 @@ class Device: ) creation_date: Mapped[datetime] = mapped_column("creation_date", DateTime(), nullable=False) - section: Mapped["Section"] = relationship("Section", foreign_keys=section_id) + section: Mapped["Section"] = relationship(back_populates="devices") login_history: Mapped[List["LoginHistory"]] = relationship( - "LoginHistory", order_by=desc(LoginHistory.date), back_populates="device" + order_by=desc(LoginHistory.date), back_populates="device" ) def __init__(self, name=None, enabled=None, section_id=None, creation_date=None, id_=None): diff --git a/barker/barker/models/food_table.py b/barker/barker/models/food_table.py index 7fab766..7ff3452 100644 --- a/barker/barker/models/food_table.py +++ b/barker/barker/models/food_table.py @@ -30,7 +30,7 @@ class FoodTable: sort_order: Mapped[bool] = mapped_column("sort_order", Integer, nullable=False) section: Mapped["Section"] = relationship("Section", foreign_keys=section_id) - status: Mapped[Optional["Overview"]] = relationship("Overview", back_populates="food_table", uselist=False) + status: Mapped[Optional["Overview"]] = relationship(back_populates="food_table", uselist=False) @property def __name__(self): diff --git a/barker/barker/models/guest_book.py b/barker/barker/models/guest_book.py index 8867ba9..9194f0e 100644 --- a/barker/barker/models/guest_book.py +++ b/barker/barker/models/guest_book.py @@ -28,7 +28,7 @@ class GuestBook: date: Mapped[datetime] = mapped_column("creation_date", DateTime(), nullable=False) customer: Mapped["Customer"] = relationship("Customer") - status: Mapped[Optional["Overview"]] = relationship("Overview", back_populates="guest", uselist=False) + status: Mapped[Optional["Overview"]] = relationship(back_populates="guest", uselist=False) def __init__(self, pax=None, id_=None, customer_id=None, customer=None): self.customer_id = customer_id diff --git a/barker/barker/models/inventory.py b/barker/barker/models/inventory.py index 2bbfe60..84f239c 100644 --- a/barker/barker/models/inventory.py +++ b/barker/barker/models/inventory.py @@ -50,25 +50,25 @@ class Inventory: discount: Mapped[Decimal] = mapped_column("discount", Numeric(precision=15, scale=5), nullable=False) sort_order: Mapped[int] = mapped_column("sort_order", Integer, nullable=False) - kot: Mapped["Kot"] = relationship("Kot", back_populates="inventories") + kot: Mapped["Kot"] = relationship(back_populates="inventories") tax: Mapped["Tax"] = relationship("Tax", foreign_keys=tax_id) product: Mapped["ProductVersion"] = relationship( - "ProductVersion", secondary=Product.__table__, back_populates="inventories", viewonly=True + secondary=Product.__table__, back_populates="inventories", viewonly=True # type: ignore[attr-defined] ) - modifiers: Mapped[List["InventoryModifier"]] = relationship("InventoryModifier", back_populates="inventory") + modifiers: Mapped[List["InventoryModifier"]] = relationship(back_populates="inventory") def __init__( self, - kot_id, - product_id, - quantity, - price, - discount, - is_hh, - tax_id, - tax_rate, - sort_order, + kot_id=None, + product_id=None, + quantity=None, + price=None, + discount=None, + is_hh=None, + tax_id=None, + tax_rate=None, + sort_order=None, product=None, tax=None, ): diff --git a/barker/barker/models/inventory_modifier.py b/barker/barker/models/inventory_modifier.py index 6bdb2f6..93472ef 100644 --- a/barker/barker/models/inventory_modifier.py +++ b/barker/barker/models/inventory_modifier.py @@ -31,10 +31,12 @@ class InventoryModifier: ) price: Mapped[Decimal] = mapped_column("price", Numeric(precision=15, scale=2), nullable=False) - inventory: Mapped["Inventory"] = relationship("Inventory", back_populates="modifiers") + inventory: Mapped["Inventory"] = relationship(back_populates="modifiers") modifier: Mapped["Modifier"] = relationship("Modifier") def __init__(self, inventory_id: uuid.UUID | None, modifier_id: uuid.UUID | None, price: Decimal): - self.inventory_id = inventory_id - self.modifier_id = modifier_id + if inventory_id is not None: + self.inventory_id = inventory_id + if modifier_id is not None: + self.modifier_id = modifier_id self.price = price diff --git a/barker/barker/models/kot.py b/barker/barker/models/kot.py index 6c3c56f..e67a635 100644 --- a/barker/barker/models/kot.py +++ b/barker/barker/models/kot.py @@ -41,10 +41,10 @@ class Kot: date: Mapped[datetime] = mapped_column("date", DateTime, nullable=False, index=True) user_id: Mapped[uuid.UUID] = mapped_column("user_id", UUID(as_uuid=True), ForeignKey("users.id"), nullable=False) - voucher: Mapped["Voucher"] = relationship("Voucher", back_populates="kots") - user: Mapped["User"] = relationship("User", backref="kots") - food_table: Mapped["FoodTable"] = relationship("FoodTable", backref="kots") - inventories: Mapped[List["Inventory"]] = relationship("Inventory", back_populates="kot") + voucher: Mapped["Voucher"] = relationship(back_populates="kots") + user: Mapped["User"] = relationship(backref="kots") + food_table: Mapped["FoodTable"] = relationship(backref="kots") + inventories: Mapped[List["Inventory"]] = relationship(back_populates="kot") def __init__( self, diff --git a/barker/barker/models/login_history.py b/barker/barker/models/login_history.py index 350ef2e..989dd07 100644 --- a/barker/barker/models/login_history.py +++ b/barker/barker/models/login_history.py @@ -30,8 +30,9 @@ class LoginHistory: nullable=False, ) date: Mapped[datetime] = mapped_column("date", DateTime(), nullable=False) - device: Mapped["Device"] = relationship("Device", back_populates="login_history") - user: Mapped["User"] = relationship("User", back_populates="login_history") + + device: Mapped["Device"] = relationship(back_populates="login_history") + user: Mapped["User"] = relationship(back_populates="login_history") def __init__(self, user_id=None, device_id=None, date=None, id_=None): self.user_id = user_id diff --git a/barker/barker/models/menu_category.py b/barker/barker/models/menu_category.py index 30cc0a7..866e79f 100644 --- a/barker/barker/models/menu_category.py +++ b/barker/barker/models/menu_category.py @@ -26,7 +26,7 @@ class MenuCategory: is_fixture: Mapped[bool] = mapped_column("is_fixture", Boolean, nullable=False) sort_order: Mapped[int] = mapped_column("sort_order", Integer, nullable=False) - products: Mapped[List["ProductVersion"]] = relationship("ProductVersion", back_populates="menu_category") + products: Mapped[List["ProductVersion"]] = relationship(back_populates="menu_category") def __init__(self, name, is_active, sort_order, is_fixture=False, id_=None): self.name = name diff --git a/barker/barker/models/modifier.py b/barker/barker/models/modifier.py index 58da937..3e861cc 100644 --- a/barker/barker/models/modifier.py +++ b/barker/barker/models/modifier.py @@ -32,7 +32,7 @@ class Modifier: nullable=False, ) - modifier_category: Mapped["ModifierCategory"] = relationship("ModifierCategory", back_populates="modifiers") + modifier_category: Mapped["ModifierCategory"] = relationship(back_populates="modifiers") def __init__( self, diff --git a/barker/barker/models/modifier_category.py b/barker/barker/models/modifier_category.py index 6195d56..b9245eb 100644 --- a/barker/barker/models/modifier_category.py +++ b/barker/barker/models/modifier_category.py @@ -29,11 +29,10 @@ class ModifierCategory: sort_order: Mapped[int] = mapped_column("sort_order", Integer, nullable=False) products: Mapped[List["Product"]] = relationship( - "Product", - secondary=ModifierCategoryProduct.__table__, + secondary=ModifierCategoryProduct.__table__, # type: ignore[attr-defined] back_populates="modifier_categories", ) - modifiers: Mapped[List["Modifier"]] = relationship("Modifier", back_populates="modifier_category") + modifiers: Mapped[List["Modifier"]] = relationship(back_populates="modifier_category") def __init__( self, diff --git a/barker/barker/models/overview.py b/barker/barker/models/overview.py index 16c9143..f5fea54 100644 --- a/barker/barker/models/overview.py +++ b/barker/barker/models/overview.py @@ -40,9 +40,9 @@ class Overview: ) status: Mapped[str] = mapped_column("status", Unicode(255), nullable=False) - voucher: Mapped["Voucher"] = relationship("Voucher", back_populates="status") - food_table: Mapped["FoodTable"] = relationship("FoodTable", back_populates="status") - guest: Mapped["GuestBook"] = relationship("GuestBook", back_populates="status") + voucher: Mapped["Voucher"] = relationship(back_populates="status") + food_table: Mapped["FoodTable"] = relationship(back_populates="status") + guest: Mapped["GuestBook"] = relationship(back_populates="status") def __init__(self, voucher_id, food_table_id, guest_book_id, status, id_=None): self.voucher_id = voucher_id diff --git a/barker/barker/models/permission.py b/barker/barker/models/permission.py index 6fac02f..e666158 100644 --- a/barker/barker/models/permission.py +++ b/barker/barker/models/permission.py @@ -23,7 +23,9 @@ class Permission: ) name: Mapped[str] = mapped_column("name", Unicode(255), unique=True, nullable=False) - roles: Mapped[List["Role"]] = relationship("Role", secondary=RolePermission.__table__, back_populates="permissions") + roles: Mapped[List["Role"]] = relationship( + secondary=RolePermission.__table__, back_populates="permissions" # type: ignore[attr-defined] + ) def __init__(self, name=None, id_=None): self.name = name diff --git a/barker/barker/models/product.py b/barker/barker/models/product.py index 5002e69..44b415b 100644 --- a/barker/barker/models/product.py +++ b/barker/barker/models/product.py @@ -24,7 +24,7 @@ class Product: versions: Mapped[List["ProductVersion"]] = relationship("ProductVersion", back_populates="product") modifier_categories: Mapped[List["ModifierCategory"]] = relationship( "ModifierCategory", - secondary=ModifierCategoryProduct.__table__, + secondary=ModifierCategoryProduct.__table__, # type: ignore[attr-defined] order_by="ModifierCategory.sort_order", back_populates="products", ) diff --git a/barker/barker/models/product_version.py b/barker/barker/models/product_version.py index d07edf0..1689989 100644 --- a/barker/barker/models/product_version.py +++ b/barker/barker/models/product_version.py @@ -62,12 +62,12 @@ class ProductVersion: valid_from: Mapped[Optional[date]] = mapped_column("valid_from", Date(), nullable=True) valid_till: Mapped[Optional[date]] = mapped_column("valid_till", Date(), nullable=True) - menu_category: Mapped["MenuCategory"] = relationship("MenuCategory", back_populates="products") - sale_category: Mapped["SaleCategory"] = relationship("SaleCategory", back_populates="products") + menu_category: Mapped["MenuCategory"] = relationship(back_populates="products") + sale_category: Mapped["SaleCategory"] = relationship(back_populates="products") - product: Mapped["Product"] = relationship("Product", back_populates="versions") + product: Mapped["Product"] = relationship(back_populates="versions") inventories: Mapped[List["Inventory"]] = relationship( - "Inventory", secondary=Product.__table__, back_populates="product", viewonly=True + secondary=Product.__table__, back_populates="product", viewonly=True # type: ignore[attr-defined] ) __table_args__ = ( diff --git a/barker/barker/models/regime.py b/barker/barker/models/regime.py new file mode 100644 index 0000000..f6853fc --- /dev/null +++ b/barker/barker/models/regime.py @@ -0,0 +1,53 @@ +from typing import TYPE_CHECKING, List + +from sqlalchemy import Boolean, Integer, Unicode +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from ..db.base_class import reg + + +if TYPE_CHECKING: + from .bill import Bill + from .tax import Tax + + +@reg.mapped_as_dataclass(unsafe_hash=True) +class Regime: + __tablename__ = "regimes" + + id: Mapped[int] = mapped_column("id", Integer, primary_key=True) + name: Mapped[str] = mapped_column("name", Unicode(255), nullable=False, unique=True) + header: Mapped[str] = mapped_column("header", Unicode(255), nullable=False, unique=True) + prefix: Mapped[str] = mapped_column("prefix", Unicode(255), nullable=False, unique=True) + is_fixture: Mapped[bool] = mapped_column("is_fixture", Boolean, nullable=False) + + taxes: Mapped[List["Tax"]] = relationship(back_populates="regime") + bills: Mapped[List["Bill"]] = relationship(back_populates="regime") + + def __init__(self, id=None, name=None, header=None, prefix=None, is_fixture=False, id_=None): + self.id = id + self.name = name + self.header = header + self.prefix = prefix + self.is_fixture = is_fixture + self.id = id_ + + @classmethod + def KOT(cls): + return 0 + + @classmethod + def REGULAR_BILL(cls): + return 1 + + @classmethod + def NO_CHARGE(cls): + return 2 + + @classmethod + def STAFF(cls): + return 4 + + @classmethod + def VOID(cls): + return 8 diff --git a/barker/barker/models/reprint.py b/barker/barker/models/reprint.py index e2215a0..e560bc0 100644 --- a/barker/barker/models/reprint.py +++ b/barker/barker/models/reprint.py @@ -32,8 +32,8 @@ class Reprint: ) user_id: Mapped[uuid.UUID] = mapped_column("user_id", UUID(as_uuid=True), ForeignKey("users.id"), nullable=False) - user: Mapped["User"] = relationship("User", backref="reprints") - voucher: Mapped["Voucher"] = relationship("Voucher", back_populates="reprints") + user: Mapped["User"] = relationship(backref="reprints") + voucher: Mapped["Voucher"] = relationship(back_populates="reprints") def __init__(self, voucher_id=None, user_id=None, id_=None): self.id = id_ diff --git a/barker/barker/models/role.py b/barker/barker/models/role.py index 2cd7ccb..abc9a34 100644 --- a/barker/barker/models/role.py +++ b/barker/barker/models/role.py @@ -24,7 +24,7 @@ class Role: name: Mapped[str] = mapped_column("name", Unicode(255), unique=True, nullable=False) permissions: Mapped[List["Permission"]] = relationship( - "Permission", secondary=RolePermission.__table__, back_populates="roles" + "Permission", secondary=RolePermission.__table__, back_populates="roles" # type: ignore[attr-defined] ) def __init__(self, name=None, id_=None): diff --git a/barker/barker/models/sale_category.py b/barker/barker/models/sale_category.py index a77d402..e599164 100644 --- a/barker/barker/models/sale_category.py +++ b/barker/barker/models/sale_category.py @@ -26,8 +26,8 @@ class SaleCategory: discount_limit: Mapped[Decimal] = mapped_column("discount_limit", Numeric(precision=15, scale=5), nullable=False) tax_id: Mapped[uuid.UUID] = mapped_column("tax_id", UUID(as_uuid=True), ForeignKey("taxes.id"), nullable=False) - tax: Mapped["Tax"] = relationship("Tax", back_populates="sale_categories") - products: Mapped[List["ProductVersion"]] = relationship("ProductVersion", back_populates="sale_category") + tax: Mapped["Tax"] = relationship(back_populates="sale_categories") + products: Mapped[List["ProductVersion"]] = relationship(back_populates="sale_category") def __init__(self, name, discount_limit=None, tax_id=False, id_=None): self.name = name diff --git a/barker/barker/models/section.py b/barker/barker/models/section.py index e685c44..bbd5ec0 100644 --- a/barker/barker/models/section.py +++ b/barker/barker/models/section.py @@ -1,12 +1,18 @@ import uuid +from typing import TYPE_CHECKING, List + from sqlalchemy import Boolean, Unicode, text from sqlalchemy.dialects.postgresql import UUID -from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy.orm import Mapped, mapped_column, relationship from ..db.base_class import reg +if TYPE_CHECKING: + from .device import Device + + @reg.mapped_as_dataclass(unsafe_hash=True) class Section: __tablename__ = "sections" @@ -17,6 +23,8 @@ class Section: name: Mapped[str] = mapped_column("name", Unicode(255), unique=True, nullable=False) is_fixture: Mapped[bool] = mapped_column("is_fixture", Boolean, nullable=False, insert_default=False) + devices: Mapped[List["Device"]] = relationship(back_populates="section") + def __init__(self, name=None, is_fixture=None, id_=None): self.id = id_ self.name = name diff --git a/barker/barker/models/section_printer.py b/barker/barker/models/section_printer.py index 4250d9a..1d44a0e 100644 --- a/barker/barker/models/section_printer.py +++ b/barker/barker/models/section_printer.py @@ -34,9 +34,9 @@ class SectionPrinter: ) copies: Mapped[int] = mapped_column("copies", Integer, nullable=False) - sale_category: Mapped["SaleCategory"] = relationship("SaleCategory", backref="section_printers") - section: Mapped["Section"] = relationship("Section", backref="section_printers") - printer: Mapped["Printer"] = relationship("Printer", backref="section_printers") + sale_category: Mapped["SaleCategory"] = relationship(backref="section_printers") + section: Mapped["Section"] = relationship(backref="section_printers") + printer: Mapped["Printer"] = relationship(backref="section_printers") def __init__( self, diff --git a/barker/barker/models/settlement.py b/barker/barker/models/settlement.py index c5abe9c..301e6b0 100644 --- a/barker/barker/models/settlement.py +++ b/barker/barker/models/settlement.py @@ -12,6 +12,7 @@ from ..db.base_class import reg if TYPE_CHECKING: from .settle_option import SettleOption + from .voucher import Voucher @reg.mapped_as_dataclass(unsafe_hash=True) @@ -33,6 +34,7 @@ class Settlement: amount: Mapped[Decimal] = mapped_column("amount", Numeric(precision=15, scale=2), nullable=False) settle_option: Mapped["SettleOption"] = relationship("SettleOption") + voucher: Mapped["Voucher"] = relationship(back_populates="settlements") def __init__(self, voucher_id=None, settled=None, amount=None, id_=None): self.id = id_ diff --git a/barker/barker/models/tax.py b/barker/barker/models/tax.py index 774f21a..8713757 100644 --- a/barker/barker/models/tax.py +++ b/barker/barker/models/tax.py @@ -1,9 +1,9 @@ import uuid from decimal import Decimal -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List -from sqlalchemy import Boolean, Numeric, Unicode, text +from sqlalchemy import Boolean, ForeignKey, Integer, Numeric, Unicode, text from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -11,6 +11,7 @@ from ..db.base_class import reg if TYPE_CHECKING: + from .regime import Regime from .sale_category import SaleCategory @@ -23,12 +24,15 @@ class Tax: ) name: Mapped[str] = mapped_column("name", Unicode(255), nullable=False, unique=True) rate: Mapped[Decimal] = mapped_column("rate", Numeric(precision=15, scale=5), nullable=False) + regime_id: Mapped[int] = mapped_column("regime_id", Integer, ForeignKey("regimes.id"), nullable=False) is_fixture: Mapped[bool] = mapped_column("is_fixture", Boolean, nullable=False) - sale_categories: Mapped["SaleCategory"] = relationship("SaleCategory", back_populates="tax") + sale_categories: Mapped[List["SaleCategory"]] = relationship(back_populates="tax") + regime: Mapped["Regime"] = relationship(back_populates="taxes") - def __init__(self, name=None, rate=None, is_fixture=False, id_=None): + def __init__(self, name=None, rate=None, regime_id=None, is_fixture=False, id_=None): self.name = name self.rate = rate + self.regime_id = regime_id self.is_fixture = is_fixture self.id = id_ diff --git a/barker/barker/models/user.py b/barker/barker/models/user.py index 3b99716..c2de9b6 100644 --- a/barker/barker/models/user.py +++ b/barker/barker/models/user.py @@ -27,10 +27,10 @@ class User: _password: Mapped[str] = mapped_column("password", Unicode(60), nullable=False) locked_out: Mapped[bool] = mapped_column("locked_out", Boolean, nullable=False) - roles: Mapped[List["Role"]] = relationship("Role", secondary=UserRole.__table__, order_by="Role.name") - login_history: Mapped[List["LoginHistory"]] = relationship( - "LoginHistory", order_by=desc(LoginHistory.date), back_populates="user" + roles: Mapped[List["Role"]] = relationship( + "Role", secondary=UserRole.__table__, order_by="Role.name" # type: ignore[attr-defined] ) + login_history: Mapped[List["LoginHistory"]] = relationship(order_by=desc(LoginHistory.date), back_populates="user") Index("uq_users_name", func.lower(name), unique=True) diff --git a/barker/barker/models/voucher.py b/barker/barker/models/voucher.py index 849d694..49e8316 100644 --- a/barker/barker/models/voucher.py +++ b/barker/barker/models/voucher.py @@ -3,15 +3,7 @@ import uuid from datetime import datetime from typing import TYPE_CHECKING, List, Optional -from sqlalchemy import ( - DateTime, - Enum, - ForeignKey, - Integer, - Unicode, - UniqueConstraint, - text, -) +from sqlalchemy import DateTime, Enum, ForeignKey, Integer, Unicode, text from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -21,6 +13,7 @@ from .voucher_type import VoucherType if TYPE_CHECKING: + from .bill import Bill from .customer import Customer from .food_table import FoodTable from .overview import Overview @@ -32,14 +25,12 @@ if TYPE_CHECKING: @reg.mapped_as_dataclass(unsafe_hash=True) class Voucher: __tablename__ = "vouchers" - __table_args__ = (UniqueConstraint("bill_id", "voucher_type"),) id: Mapped[uuid.UUID] = mapped_column( "id", UUID(as_uuid=True), primary_key=True, server_default=text("gen_random_uuid()"), insert_default=uuid.uuid4 ) date: Mapped[datetime] = mapped_column("date", DateTime, nullable=False, index=True) pax: Mapped[int] = mapped_column("pax", Integer, nullable=False) - bill_id: Mapped[Optional[int]] = mapped_column("bill_id", Integer) kot_id: Mapped[uuid.UUID] = mapped_column("kot_id", Integer, nullable=False, unique=True) creation_date: Mapped[datetime] = mapped_column("creation_date", DateTime(), nullable=False) last_edit_date: Mapped[datetime] = mapped_column("last_edit_date", DateTime(), nullable=False) @@ -57,36 +48,33 @@ class Voucher: voucher_type: Mapped[VoucherType] = mapped_column("voucher_type", Enum(VoucherType), nullable=False) user_id: Mapped[uuid.UUID] = mapped_column("user_id", UUID(as_uuid=True), ForeignKey("users.id"), nullable=False) - user: Mapped["User"] = relationship("User", backref="vouchers") - food_table: Mapped["FoodTable"] = relationship("FoodTable", backref="vouchers") - customer: Mapped["Customer"] = relationship("Customer", backref="vouchers") + user: Mapped["User"] = relationship(backref="vouchers") + food_table: Mapped["FoodTable"] = relationship(backref="vouchers") + customer: Mapped["Customer"] = relationship(backref="vouchers") - kots: Mapped[List["Kot"]] = relationship( - "Kot", + bills: Mapped[List["Bill"]] = relationship( + back_populates="voucher", + cascade="delete, delete-orphan", + ) + kots: Mapped[List["Kot"]] = relationship( back_populates="voucher", cascade="delete, delete-orphan", - cascade_backrefs=False, order_by=Kot.code, ) settlements: Mapped[List["Settlement"]] = relationship( - "Settlement", - backref="voucher", - cascade="delete, delete-orphan", - cascade_backrefs=False, - ) - reprints: Mapped[List["Reprint"]] = relationship( - "Reprint", back_populates="voucher", cascade="delete, delete-orphan", - cascade_backrefs=False, ) - status: Mapped[Optional["Overview"]] = relationship("Overview", back_populates="voucher", uselist=False) + reprints: Mapped[List["Reprint"]] = relationship( + back_populates="voucher", + cascade="delete, delete-orphan", + ) + status: Mapped[Optional["Overview"]] = relationship(back_populates="voucher", uselist=False) def __init__( self, date, pax, - bill_id, kot_id, food_table_id, customer_id, @@ -97,7 +85,6 @@ class Voucher: self.pax = pax self.creation_date = date self.last_edit_date = date - self.bill_id = bill_id self.kot_id = kot_id self.food_table_id = food_table_id self.customer_id = customer_id @@ -105,21 +92,6 @@ class Voucher: self.voucher_type = voucher_type self.user_id = user_id - @property - def full_bill_id(self): - if self.voucher_type == VoucherType.KOT: - return "K-" + str(self.kot_id) - if self.voucher_type == VoucherType.NO_CHARGE: - return "NC-" + str(self.bill_id) - if self.voucher_type == VoucherType.STAFF: - return "ST-" + str(self.bill_id) - if self.voucher_type == VoucherType.REGULAR_BILL: - return str(self.bill_id // 10000) + "-" + str(self.bill_id % 10000) - if self.voucher_type == VoucherType.VOID: - return "K-" + str(self.kot_id) - else: - raise Exception - @property def amount(self): return round(sum(i.amount for k in self.kots for i in k.inventories), 2) diff --git a/barker/barker/printing/bill.py b/barker/barker/printing/bill.py index cc70d58..bad6928 100644 --- a/barker/barker/printing/bill.py +++ b/barker/barker/printing/bill.py @@ -5,6 +5,7 @@ import uuid from datetime import timedelta from decimal import Decimal +from typing import Dict, List, Tuple from arq import ArqRedis, create_pool from barker.core.config import settings @@ -12,6 +13,7 @@ from sqlalchemy import and_, or_, select from sqlalchemy.orm import Session from ..core.arq import settings as redis_settings +from ..models.bill import Bill from ..models.db_setting import DbSetting from ..models.inventory import Inventory from ..models.printer import Printer @@ -33,8 +35,35 @@ def print_bill(voucher_id: uuid.UUID, db: Session): .where(SectionPrinter.sale_category_id == None) # noqa: E711 ).scalar_one() - items_dict: dict[tuple[uuid.UUID, bool, Decimal, set[uuid.UUID]], Inventory] = {} - tax: dict[tuple[uuid.UUID, int], tuple[str, Decimal]] = {} + regimes = get_regimes(get_inventories(voucher), voucher.voucher_type, db) + bills: List[str] = [] + 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) + 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) + + loop = asyncio.new_event_loop() + redis: ArqRedis = loop.run_until_complete(create_pool(redis_settings)) + total = design_total(voucher, regimes, db) + loop.run_until_complete( + redis.enqueue_job( + "sent_to_printer", total, printer.address, printer.cut_code, _queue_name=f"barker:print:{printer.name}" + ) + ) + for bill in bills: + loop.run_until_complete( + redis.enqueue_job( + "sent_to_printer", bill, printer.address, printer.cut_code, _queue_name=f"barker:print:{printer.name}" + ) + ) + loop.close() + + +def get_inventories(voucher: Voucher) -> List[Inventory]: + items_dict: dict[tuple[uuid.UUID, bool, Decimal, frozenset[uuid.UUID]], Inventory] = {} for i in [i for k in voucher.kots for i in k.inventories]: key = ( i.product_id, @@ -46,84 +75,84 @@ def print_bill(voucher_id: uuid.UUID, db: Session): items_dict[key].quantity += i.quantity else: items_dict[key] = Inventory( - None, - i.product_id, - i.quantity, - i.price, - i.discount, - i.is_happy_hour, - None, - i.tax_rate, - 0, - i.product, - i.tax, + product_id=i.product_id, + quantity=i.quantity, + price=i.price, + discount=i.discount, + is_hh=i.is_happy_hour, + tax_rate=i.tax_rate, + product=i.product, + tax=i.tax, ) - - for i in [it for it in items_dict.values() if it.quantity != 0]: - get_tax_item(i.tax.id, i.tax.name, i.tax_amount, tax) - data = design_bill(voucher, list(items_dict.values()), list(tax.values()), db) - loop = asyncio.new_event_loop() - redis: ArqRedis = loop.run_until_complete(create_pool(redis_settings)) - loop.run_until_complete( - redis.enqueue_job( - "sent_to_printer", data, printer.address, printer.cut_code, _queue_name=f"barker:print:{printer.name}" - ) - ) - loop.close() + return list([item for item in items_dict.values() if item.quantity != 0]) -def get_tax_item( - id_: uuid.UUID, tax_name: str, amount: Decimal, tax_dict: dict[tuple[uuid.UUID, int], tuple[str, Decimal]] -) -> None: +def get_regimes( + inventories: List[Inventory], voucher_type: VoucherType, db: Session +) -> List[Tuple[int, List[Inventory]]]: + if voucher_type != VoucherType.REGULAR_BILL: + return [ + (int(voucher_type), inventories), + ] + + items_dict: Dict[int, List[Inventory]] = {} + + for inv in inventories: + key = inv.tax.regime_id + if key not in items_dict: + items_dict[key] = [] + items_dict[key].append(inv) + return list(items_dict.items()) + + +def get_tax_item(id_: uuid.UUID, tax_name: str, amount: Decimal, taxes: Dict[str, Decimal]) -> None: items = tax_name.split(";") if len(items) == 1: - key = (id_, 0) - old_amount = tax_dict[key][1] if key in tax_dict else Decimal(0) - tax_dict[key] = tax_name, round(amount + old_amount, 2) + if tax_name not in taxes: + taxes[tax_name] = Decimal(0) + taxes[tax_name] += round(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 Decimal(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: Decimal = round(amount * Decimal(match.group(2)) / Decimal(match.group(3)), 2) - tax_dict[(id_, i)] = match.group(1), round(sub_amount + old_amount, 2) + if not match.group(1) in taxes: + taxes[match.group(1)] = Decimal(0) + taxes[match.group(1)] += round(sub_amount, 2) def design_bill( voucher: Voucher, - items: list[Inventory], - tax: list[tuple[str, Decimal]], + regime_id: int, + inventories: List[Inventory], + taxes: List[Tuple[str, Decimal]], db: Session, -): +) -> str: # Header s = "\n\r" + db.execute(select(DbSetting).where(DbSetting.name == "Header")).scalar_one().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" - + # Bill Regime + bill_object: Bill = db.execute( + select(Bill) + .join(Bill.regime) + .where(Bill.voucher_id == voucher.id, Bill.regime_id == regime_id, Bill.is_valid == True) # noqa: E712 + ).scalar_one() + s += "\n\r" + bill_object.regime.header # Products - product_date = ( - voucher.date + timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES - settings.NEW_DAY_OFFSET_MINUTES) - ).date() voucher_date = voucher.date + timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES) - s += "\n\r" + f"Bill No: {voucher.full_bill_id:>12} {voucher_date:%d-%b-%Y %H:%M}" + 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}" 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]: + for inv in inventories: product: ProductVersion = db.execute( select(ProductVersion).where( and_( - ProductVersion.product_id == item.product_id, + ProductVersion.product_id == inv.product_id, or_( ProductVersion.valid_from == None, # noqa: E711 ProductVersion.valid_from <= product_date, @@ -135,39 +164,34 @@ def design_bill( ) ) ).scalar_one() - name = "H H " + product.full_name if item.is_happy_hour else product.full_name + name = "H H " + product.full_name if inv.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}" + 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 item.modifiers if m.modifier.show_in_bill]: + 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 items) + amount = sum(round(i.quantity * i.price, 2) for i in inventories) s += f"\n\r{'Subtotal :': >32} {currency_format(amount): >9}" - amount = sum(round(i.quantity * i.price, 2) for i in items if i.is_happy_hour) + amount = sum(round(i.quantity * i.price, 2) for i in inventories if i.is_happy_hour) 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 items) + amount = sum(round(i.quantity * i.effective_price * i.discount, 2) for i in inventories) 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}" + 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(voucher.amount)): >9}" + s += f"\n\r{'Total Amount :': >32} {currency_format(round(sum([i.amount for i in inventories]))): >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 @@ -179,3 +203,33 @@ def design_bill( s += "\n\r" + "Cashier : " + voucher.user.name s += "\n\r" + db.execute(select(DbSetting).where(DbSetting.name == "Footer")).scalar_one().data["Text"] return s + + +def design_total( + voucher: Voucher, + regimes: List[Tuple[int, List[Inventory]]], + db: Session, +) -> str: + # Header + s = "\n\r" + db.execute(select(DbSetting).where(DbSetting.name == "Header")).scalar_one().data["Text"] + voucher_date = voucher.date + timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES) + + s += "\n\r" + f"{voucher_date:%d-%b-%Y %H:%M} Table No.: {voucher.food_table.name} " + s += "\n\r" + "-" * 42 + s += "\n\r" + " Total Amount " + s += "\n\r" + "------------------------------------------" + # Bill Regime + for regime_id, inventories in regimes: + bill_object: Bill = db.execute( + select(Bill) + .join(Bill.regime) + .where(Bill.voucher_id == voucher.id, Bill.regime_id == regime_id, Bill.is_valid == True) # noqa: E712 + ).scalar_one() + amount = currency_format(round(sum([i.amount for i in inventories]))) + bill_number = f"{bill_object.regime.prefix}-{bill_object.bill_number}" + s += "\n\r" + f"Bill No: {bill_number:>12} Amount: {amount: >9}" + s += "\n\r" + "------------------------------------------" + s += f"\n\r{'Total Amount :': >32} {currency_format(round(voucher.amount)): >9}" + s += "\n\r" + "-" * 42 + s += "\n\r" + db.execute(select(DbSetting).where(DbSetting.name == "Footer")).scalar_one().data["Text"] + return s diff --git a/barker/barker/printing/kot.py b/barker/barker/printing/kot.py index db40a57..752548d 100644 --- a/barker/barker/printing/kot.py +++ b/barker/barker/printing/kot.py @@ -88,7 +88,7 @@ def print_kot(voucher_id: uuid.UUID, db: Session): ) ).scalar_one() - printer, copies = db.execute( + row = db.execute( select(Printer, SectionPrinter.copies) .join(SectionPrinter.printer) .where(SectionPrinter.section_id == voucher.food_table.section_id) @@ -100,6 +100,8 @@ def print_kot(voucher_id: uuid.UUID, db: Session): ) .order_by(SectionPrinter.sale_category_id) ).first() + assert row is not None + printer, copies = row key = (printer.id, copies) if key not in my_hash: my_hash[key] = (printer, []) diff --git a/barker/barker/routers/customer.py b/barker/barker/routers/customer.py index 1836675..1ab4e92 100644 --- a/barker/barker/routers/customer.py +++ b/barker/barker/routers/customer.py @@ -66,7 +66,7 @@ def update_route( def add_discounts(customer: Customer, discounts: list[schemas.DiscountItem], db: Session) -> None: for discount in discounts: - cd = next((d for d in customer.discounts if d.sale_category_id == discount.id_), None) + cd: CustomerDiscount | None = next((d for d in customer.discounts if d.sale_category_id == discount.id_), None) if cd is None: cd = CustomerDiscount(discount.id_, round(discount.discount, 5), customer=customer) customer.discounts.append(cd) @@ -142,7 +142,7 @@ def customer_info(item: Customer, sale_categories: Sequence[SaleCategory]) -> sc { "id": sc.id, "name": sc.name, - "discount": next((d.discount for d in item.discounts if d.sale_category_id == sc.id), 0), + "discount": next((d.discount for d in item.discounts if d.sale_category_id == sc.id), None), } for sc in sale_categories ], @@ -159,7 +159,7 @@ def blank_customer_info(sale_categories: Sequence[SaleCategory]) -> schemas.Cust { "id": sc.id, "name": sc.name, - "discount": 0, + "discount": None, } for sc in sale_categories ], diff --git a/barker/barker/routers/regime.py b/barker/barker/routers/regime.py new file mode 100644 index 0000000..3a54fce --- /dev/null +++ b/barker/barker/routers/regime.py @@ -0,0 +1,126 @@ +import barker.schemas.regime as schemas + +from fastapi import APIRouter, Depends, HTTPException, Security, status +from sqlalchemy import func, select +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.sql.functions import count + +from ..core.security import get_current_active_user as get_user +from ..db.session import SessionFuture +from ..models.bill import Bill +from ..models.regime import Regime +from ..models.tax import Tax +from ..schemas.user_token import UserToken + + +router = APIRouter() + + +@router.post("", response_model=schemas.Regime) +def save( + data: schemas.RegimeIn, + user: UserToken = Security(get_user, scopes=["regimes"]), +) -> schemas.Regime: + try: + with SessionFuture() as db: + regime_id = db.execute(select(func.coalesce(func.max(Regime.id), 0) + 1)).scalar_one() + item = Regime(id=regime_id, name=data.name, header=data.header, prefix=data.prefix) + db.add(item) + db.commit() + return regime_info(item) + except SQLAlchemyError as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + + +@router.put("/{id_}", response_model=schemas.Regime) +def update_route( + id_: int, + data: schemas.RegimeIn, + user: UserToken = Security(get_user, scopes=["regimes"]), +) -> schemas.Regime: + try: + with SessionFuture() as db: + item: Regime = db.execute(select(Regime).where(Regime.id == id_)).scalar_one() + if item.is_fixture: + raise HTTPException( + status_code=status.HTTP_423_LOCKED, + detail=f"{item.name} is a fixture and cannot be edited or deleted.", + ) + item.name = data.name + item.header = data.header + item.prefix = data.prefix + db.commit() + return regime_info(item) + except SQLAlchemyError as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + + +@router.delete("/{id_}", response_model=schemas.RegimeBlank) +def delete_route( + id_: int, + user: UserToken = Security(get_user, scopes=["regimes"]), +) -> schemas.RegimeBlank: + with SessionFuture() as db: + item: Regime = db.execute(select(Regime).where(Regime.id == id_)).scalar_one() + if item.is_fixture: + raise HTTPException( + status_code=status.HTTP_423_LOCKED, + detail=f"{item.name} is a fixture and cannot be edited or deleted.", + ) + if db.execute(select(count(Tax.regime_id)).where(Tax.regime_id == item.id)).scalar_one() > 0: + raise HTTPException( + status_code=status.HTTP_423_LOCKED, + detail=f"{item.name} has associated Taxes and cannot be deleted", + ) + if db.execute(select(count(Bill.regime_id)).where(Bill.regime_id == item.id)).scalar_one() > 0: + raise HTTPException( + status_code=status.HTTP_423_LOCKED, + detail=f"{item.name} has associated Bills and cannot be deleted", + ) + db.delete(item) + db.commit() + return regime_blank() + + +@router.get("", response_model=schemas.RegimeBlank) +def show_blank( + user: UserToken = Security(get_user, scopes=["regimes"]), +) -> schemas.RegimeBlank: + return regime_blank() + + +@router.get("/list", response_model=list[schemas.Regime]) +def show_list(user: UserToken = Depends(get_user)) -> list[schemas.Regime]: + with SessionFuture() as db: + items = db.execute(select(Regime).order_by(Regime.name)).scalars().all() + return [regime_info(item) for item in items] + + +@router.get("/{id_}", response_model=schemas.Regime) +def show_id( + id_: int, + user: UserToken = Security(get_user, scopes=["regimes"]), +) -> schemas.Regime: + with SessionFuture() as db: + item: Regime = db.execute(select(Regime).where(Regime.id == id_)).scalar_one() + return regime_info(item) + + +def regime_info(item: Regime) -> schemas.Regime: + return schemas.Regime( + id=item.id, + name=item.name, + header=item.header, + prefix=item.prefix, + isFixture=item.is_fixture, + ) + + +def regime_blank() -> schemas.RegimeBlank: + return schemas.RegimeBlank(name="", header="", prefix="", isFixture=False) diff --git a/barker/barker/routers/reports/bill_settlement_report.py b/barker/barker/routers/reports/bill_settlement_report.py index 6d8be5e..76a0d5e 100644 --- a/barker/barker/routers/reports/bill_settlement_report.py +++ b/barker/barker/routers/reports/bill_settlement_report.py @@ -49,7 +49,6 @@ def settlements(s: date, f: date, db: Session) -> list[BillSettlementItem]: .join(Settlement.settle_option) .where(Voucher.date >= start_date, Voucher.date <= finish_date) .order_by(Voucher.voucher_type) - .order_by(Voucher.bill_id) ) .unique() .scalars() @@ -68,7 +67,7 @@ def settlements(s: date, f: date, db: Session) -> list[BillSettlementItem]: report.append( BillSettlementItem( date=item.date, - billId=item.full_bill_id, + billId=", ".join(f"{x.regime.prefix}-{x.bill_number}" for x in item.bills), settlement=details, amount=round(so.amount, 2), ) @@ -86,7 +85,7 @@ def reprints(s: date, f: date, db: Session) -> list[BillSettlementItem]: return [ BillSettlementItem( date=item.date, - billId=item.voucher.full_bill_id, + billId=", ".join(f"{x.regime.prefix}-{x.bill_number}" for x in item.voucher.bills), settlement=f"Reprinted by {item.user.name}", amount=round( next(s.amount for s in item.voucher.settlements if s.settled == SettleOption.AMOUNT()) * -1, diff --git a/barker/barker/routers/reports/cashier_report.py b/barker/barker/routers/reports/cashier_report.py index f7ce034..63a0ce3 100644 --- a/barker/barker/routers/reports/cashier_report.py +++ b/barker/barker/routers/reports/cashier_report.py @@ -96,7 +96,6 @@ def get_id(id_: uuid.UUID, start_date: date, finish_date: date, user: UserLink, Voucher.user_id == id_, ) .order_by(Voucher.voucher_type) - .order_by(Voucher.bill_id) ) .unique() .scalars() @@ -117,7 +116,7 @@ def get_id(id_: uuid.UUID, start_date: date, finish_date: date, user: UserLink, info[so.settle_option.name].append( InfoItem( date=item.date, - billId=item.full_bill_id, + billId=", ".join(f"{x.regime.prefix}-{x.bill_number}" for x in item.bills), customer=item.customer.name if item.customer is not None else "", amount=so.amount, ) diff --git a/barker/barker/routers/tax.py b/barker/barker/routers/tax.py index 09b4644..8aba1d9 100644 --- a/barker/barker/routers/tax.py +++ b/barker/barker/routers/tax.py @@ -28,7 +28,7 @@ def save( ) -> schemas.Tax: try: with SessionFuture() as db: - item = Tax(name=data.name, rate=round(data.rate, 5)) + item = Tax(name=data.name, rate=round(data.rate, 5), regime_id=data.regime.id_) if not name_valid(data.name): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -63,6 +63,7 @@ def update_route( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="The name is not valid", ) + item.regime_id = data.regime.id_ item.name = data.name item.rate = round(data.rate, 5) db.commit() @@ -129,6 +130,7 @@ def tax_info(item: Tax) -> schemas.Tax: id=item.id, name=item.name, rate=item.rate, + regime=schemas.RegimeLink(id=item.regime_id, name=item.regime.name), isFixture=item.is_fixture, ) diff --git a/barker/barker/routers/voucher/__init__.py b/barker/barker/routers/voucher/__init__.py index 344c785..cd11008 100644 --- a/barker/barker/routers/voucher/__init__.py +++ b/barker/barker/routers/voucher/__init__.py @@ -1,12 +1,16 @@ import uuid +from datetime import datetime from decimal import Decimal +from typing import List, Set import barker.schemas.voucher as schemas +from barker.models.bill import Bill from fastapi import HTTPException, status -from sqlalchemy import func, select +from sqlalchemy import func from sqlalchemy.orm import Session +from sqlalchemy.sql import expression from ...models.guest_book import GuestBook from ...models.overview import Overview @@ -29,17 +33,6 @@ def get_tax(tax, voucher_type): ) -def get_bill_id(voucher_type: VoucherType, db: Session) -> int | None: - if voucher_type == VoucherType.KOT: - return None - bill_id = db.execute( - select(func.coalesce(func.max(Voucher.bill_id), 0) + 1).where(Voucher.voucher_type == voucher_type) - ).scalar_one() - if voucher_type == VoucherType.REGULAR_BILL and bill_id % 10000 == 0: - bill_id += 1 - return bill_id - - def do_update_table(item: Voucher, guest_book: GuestBook | None, db: Session): status_ = "running" if item.voucher_type == VoucherType.KOT else "printed" if item.status is None: @@ -90,7 +83,7 @@ def check_permissions(item: Voucher | None, voucher_type: VoucherType, permissio def get_guest_book(id_: uuid.UUID | None, db: Session) -> GuestBook | None: if id_ is None: return id_ - return db.execute(select(GuestBook).where(GuestBook.id == id_)).scalar_one() + return db.execute(expression.select(GuestBook).where(GuestBook.id == id_)).scalar_one() def do_update_settlements(voucher: Voucher, others: list[SettleSchema], db: Session) -> bool: @@ -127,6 +120,33 @@ def do_update_settlements(voucher: Voucher, others: list[SettleSchema], db: Sess return fully_settled +def do_update_bill_numbers(voucher: Voucher, db: Session) -> bool: + regimes: Set[int] = set() + old_regimes: Set[int] = set() + old_regimes = set( + db.execute(expression.select(Bill.regime_id).where(Bill.voucher_id == voucher.id)).scalars().all() + ) + if voucher.voucher_type != VoucherType.REGULAR_BILL: + regimes = set([int(voucher.voucher_type)]) + else: + regimes = set(i.tax.regime.id for k in voucher.kots for i in k.inventories) + + db.execute( + expression.update(Bill) + .where(Bill.voucher_id == voucher.id, Bill.regime_id.in_(old_regimes - regimes)) + .values(is_valid=False) + ) + + for r in regimes - old_regimes: + bill_id = db.execute( + expression.select(func.coalesce(func.max(Bill.bill_number), 0) + 1).where(Bill.regime_id == r) + ).scalar_one() + bill = Bill(regime_id=r, is_valid=True, bill_number=bill_id) + voucher.bills.append(bill) + db.add(bill) + return True + + 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) products = set(i.product.id_ for i in inventories if i.is_happy_hour) diff --git a/barker/barker/routers/voucher/change.py b/barker/barker/routers/voucher/change.py index 5d9927d..2254695 100644 --- a/barker/barker/routers/voucher/change.py +++ b/barker/barker/routers/voucher/change.py @@ -16,7 +16,11 @@ from ...models.voucher import Voucher from ...models.voucher_type import VoucherType from ...printing.bill import print_bill from ...printing.kot import print_kot -from ...routers.voucher import do_update_settlements, get_guest_book +from ...routers.voucher import ( + do_update_bill_numbers, + do_update_settlements, + get_guest_book, +) from ...schemas.receive_payment import ReceivePaymentItem as SettleSchema from ...schemas.user_token import UserToken from .save import do_save @@ -82,9 +86,11 @@ def void_and_issue_new_bill( guest_book = get_guest_book(g, db) item: Voucher = do_save(data, old.voucher_type, guest_book, db, user) db.flush() - old.reason = f"Bill Discounted or Changed / Old Bill: {old.full_bill_id} / New Bill: {item.full_bill_id}." - old.bill_id = None + new_bill_id = (", ".join(f"{x.regime.prefix}-{x.bill_number}" for x in item.bills),) + old_bill_id = (", ".join(f"{x.regime.prefix}-{x.bill_number}" for x in old.bills),) + old.reason = f"Bill Discounted or Changed / Old Bill: {old_bill_id} / New Bill: {new_bill_id}." old.voucher_type = VoucherType.VOID + do_update_bill_numbers(old, db) do_update_settlements(old, [SettleSchema(id=SettleOption.VOID(), amount=round(old.amount))], db) if update_table: diff --git a/barker/barker/routers/voucher/merge_move.py b/barker/barker/routers/voucher/merge_move.py index bfa99dd..8e7f66d 100644 --- a/barker/barker/routers/voucher/merge_move.py +++ b/barker/barker/routers/voucher/merge_move.py @@ -16,7 +16,11 @@ from ...models.overview import Overview from ...models.settlement import Settlement from ...models.voucher import Voucher from ...models.voucher_type import VoucherType -from ...routers.voucher import do_update_settlements, do_update_table, get_bill_id +from ...routers.voucher import ( + do_update_bill_numbers, + do_update_settlements, + do_update_table, +) from ...schemas.user_token import UserToken @@ -69,13 +73,11 @@ def move_kot( detail="Single kot cannot be used, move table instead. This error should not show up in frontend", ) kot = db.execute(select(Kot).where(Kot.id == data.kot_id)).scalar_one() - bill_id = get_bill_id(kot.voucher.voucher_type, db) kot_id = db.execute(select(func.coalesce(func.max(Voucher.kot_id), 0) + 1)).scalar_one() item = Voucher( now, kot.voucher.pax, - bill_id, kot_id, data.table_id, kot.voucher.customer_id if kot.voucher.customer is not None else None, @@ -85,6 +87,7 @@ def move_kot( db.add(item) item.kots.append(kot) db.flush() + do_update_bill_numbers(item, db) do_update_table(item, None, db) update_settlements([data.voucher_id, item.id], db) db.commit() diff --git a/barker/barker/routers/voucher/save.py b/barker/barker/routers/voucher/save.py index 70670ed..ceefea0 100644 --- a/barker/barker/routers/voucher/save.py +++ b/barker/barker/routers/voucher/save.py @@ -24,9 +24,9 @@ from ...printing.bill import print_bill from ...printing.kot import print_kot from ...routers.voucher import ( check_permissions, + do_update_bill_numbers, do_update_settlements, do_update_table, - get_bill_id, get_guest_book, get_tax, happy_hour_has_discount, @@ -78,13 +78,11 @@ def do_save( product_date = (now + timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES - settings.NEW_DAY_OFFSET_MINUTES)).date() check_permissions(None, voucher_type, user.permissions) - bill_id = get_bill_id(voucher_type, db) kot_id = db.execute(select(func.coalesce(func.max(Voucher.kot_id), 0) + 1)).scalar_one() item = Voucher( now, guest_book.pax if guest_book is not None else data.pax, - bill_id, kot_id, data.table.id_, data.customer.id_ if data.customer is not None else None, @@ -165,6 +163,7 @@ def do_save( db.add(mod) db.flush() do_update_settlements(item, [], db) + do_update_bill_numbers(item, db) if len(item.kots) == 0 or len(item.kots[0].inventories) == 0: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, diff --git a/barker/barker/routers/voucher/show.py b/barker/barker/routers/voucher/show.py index f5d5856..941052a 100644 --- a/barker/barker/routers/voucher/show.py +++ b/barker/barker/routers/voucher/show.py @@ -5,15 +5,17 @@ from datetime import datetime, timedelta from fastapi import APIRouter, HTTPException, Security, status from sqlalchemy import and_, or_, select -from sqlalchemy.orm import Session +from sqlalchemy.orm import Session, joinedload from ...core.config import settings from ...core.security import get_current_active_user as get_user from ...db.session import SessionFuture +from ...models.bill import Bill from ...models.food_table import FoodTable from ...models.guest_book import GuestBook from ...models.overview import Overview from ...models.product_version import ProductVersion +from ...models.regime import Regime from ...models.voucher import Voucher from ...models.voucher_type import VoucherType from ...schemas.user_token import UserToken @@ -38,37 +40,19 @@ def from_bill( user: UserToken = Security(get_user), ): with SessionFuture() as db: - query = select(Voucher) - if re.compile(r"^\d{1,3}-\d{1,4}$").match(id_): - s, n = id_.split("-") - i = (int(s) * 10000) + int(n) - query = query.where(Voucher.bill_id == i, Voucher.voucher_type == VoucherType.REGULAR_BILL) - elif re.compile(r"^K-\d+$").match(id_): - query = query.where( - Voucher.kot_id == int(id_.replace("K-", "")), - Voucher.voucher_type == VoucherType.KOT, - ) - elif re.compile(r"^NC-\d+$").match(id_): - query = query.where( - Voucher.bill_id == int(id_.replace("NC-", "")), - Voucher.voucher_type == VoucherType.NO_CHARGE, - ) - elif re.compile(r"^ST-\d+$").match(id_): - query = query.where( - Voucher.bill_id == int(id_.replace("ST-", "")), - Voucher.voucher_type == VoucherType.STAFF, - ) - elif re.compile(r"^V-\d+$").match(id_): - query = query.where( - Voucher.kot_id == int(id_.replace("V-", "")), - Voucher.voucher_type == VoucherType.VOID, - ) - else: + match = re.compile(r"^(\w+)-(\d+)$").match(id_) + if not match or len(match.groups()) != 2: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Bill Number is invalid", ) - item: Voucher | None = db.execute(query).scalars().one_or_none() + voucher_id = ( + select(Bill.voucher_id) + .join(Bill.regime) + .where(Regime.prefix == match.group(1), Bill.bill_number == int(match.group(2))) + .options(joinedload(Regime.prefix, innerjoin=True)) + ) + item = db.execute(select(Voucher).where(Voucher.id.in_(voucher_id))).scalars().one_or_none() if item is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -162,7 +146,7 @@ def voucher_info(item: Voucher, db: Session): "%d-%b-%Y %H:%M:%S" ), "kotId": item.kot_id, - "billId": item.full_bill_id, + "billId": ", ".join(f"{x.regime.prefix}-{x.bill_number}" for x in item.bills), "table": {"id": item.food_table_id, "name": item.food_table.name}, "customer": {"id": item.customer_id, "name": item.customer.name} if item.customer is not None else None, "guest": None, # TODO: Wire this functionality diff --git a/barker/barker/routers/voucher/split.py b/barker/barker/routers/voucher/split.py index a9c635a..50a1b15 100644 --- a/barker/barker/routers/voucher/split.py +++ b/barker/barker/routers/voucher/split.py @@ -22,9 +22,9 @@ from ...models.voucher import Voucher from ...models.voucher_type import VoucherType from ...routers.voucher import ( check_permissions, + do_update_bill_numbers, do_update_settlements, do_update_table, - get_bill_id, ) from ...schemas.receive_payment import ReceivePaymentItem as SettleSchema from ...schemas.user_token import UserToken @@ -45,10 +45,10 @@ def split( now = datetime.utcnow() update_table = u item: Voucher = db.execute(select(Voucher).where(Voucher.id == id_)).scalar_one() - item.bill_id = None original_voucher_type = item.voucher_type item.voucher_type = VoucherType.VOID item.reason = "Bill Split" + do_update_bill_numbers(item, db) do_update_settlements(item, [SettleSchema(id=SettleOption.VOID(), amount=round(item.amount))], db) if update_table: db.execute(delete(Overview).where(Overview.voucher_id == item.id)) @@ -115,10 +115,9 @@ def save( 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.execute(select(func.coalesce(func.max(Voucher.kot_id), 0) + 1)).scalar_one() - item: Voucher = Voucher(now, pax, bill_id, kot_id, table_id, customer_id, voucher_type, user_id) + item: Voucher = Voucher(now, pax, 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): @@ -150,6 +149,7 @@ def save( inv.modifiers.append(mod) db.add(mod) db.flush() + do_update_bill_numbers(item, db) do_update_settlements(item, [], db) db.flush() return item diff --git a/barker/barker/routers/voucher/update.py b/barker/barker/routers/voucher/update.py index be83b62..5828b60 100644 --- a/barker/barker/routers/voucher/update.py +++ b/barker/barker/routers/voucher/update.py @@ -22,9 +22,9 @@ from ...printing.bill import print_bill from ...printing.kot import print_kot from ...routers.voucher import ( check_permissions, + do_update_bill_numbers, do_update_settlements, do_update_table, - get_bill_id, get_guest_book, get_tax, happy_hour_has_discount, @@ -71,9 +71,6 @@ def update_route( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Internal error, reprints should not reach here", ) - if item.voucher_type == VoucherType.KOT and voucher_type != VoucherType.KOT: - item.date = now - item.bill_id = get_bill_id(voucher_type, db) item.voucher_type = voucher_type item.user_id = user.id_ item.last_edit_date = now @@ -138,12 +135,14 @@ def update_route( detail=f"You are not allowed to delete printed products." f"\n In this case {product.full_name}", ) - minimum = round( - sum( - inv.quantity - for ko in item.kots - for inv in ko.inventories - if ko.id and inv.product_id == product.product_id + minimum: Decimal = round( + Decimal( + sum( + inv.quantity + for ko in item.kots + for inv in ko.inventories + if ko.id is not None and inv.product_id == product.product_id + ) ) * -1, 2, @@ -171,6 +170,7 @@ def update_route( mod = InventoryModifier(None, m.id_, Decimal(0)) inv.modifiers.append(mod) db.add(mod) + do_update_bill_numbers(item, db) do_update_settlements(item, [], db) if update_table: do_update_table(item, guest_book, db) diff --git a/barker/barker/routers/voucher/void.py b/barker/barker/routers/voucher/void.py index 0d31118..7a2ce77 100644 --- a/barker/barker/routers/voucher/void.py +++ b/barker/barker/routers/voucher/void.py @@ -12,7 +12,7 @@ from ...models.voucher import Voucher from ...models.voucher_type import VoucherType from ...schemas.receive_payment import ReceivePaymentItem as SettleSchema from ...schemas.user_token import UserToken -from . import do_update_settlements +from . import do_update_bill_numbers, do_update_settlements router = APIRouter() @@ -28,9 +28,9 @@ def void( try: with SessionFuture() as db: item: Voucher = db.execute(select(Voucher).where(Voucher.id == id_)).scalar_one() - item.reason = f"{reason} / Old Bill ID was: {item.full_bill_id}" - item.bill_id = None + item.reason = f"{reason}" item.voucher_type = VoucherType.VOID + do_update_bill_numbers(item, db) do_update_settlements(item, [SettleSchema(id=SettleOption.VOID(), amount=round(item.amount))], db) if u: # Update table diff --git a/barker/barker/schemas/customer.py b/barker/barker/schemas/customer.py index 15e9b4d..9d9aa20 100644 --- a/barker/barker/schemas/customer.py +++ b/barker/barker/schemas/customer.py @@ -11,8 +11,8 @@ class DiscountItem(BaseModel): id_: uuid.UUID name: str discount: Decimal = Field(ge=0, multiple_of=0.0001, default=0, le=1) - discount_limit: Decimal | None - customer_discount: Decimal | None + discount_limit: Decimal + customer_discount: Decimal class Config: alias_generator = to_camel diff --git a/barker/barker/schemas/regime.py b/barker/barker/schemas/regime.py new file mode 100644 index 0000000..851b8d1 --- /dev/null +++ b/barker/barker/schemas/regime.py @@ -0,0 +1,40 @@ +from pydantic import BaseModel, Field + +from . import to_camel + + +class RegimeIn(BaseModel): + name: str = Field(..., min_length=1) + header: str + prefix: str = Field(..., min_length=1) + is_fixture: bool + + class Config: + anystr_strip_whitespace = True + alias_generator = to_camel + + +class Regime(RegimeIn): + id_: int + + class Config: + anystr_strip_whitespace = True + alias_generator = to_camel + + +class RegimeBlank(RegimeIn): + name: str + header: str + prefix: str + + class Config: + anystr_strip_whitespace = True + alias_generator = to_camel + + +class RegimeLink(BaseModel): + id_: int = Field(...) + name: str | None + + class Config: + fields = {"id_": "id"} diff --git a/barker/barker/schemas/tax.py b/barker/barker/schemas/tax.py index 85043d7..bd88a81 100644 --- a/barker/barker/schemas/tax.py +++ b/barker/barker/schemas/tax.py @@ -2,6 +2,7 @@ import uuid from decimal import Decimal +from barker.schemas.regime import RegimeLink from pydantic import BaseModel, Field from . import to_camel @@ -10,6 +11,7 @@ from . import to_camel class TaxIn(BaseModel): name: str = Field(..., min_length=1) rate: Decimal = Field(ge=0, default=0) + regime: RegimeLink = Field(...) is_fixture: bool class Config: @@ -25,8 +27,10 @@ class Tax(TaxIn): alias_generator = to_camel -class TaxBlank(TaxIn): +class TaxBlank(BaseModel): name: str + rate: Decimal = Field(ge=0, default=0) + is_fixture: bool class Config: anystr_strip_whitespace = True diff --git a/barker/barker/worker.py b/barker/barker/worker.py index 076964a..460e453 100644 --- a/barker/barker/worker.py +++ b/barker/barker/worker.py @@ -1,11 +1,10 @@ import sys from aiohttp import ClientSession +from barker.core.arq import settings from barker.core.config import settings as sett from barker.tasks.printing import sent_to_printer -from .core.arq_worker import settings - sys.path.extend(["./"]) diff --git a/bookie/src/app/app-routing.module.ts b/bookie/src/app/app-routing.module.ts index 28f2bd6..68e0d89 100644 --- a/bookie/src/app/app-routing.module.ts +++ b/bookie/src/app/app-routing.module.ts @@ -56,6 +56,10 @@ const routes: Routes = [ (mod) => mod.ModifierCategoriesModule, ), }, + { + path: 'regimes', + loadChildren: () => import('./regimes/regimes.module').then((mod) => mod.RegimesModule), + }, { path: 'printers', loadChildren: () => import('./printers/printers.module').then((mod) => mod.PrintersModule), diff --git a/bookie/src/app/auth/login/login.component.ts b/bookie/src/app/auth/login/login.component.ts index 304fa06..8e4213d 100644 --- a/bookie/src/app/auth/login/login.component.ts +++ b/bookie/src/app/auth/login/login.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { ToasterService } from '../../core/toaster.service'; @@ -13,7 +13,11 @@ import { AuthService } from '../auth.service'; }) export class LoginComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; - form: UntypedFormGroup; + form: FormGroup<{ + username: FormControl; + password: FormControl; + }>; + hide: boolean; unregisteredDevice: boolean; deviceName: string; @@ -25,7 +29,6 @@ export class LoginComponent implements OnInit, AfterViewInit { private router: Router, private toaster: ToasterService, private cs: CookieService, - private fb: UntypedFormBuilder, ) { this.hide = true; this.unregisteredDevice = false; @@ -33,15 +36,14 @@ export class LoginComponent implements OnInit, AfterViewInit { this.returnUrl = ''; // Create form - this.form = this.fb.group({ - username: '', - password: '', - otp: '', + this.form = new FormGroup({ + username: new FormControl('', { validators: Validators.required, nonNullable: true }), + password: new FormControl('', { validators: Validators.required, nonNullable: true }), }); } ngOnInit() { - this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/'; + this.returnUrl = this.route.snapshot.queryParams['returnUrl'] ?? '/'; } ngAfterViewInit() { @@ -54,8 +56,8 @@ export class LoginComponent implements OnInit, AfterViewInit { login(): void { const formModel = this.form.value; - const { username } = formModel; - const { password } = formModel; + const username = formModel.username ?? ''; + const password = formModel.password ?? ''; this.auth .login(username.trim(), password.trim()) // .pipe(first()) diff --git a/bookie/src/app/beer-sale-report/beer-sale-report-resolver.service.ts b/bookie/src/app/beer-sale-report/beer-sale-report-resolver.service.ts index 002c312..7004ff7 100644 --- a/bookie/src/app/beer-sale-report/beer-sale-report-resolver.service.ts +++ b/bookie/src/app/beer-sale-report/beer-sale-report-resolver.service.ts @@ -12,8 +12,8 @@ export class BeerSaleReportResolver implements Resolve { constructor(private ser: BeerSaleReportService) {} resolve(route: ActivatedRouteSnapshot): Observable { - const startDate = route.queryParamMap.get('startDate') || null; - const finishDate = route.queryParamMap.get('finishDate') || null; + const startDate = route.queryParamMap.get('startDate') ?? null; + const finishDate = route.queryParamMap.get('finishDate') ?? null; const regular = route.queryParamMap.get('regular') !== 'false'; const happy = route.queryParamMap.get('happy') !== 'false'; const staff = route.queryParamMap.get('staff') !== 'false'; diff --git a/bookie/src/app/beer-sale-report/beer-sale-report.component.ts b/bookie/src/app/beer-sale-report/beer-sale-report.component.ts index da48d1e..82de53b 100644 --- a/bookie/src/app/beer-sale-report/beer-sale-report.component.ts +++ b/bookie/src/app/beer-sale-report/beer-sale-report.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; @@ -17,25 +17,27 @@ import { BeerSaleReportDataSource } from './beer-sale-report-datasource'; export class BeerSaleReportComponent implements OnInit { info: BeerSaleReport = new BeerSaleReport(); dataSource: BeerSaleReportDataSource = new BeerSaleReportDataSource(this.info.data); - form: UntypedFormGroup; + form: FormGroup<{ + startDate: FormControl; + finishDate: FormControl; + regular: FormControl; + happy: FormControl; + staff: FormControl; + nc: FormControl; + }>; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns: string[] = ['date']; - constructor( - private route: ActivatedRoute, - private router: Router, - private fb: UntypedFormBuilder, - private toCsv: ToCsvService, - ) { + constructor(private route: ActivatedRoute, private router: Router, private toCsv: ToCsvService) { // Create form - this.form = this.fb.group({ - startDate: '', - finishDate: '', - regular: true, - happy: true, - staff: true, - nc: true, + this.form = new FormGroup({ + startDate: new FormControl(new Date(), { nonNullable: true }), + finishDate: new FormControl(new Date(), { nonNullable: true }), + regular: new FormControl(true, { nonNullable: true }), + happy: new FormControl(true, { nonNullable: true }), + staff: new FormControl(true, { nonNullable: true }), + nc: new FormControl(true, { nonNullable: true }), }); } diff --git a/bookie/src/app/bill-settlement-report/bill-settlement-report-resolver.service.ts b/bookie/src/app/bill-settlement-report/bill-settlement-report-resolver.service.ts index b8e4cf9..aaeb85d 100644 --- a/bookie/src/app/bill-settlement-report/bill-settlement-report-resolver.service.ts +++ b/bookie/src/app/bill-settlement-report/bill-settlement-report-resolver.service.ts @@ -12,8 +12,8 @@ export class BillSettlementReportResolver implements Resolve { - const startDate = route.queryParamMap.get('startDate') || null; - const finishDate = route.queryParamMap.get('finishDate') || null; + const startDate = route.queryParamMap.get('startDate') ?? null; + const finishDate = route.queryParamMap.get('finishDate') ?? null; return this.ser.get(startDate, finishDate); } } diff --git a/bookie/src/app/bill-settlement-report/bill-settlement-report.component.ts b/bookie/src/app/bill-settlement-report/bill-settlement-report.component.ts index 07ca2bb..329aa82 100644 --- a/bookie/src/app/bill-settlement-report/bill-settlement-report.component.ts +++ b/bookie/src/app/bill-settlement-report/bill-settlement-report.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; @@ -19,21 +19,19 @@ export class BillSettlementReportComponent implements OnInit { this.info.amounts, ); - form: UntypedFormGroup; + form: FormGroup<{ + startDate: FormControl; + finishDate: FormControl; + }>; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['date', 'billId', 'amount', 'settlement']; - constructor( - private route: ActivatedRoute, - private router: Router, - private fb: UntypedFormBuilder, - private toCsv: ToCsvService, - ) { + constructor(private route: ActivatedRoute, private router: Router, private toCsv: ToCsvService) { // Create form - this.form = this.fb.group({ - startDate: '', - finishDate: '', + this.form = new FormGroup({ + startDate: new FormControl(new Date(), { nonNullable: true }), + finishDate: new FormControl(new Date(), { nonNullable: true }), }); } diff --git a/bookie/src/app/cashier-report/active-cashiers-resolver.service.ts b/bookie/src/app/cashier-report/active-cashiers-resolver.service.ts index 142a293..f0063c6 100644 --- a/bookie/src/app/cashier-report/active-cashiers-resolver.service.ts +++ b/bookie/src/app/cashier-report/active-cashiers-resolver.service.ts @@ -13,8 +13,8 @@ export class ActiveCashiersResolver implements Resolve { constructor(private ser: CashierReportService) {} resolve(route: ActivatedRouteSnapshot): Observable { - const startDate = route.queryParamMap.get('startDate') || null; - const finishDate = route.queryParamMap.get('finishDate') || null; + const startDate = route.queryParamMap.get('startDate') ?? null; + const finishDate = route.queryParamMap.get('finishDate') ?? null; return this.ser.activeCashiers(startDate, finishDate); } } diff --git a/bookie/src/app/cashier-report/cashier-report-resolver.service.ts b/bookie/src/app/cashier-report/cashier-report-resolver.service.ts index 496cc97..ed169ec 100644 --- a/bookie/src/app/cashier-report/cashier-report-resolver.service.ts +++ b/bookie/src/app/cashier-report/cashier-report-resolver.service.ts @@ -13,8 +13,8 @@ export class CashierReportResolver implements Resolve { resolve(route: ActivatedRouteSnapshot): Observable { const id = route.paramMap.get('id'); - const startDate = route.queryParamMap.get('startDate') || null; - const finishDate = route.queryParamMap.get('finishDate') || null; + const startDate = route.queryParamMap.get('startDate') ?? null; + const finishDate = route.queryParamMap.get('finishDate') ?? null; return this.ser.list(id, startDate, finishDate); } } diff --git a/bookie/src/app/cashier-report/cashier-report.component.ts b/bookie/src/app/cashier-report/cashier-report.component.ts index 04ac3f7..b128552 100644 --- a/bookie/src/app/cashier-report/cashier-report.component.ts +++ b/bookie/src/app/cashier-report/cashier-report.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; @@ -20,7 +20,11 @@ export class CashierReportComponent implements OnInit { activeCashiers: User[] = []; info: CashierReport = new CashierReport(); dataSource: CashierReportDataSource = new CashierReportDataSource(this.info.amounts); - form: UntypedFormGroup; + form: FormGroup<{ + startDate: FormControl; + finishDate: FormControl; + cashier: FormControl; + }>; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['name', 'amount']; @@ -28,16 +32,15 @@ export class CashierReportComponent implements OnInit { constructor( private route: ActivatedRoute, private router: Router, - private fb: UntypedFormBuilder, private toCsv: ToCsvService, private toaster: ToasterService, private ser: CashierReportService, ) { // Create form - this.form = this.fb.group({ - startDate: '', - finishDate: '', - cashier: '', + this.form = new FormGroup({ + startDate: new FormControl(new Date(), { nonNullable: true }), + finishDate: new FormControl(new Date(), { nonNullable: true }), + cashier: new FormControl(null), }); } @@ -47,7 +50,7 @@ export class CashierReportComponent implements OnInit { this.activeCashiers = data.cashiers; this.info = data.info; this.form.setValue({ - cashier: this.info.cashier.id, + cashier: this.info.cashier.id ?? '', startDate: moment(this.info.startDate, 'DD-MMM-YYYY').toDate(), finishDate: moment(this.info.finishDate, 'DD-MMM-YYYY').toDate(), }); @@ -86,7 +89,7 @@ export class CashierReportComponent implements OnInit { const formModel = this.form.value; return new CashierReport({ - cashier: new User({ id: formModel.cashier }), + cashier: new User({ id: formModel.cashier ?? undefined }), cashiers: this.info.cashiers, startDate: moment(formModel.startDate).format('DD-MMM-YYYY'), finishDate: moment(formModel.finishDate).format('DD-MMM-YYYY'), diff --git a/bookie/src/app/core/customer-discount.ts b/bookie/src/app/core/customer-discount.ts index 17917a2..728d521 100644 --- a/bookie/src/app/core/customer-discount.ts +++ b/bookie/src/app/core/customer-discount.ts @@ -1,12 +1,12 @@ export class CustomerDiscount { id: string; name: string; - discount: number; + discount: number | null; public constructor(init?: Partial) { this.id = ''; this.name = ''; - this.discount = 0; + this.discount = null; Object.assign(this, init); } } diff --git a/bookie/src/app/core/customer.ts b/bookie/src/app/core/customer.ts index b914035..98bc0ac 100644 --- a/bookie/src/app/core/customer.ts +++ b/bookie/src/app/core/customer.ts @@ -4,7 +4,7 @@ export class Customer { id: string; name: string; phone: string; - address: string; + address: string | null; printInBill: boolean; discounts: CustomerDiscount[]; @@ -12,7 +12,7 @@ export class Customer { this.id = ''; this.name = ''; this.phone = ''; - this.address = ''; + this.address = null; this.printInBill = false; this.discounts = []; Object.assign(this, init); diff --git a/bookie/src/app/core/http-auth-interceptor.ts b/bookie/src/app/core/http-auth-interceptor.ts index 88e10df..04ac1c5 100644 --- a/bookie/src/app/core/http-auth-interceptor.ts +++ b/bookie/src/app/core/http-auth-interceptor.ts @@ -30,13 +30,13 @@ export class ErrorInterceptor implements HttpInterceptor { if (request.url.includes('/refresh')) { this.authService.logout(); } - return throwError(err); + return throwError(() => err); } // If error status is different than 401 we want to skip refresh token // So we check that and throw the error if it's the case if (err.status !== 401) { - const error = err.error.message || err.error.detail || err.statusText; - return throwError(error); + const error = err.error.message ?? err.error.detail ?? err.statusText; + return throwError(() => error); } // auto logout if 401 response returned from api this.authService.logout(); @@ -56,7 +56,7 @@ export class ErrorInterceptor implements HttpInterceptor { this.router.navigate(['login']); } }); - return throwError(err); + return throwError(() => err); }), ); } diff --git a/bookie/src/app/core/modifier-category.ts b/bookie/src/app/core/modifier-category.ts index c4c57a8..aed284d 100644 --- a/bookie/src/app/core/modifier-category.ts +++ b/bookie/src/app/core/modifier-category.ts @@ -6,7 +6,7 @@ export class ModifierCategory { id: string | undefined; name: string; minimum: number; - maximum: number; + maximum: number | null; isActive: boolean; menuCategories: MenuCategory[]; modifiers: Modifier[]; @@ -15,7 +15,7 @@ export class ModifierCategory { this.id = undefined; this.name = ''; this.minimum = 0; - this.maximum = 0; + this.maximum = null; this.isActive = true; this.menuCategories = []; this.modifiers = []; diff --git a/bookie/src/app/core/regime.ts b/bookie/src/app/core/regime.ts new file mode 100644 index 0000000..9ffc430 --- /dev/null +++ b/bookie/src/app/core/regime.ts @@ -0,0 +1,16 @@ +export class Regime { + id: number | undefined; + name: string; + header: string; + prefix: string; + isFixture: boolean; + + public constructor(init?: Partial) { + this.id = undefined; + this.name = ''; + this.header = ''; + this.prefix = ''; + this.isFixture = false; + Object.assign(this, init); + } +} diff --git a/bookie/src/app/core/tax.ts b/bookie/src/app/core/tax.ts index 31e0b8b..972aeb6 100644 --- a/bookie/src/app/core/tax.ts +++ b/bookie/src/app/core/tax.ts @@ -1,13 +1,17 @@ +import { Regime } from './regime'; + export class Tax { id: string | undefined; name: string; rate: number; + regime: Regime; isFixture: boolean; public constructor(init?: Partial) { this.id = undefined; this.name = ''; this.rate = 0; + this.regime = new Regime(); this.isFixture = false; Object.assign(this, init); } 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 29dfae8..f1f151e 100644 --- a/bookie/src/app/customers/customer-detail/customer-detail.component.ts +++ b/bookie/src/app/customers/customer-detail/customer-detail.component.ts @@ -1,10 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { - AbstractControl, - UntypedFormArray, - UntypedFormBuilder, - UntypedFormGroup, -} from '@angular/forms'; +import { FormArray, FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import { round } from 'mathjs'; @@ -21,26 +16,36 @@ import { CustomerService } from '../customer.service'; }) export class CustomerDetailComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; - form: UntypedFormGroup; + form: FormGroup<{ + name: FormControl; + phone: FormControl; + address: FormControl; + printInBill: FormControl; + discounts: FormArray< + FormGroup<{ + discount: FormControl; + }> + >; + }>; + item: Customer = new Customer(); hide: boolean; constructor( private route: ActivatedRoute, private router: Router, - private fb: UntypedFormBuilder, private toaster: ToasterService, private dialog: MatDialog, private ser: CustomerService, ) { this.hide = true; // Create form - this.form = this.fb.group({ - name: '', - phone: '', - address: '', - printInBill: false, - discounts: this.fb.array([]), + this.form = new FormGroup({ + name: new FormControl('', { nonNullable: true }), + phone: new FormControl('', { nonNullable: true }), + address: new FormControl(null), + printInBill: new FormControl(false, { nonNullable: true }), + discounts: new FormArray }>>([]), }); } @@ -53,18 +58,16 @@ export class CustomerDetailComponent implements OnInit, AfterViewInit { showItem(item: Customer) { this.item = item; - (this.form.get('name') as AbstractControl).setValue(item.name); - (this.form.get('phone') as AbstractControl).setValue(item.phone); - (this.form.get('address') as AbstractControl).setValue(item.address); - (this.form.get('printInBill') as AbstractControl).setValue(item.printInBill); - this.form.setControl( - 'discounts', - this.fb.array( - item.discounts.map((x) => - this.fb.group({ - discount: '' + x.discount * 100, - }), - ), + this.form.controls.name.setValue(item.name); + this.form.controls.phone.setValue(item.phone); + this.form.controls.address.setValue(item.address); + this.form.controls.printInBill.setValue(item.printInBill); + this.form.controls.discounts.clear(); + this.item.discounts.forEach((x) => + this.form.controls.discounts.push( + new FormGroup({ + discount: new FormControl(x.discount ? x.discount * 100 : null), + }), ), ); } @@ -116,17 +119,22 @@ export class CustomerDetailComponent implements OnInit, AfterViewInit { getItem(): Customer { const formModel = this.form.value; - this.item.name = formModel.name; - this.item.phone = formModel.phone; - this.item.address = formModel.address; - this.item.printInBill = formModel.printInBill; - const array = this.form.get('discounts') as UntypedFormArray; - this.item.discounts.forEach((item, index) => { - item.discount = Math.max( - Math.min(round(array.controls[index].value.discount / 100, 5), 100), - 0, - ); - }); + this.item.name = formModel.name ?? ''; + this.item.phone = formModel.phone ?? ''; + this.item.address = formModel.address ?? null; + this.item.printInBill = formModel.printInBill ?? false; + + const array = formModel.discounts; + if (array) { + this.item.discounts.forEach((item, index) => { + const array_item = array.at(index); + if (array_item && array_item?.discount) { + item.discount = Math.max(Math.min(round(array_item.discount / 100, 5), 100), 0); + } else { + item.discount = null; + } + }); + } return this.item; } } diff --git a/bookie/src/app/devices/device-detail/device-detail.component.ts b/bookie/src/app/devices/device-detail/device-detail.component.ts index 518b42b..260e7eb 100644 --- a/bookie/src/app/devices/device-detail/device-detail.component.ts +++ b/bookie/src/app/devices/device-detail/device-detail.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -16,7 +16,12 @@ import { DeviceService } from '../device.service'; }) export class DeviceDetailComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; - form: UntypedFormGroup; + form: FormGroup<{ + name: FormControl; + section: FormControl; + enabled: FormControl; + }>; + sections: Section[] = []; item: Device = new Device(); @@ -24,15 +29,14 @@ export class DeviceDetailComponent implements OnInit, AfterViewInit { private route: ActivatedRoute, private router: Router, private dialog: MatDialog, - private fb: UntypedFormBuilder, private toaster: ToasterService, private ser: DeviceService, ) { // Create form - this.form = this.fb.group({ - name: '', - section: '', - enabled: '', + this.form = new FormGroup({ + name: new FormControl('', { nonNullable: true }), + section: new FormControl(null), + enabled: new FormControl(false, { nonNullable: true }), }); } @@ -47,7 +51,7 @@ export class DeviceDetailComponent implements OnInit, AfterViewInit { showItem(item: Device) { this.item = item; this.form.setValue({ - name: this.item.name || '', + name: this.item.name ?? '', section: this.item.section.id ? this.item.section.id : '', enabled: this.item.enabled, }); @@ -100,9 +104,9 @@ export class DeviceDetailComponent implements OnInit, AfterViewInit { getItem(): Device { const formModel = this.form.value; - this.item.name = formModel.name; - this.item.section.id = formModel.section; - this.item.enabled = formModel.enabled; + this.item.name = formModel.name ?? ''; + this.item.section.id = formModel.section ?? ''; + this.item.enabled = formModel.enabled ?? true; return this.item; } } diff --git a/bookie/src/app/discount-report/discount-report-resolver.service.ts b/bookie/src/app/discount-report/discount-report-resolver.service.ts index fd74ae3..94577a8 100644 --- a/bookie/src/app/discount-report/discount-report-resolver.service.ts +++ b/bookie/src/app/discount-report/discount-report-resolver.service.ts @@ -12,8 +12,8 @@ export class DiscountReportResolver implements Resolve { constructor(private ser: DiscountReportService) {} resolve(route: ActivatedRouteSnapshot): Observable { - const startDate = route.queryParamMap.get('startDate') || null; - const finishDate = route.queryParamMap.get('finishDate') || null; + const startDate = route.queryParamMap.get('startDate') ?? null; + const finishDate = route.queryParamMap.get('finishDate') ?? null; return this.ser.get(startDate, finishDate); } } diff --git a/bookie/src/app/discount-report/discount-report.component.ts b/bookie/src/app/discount-report/discount-report.component.ts index bac885a..741dd4c 100644 --- a/bookie/src/app/discount-report/discount-report.component.ts +++ b/bookie/src/app/discount-report/discount-report.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; @@ -18,7 +18,10 @@ import { DiscountReportService } from './discount-report.service'; export class DiscountReportComponent implements OnInit { info: DiscountReport = new DiscountReport(); dataSource: DiscountReportDataSource = new DiscountReportDataSource(this.info.amounts); - form: UntypedFormGroup; + form: FormGroup<{ + startDate: FormControl; + finishDate: FormControl; + }>; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['name', 'amount']; @@ -26,15 +29,14 @@ export class DiscountReportComponent implements OnInit { constructor( private route: ActivatedRoute, private router: Router, - private fb: UntypedFormBuilder, private toCsv: ToCsvService, private toaster: ToasterService, private ser: DiscountReportService, ) { // Create form - this.form = this.fb.group({ - startDate: '', - finishDate: '', + this.form = new FormGroup({ + startDate: new FormControl(new Date(), { nonNullable: true }), + finishDate: new FormControl(new Date(), { nonNullable: true }), }); } diff --git a/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.ts b/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.ts index 6d572ca..cefc418 100644 --- a/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.ts +++ b/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.ts @@ -1,10 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { - UntypedFormBuilder, - UntypedFormControl, - UntypedFormGroup, - Validators, -} from '@angular/forms'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { ActivatedRoute, Router } from '@angular/router'; import { Observable, of as observableOf } from 'rxjs'; @@ -23,12 +18,17 @@ import { GuestBookService } from '../guest-book.service'; }) export class GuestBookDetailComponent implements OnInit, AfterViewInit { @ViewChild('phone', { static: true }) phoneElement?: ElementRef; - form: UntypedFormGroup; + form: FormGroup<{ + name: FormControl; + phone: FormControl; + pax: FormControl; + address: FormControl; + }>; + item: GuestBook = new GuestBook(); customers: Observable; constructor( - private fb: UntypedFormBuilder, private route: ActivatedRoute, private router: Router, private toaster: ToasterService, @@ -36,19 +36,24 @@ export class GuestBookDetailComponent implements OnInit, AfterViewInit { private customerService: CustomerService, ) { // Create form - this.form = this.fb.group({ - name: [null, Validators.required], - phone: [null, Validators.required], - pax: ['0', Validators.required], - address: null, + this.form = new FormGroup({ + name: new FormControl('', { validators: Validators.required, nonNullable: true }), + phone: new FormControl('', { + validators: Validators.required, + nonNullable: true, + }), + pax: new FormControl(0, { validators: Validators.required, nonNullable: true }), + address: new FormControl(null), }); // Setup Account Autocomplete - this.customers = (this.form.get('phone') as UntypedFormControl).valueChanges.pipe( + this.customers = this.form.controls.phone.valueChanges.pipe( startWith(null), - map((x) => (x !== null && x.length >= 1 ? x : null)), + map((x) => (x !== null && (x as string).length >= 1 ? x : null)), debounceTime(150), distinctUntilChanged(), - switchMap((x) => (x === null ? observableOf([]) : this.customerService.autocomplete(x))), + switchMap((x) => + x === null ? observableOf([]) : this.customerService.autocomplete(x as string), + ), ); } @@ -104,14 +109,14 @@ export class GuestBookDetailComponent implements OnInit, AfterViewInit { getItem(): GuestBook { const formModel = this.form.value; - this.item.name = formModel.name; + this.item.name = formModel.name ?? ''; if (typeof formModel.phone === 'string') { this.item.phone = formModel.phone; } else { this.item.phone = (formModel.phone as Customer).phone; } - this.item.pax = parseInt(formModel.pax, 10); - this.item.address = formModel.address; + this.item.pax = formModel.pax ?? 0; + this.item.address = formModel.address ?? null; return this.item; } } diff --git a/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.ts b/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.ts index 9ce30db..2aa48f5 100644 --- a/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.ts +++ b/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; @@ -22,27 +22,29 @@ import { GuestBookListDataSource } from './guest-book-list-datasource'; export class GuestBookListComponent implements OnInit { data: BehaviorSubject = new BehaviorSubject([]); dataSource: GuestBookListDataSource = new GuestBookListDataSource(this.data); - form: UntypedFormGroup; + form: FormGroup<{ + date: FormControl; + }>; + /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['sno', 'name', 'phone', 'pax', 'date', 'action']; constructor( private route: ActivatedRoute, private router: Router, - private fb: UntypedFormBuilder, private dialog: MatDialog, private toaster: ToasterService, private ser: GuestBookService, ) { // Create form - this.form = this.fb.group({ - date: '', + this.form = new FormGroup({ + date: new FormControl(new Date(), { nonNullable: true }), }); this.listenToDateChange(); } listenToDateChange(): void { - (this.form.get('date') as UntypedFormControl).valueChanges + this.form.controls.date.valueChanges .pipe(map((x) => moment(x).format('DD-MMM-YYYY'))) .subscribe((x) => this.ser.list(x).subscribe((list: GuestBookList) => { diff --git a/bookie/src/app/guest-book/guest-book.ts b/bookie/src/app/guest-book/guest-book.ts index c88c6b7..5f63aef 100644 --- a/bookie/src/app/guest-book/guest-book.ts +++ b/bookie/src/app/guest-book/guest-book.ts @@ -4,7 +4,7 @@ export class GuestBook { name: string; phone: string; pax: number; - address: string; + address: string | null; date: string; status?: string; tableId?: string; @@ -17,7 +17,7 @@ export class GuestBook { this.name = ''; this.phone = ''; this.pax = 0; - this.address = ''; + this.address = null; this.date = ''; Object.assign(this, init); } diff --git a/bookie/src/app/header-footer/header-footer.component.ts b/bookie/src/app/header-footer/header-footer.component.ts index 79d9345..562608e 100644 --- a/bookie/src/app/header-footer/header-footer.component.ts +++ b/bookie/src/app/header-footer/header-footer.component.ts @@ -1,5 +1,5 @@ import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { MatSelectChange } from '@angular/material/select'; import { ActivatedRoute, Router } from '@angular/router'; @@ -17,22 +17,25 @@ import { HeaderFooterService } from './header-footer.service'; }) export class HeaderFooterComponent implements OnInit { @ViewChild('section', { static: true }) sectionElement?: ElementRef; - form: UntypedFormGroup; + form: FormGroup<{ + id: FormControl; + text: FormControl; + }>; + list: HeaderFooter[] = []; id = ''; constructor( private route: ActivatedRoute, private router: Router, - private fb: UntypedFormBuilder, private toaster: ToasterService, private dialog: MatDialog, private ser: HeaderFooterService, ) { // Create form - this.form = this.fb.group({ - id: '', - text: '', + this.form = new FormGroup({ + id: new FormControl('', { nonNullable: true }), + text: new FormControl('', { nonNullable: true }), }); route.params.pipe(map((p) => p['id'])).subscribe((x) => { this.id = x; @@ -74,7 +77,7 @@ export class HeaderFooterComponent implements OnInit { if (item === undefined) { return new HeaderFooter(); } - item.text = formModel.text; + item.text = formModel.text ?? ''; return item; } diff --git a/bookie/src/app/home/home.component.html b/bookie/src/app/home/home.component.html index 12a2497..e7e009e 100644 --- a/bookie/src/app/home/home.component.html +++ b/bookie/src/app/home/home.component.html @@ -173,6 +173,14 @@ >

Taxes

+ +

Regimes

+
; + isActive: FormControl; + }>; + item: MenuCategory = new MenuCategory(); constructor( private route: ActivatedRoute, private router: Router, private dialog: MatDialog, - private fb: UntypedFormBuilder, private toaster: ToasterService, private ser: MenuCategoryService, ) { // Create form - this.form = this.fb.group({ - name: '', - isActive: '', + this.form = new FormGroup({ + name: new FormControl('', { nonNullable: true }), + isActive: new FormControl(true, { nonNullable: true }), }); } @@ -95,8 +98,8 @@ export class MenuCategoryDetailComponent implements OnInit, AfterViewInit { getItem(): MenuCategory { const formModel = this.form.value; - this.item.name = formModel.name; - this.item.isActive = formModel.isActive; + this.item.name = formModel.name ?? ''; + this.item.isActive = formModel.isActive ?? true; return this.item; } } diff --git a/bookie/src/app/modifier-categories/modifier-category-detail/modifier-category-detail.component.ts b/bookie/src/app/modifier-categories/modifier-category-detail/modifier-category-detail.component.ts index ff3a7e4..364c62f 100644 --- a/bookie/src/app/modifier-categories/modifier-category-detail/modifier-category-detail.component.ts +++ b/bookie/src/app/modifier-categories/modifier-category-detail/modifier-category-detail.component.ts @@ -1,6 +1,6 @@ import { NestedTreeControl } from '@angular/cdk/tree'; import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { MatTreeNestedDataSource } from '@angular/material/tree'; import { ActivatedRoute, Router } from '@angular/router'; @@ -21,7 +21,13 @@ import { NodeItem } from '../node-item'; }) export class ModifierCategoryDetailComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; - form: UntypedFormGroup; + form: FormGroup<{ + name: FormControl; + minimum: FormControl; + maximum: FormControl; + isActive: FormControl; + }>; + item: ModifierCategory = new ModifierCategory(); treeControl = new NestedTreeControl((node: NodeItem) => node.children); dataSource = new MatTreeNestedDataSource(); @@ -31,17 +37,16 @@ export class ModifierCategoryDetailComponent implements OnInit, AfterViewInit { constructor( private route: ActivatedRoute, private router: Router, - private fb: UntypedFormBuilder, private toaster: ToasterService, private dialog: MatDialog, private ser: ModifierCategoryService, ) { // Create form - this.form = this.fb.group({ - name: '', - minimum: '', - maximum: '', - isActive: '', + this.form = new FormGroup({ + name: new FormControl('', { nonNullable: true }), + minimum: new FormControl(0, { nonNullable: true }), + maximum: new FormControl(null), + isActive: new FormControl(true, { nonNullable: true }), }); this.dataSource.data = []; } @@ -68,9 +73,9 @@ export class ModifierCategoryDetailComponent implements OnInit, AfterViewInit { this.item = item; this.dataSource.data = tree; this.form.patchValue({ - name: this.item.name || '', - minimum: `${this.item.minimum}`, - maximum: `${this.item.maximum}`, + name: this.item.name ?? '', + minimum: this.item.minimum, + maximum: this.item.maximum, isActive: this.item.isActive, }); this.products = new Map(); @@ -139,10 +144,10 @@ export class ModifierCategoryDetailComponent implements OnInit, AfterViewInit { getItem(): ModifierCategory { const formModel = this.form.value; - this.item.name = formModel.name; - this.item.minimum = +formModel.minimum; - this.item.maximum = +formModel.maximum; - this.item.isActive = formModel.isActive; + this.item.name = formModel.name ?? ''; + this.item.minimum = formModel.minimum ?? 0; + this.item.maximum = formModel.maximum ?? null; + this.item.isActive = formModel.isActive ?? true; return this.item; } diff --git a/bookie/src/app/modifiers/modifier-detail/modifier-detail.component.ts b/bookie/src/app/modifiers/modifier-detail/modifier-detail.component.ts index 184c713..08f3aa1 100644 --- a/bookie/src/app/modifiers/modifier-detail/modifier-detail.component.ts +++ b/bookie/src/app/modifiers/modifier-detail/modifier-detail.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -16,7 +16,14 @@ import { ModifierService } from '../modifier.service'; }) export class ModifierDetailComponent implements OnInit, AfterViewInit { @ViewChild('name', { static: true }) nameElement?: ElementRef; - form: UntypedFormGroup; + form: FormGroup<{ + name: FormControl; + showInBill: FormControl; + price: FormControl; + modifierCategory: FormControl; + isActive: FormControl; + }>; + modifierCategories: ModifierCategory[] = []; item: Modifier = new Modifier(); @@ -24,17 +31,16 @@ export class ModifierDetailComponent implements OnInit, AfterViewInit { private route: ActivatedRoute, private router: Router, private dialog: MatDialog, - private fb: UntypedFormBuilder, private toaster: ToasterService, private ser: ModifierService, ) { // Create form - this.form = this.fb.group({ - name: '', - showInBill: '', - price: '', - modifierCategory: '', - isActive: '', + this.form = new FormGroup({ + name: new FormControl('', { nonNullable: true }), + showInBill: new FormControl(true, { nonNullable: true }), + price: new FormControl(0, { nonNullable: true }), + modifierCategory: new FormControl('', { nonNullable: true }), + isActive: new FormControl(true, { nonNullable: true }), }); } @@ -49,9 +55,9 @@ export class ModifierDetailComponent implements OnInit, AfterViewInit { showItem(item: Modifier) { this.item = item; this.form.setValue({ - name: this.item.name || '', + name: this.item.name ?? '', showInBill: this.item.showInBill, - price: this.item.price || '', + price: this.item.price ?? 0, isActive: this.item.isActive, modifierCategory: this.item.modifierCategory ? (this.item.modifierCategory.id as string) : '', }); @@ -104,10 +110,10 @@ export class ModifierDetailComponent implements OnInit, AfterViewInit { getItem(): Modifier { const formModel = this.form.value; - this.item.name = formModel.name; - this.item.showInBill = formModel.showInBill; - this.item.price = +formModel.price; - this.item.isActive = formModel.isActive; + this.item.name = formModel.name ?? ''; + this.item.showInBill = formModel.showInBill ?? false; + this.item.price = formModel.price ?? 0; + this.item.isActive = formModel.isActive ?? true; this.item.modifierCategory = new ModifierCategory({ id: formModel.modifierCategory }); return this.item; } diff --git a/bookie/src/app/modifiers/modifier-list/modifier-list.component.ts b/bookie/src/app/modifiers/modifier-list/modifier-list.component.ts index 8f7d457..3846bb0 100644 --- a/bookie/src/app/modifiers/modifier-list/modifier-list.component.ts +++ b/bookie/src/app/modifiers/modifier-list/modifier-list.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit, ViewChild } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { MatSelectChange } from '@angular/material/select'; import { MatTable } from '@angular/material/table'; import { ActivatedRoute, Router } from '@angular/router'; @@ -20,19 +20,18 @@ export class ModifierListComponent implements OnInit { filter: BehaviorSubject = new BehaviorSubject(''); data: BehaviorSubject = new BehaviorSubject([]); dataSource: ModifierListDataSource = new ModifierListDataSource(this.filter, this.data); - form: UntypedFormGroup; + form: FormGroup<{ + modifierCategory: FormControl; + }>; + list: Modifier[] = []; modifierCategories: ModifierCategory[] = []; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns: string[] = ['name', 'showInBill', 'price', 'isActive', 'modifierCategory']; - constructor( - private route: ActivatedRoute, - private fb: UntypedFormBuilder, - private router: Router, - ) { - this.form = this.fb.group({ - modifierCategory: '', + constructor(private route: ActivatedRoute, private router: Router) { + this.form = new FormGroup({ + modifierCategory: new FormControl(''), }); this.data.subscribe((data: Modifier[]) => { this.list = data; diff --git a/bookie/src/app/printers/printer-detail/printer-detail.component.ts b/bookie/src/app/printers/printer-detail/printer-detail.component.ts index fe51625..63a0b8b 100644 --- a/bookie/src/app/printers/printer-detail/printer-detail.component.ts +++ b/bookie/src/app/printers/printer-detail/printer-detail.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -15,22 +15,26 @@ import { PrinterService } from '../printer.service'; }) export class PrinterDetailComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; - form: UntypedFormGroup; + form: FormGroup<{ + name: FormControl; + address: FormControl; + cutCode: FormControl; + }>; + item: Printer = new Printer(); constructor( private route: ActivatedRoute, private router: Router, private dialog: MatDialog, - private fb: UntypedFormBuilder, private toaster: ToasterService, private ser: PrinterService, ) { // Create form - this.form = this.fb.group({ - name: '', - address: '', - cutCode: '', + this.form = new FormGroup({ + name: new FormControl('', { nonNullable: true }), + address: new FormControl('', { nonNullable: true }), + cutCode: new FormControl('', { nonNullable: true }), }); } @@ -44,9 +48,9 @@ export class PrinterDetailComponent implements OnInit, AfterViewInit { showItem(item: Printer) { this.item = item; this.form.setValue({ - name: this.item.name || '', - address: this.item.address || '', - cutCode: this.item.cutCode || '', + name: this.item.name ?? '', + address: this.item.address ?? '', + cutCode: this.item.cutCode ?? '', }); } diff --git a/bookie/src/app/product-sale-report/product-sale-report-resolver.service.ts b/bookie/src/app/product-sale-report/product-sale-report-resolver.service.ts index 2b1b847..baf8632 100644 --- a/bookie/src/app/product-sale-report/product-sale-report-resolver.service.ts +++ b/bookie/src/app/product-sale-report/product-sale-report-resolver.service.ts @@ -12,8 +12,8 @@ export class ProductSaleReportResolver implements Resolve { constructor(private ser: ProductSaleReportService) {} resolve(route: ActivatedRouteSnapshot): Observable { - const startDate = route.queryParamMap.get('startDate') || null; - const finishDate = route.queryParamMap.get('finishDate') || null; + const startDate = route.queryParamMap.get('startDate') ?? null; + const finishDate = route.queryParamMap.get('finishDate') ?? null; return this.ser.get(startDate, finishDate); } } diff --git a/bookie/src/app/product-sale-report/product-sale-report.component.ts b/bookie/src/app/product-sale-report/product-sale-report.component.ts index 128824b..bd897dd 100644 --- a/bookie/src/app/product-sale-report/product-sale-report.component.ts +++ b/bookie/src/app/product-sale-report/product-sale-report.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; @@ -18,7 +18,10 @@ import { ProductSaleReportService } from './product-sale-report.service'; export class ProductSaleReportComponent implements OnInit { info: ProductSaleReport = new ProductSaleReport(); dataSource: ProductSaleReportDataSource = new ProductSaleReportDataSource(this.info.amounts); - form: UntypedFormGroup; + form: FormGroup<{ + startDate: FormControl; + finishDate: FormControl; + }>; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['name', 'unbilled', 'sale', 'noCharge', 'staff', 'void']; @@ -26,15 +29,14 @@ export class ProductSaleReportComponent implements OnInit { constructor( private route: ActivatedRoute, private router: Router, - private fb: UntypedFormBuilder, private toCsv: ToCsvService, private toaster: ToasterService, private ser: ProductSaleReportService, ) { // Create form - this.form = this.fb.group({ - startDate: '', - finishDate: '', + this.form = new FormGroup({ + startDate: new FormControl(new Date(), { nonNullable: true }), + finishDate: new FormControl(new Date(), { nonNullable: true }), }); } diff --git a/bookie/src/app/product-updates-report/product-updates-report-resolver.service.ts b/bookie/src/app/product-updates-report/product-updates-report-resolver.service.ts index c31dde7..cccb9e5 100644 --- a/bookie/src/app/product-updates-report/product-updates-report-resolver.service.ts +++ b/bookie/src/app/product-updates-report/product-updates-report-resolver.service.ts @@ -12,8 +12,8 @@ export class ProductUpdatesReportResolver implements Resolve { - const startDate = route.queryParamMap.get('startDate') || null; - const finishDate = route.queryParamMap.get('finishDate') || null; + const startDate = route.queryParamMap.get('startDate') ?? null; + const finishDate = route.queryParamMap.get('finishDate') ?? null; return this.ser.get(startDate, finishDate); } } diff --git a/bookie/src/app/product-updates-report/product-updates-report.component.ts b/bookie/src/app/product-updates-report/product-updates-report.component.ts index 74b4cfb..4e80ccb 100644 --- a/bookie/src/app/product-updates-report/product-updates-report.component.ts +++ b/bookie/src/app/product-updates-report/product-updates-report.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; @@ -16,21 +16,19 @@ import { ProductUpdatesReportDataSource } from './product-updates-report-datasou export class ProductUpdatesReportComponent implements OnInit { info: ProductUpdatesReport = new ProductUpdatesReport(); dataSource: ProductUpdatesReportDataSource = new ProductUpdatesReportDataSource(this.info.report); - form: UntypedFormGroup; + form: FormGroup<{ + startDate: FormControl; + finishDate: FormControl; + }>; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['details']; - constructor( - private route: ActivatedRoute, - private router: Router, - private fb: UntypedFormBuilder, - private toCsv: ToCsvService, - ) { + constructor(private route: ActivatedRoute, private router: Router, private toCsv: ToCsvService) { // Create form - this.form = this.fb.group({ - startDate: '', - finishDate: '', + this.form = new FormGroup({ + startDate: new FormControl(new Date(), { nonNullable: true }), + finishDate: new FormControl(new Date(), { nonNullable: true }), }); } diff --git a/bookie/src/app/product/product-detail/product-detail.component.ts b/bookie/src/app/product/product-detail/product-detail.component.ts index 7d5bc9b..921f6e4 100644 --- a/bookie/src/app/product/product-detail/product-detail.component.ts +++ b/bookie/src/app/product/product-detail/product-detail.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -17,7 +17,17 @@ import { ProductService } from '../product.service'; }) export class ProductDetailComponent implements OnInit, AfterViewInit { @ViewChild('name', { static: true }) nameElement?: ElementRef; - form: UntypedFormGroup; + form: FormGroup<{ + name: FormControl; + units: FormControl; + menuCategory: FormControl; + saleCategory: FormControl; + price: FormControl; + hasHappyHour: FormControl; + isNotAvailable: FormControl; + quantity: FormControl; + }>; + menuCategories: MenuCategory[] = []; saleCategories: SaleCategory[] = []; item: Product = new Product(); @@ -26,20 +36,19 @@ export class ProductDetailComponent implements OnInit, AfterViewInit { private route: ActivatedRoute, private router: Router, private dialog: MatDialog, - private fb: UntypedFormBuilder, private toaster: ToasterService, private ser: ProductService, ) { // Create form - this.form = this.fb.group({ - name: '', - units: '', - menuCategory: '', - saleCategory: '', - price: '', - hasHappyHour: '', - isNotAvailable: '', - quantity: '', + this.form = new FormGroup({ + name: new FormControl('', { nonNullable: true }), + units: new FormControl('', { nonNullable: true }), + menuCategory: new FormControl('', { nonNullable: true }), + saleCategory: new FormControl('', { nonNullable: true }), + price: new FormControl(0, { nonNullable: true }), + hasHappyHour: new FormControl(false, { nonNullable: true }), + isNotAvailable: new FormControl(false, { nonNullable: true }), + quantity: new FormControl(0, { nonNullable: true }), }); } @@ -59,14 +68,14 @@ export class ProductDetailComponent implements OnInit, AfterViewInit { showItem(item: Product) { this.item = item; this.form.setValue({ - name: this.item.name || '', - units: this.item.units || '', - menuCategory: this.item.menuCategory ? this.item.menuCategory.id : '', - saleCategory: this.item.saleCategory ? this.item.saleCategory.id : '', - price: this.item.price || '', + name: this.item.name ?? '', + units: this.item.units ?? '', + menuCategory: this.item.menuCategory?.id ?? '', + saleCategory: this.item.saleCategory?.id ?? '', + price: this.item.price ?? 0, hasHappyHour: this.item.hasHappyHour, isNotAvailable: this.item.isNotAvailable, - quantity: this.item.quantity || '', + quantity: this.item.quantity ?? 0, }); } @@ -117,8 +126,8 @@ export class ProductDetailComponent implements OnInit, AfterViewInit { getItem(): Product { const formModel = this.form.value; - this.item.name = formModel.name; - this.item.units = formModel.units; + this.item.name = formModel.name ?? ''; + this.item.units = formModel.units ?? ''; if (this.item.menuCategory === null || this.item.menuCategory === undefined) { this.item.menuCategory = new MenuCategory(); } @@ -127,10 +136,10 @@ export class ProductDetailComponent implements OnInit, AfterViewInit { this.item.saleCategory = new SaleCategory(); } this.item.saleCategory.id = formModel.saleCategory; - this.item.price = +formModel.price; - this.item.hasHappyHour = formModel.hasHappyHour; - this.item.isNotAvailable = formModel.isNotAvailable; - this.item.quantity = +formModel.quantity; + this.item.price = formModel.price ?? 0; + this.item.hasHappyHour = formModel.hasHappyHour ?? false; + this.item.isNotAvailable = formModel.isNotAvailable ?? false; + this.item.quantity = formModel.quantity ?? 0; return this.item; } } diff --git a/bookie/src/app/product/product-list/product-list.component.ts b/bookie/src/app/product/product-list/product-list.component.ts index 7e71b75..9baa345 100644 --- a/bookie/src/app/product/product-list/product-list.component.ts +++ b/bookie/src/app/product/product-list/product-list.component.ts @@ -1,10 +1,10 @@ import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; import { Component, OnInit } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; import { BehaviorSubject } from 'rxjs'; import { Observable } from 'rxjs'; -import { debounceTime, distinctUntilChanged, startWith } from 'rxjs/operators'; +import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; import { MenuCategory } from '../../core/menu-category'; import { Product } from '../../core/product'; @@ -29,7 +29,11 @@ export class ProductListComponent implements OnInit { this.data, ); - form: UntypedFormGroup; + form: FormGroup<{ + menuCategory: FormControl; + filter: FormControl; + }>; + list: Product[] = []; menuCategories: MenuCategory[] = []; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ @@ -44,20 +48,18 @@ export class ProductListComponent implements OnInit { constructor( private route: ActivatedRoute, - private fb: UntypedFormBuilder, private toaster: ToasterService, private toCsv: ToCsvService, private ser: ProductService, ) { - this.form = this.fb.group({ - menuCategory: '', - filter: '', + this.form = new FormGroup({ + menuCategory: new FormControl(''), + filter: new FormControl('', { nonNullable: true }), }); this.data.subscribe((data: Product[]) => { this.list = data; }); - this.searchFilter = (this.form.get('filter') as UntypedFormControl).valueChanges.pipe( - startWith(''), + this.searchFilter = this.form.controls.filter.valueChanges.pipe( debounceTime(150), distinctUntilChanged(), ); diff --git a/bookie/src/app/regimes/regime-detail/regime-detail.component.css b/bookie/src/app/regimes/regime-detail/regime-detail.component.css new file mode 100644 index 0000000..12052db --- /dev/null +++ b/bookie/src/app/regimes/regime-detail/regime-detail.component.css @@ -0,0 +1,3 @@ +.right-align { + text-align: right; +} diff --git a/bookie/src/app/regimes/regime-detail/regime-detail.component.html b/bookie/src/app/regimes/regime-detail/regime-detail.component.html new file mode 100644 index 0000000..3a35d30 --- /dev/null +++ b/bookie/src/app/regimes/regime-detail/regime-detail.component.html @@ -0,0 +1,47 @@ + + + Regime + + +
+
+ + Name + + +
+
+ + Header + + +
+
+ + Prefix + + +
+
+
+ + + + +
diff --git a/bookie/src/app/regimes/regime-detail/regime-detail.component.spec.ts b/bookie/src/app/regimes/regime-detail/regime-detail.component.spec.ts new file mode 100644 index 0000000..b135560 --- /dev/null +++ b/bookie/src/app/regimes/regime-detail/regime-detail.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { RegimeDetailComponent } from './regime-detail.component'; + +describe('RegimeDetailComponent', () => { + let component: RegimeDetailComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [RegimeDetailComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(RegimeDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/bookie/src/app/regimes/regime-detail/regime-detail.component.ts b/bookie/src/app/regimes/regime-detail/regime-detail.component.ts new file mode 100644 index 0000000..9712fd1 --- /dev/null +++ b/bookie/src/app/regimes/regime-detail/regime-detail.component.ts @@ -0,0 +1,109 @@ +import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { ActivatedRoute, Router } from '@angular/router'; + +import { Regime } from '../../core/regime'; +import { ToasterService } from '../../core/toaster.service'; +import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component'; +import { RegimeService } from '../regime.service'; + +@Component({ + selector: 'app-regime-detail', + templateUrl: './regime-detail.component.html', + styleUrls: ['./regime-detail.component.css'], +}) +export class RegimeDetailComponent implements OnInit, AfterViewInit { + @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; + form: FormGroup<{ + name: FormControl; + header: FormControl; + prefix: FormControl; + }>; + + item: Regime = new Regime(); + + constructor( + private route: ActivatedRoute, + private router: Router, + private dialog: MatDialog, + private toaster: ToasterService, + private ser: RegimeService, + ) { + // Create form + this.form = new FormGroup({ + name: new FormControl('', { nonNullable: true }), + header: new FormControl('', { nonNullable: true }), + prefix: new FormControl('', { nonNullable: true }), + }); + } + + ngOnInit() { + this.route.data.subscribe((value) => { + const data = value as { item: Regime }; + this.showItem(data.item); + }); + } + + showItem(item: Regime) { + this.item = item; + this.form.setValue({ + name: this.item.name ?? '', + header: this.item.header ?? '', + prefix: this.item.prefix ?? '', + }); + } + + ngAfterViewInit() { + setTimeout(() => { + if (this.nameElement !== undefined) { + this.nameElement.nativeElement.focus(); + } + }, 0); + } + + save() { + this.ser.saveOrUpdate(this.getItem()).subscribe( + () => { + this.toaster.show('Success', ''); + this.router.navigateByUrl('/regimes'); + }, + (error) => { + this.toaster.show('Error', error); + }, + ); + } + + delete() { + this.ser.delete(this.item.id as number).subscribe( + () => { + this.toaster.show('Success', ''); + this.router.navigateByUrl('/regimes'); + }, + (error) => { + this.toaster.show('Error', error); + }, + ); + } + + confirmDelete(): void { + const dialogRef = this.dialog.open(ConfirmDialogComponent, { + width: '250px', + data: { title: 'Delete Regime?', content: 'Are you sure? This cannot be undone.' }, + }); + + dialogRef.afterClosed().subscribe((result: boolean) => { + if (result) { + this.delete(); + } + }); + } + + getItem(): Regime { + const formModel = this.form.value; + this.item.name = formModel.name ?? ''; + this.item.header = formModel.header ?? ''; + this.item.prefix = formModel.prefix ?? ''; + return this.item; + } +} diff --git a/bookie/src/app/regimes/regime-list-resolver.service.spec.ts b/bookie/src/app/regimes/regime-list-resolver.service.spec.ts new file mode 100644 index 0000000..f8a9cdc --- /dev/null +++ b/bookie/src/app/regimes/regime-list-resolver.service.spec.ts @@ -0,0 +1,15 @@ +import { inject, TestBed } from '@angular/core/testing'; + +import { RegimeListResolver } from './regime-list-resolver.service'; + +describe('RegimeListResolverService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [RegimeListResolver], + }); + }); + + it('should be created', inject([RegimeListResolver], (service: RegimeListResolver) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/bookie/src/app/regimes/regime-list-resolver.service.ts b/bookie/src/app/regimes/regime-list-resolver.service.ts new file mode 100644 index 0000000..3f4cb56 --- /dev/null +++ b/bookie/src/app/regimes/regime-list-resolver.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { Resolve } from '@angular/router'; +import { Observable } from 'rxjs'; + +import { Regime } from '../core/regime'; + +import { RegimeService } from './regime.service'; + +@Injectable({ + providedIn: 'root', +}) +export class RegimeListResolver implements Resolve { + constructor(private ser: RegimeService) {} + + resolve(): Observable { + return this.ser.list(); + } +} diff --git a/bookie/src/app/regimes/regime-list/regime-list-datasource.ts b/bookie/src/app/regimes/regime-list/regime-list-datasource.ts new file mode 100644 index 0000000..5129d63 --- /dev/null +++ b/bookie/src/app/regimes/regime-list/regime-list-datasource.ts @@ -0,0 +1,16 @@ +import { DataSource } from '@angular/cdk/collections'; +import { Observable, of as observableOf } from 'rxjs'; + +import { Regime } from '../../core/regime'; + +export class RegimeListDataSource extends DataSource { + constructor(public data: Regime[]) { + super(); + } + + connect(): Observable { + return observableOf(this.data); + } + + disconnect() {} +} diff --git a/bookie/src/app/regimes/regime-list/regime-list.component.css b/bookie/src/app/regimes/regime-list/regime-list.component.css new file mode 100644 index 0000000..e69de29 diff --git a/bookie/src/app/regimes/regime-list/regime-list.component.html b/bookie/src/app/regimes/regime-list/regime-list.component.html new file mode 100644 index 0000000..72dfd7f --- /dev/null +++ b/bookie/src/app/regimes/regime-list/regime-list.component.html @@ -0,0 +1,43 @@ + + + + Regimes + + add_box + Add + + + + + + + + Name + {{ row.name }} + + + + + Header + {{ row.header }} + + + + + Prefix + {{ row.prefix }} + + + + + Is Fixture? + {{ row.isFixture }} + + + + + + + diff --git a/bookie/src/app/regimes/regime-list/regime-list.component.spec.ts b/bookie/src/app/regimes/regime-list/regime-list.component.spec.ts new file mode 100644 index 0000000..44a548a --- /dev/null +++ b/bookie/src/app/regimes/regime-list/regime-list.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; + +import { RegimeListComponent } from './regime-list.component'; + +describe('RegimeListComponent', () => { + let component: RegimeListComponent; + let fixture: ComponentFixture; + + beforeEach(fakeAsync(() => { + TestBed.configureTestingModule({ + declarations: [RegimeListComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(RegimeListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should compile', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/bookie/src/app/regimes/regime-list/regime-list.component.ts b/bookie/src/app/regimes/regime-list/regime-list.component.ts new file mode 100644 index 0000000..e0a356c --- /dev/null +++ b/bookie/src/app/regimes/regime-list/regime-list.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import { Regime } from '../../core/regime'; + +import { RegimeListDataSource } from './regime-list-datasource'; + +@Component({ + selector: 'app-regime-list', + templateUrl: './regime-list.component.html', + styleUrls: ['./regime-list.component.css'], +}) +export class RegimeListComponent implements OnInit { + list: Regime[] = []; + dataSource: RegimeListDataSource = new RegimeListDataSource(this.list); + /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ + displayedColumns = ['name', 'header', 'prefix', 'isFixture']; + + constructor(private route: ActivatedRoute) {} + + ngOnInit() { + this.route.data.subscribe((value) => { + const data = value as { list: Regime[] }; + this.list = data.list; + }); + this.dataSource = new RegimeListDataSource(this.list); + } +} diff --git a/bookie/src/app/regimes/regime-resolver.service.spec.ts b/bookie/src/app/regimes/regime-resolver.service.spec.ts new file mode 100644 index 0000000..afedf49 --- /dev/null +++ b/bookie/src/app/regimes/regime-resolver.service.spec.ts @@ -0,0 +1,15 @@ +import { inject, TestBed } from '@angular/core/testing'; + +import { RegimeResolver } from './regime-resolver.service'; + +describe('RegimeResolver', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [RegimeResolver], + }); + }); + + it('should be created', inject([RegimeResolver], (service: RegimeResolver) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/bookie/src/app/regimes/regime-resolver.service.ts b/bookie/src/app/regimes/regime-resolver.service.ts new file mode 100644 index 0000000..67afb4c --- /dev/null +++ b/bookie/src/app/regimes/regime-resolver.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve } from '@angular/router'; +import { Observable } from 'rxjs'; + +import { Regime } from '../core/regime'; + +import { RegimeService } from './regime.service'; + +@Injectable({ + providedIn: 'root', +}) +export class RegimeResolver implements Resolve { + constructor(private ser: RegimeService) {} + + resolve(route: ActivatedRouteSnapshot): Observable { + const id = route.paramMap.get('id'); + return this.ser.get(id ? +id : null); + } +} diff --git a/bookie/src/app/regimes/regime.service.spec.ts b/bookie/src/app/regimes/regime.service.spec.ts new file mode 100644 index 0000000..855e017 --- /dev/null +++ b/bookie/src/app/regimes/regime.service.spec.ts @@ -0,0 +1,15 @@ +import { inject, TestBed } from '@angular/core/testing'; + +import { RegimeService } from './regime.service'; + +describe('RegimeService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [RegimeService], + }); + }); + + it('should be created', inject([RegimeService], (service: RegimeService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/bookie/src/app/regimes/regime.service.ts b/bookie/src/app/regimes/regime.service.ts new file mode 100644 index 0000000..9e776a6 --- /dev/null +++ b/bookie/src/app/regimes/regime.service.ts @@ -0,0 +1,58 @@ +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { catchError } from 'rxjs/operators'; + +import { ErrorLoggerService } from '../core/error-logger.service'; +import { Regime } from '../core/regime'; + +const httpOptions = { + headers: new HttpHeaders({ 'Content-Type': 'application/json' }), +}; +const url = '/api/regimes'; +const serviceName = 'RegimeService'; + +@Injectable({ + providedIn: 'root', +}) +export class RegimeService { + constructor(private http: HttpClient, private log: ErrorLoggerService) {} + + get(id: number | null): Observable { + const getUrl: string = id === null ? url : `${url}/${id}`; + return this.http + .get(getUrl) + .pipe(catchError(this.log.handleError(serviceName, `get id=${id}`))) as Observable; + } + + list(): Observable { + return this.http + .get(`${url}/list`) + .pipe(catchError(this.log.handleError(serviceName, 'list'))) as Observable; + } + + save(regime: Regime): Observable { + return this.http + .post(url, regime, httpOptions) + .pipe(catchError(this.log.handleError(serviceName, 'save'))) as Observable; + } + + update(regime: Regime): Observable { + return this.http + .put(`${url}/${regime.id}`, regime, httpOptions) + .pipe(catchError(this.log.handleError(serviceName, 'update'))) as Observable; + } + + saveOrUpdate(regime: Regime): Observable { + if (!regime.id) { + return this.save(regime); + } + return this.update(regime); + } + + delete(id: number): Observable { + return this.http + .delete(`${url}/${id}`, httpOptions) + .pipe(catchError(this.log.handleError(serviceName, 'delete'))) as Observable; + } +} diff --git a/bookie/src/app/regimes/regimes-routing.module.spec.ts b/bookie/src/app/regimes/regimes-routing.module.spec.ts new file mode 100644 index 0000000..ad69d12 --- /dev/null +++ b/bookie/src/app/regimes/regimes-routing.module.spec.ts @@ -0,0 +1,13 @@ +import { RegimesRoutingModule } from './regimes-routing.module'; + +describe('RegimesRoutingModule', () => { + let regimesRoutingModule: RegimesRoutingModule; + + beforeEach(() => { + regimesRoutingModule = new RegimesRoutingModule(); + }); + + it('should create an instance', () => { + expect(regimesRoutingModule).toBeTruthy(); + }); +}); diff --git a/bookie/src/app/regimes/regimes-routing.module.ts b/bookie/src/app/regimes/regimes-routing.module.ts new file mode 100644 index 0000000..a864680 --- /dev/null +++ b/bookie/src/app/regimes/regimes-routing.module.ts @@ -0,0 +1,53 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { AuthGuard } from '../auth/auth-guard.service'; + +import { RegimeDetailComponent } from './regime-detail/regime-detail.component'; +import { RegimeListComponent } from './regime-list/regime-list.component'; +import { RegimeListResolver } from './regime-list-resolver.service'; +import { RegimeResolver } from './regime-resolver.service'; + +const regimesRoutes: Routes = [ + { + path: '', + component: RegimeListComponent, + canActivate: [AuthGuard], + data: { + permission: 'Regimes', + }, + resolve: { + list: RegimeListResolver, + }, + }, + { + path: 'new', + component: RegimeDetailComponent, + canActivate: [AuthGuard], + data: { + permission: 'Regimes', + }, + resolve: { + item: RegimeResolver, + }, + }, + { + path: ':id', + component: RegimeDetailComponent, + canActivate: [AuthGuard], + data: { + permission: 'Regimes', + }, + resolve: { + item: RegimeResolver, + }, + }, +]; + +@NgModule({ + imports: [CommonModule, RouterModule.forChild(regimesRoutes)], + exports: [RouterModule], + providers: [RegimeListResolver, RegimeResolver], +}) +export class RegimesRoutingModule {} diff --git a/bookie/src/app/regimes/regimes.module.spec.ts b/bookie/src/app/regimes/regimes.module.spec.ts new file mode 100644 index 0000000..3fa66e2 --- /dev/null +++ b/bookie/src/app/regimes/regimes.module.spec.ts @@ -0,0 +1,13 @@ +import { RegimesModule } from './regimes.module'; + +describe('RegimesModule', () => { + let regimesModule: RegimesModule; + + beforeEach(() => { + regimesModule = new RegimesModule(); + }); + + it('should create an instance', () => { + expect(regimesModule).toBeTruthy(); + }); +}); diff --git a/bookie/src/app/regimes/regimes.module.ts b/bookie/src/app/regimes/regimes.module.ts new file mode 100644 index 0000000..d4fe38e --- /dev/null +++ b/bookie/src/app/regimes/regimes.module.ts @@ -0,0 +1,31 @@ +import { CdkTableModule } from '@angular/cdk/table'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatTableModule } from '@angular/material/table'; + +import { RegimeDetailComponent } from './regime-detail/regime-detail.component'; +import { RegimeListComponent } from './regime-list/regime-list.component'; +import { RegimesRoutingModule } from './regimes-routing.module'; + +@NgModule({ + imports: [ + CommonModule, + CdkTableModule, + MatButtonModule, + MatCardModule, + MatIconModule, + MatInputModule, + MatProgressSpinnerModule, + MatTableModule, + ReactiveFormsModule, + RegimesRoutingModule, + ], + declarations: [RegimeListComponent, RegimeDetailComponent], +}) +export class RegimesModule {} diff --git a/bookie/src/app/sale-category/sale-category-detail/sale-category-detail.component.ts b/bookie/src/app/sale-category/sale-category-detail/sale-category-detail.component.ts index c1ce515..1307b41 100644 --- a/bookie/src/app/sale-category/sale-category-detail/sale-category-detail.component.ts +++ b/bookie/src/app/sale-category/sale-category-detail/sale-category-detail.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import { round } from 'mathjs'; @@ -21,7 +21,12 @@ import { SaleCategoryDetailDatasource } from './sale-category-detail-datasource' }) export class SaleCategoryDetailComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; - form: UntypedFormGroup; + form: FormGroup<{ + name: FormControl; + discountLimit: FormControl; + tax: FormControl; + }>; + taxes: Tax[] = []; item: SaleCategory = new SaleCategory(); products: BehaviorSubject = new BehaviorSubject([]); @@ -32,15 +37,14 @@ export class SaleCategoryDetailComponent implements OnInit, AfterViewInit { private route: ActivatedRoute, private router: Router, private dialog: MatDialog, - private fb: UntypedFormBuilder, private toaster: ToasterService, private ser: SaleCategoryService, ) { // Create form - this.form = this.fb.group({ - name: '', - discountLimit: '', - tax: '', + this.form = new FormGroup({ + name: new FormControl('', { nonNullable: true }), + discountLimit: new FormControl(100, { nonNullable: true }), + tax: new FormControl('', { nonNullable: true }), }); } @@ -58,7 +62,7 @@ export class SaleCategoryDetailComponent implements OnInit, AfterViewInit { this.form.setValue({ name: this.item.name, discountLimit: this.item.discountLimit * 100, - tax: this.item.tax ? this.item.tax.id : '', + tax: this.item.tax?.id ?? '', }); } @@ -109,8 +113,8 @@ export class SaleCategoryDetailComponent implements OnInit, AfterViewInit { getItem(): SaleCategory { const formModel = this.form.value; - this.item.name = formModel.name; - this.item.discountLimit = round(+formModel.discountLimit / 100, 5); + this.item.name = formModel.name ?? ''; + this.item.discountLimit = round((formModel.discountLimit ?? 100) / 100, 5); if (this.item.tax === null || this.item.tax === undefined) { this.item.tax = new Tax(); } diff --git a/bookie/src/app/sale-report/sale-report-resolver.service.ts b/bookie/src/app/sale-report/sale-report-resolver.service.ts index 666b0e2..55a9042 100644 --- a/bookie/src/app/sale-report/sale-report-resolver.service.ts +++ b/bookie/src/app/sale-report/sale-report-resolver.service.ts @@ -12,8 +12,8 @@ export class SaleReportResolver implements Resolve { constructor(private ser: SaleReportService) {} resolve(route: ActivatedRouteSnapshot): Observable { - const startDate = route.queryParamMap.get('startDate') || null; - const finishDate = route.queryParamMap.get('finishDate') || null; + const startDate = route.queryParamMap.get('startDate') ?? null; + const finishDate = route.queryParamMap.get('finishDate') ?? null; return this.ser.get(startDate, finishDate); } } diff --git a/bookie/src/app/sale-report/sale-report.component.ts b/bookie/src/app/sale-report/sale-report.component.ts index 22ad185..f04ce05 100644 --- a/bookie/src/app/sale-report/sale-report.component.ts +++ b/bookie/src/app/sale-report/sale-report.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; @@ -18,7 +18,10 @@ import { SaleReportService } from './sale-report.service'; export class SaleReportComponent implements OnInit { info: SaleReport = new SaleReport(); dataSource: SaleReportDatasource = new SaleReportDatasource(this.info.amounts); - form: UntypedFormGroup; + form: FormGroup<{ + startDate: FormControl; + finishDate: FormControl; + }>; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['name', 'amount']; @@ -26,15 +29,14 @@ export class SaleReportComponent implements OnInit { constructor( private route: ActivatedRoute, private router: Router, - private fb: UntypedFormBuilder, private toCsv: ToCsvService, private toaster: ToasterService, private ser: SaleReportService, ) { // Create form - this.form = this.fb.group({ - startDate: '', - finishDate: '', + this.form = new FormGroup({ + startDate: new FormControl(new Date(), { nonNullable: true }), + finishDate: new FormControl(new Date(), { nonNullable: true }), }); } diff --git a/bookie/src/app/sales/bill-number/bill-number.component.html b/bookie/src/app/sales/bill-number/bill-number.component.html index be3d914..d7ccae8 100644 --- a/bookie/src/app/sales/bill-number/bill-number.component.html +++ b/bookie/src/app/sales/bill-number/bill-number.component.html @@ -2,12 +2,11 @@
- - KOT - Regular Bill - Staff - No Charge - Void + Regime + + + {{ r.name }} + diff --git a/bookie/src/app/sales/bill-number/bill-number.component.ts b/bookie/src/app/sales/bill-number/bill-number.component.ts index 3066db8..9d1b6f6 100644 --- a/bookie/src/app/sales/bill-number/bill-number.component.ts +++ b/bookie/src/app/sales/bill-number/bill-number.component.ts @@ -1,6 +1,7 @@ -import { Component, OnInit } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; -import { MatDialogRef } from '@angular/material/dialog'; +import { Component, Inject, OnInit } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { Regime } from 'src/app/core/regime'; @Component({ selector: 'app-bill-number', @@ -8,50 +9,40 @@ import { MatDialogRef } from '@angular/material/dialog'; styleUrls: ['./bill-number.component.css'], }) export class BillNumberComponent implements OnInit { - form: UntypedFormGroup; + form: FormGroup<{ + regime: FormControl; + billNumber: FormControl; + }>; - constructor(public dialogRef: MatDialogRef, private fb: UntypedFormBuilder) { + regimes: Regime[] = []; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) + public data: Regime[], + ) { + this.regimes = data; // Create form - this.form = this.fb.group({ - billType: '', - billNumber: '', + this.form = new FormGroup({ + regime: new FormControl(null), + billNumber: new FormControl(0, { nonNullable: true }), }); } ngOnInit() { this.form.setValue({ - billType: '1', - billNumber: '', + regime: null, + billNumber: 0, }); } accept(): void { const formValue = this.form.value; - const billNumber = parseInt(formValue.billNumber.replace('-', ''), 10); - if (isNaN(billNumber)) { + const billNumber = formValue.billNumber ?? 0; + const regime = formValue.regime ?? null; + if (regime == null) { this.dialogRef.close(undefined); - } else { - let billId: string; - switch (formValue.billType) { - case '0': // KOT - billId = 'K-' + billNumber; - break; - case '1': // Regular Bill - billId = Math.floor(billNumber / 10000) + '-' + (billNumber % 10000); - break; - case '4': // Staff - billId = 'ST-' + billNumber; - break; - case '2': // No Charge - billId = 'NC-' + billNumber; - break; - case '8': // Void - billId = 'V-' + billNumber; - break; - default: - throw new Error('Unknown Bill Type'); - } - this.dialogRef.close(billId); } + this.dialogRef.close(`${regime?.prefix}-${billNumber}`); } } diff --git a/bookie/src/app/sales/bills/bills.component.html b/bookie/src/app/sales/bills/bills.component.html index 70dd6d3..452122e 100644 --- a/bookie/src/app/sales/bills/bills.component.html +++ b/bookie/src/app/sales/bills/bills.component.html @@ -46,7 +46,7 @@ > / / diff --git a/bookie/src/app/sales/choose-customer/choose-customer.component.ts b/bookie/src/app/sales/choose-customer/choose-customer.component.ts index 39ec129..bb80aae 100644 --- a/bookie/src/app/sales/choose-customer/choose-customer.component.ts +++ b/bookie/src/app/sales/choose-customer/choose-customer.component.ts @@ -1,10 +1,5 @@ import { Component, Inject } from '@angular/core'; -import { - UntypedFormArray, - UntypedFormBuilder, - UntypedFormControl, - UntypedFormGroup, -} from '@angular/forms'; +import { FormArray, FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { round } from 'mathjs'; @@ -20,26 +15,36 @@ import { CustomerService } from '../../customers/customer.service'; styleUrls: ['./choose-customer.component.css'], }) export class ChooseCustomerComponent { - form: UntypedFormGroup; + form: FormGroup<{ + name: FormControl; + phone: FormControl; + address: FormControl; + printInBill: FormControl; + discounts: FormArray< + FormGroup<{ + discount: FormControl; + }> + >; + }>; + item: Customer = new Customer(); customers: Observable; constructor( public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: string | undefined, - private fb: UntypedFormBuilder, private ser: CustomerService, ) { // Create form - this.form = this.fb.group({ - name: '', - phone: '', - address: '', - printInBill: false, - discounts: this.fb.array([]), + this.form = new FormGroup({ + name: new FormControl('', { nonNullable: true }), + phone: new FormControl('', { nonNullable: true }), + address: new FormControl(null), + printInBill: new FormControl(false, { nonNullable: true }), + discounts: new FormArray }>>([]), }); // Setup Account Autocomplete - this.customers = (this.form.get('phone') as UntypedFormControl).valueChanges.pipe( + this.customers = this.form.controls.phone.valueChanges.pipe( startWith(null), map((x) => (x === this.item.phone ? '' : x)), map((x) => (x !== null && x.length >= 1 ? x : null)), @@ -54,20 +59,16 @@ export class ChooseCustomerComponent { showItem(item: Customer) { this.item = item; - this.form.patchValue({ - name: item.name, - phone: item.phone, - address: item.address, - printInBill: item.printInBill, - }); - this.form.setControl( - 'discounts', - this.fb.array( - item.discounts.map((x) => - this.fb.group({ - discount: '' + x.discount * 100, - }), - ), + this.form.controls.name.setValue(item.name); + this.form.controls.phone.setValue(item.phone); + this.form.controls.address.setValue(item.address); + this.form.controls.printInBill.setValue(item.printInBill); + this.form.controls.discounts.clear(); + this.item.discounts.forEach((x) => + this.form.controls.discounts.push( + new FormGroup({ + discount: new FormControl(x.discount ? x.discount * 100 : null), + }), ), ); this.form.markAsPristine(); @@ -93,18 +94,22 @@ export class ChooseCustomerComponent { getItem(): Customer { const formModel = this.form.value; - this.item.id = this.item.phone.trim() !== formModel.phone.trim() ? '' : this.item.id; - this.item.name = formModel.name; - this.item.phone = formModel.phone; - this.item.address = formModel.address; - this.item.printInBill = formModel.printInBill; - const array = this.form.get('discounts') as UntypedFormArray; - this.item.discounts.forEach((item, index) => { - item.discount = Math.max( - Math.min(round(array.controls[index].value.discount / 100, 5), 100), - 0, - ); - }); + this.item.id = this.item.phone.trim() !== formModel.phone?.trim() ? '' : this.item.id; + this.item.name = formModel.name ?? ''; + this.item.phone = formModel.phone ?? ''; + this.item.address = formModel.address ?? ''; + this.item.printInBill = formModel.printInBill ?? false; + const array = formModel.discounts; + if (array) { + this.item.discounts.forEach((item, index) => { + const array_item = array.at(index); + if (array_item && array_item?.discount) { + item.discount = Math.max(Math.min(round(array_item.discount / 100, 5), 100), 0); + } else { + item.discount = null; + } + }); + } return this.item; } } diff --git a/bookie/src/app/sales/discount/discount.component.ts b/bookie/src/app/sales/discount/discount.component.ts index 4655324..8d04917 100644 --- a/bookie/src/app/sales/discount/discount.component.ts +++ b/bookie/src/app/sales/discount/discount.component.ts @@ -1,11 +1,5 @@ import { Component, Inject } from '@angular/core'; -import { - UntypedFormArray, - UntypedFormBuilder, - UntypedFormControl, - UntypedFormGroup, - Validators, -} from '@angular/forms'; +import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { round } from 'mathjs'; import { Observable } from 'rxjs'; @@ -20,52 +14,58 @@ import { DiscountItem } from './discount-item'; }) export class DiscountComponent { list: DiscountItem[] = []; - form: UntypedFormGroup; + form: FormGroup<{ + discounts: FormArray< + FormGroup<{ + name: FormControl; + discount: FormControl; + }> + >; + }>; + dataSource: DiscountDataSource = new DiscountDataSource([]); displayedColumns = ['name', 'discount']; constructor( public dialogRef: MatDialogRef, - private fb: UntypedFormBuilder, @Inject(MAT_DIALOG_DATA) public data: Observable, ) { - this.form = this.fb.group({ - discounts: '', + this.form = new FormGroup({ + discounts: new FormArray< + FormGroup<{ + name: FormControl; + discount: FormControl; + }> + >([]), }); + this.data.subscribe((list: DiscountItem[]) => { this.list = list; - this.form.setControl( - 'discounts', - this.fb.array( - this.list.map((x) => - this.fb.group({ - name: [x.name], - discount: [ - '' + (x.discount !== 0 ? x.discount * 100 : ''), - [Validators.min(0), Validators.max(x.discountLimit * 100)], - ], + this.form.controls.discounts.clear(); + + this.list.forEach((x) => + this.form.controls.discounts.push( + new FormGroup({ + name: new FormControl(x.name, { nonNullable: true }), + discount: new FormControl(x.discount * 100, { + validators: [Validators.min(0), Validators.max(x.discountLimit * 100)], + nonNullable: true, }), - ), + }), ), - ); - this.dataSource = new DiscountDataSource(this.list); + ), + (this.dataSource = new DiscountDataSource(this.list)); }); } accept(): void { - const array = this.form.get('discounts') as UntypedFormArray; + const array = this.form.controls.discounts; for (let i = this.list.length - 1; i >= 0; i--) { const item = this.list[i]; - const control = (array.controls[i] as UntypedFormGroup).controls[ - 'discount' - ] as UntypedFormControl; - if ( - control.value === null || - control.value === '' || - (control.pristine && control.value === '0') - ) { + const control = array.controls[i].controls.discount; + if (control.pristine && control.value === 0) { this.list.splice(i, 1); } else { item.discount = Math.max(Math.min(round(control.value / 100, 5), item.discountLimit), 0); diff --git a/bookie/src/app/sales/pax/pax.component.ts b/bookie/src/app/sales/pax/pax.component.ts index b6f9e0a..5b28591 100644 --- a/bookie/src/app/sales/pax/pax.component.ts +++ b/bookie/src/app/sales/pax/pax.component.ts @@ -1,5 +1,5 @@ import { Component, Inject, OnInit } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; @Component({ @@ -8,16 +8,17 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; styleUrls: ['./pax.component.css'], }) export class PaxComponent implements OnInit { - form: UntypedFormGroup; + form: FormGroup<{ + pax: FormControl; + }>; constructor( public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: number, - private fb: UntypedFormBuilder, ) { // Create form - this.form = this.fb.group({ - pax: '', + this.form = new FormGroup({ + pax: new FormControl(0, { nonNullable: true }), }); } @@ -28,7 +29,7 @@ export class PaxComponent implements OnInit { } accept(): void { - const pax = +this.form.value.pax; + const pax = this.form.value.pax ?? 0; this.dialogRef.close(pax); } } diff --git a/bookie/src/app/sales/reason/reason.component.ts b/bookie/src/app/sales/reason/reason.component.ts index 921f99e..10dee27 100644 --- a/bookie/src/app/sales/reason/reason.component.ts +++ b/bookie/src/app/sales/reason/reason.component.ts @@ -1,5 +1,5 @@ import { Component, ElementRef, Inject, ViewChild } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ReasonDatasource } from './reason-datasource'; @@ -11,7 +11,10 @@ import { ReasonDatasource } from './reason-datasource'; }) export class ReasonComponent { @ViewChild('son', { static: true }) son?: ElementRef; - form: UntypedFormGroup; + form: FormGroup<{ + son: FormControl; + }>; + dataSource: ReasonDatasource; title: string; selected = ''; @@ -21,12 +24,11 @@ export class ReasonComponent { constructor( public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) private data: { title: string; reasons: string[] }, - private fb: UntypedFormBuilder, ) { - this.reasons = data.reasons || []; + this.reasons = data.reasons ?? []; this.title = data.title; - this.form = this.fb.group({ - son: '', + this.form = new FormGroup({ + son: new FormControl('', { nonNullable: true }), }); this.dataSource = new ReasonDatasource(this.reasons); } diff --git a/bookie/src/app/sales/receive-payment/receive-payment.component.ts b/bookie/src/app/sales/receive-payment/receive-payment.component.ts index 8cae8df..718cea1 100644 --- a/bookie/src/app/sales/receive-payment/receive-payment.component.ts +++ b/bookie/src/app/sales/receive-payment/receive-payment.component.ts @@ -1,10 +1,5 @@ import { Component, ElementRef, Inject, ViewChild } from '@angular/core'; -import { - UntypedFormArray, - UntypedFormBuilder, - UntypedFormControl, - UntypedFormGroup, -} from '@angular/forms'; +import { FormArray, FormControl, FormGroup } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { distinctUntilChanged, map, tap } from 'rxjs/operators'; @@ -30,20 +25,33 @@ export class ReceivePaymentComponent { reason = ''; displayReason: boolean; displayTable: boolean; - form: UntypedFormGroup; + form: FormGroup<{ + amounts: FormArray< + FormGroup<{ + name: FormControl; + amount: FormControl; + }> + >; + son: FormControl; + }>; + dataSource: ReceivePaymentDatasource; displayedColumns = ['name', 'amount']; constructor( public dialogRef: MatDialogRef, - private fb: UntypedFormBuilder, private ser: SettleOptionService, @Inject(MAT_DIALOG_DATA) public data: { type: VoucherType; amount: number }, ) { - this.form = this.fb.group({ - amounts: '', - son: '', + this.form = new FormGroup({ + amounts: new FormArray< + FormGroup<{ + name: FormControl; + amount: FormControl; + }> + >([]), + son: new FormControl('', { nonNullable: true }), }); this.type = data.type; this.amount = data.amount; @@ -55,7 +63,7 @@ export class ReceivePaymentComponent { .pipe( tap( (x: SettleOption[]) => - (this.displayReason = x.reduce((o, n) => o || n.hasReason, this.displayReason)), + (this.displayReason = x.reduce((o, n) => o ?? n.hasReason, this.displayReason)), ), tap((x: SettleOption[]) => (this.displayTable = x.length > 1)), map((x: SettleOption[]) => @@ -66,15 +74,15 @@ export class ReceivePaymentComponent { ) .subscribe((x) => { this.choices = x; - this.form.setControl( - 'amounts', - this.fb.array( - this.choices.map((y: ReceivePaymentItem) => - this.fb.group({ - name: [y.name], - amount: [y.amount === 0 ? '' : '' + this.amount], + this.form.controls.amounts.clear(); + this.choices.forEach((y: ReceivePaymentItem) => + this.form.controls.amounts.push( + new FormGroup({ + name: new FormControl(y.name, { nonNullable: true }), + amount: new FormControl(y.amount === 0 ? 0 : this.amount, { + nonNullable: true, }), - ), + }), ), ); this.dataSource = new ReceivePaymentDatasource(this.choices); @@ -86,11 +94,10 @@ export class ReceivePaymentComponent { } listenToAmountChange() { - const array = this.form.get('amounts') as UntypedFormArray; + const array = this.form.controls.amounts; this.choices.forEach((z, i) => array.controls[i].valueChanges.pipe(distinctUntilChanged()).subscribe((x) => { - (this.choices.find((s) => s.name === x.name) as ReceivePaymentItem).amount = - x.amount === '' ? 0 : parseInt(x.amount, 10); + (this.choices.find((s) => s.name === x.name) as ReceivePaymentItem).amount = x.amount ?? 0; this.balance = this.amount - this.choices.reduce((a, c) => a + c.amount, 0); }), ); @@ -106,8 +113,8 @@ export class ReceivePaymentComponent { } maxAmount(row: ReceivePaymentItem, index: number) { - const array = this.form.get('amounts') as UntypedFormArray; - const ctrl = array.controls[index].get('amount') as UntypedFormControl; - ctrl.setValue('' + (row.amount + this.balance)); + const array = this.form.controls.amounts; + const ctrl = array.controls[index].controls.amount; + ctrl.setValue(row.amount + this.balance); } } diff --git a/bookie/src/app/sales/running-tables/running-tables.component.html b/bookie/src/app/sales/running-tables/running-tables.component.html index 5326beb..83ff46a 100644 --- a/bookie/src/app/sales/running-tables/running-tables.component.html +++ b/bookie/src/app/sales/running-tables/running-tables.component.html @@ -17,7 +17,7 @@ table.guest }} {{ table.pax || '-' }} / {{ table.seats }} / {{ table.section?.name }}{{ table.pax ?? '-' }} / {{ table.seats }} / {{ table.section?.name }} {{ table.date }} {{ table.amount | currency : 'INR' }} diff --git a/bookie/src/app/sales/running-tables/running-tables.component.ts b/bookie/src/app/sales/running-tables/running-tables.component.ts index ad9b711..e31d755 100644 --- a/bookie/src/app/sales/running-tables/running-tables.component.ts +++ b/bookie/src/app/sales/running-tables/running-tables.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, NavigationExtras, Router } from '@angular/router'; import { map } from 'rxjs/operators'; +import { Regime } from 'src/app/core/regime'; import { Table } from '../../core/table'; import { ToasterService } from '../../core/toaster.service'; @@ -13,6 +14,7 @@ import { BillNumberComponent } from '../bill-number/bill-number.component'; styleUrls: ['./running-tables.component.css'], }) export class RunningTablesComponent implements OnInit { + regimes: Regime[] = []; list: Table[] = []; constructor( @@ -24,7 +26,8 @@ export class RunningTablesComponent implements OnInit { ngOnInit() { this.route.data.subscribe((value) => { - const data = value as { list: Table[] }; + const data = value as { list: Table[]; regimes: Regime[] }; + this.regimes = data.regimes; this.list = data.list; }); } @@ -45,7 +48,7 @@ export class RunningTablesComponent implements OnInit { openBill() { return this.dialog - .open(BillNumberComponent) + .open(BillNumberComponent, { data: this.regimes }) .afterClosed() .pipe( map((x) => { diff --git a/bookie/src/app/sales/sales-routing.module.ts b/bookie/src/app/sales/sales-routing.module.ts index 2544065..e5425a4 100644 --- a/bookie/src/app/sales/sales-routing.module.ts +++ b/bookie/src/app/sales/sales-routing.module.ts @@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { AuthGuard } from '../auth/auth-guard.service'; +import { RegimeListResolver } from '../regimes/regime-list-resolver.service'; import { BillResolver } from './bills/bill-resolver.service'; import { BillsComponent } from './bills/bills.component'; @@ -25,6 +26,7 @@ const routes: Routes = [ }, resolve: { list: RunningTablesResolver, + regimes: RegimeListResolver, }, }, { @@ -36,6 +38,7 @@ const routes: Routes = [ }, resolve: { item: RunningTablesResolver, + regimes: RegimeListResolver, }, }, { diff --git a/bookie/src/app/sales/split-bill/split-bill.component.ts b/bookie/src/app/sales/split-bill/split-bill.component.ts index 48bd985..31ae044 100644 --- a/bookie/src/app/sales/split-bill/split-bill.component.ts +++ b/bookie/src/app/sales/split-bill/split-bill.component.ts @@ -1,5 +1,5 @@ import { Component, Inject } from '@angular/core'; -import { UntypedFormArray, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormArray, FormControl, FormGroup } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Observable } from 'rxjs'; @@ -10,36 +10,44 @@ import { Observable } from 'rxjs'; }) export class SplitBillComponent { list: { id: string; name: string; selected: boolean }[] = []; - form: UntypedFormGroup; + form: FormGroup<{ + saleCategories: FormArray< + FormGroup<{ + saleCategory: FormControl; + }> + >; + }>; constructor( public dialogRef: MatDialogRef, - private fb: UntypedFormBuilder, @Inject(MAT_DIALOG_DATA) public data: Observable<{ id: string; name: string; selected: boolean }[]>, ) { - this.form = this.fb.group({ - saleCategories: this.fb.array([]), + this.form = new FormGroup({ + saleCategories: new FormArray< + FormGroup<{ + saleCategory: FormControl; + }> + >([]), }); this.data.subscribe((list: { id: string; name: string; selected: boolean }[]) => { this.list = list; - this.form.setControl( - 'saleCategories', - this.fb.array( - this.list.map((x) => - this.fb.group({ - saleCategory: x.selected, - }), - ), + this.form.controls.saleCategories.clear(); + + this.list.forEach((x) => + this.form.controls.saleCategories.push( + new FormGroup({ + saleCategory: new FormControl(x.selected, { nonNullable: true }), + }), ), ); }); } accept(): void { - const array = this.form.get('saleCategories') as UntypedFormArray; + const array = this.form.controls.saleCategories; this.list.forEach((item, index) => { - item.selected = array.controls[index].value.saleCategory; + item.selected = array.controls[index].value.saleCategory ?? false; }); this.dialogRef.close(this.list); } diff --git a/bookie/src/app/sales/tables-dialog/tables-dialog.component.html b/bookie/src/app/sales/tables-dialog/tables-dialog.component.html index 0b38361..0b4628c 100644 --- a/bookie/src/app/sales/tables-dialog/tables-dialog.component.html +++ b/bookie/src/app/sales/tables-dialog/tables-dialog.component.html @@ -11,7 +11,7 @@ >

{{ table.name }}

{{ table.guest }} - {{ table.pax || 0 }} / {{ table.seats }} Seats + {{ table.pax ?? 0 }} / {{ table.seats }} Seats {{ table.date }} {{ table.amount | currency : 'INR' }} diff --git a/bookie/src/app/section-printers/section-printer.component.html b/bookie/src/app/section-printers/section-printer.component.html index 9fddee0..c393412 100644 --- a/bookie/src/app/section-printers/section-printer.component.html +++ b/bookie/src/app/section-printers/section-printer.component.html @@ -19,7 +19,7 @@ Sale Category - {{ row.saleCategory?.name || 'Default' }} + {{ row.saleCategory?.name ?? 'Default' }} diff --git a/bookie/src/app/section-printers/section-printer.component.ts b/bookie/src/app/section-printers/section-printer.component.ts index 1787ac9..416a980 100644 --- a/bookie/src/app/section-printers/section-printer.component.ts +++ b/bookie/src/app/section-printers/section-printer.component.ts @@ -1,10 +1,5 @@ import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { - AbstractControl, - UntypedFormArray, - UntypedFormBuilder, - UntypedFormGroup, -} from '@angular/forms'; +import { FormArray, FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { MatSelectChange } from '@angular/material/select'; import { ActivatedRoute, Router } from '@angular/router'; @@ -27,7 +22,17 @@ import { SectionPrinterService } from './section-printer.service'; }) export class SectionPrinterComponent implements OnInit { @ViewChild('section', { static: true }) sectionElement?: ElementRef; - form: UntypedFormGroup; + form: FormGroup<{ + section: FormControl; + saleCategories: FormArray< + FormGroup<{ + saleCategory: FormControl; + printer: FormControl; + copies: FormControl; + }> + >; + }>; + public listObservable = new BehaviorSubject([]); dataSource: SectionPrinterDataSource = new SectionPrinterDataSource(this.listObservable); list: SectionPrinter[] = []; @@ -39,15 +44,20 @@ export class SectionPrinterComponent implements OnInit { constructor( private route: ActivatedRoute, private router: Router, - private fb: UntypedFormBuilder, private toaster: ToasterService, private dialog: MatDialog, private ser: SectionPrinterService, ) { // Create form - this.form = this.fb.group({ - section: '', - saleCategories: this.fb.array([]), + this.form = new FormGroup({ + section: new FormControl('', { nonNullable: true }), + saleCategories: new FormArray< + FormGroup<{ + saleCategory: FormControl; + printer: FormControl; + copies: FormControl; + }> + >([]), }); route.params.pipe(map((p) => p['id'])).subscribe((x) => { this.sectionId = x; @@ -67,17 +77,17 @@ export class SectionPrinterComponent implements OnInit { showItem(list: SectionPrinter[]) { this.list = list; - (this.form.get('section') as AbstractControl).setValue(this.sectionId); - this.form.setControl( - 'saleCategories', - this.fb.array( - this.list.map((x) => - this.fb.group({ - saleCategory: x.saleCategory?.name || 'Default', - printer: x.printer?.id, - copies: `${x.copies}`, + this.form.controls.section.setValue(this.sectionId); + this.form.controls.saleCategories.clear(); + this.list.forEach((x) => + this.form.controls.saleCategories.push( + new FormGroup({ + saleCategory: new FormControl(x.saleCategory?.name ?? 'Default', { + nonNullable: true, }), - ), + printer: new FormControl(x.printer?.id ?? null), + copies: new FormControl(x.copies, { nonNullable: true }), + }), ), ); } @@ -123,8 +133,8 @@ export class SectionPrinterComponent implements OnInit { getItem(): SectionPrinter[] { const formModel = this.form.value; - this.sectionId = formModel.section; - const array = this.form.get('saleCategories') as UntypedFormArray; + this.sectionId = formModel.section ?? ''; + const array = this.form.controls.saleCategories; this.list.forEach((item, index) => { const cont = array.controls[index].value; if (cont.printer === null || cont.printer === undefined) { @@ -132,7 +142,7 @@ export class SectionPrinterComponent implements OnInit { } else { item.printer = { id: cont.printer }; } - item.copies = +cont.copies; + item.copies = cont.copies ?? 0; }); return this.list; } diff --git a/bookie/src/app/sections/section-detail/section-detail.component.ts b/bookie/src/app/sections/section-detail/section-detail.component.ts index 5e9a17a..bbbcd0b 100644 --- a/bookie/src/app/sections/section-detail/section-detail.component.ts +++ b/bookie/src/app/sections/section-detail/section-detail.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -15,20 +15,22 @@ import { SectionService } from '../section.service'; }) export class SectionDetailComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; - form: UntypedFormGroup; + form: FormGroup<{ + name: FormControl; + }>; + item: Section = new Section(); constructor( private route: ActivatedRoute, private router: Router, private dialog: MatDialog, - private fb: UntypedFormBuilder, private toaster: ToasterService, private ser: SectionService, ) { // Create form - this.form = this.fb.group({ - name: '', + this.form = new FormGroup({ + name: new FormControl('', { nonNullable: true }), }); } @@ -42,7 +44,7 @@ export class SectionDetailComponent implements OnInit, AfterViewInit { showItem(item: Section) { this.item = item; this.form.setValue({ - name: this.item.name || '', + name: this.item.name ?? '', }); } @@ -93,7 +95,7 @@ export class SectionDetailComponent implements OnInit, AfterViewInit { getItem(): Section { const formModel = this.form.value; - this.item.name = formModel.name; + this.item.name = formModel.name ?? ''; return this.item; } } diff --git a/bookie/src/app/settings/settings.component.ts b/bookie/src/app/settings/settings.component.ts index 223be8e..5fd6fa5 100644 --- a/bookie/src/app/settings/settings.component.ts +++ b/bookie/src/app/settings/settings.component.ts @@ -1,7 +1,8 @@ import { Component, OnInit } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; +import * as moment from 'moment'; import { environment } from '../../environments/environment'; import { AuthService } from '../auth/auth.service'; @@ -16,19 +17,28 @@ import { SettingsService } from './settings.service'; }) export class SettingsComponent implements OnInit { prefillCustomerDiscount = true; - version: string; + maintenanceForm: FormGroup<{ + startDate: FormControl; + finishDate: FormControl; + }>; + + beerFile: File | null = null; + saleFile: File | null = null; constructor( private route: ActivatedRoute, private router: Router, - private fb: UntypedFormBuilder, private dialog: MatDialog, private toaster: ToasterService, public auth: AuthService, private ser: SettingsService, ) { this.version = environment.version; + this.maintenanceForm = new FormGroup({ + startDate: new FormControl(new Date(), { nonNullable: true }), + finishDate: new FormControl(new Date(), { nonNullable: true }), + }); } ngOnInit() { @@ -46,4 +56,31 @@ export class SettingsComponent implements OnInit { this.prefillCustomerDiscount = x; }); } + + detectBeer(event: Event) { + this.beerFile = ((event.target as HTMLInputElement).files as FileList)[0]; + } + + detectSale(event: Event) { + this.saleFile = ((event.target as HTMLInputElement).files as FileList)[0]; + } + + runMaintenance() { + const formModel = this.maintenanceForm.value; + const startDate = moment(formModel.startDate).format('DD-MMM-YYYY'); + const finishDate = moment(formModel.finishDate).format('DD-MMM-YYYY'); + + if (!this.beerFile || !this.saleFile) { + this.toaster.show('Danger', 'Please choose both files first!'); + return; + } + this.ser.runMaintenance(startDate, finishDate, this.beerFile, this.saleFile).subscribe( + () => { + this.toaster.show('Success', 'Maintenance done'); + }, + (error) => { + this.toaster.show('Danger', error); + }, + ); + } } diff --git a/bookie/src/app/settings/settings.service.ts b/bookie/src/app/settings/settings.service.ts index 172777d..0ae5b9d 100644 --- a/bookie/src/app/settings/settings.service.ts +++ b/bookie/src/app/settings/settings.service.ts @@ -26,4 +26,20 @@ export class SettingsService { catchError(this.log.handleError(serviceName, 'setPrefillCustomerDiscount')), ) as Observable; } + + runMaintenance( + startDate: string, + finishDate: string, + beerFile: File, + saleFile: File, + ): Observable { + const options = { params: new HttpParams().set('s', startDate).set('f', finishDate) }; + const url = '/api/maintenance'; + const fd = new FormData(); + fd.append('beer_file', beerFile); + fd.append('sale_file', saleFile); + return this.http + .post(url, fd, options) + .pipe(catchError(this.log.handleError(serviceName, 'runMaintenance'))) as Observable; + } } diff --git a/bookie/src/app/settle-option/settle-option-detail/settle-option-detail.component.ts b/bookie/src/app/settle-option/settle-option-detail/settle-option-detail.component.ts index d9a57b1..186dbb9 100644 --- a/bookie/src/app/settle-option/settle-option-detail/settle-option-detail.component.ts +++ b/bookie/src/app/settle-option/settle-option-detail/settle-option-detail.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -17,7 +17,13 @@ import { SettleOptionService } from '../settle-option.service'; }) export class SettleOptionDetailComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; - form: UntypedFormGroup; + form: FormGroup<{ + name: FormControl; + voucherType: FormControl; + reportingLevel: FormControl; + hasReason: FormControl; + }>; + item: SettleOption = new SettleOption(); voucherTypes: { id: number; name: string }[]; reportingLevel: { id: number; name: string }[]; @@ -26,16 +32,15 @@ export class SettleOptionDetailComponent implements OnInit, AfterViewInit { private route: ActivatedRoute, private router: Router, private dialog: MatDialog, - private fb: UntypedFormBuilder, private toaster: ToasterService, private ser: SettleOptionService, ) { // Create form - this.form = this.fb.group({ - name: '', - voucherType: '', - reportingLevel: '', - hasReason: '', + this.form = new FormGroup({ + name: new FormControl('', { nonNullable: true }), + voucherType: new FormControl(0, { nonNullable: true }), + reportingLevel: new FormControl(0, { nonNullable: true }), + hasReason: new FormControl(false, { nonNullable: true }), }); this.voucherTypes = Object.keys(VoucherType) .filter((e) => !isNaN(+e)) @@ -109,10 +114,10 @@ export class SettleOptionDetailComponent implements OnInit, AfterViewInit { getItem(): SettleOption { const formModel = this.form.value; - this.item.name = formModel.name; - this.item.voucherType = formModel.voucherType; - this.item.reportingLevel = formModel.reportingLevel; - this.item.hasReason = formModel.hasReason; + this.item.name = formModel.name ?? ''; + this.item.voucherType = formModel.voucherType ?? 0; + this.item.reportingLevel = formModel.reportingLevel ?? 0; + this.item.hasReason = formModel.hasReason ?? false; return this.item; } } diff --git a/bookie/src/app/tables/table-detail/table-detail.component.ts b/bookie/src/app/tables/table-detail/table-detail.component.ts index 849aa3f..cb9ff91 100644 --- a/bookie/src/app/tables/table-detail/table-detail.component.ts +++ b/bookie/src/app/tables/table-detail/table-detail.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -16,7 +16,13 @@ import { TableService } from '../table.service'; }) export class TableDetailComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; - form: UntypedFormGroup; + form: FormGroup<{ + name: FormControl; + seats: FormControl; + section: FormControl; + isActive: FormControl; + }>; + sections: Section[] = []; item: Table = new Table(); @@ -24,16 +30,15 @@ export class TableDetailComponent implements OnInit, AfterViewInit { private route: ActivatedRoute, private router: Router, private dialog: MatDialog, - private fb: UntypedFormBuilder, private toaster: ToasterService, private ser: TableService, ) { // Create form - this.form = this.fb.group({ - name: '', - seats: '', - section: '', - isActive: '', + this.form = new FormGroup({ + name: new FormControl('', { nonNullable: true }), + seats: new FormControl(0, { nonNullable: true }), + section: new FormControl('', { nonNullable: true }), + isActive: new FormControl(true, { nonNullable: true }), }); } @@ -50,7 +55,7 @@ export class TableDetailComponent implements OnInit, AfterViewInit { this.form.setValue({ name: this.item.name, seats: this.item.seats, - section: this.item.section ? this.item.section.id : '', + section: this.item.section?.id ?? '', isActive: this.item.isActive, }); } @@ -102,13 +107,13 @@ export class TableDetailComponent implements OnInit, AfterViewInit { getItem(): Table { const formModel = this.form.value; - this.item.name = formModel.name; - this.item.seats = +formModel.seats; + this.item.name = formModel.name ?? ''; + this.item.seats = formModel.seats ?? 0; if (this.item.section === null || this.item.section === undefined) { this.item.section = new Section(); } this.item.section.id = formModel.section; - this.item.isActive = formModel.isActive; + this.item.isActive = formModel.isActive ?? true; return this.item; } } diff --git a/bookie/src/app/tax-report/tax-report-resolver.service.ts b/bookie/src/app/tax-report/tax-report-resolver.service.ts index d610c7a..6883efa 100644 --- a/bookie/src/app/tax-report/tax-report-resolver.service.ts +++ b/bookie/src/app/tax-report/tax-report-resolver.service.ts @@ -12,8 +12,8 @@ export class TaxReportResolver implements Resolve { constructor(private ser: TaxReportService) {} resolve(route: ActivatedRouteSnapshot): Observable { - const startDate = route.queryParamMap.get('startDate') || null; - const finishDate = route.queryParamMap.get('finishDate') || null; + const startDate = route.queryParamMap.get('startDate') ?? null; + const finishDate = route.queryParamMap.get('finishDate') ?? null; return this.ser.get(startDate, finishDate); } } diff --git a/bookie/src/app/tax-report/tax-report.component.ts b/bookie/src/app/tax-report/tax-report.component.ts index 1ae3949..187b000 100644 --- a/bookie/src/app/tax-report/tax-report.component.ts +++ b/bookie/src/app/tax-report/tax-report.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; @@ -16,21 +16,19 @@ import { TaxReportDatasource } from './tax-report-datasource'; export class TaxReportComponent implements OnInit { info: TaxReport = new TaxReport(); dataSource: TaxReportDatasource = new TaxReportDatasource(this.info.amounts); - form: UntypedFormGroup; + form: FormGroup<{ + startDate: FormControl; + finishDate: FormControl; + }>; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['name', 'taxRate', 'saleAmount', 'taxAmount']; - constructor( - private route: ActivatedRoute, - private router: Router, - private fb: UntypedFormBuilder, - private toCsv: ToCsvService, - ) { + constructor(private route: ActivatedRoute, private router: Router, private toCsv: ToCsvService) { // Create form - this.form = this.fb.group({ - startDate: '', - finishDate: '', + this.form = new FormGroup({ + startDate: new FormControl(new Date(), { nonNullable: true }), + finishDate: new FormControl(new Date(), { nonNullable: true }), }); } diff --git a/bookie/src/app/taxes/tax-detail/tax-detail.component.html b/bookie/src/app/taxes/tax-detail/tax-detail.component.html index 04357f7..3a5320a 100644 --- a/bookie/src/app/taxes/tax-detail/tax-detail.component.html +++ b/bookie/src/app/taxes/tax-detail/tax-detail.component.html @@ -18,6 +18,16 @@ %
+
+ + Regime + + + {{ reg.name }} + + + +
diff --git a/bookie/src/app/taxes/tax-detail/tax-detail.component.ts b/bookie/src/app/taxes/tax-detail/tax-detail.component.ts index a843b14..7a3c661 100644 --- a/bookie/src/app/taxes/tax-detail/tax-detail.component.ts +++ b/bookie/src/app/taxes/tax-detail/tax-detail.component.ts @@ -1,8 +1,9 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import { round } from 'mathjs'; +import { Regime } from 'src/app/core/regime'; import { Tax } from '../../core/tax'; import { ToasterService } from '../../core/toaster.service'; @@ -16,27 +17,34 @@ import { TaxService } from '../tax.service'; }) export class TaxDetailComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; - form: UntypedFormGroup; + form: FormGroup<{ + name: FormControl; + rate: FormControl; + regime: FormControl; + }>; + item: Tax = new Tax(); + regimes: Regime[] = []; constructor( private route: ActivatedRoute, private router: Router, private dialog: MatDialog, - private fb: UntypedFormBuilder, private toaster: ToasterService, private ser: TaxService, ) { // Create form - this.form = this.fb.group({ - name: '', - rate: '', + this.form = new FormGroup({ + name: new FormControl('', { nonNullable: true }), + rate: new FormControl(100, { nonNullable: true }), + regime: new FormControl(0, { nonNullable: true }), }); } ngOnInit() { this.route.data.subscribe((value) => { - const data = value as { item: Tax }; + const data = value as { item: Tax; regimes: Regime[] }; + this.regimes = data.regimes; this.showItem(data.item); }); } @@ -44,8 +52,9 @@ export class TaxDetailComponent implements OnInit, AfterViewInit { showItem(item: Tax) { this.item = item; this.form.setValue({ - name: this.item.name || '', + name: this.item.name ?? '', rate: this.item.rate * 100, + regime: this.item.regime?.id ?? 0, }); } @@ -96,8 +105,9 @@ export class TaxDetailComponent implements OnInit, AfterViewInit { getItem(): Tax { const formModel = this.form.value; - this.item.name = formModel.name; - this.item.rate = round(+formModel.rate / 100, 5); + this.item.name = formModel.name ?? ''; + this.item.rate = round((formModel.rate ?? 0) / 100, 5); + this.item.regime.id = formModel.regime; return this.item; } } diff --git a/bookie/src/app/taxes/tax-list/tax-list.component.html b/bookie/src/app/taxes/tax-list/tax-list.component.html index 7e9d2a4..59755fd 100644 --- a/bookie/src/app/taxes/tax-list/tax-list.component.html +++ b/bookie/src/app/taxes/tax-list/tax-list.component.html @@ -24,7 +24,13 @@ {{ row.rate | percent : '1.2-2' }} - + + + Regime + {{ row.regime.name }} + + + Is Fixture? {{ row.isFixture }} diff --git a/bookie/src/app/taxes/tax-list/tax-list.component.ts b/bookie/src/app/taxes/tax-list/tax-list.component.ts index 9deed7d..fe1a94b 100644 --- a/bookie/src/app/taxes/tax-list/tax-list.component.ts +++ b/bookie/src/app/taxes/tax-list/tax-list.component.ts @@ -14,7 +14,7 @@ export class TaxListComponent implements OnInit { list: Tax[] = []; dataSource: TaxListDataSource = new TaxListDataSource(this.list); /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ - displayedColumns = ['name', 'rate', 'isFixture']; + displayedColumns = ['name', 'rate', 'regime', 'isFixture']; constructor(private route: ActivatedRoute) {} diff --git a/bookie/src/app/taxes/taxes-routing.module.ts b/bookie/src/app/taxes/taxes-routing.module.ts index 1cd9c2a..a4395b4 100644 --- a/bookie/src/app/taxes/taxes-routing.module.ts +++ b/bookie/src/app/taxes/taxes-routing.module.ts @@ -3,6 +3,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { AuthGuard } from '../auth/auth-guard.service'; +import { RegimeListResolver } from '../regimes/regime-list-resolver.service'; import { TaxDetailComponent } from './tax-detail/tax-detail.component'; import { TaxListComponent } from './tax-list/tax-list.component'; @@ -30,6 +31,7 @@ const taxesRoutes: Routes = [ }, resolve: { item: TaxResolver, + regimes: RegimeListResolver, }, }, { @@ -41,6 +43,7 @@ const taxesRoutes: Routes = [ }, resolve: { item: TaxResolver, + regimes: RegimeListResolver, }, }, ]; diff --git a/bookie/src/app/taxes/taxes.module.ts b/bookie/src/app/taxes/taxes.module.ts index 9e1294c..75bde8b 100644 --- a/bookie/src/app/taxes/taxes.module.ts +++ b/bookie/src/app/taxes/taxes.module.ts @@ -7,6 +7,7 @@ import { MatCardModule } from '@angular/material/card'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatSelectModule } from '@angular/material/select'; import { MatTableModule } from '@angular/material/table'; import { TaxDetailComponent } from './tax-detail/tax-detail.component'; @@ -22,6 +23,7 @@ import { TaxesRoutingModule } from './taxes-routing.module'; MatIconModule, MatInputModule, MatProgressSpinnerModule, + MatSelectModule, MatTableModule, ReactiveFormsModule, TaxesRoutingModule, diff --git a/bookie/src/app/temporal-product/temporal-product-detail/temporal-product-detail.component.ts b/bookie/src/app/temporal-product/temporal-product-detail/temporal-product-detail.component.ts index d00e448..bbb846c 100644 --- a/bookie/src/app/temporal-product/temporal-product-detail/temporal-product-detail.component.ts +++ b/bookie/src/app/temporal-product/temporal-product-detail/temporal-product-detail.component.ts @@ -1,5 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; @@ -18,7 +18,20 @@ import { TemporalProductService } from '../temporal-product.service'; }) export class TemporalProductDetailComponent implements OnInit, AfterViewInit { @ViewChild('name', { static: true }) nameElement?: ElementRef; - form: UntypedFormGroup; + form: FormGroup<{ + id: FormControl; + name: FormControl; + units: FormControl; + menuCategory: FormControl; + saleCategory: FormControl; + price: FormControl; + hasHappyHour: FormControl; + isNotAvailable: FormControl; + quantity: FormControl; + validFrom: FormControl; + validTill: FormControl; + }>; + menuCategories: MenuCategory[] = []; saleCategories: SaleCategory[] = []; item: Product = new Product(); @@ -27,23 +40,22 @@ export class TemporalProductDetailComponent implements OnInit, AfterViewInit { private route: ActivatedRoute, private router: Router, private dialog: MatDialog, - private fb: UntypedFormBuilder, private toaster: ToasterService, private ser: TemporalProductService, ) { // Create form - this.form = this.fb.group({ - id: '', - name: '', - units: '', - menuCategory: '', - saleCategory: '', - price: '', - hasHappyHour: '', - isNotAvailable: '', - quantity: '', - validFrom: '', - validTill: '', + this.form = new FormGroup({ + id: new FormControl('', { nonNullable: true }), + name: new FormControl('', { nonNullable: true }), + units: new FormControl('', { nonNullable: true }), + menuCategory: new FormControl('', { nonNullable: true }), + saleCategory: new FormControl('', { nonNullable: true }), + price: new FormControl(0, { nonNullable: true }), + hasHappyHour: new FormControl(false, { nonNullable: true }), + isNotAvailable: new FormControl(false, { nonNullable: true }), + quantity: new FormControl(0, { nonNullable: true }), + validFrom: new FormControl(new Date()), + validTill: new FormControl(new Date()), }); } @@ -63,19 +75,19 @@ export class TemporalProductDetailComponent implements OnInit, AfterViewInit { showItem(item: Product) { this.item = item; this.form.setValue({ - id: this.item.id, + id: this.item.id ?? '', name: this.item.name, units: this.item.units, - menuCategory: this.item.menuCategory?.id, - saleCategory: this.item.saleCategory?.id, + menuCategory: this.item.menuCategory?.id ?? '', + saleCategory: this.item.saleCategory?.id ?? '', price: this.item.price, hasHappyHour: this.item.hasHappyHour, isNotAvailable: this.item.isNotAvailable, quantity: this.item.quantity, validFrom: - this.item.validFrom === null ? '' : moment(this.item.validFrom, 'DD-MMM-YYYY').toDate(), + this.item.validFrom === null ? null : moment(this.item.validFrom, 'DD-MMM-YYYY').toDate(), validTill: - this.item.validTill === null ? '' : moment(this.item.validTill, 'DD-MMM-YYYY').toDate(), + this.item.validTill === null ? null : moment(this.item.validTill, 'DD-MMM-YYYY').toDate(), }); } @@ -127,8 +139,8 @@ export class TemporalProductDetailComponent implements OnInit, AfterViewInit { getItem(): Product { const formModel = this.form.value; this.item.id = formModel.id; - this.item.name = formModel.name; - this.item.units = formModel.units; + this.item.name = formModel.name ?? ''; + this.item.units = formModel.units ?? ''; if (this.item.menuCategory === null || this.item.menuCategory === undefined) { this.item.menuCategory = new MenuCategory(); } @@ -137,10 +149,10 @@ export class TemporalProductDetailComponent implements OnInit, AfterViewInit { this.item.saleCategory = new SaleCategory(); } this.item.saleCategory.id = formModel.saleCategory; - this.item.price = +formModel.price; - this.item.hasHappyHour = formModel.hasHappyHour; - this.item.isNotAvailable = formModel.isNotAvailable; - this.item.quantity = +formModel.quantity; + this.item.price = formModel.price ?? 0; + this.item.hasHappyHour = formModel.hasHappyHour ?? false; + this.item.isNotAvailable = formModel.isNotAvailable ?? false; + this.item.quantity = formModel.quantity ?? 0; this.item.validFrom = !formModel.validFrom ? null : moment(formModel.validFrom).format('DD-MMM-YYYY'); diff --git a/bookie/src/app/temporal-product/temporal-product-list/temporal-product-list.component.ts b/bookie/src/app/temporal-product/temporal-product-list/temporal-product-list.component.ts index 132fa01..e610095 100644 --- a/bookie/src/app/temporal-product/temporal-product-list/temporal-product-list.component.ts +++ b/bookie/src/app/temporal-product/temporal-product-list/temporal-product-list.component.ts @@ -1,9 +1,9 @@ import { Component, OnInit } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; import { BehaviorSubject } from 'rxjs'; import { Observable } from 'rxjs'; -import { debounceTime, distinctUntilChanged, startWith } from 'rxjs/operators'; +import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; import { MenuCategory } from '../../core/menu-category'; import { Product } from '../../core/product'; @@ -28,7 +28,12 @@ export class TemporalProductListComponent implements OnInit { this.data, ); - form: UntypedFormGroup; + form: FormGroup<{ + filter: FormControl; + menuCategory: FormControl; + saleCategory: FormControl; + }>; + list: Product[][] = []; menuCategories: MenuCategory[] = []; saleCategories: SaleCategory[] = []; @@ -42,17 +47,16 @@ export class TemporalProductListComponent implements OnInit { 'quantity', ]; - constructor(private route: ActivatedRoute, private fb: UntypedFormBuilder) { - this.form = this.fb.group({ - filter: '', - menuCategory: '', - saleCategory: '', + constructor(private route: ActivatedRoute) { + this.form = new FormGroup({ + filter: new FormControl('', { nonNullable: true }), + menuCategory: new FormControl(''), + saleCategory: new FormControl(''), }); this.data.subscribe((data: Product[][]) => { this.list = data; }); - this.searchFilter = (this.form.get('filter') as UntypedFormControl).valueChanges.pipe( - startWith(''), + this.searchFilter = this.form.controls.filter.valueChanges.pipe( debounceTime(150), distinctUntilChanged(), ); diff --git a/bookie/src/app/update-product-prices/update-product-prices-item.ts b/bookie/src/app/update-product-prices/update-product-prices-item.ts index c2afcd8..51309a9 100644 --- a/bookie/src/app/update-product-prices/update-product-prices-item.ts +++ b/bookie/src/app/update-product-prices/update-product-prices-item.ts @@ -2,13 +2,13 @@ export class UpdateProductPricesItem { id: string; name: string; oldPrice: number; - newPrice: number; + newPrice: number | null; public constructor(init?: Partial) { this.id = ''; this.name = ''; this.oldPrice = 0; - this.newPrice = 0; + this.newPrice = null; Object.assign(this, init); } } diff --git a/bookie/src/app/update-product-prices/update-product-prices-resolver.service.ts b/bookie/src/app/update-product-prices/update-product-prices-resolver.service.ts index 76670de..5b16eb9 100644 --- a/bookie/src/app/update-product-prices/update-product-prices-resolver.service.ts +++ b/bookie/src/app/update-product-prices/update-product-prices-resolver.service.ts @@ -12,7 +12,7 @@ export class UpdateProductPricesResolver implements Resolve constructor(private ser: UpdateProductPricesService) {} resolve(route: ActivatedRouteSnapshot): Observable { - const date = route.queryParamMap.get('date') || null; + const date = route.queryParamMap.get('date') ?? null; const id = route.paramMap.get('id'); return this.ser.get(id, date); } diff --git a/bookie/src/app/update-product-prices/update-product-prices.component.ts b/bookie/src/app/update-product-prices/update-product-prices.component.ts index 720aeaa..53abc6e 100644 --- a/bookie/src/app/update-product-prices/update-product-prices.component.ts +++ b/bookie/src/app/update-product-prices/update-product-prices.component.ts @@ -1,10 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { - AbstractControl, - UntypedFormArray, - UntypedFormBuilder, - UntypedFormGroup, -} from '@angular/forms'; +import { FormArray, FormControl, FormGroup } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; import { map } from 'rxjs/operators'; @@ -25,7 +20,16 @@ import { UpdateProductPricesService } from './update-product-prices.service'; export class UpdateProductPricesComponent implements OnInit { info: UpdateProductPrices = new UpdateProductPrices(); dataSource: UpdateProductPricesDataSource = new UpdateProductPricesDataSource(this.info.items); - form: UntypedFormGroup; + form: FormGroup<{ + date: FormControl; + menuCategory: FormControl; + prices: FormArray< + FormGroup<{ + newPrice: FormControl; + }> + >; + }>; + menuCategories: MenuCategory[] = []; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ @@ -34,16 +38,19 @@ export class UpdateProductPricesComponent implements OnInit { constructor( private route: ActivatedRoute, private router: Router, - private fb: UntypedFormBuilder, private toCsv: ToCsvService, private toaster: ToasterService, private ser: UpdateProductPricesService, ) { // Create form - this.form = this.fb.group({ - date: '', - menuCategory: '', - prices: this.fb.array([]), + this.form = new FormGroup({ + date: new FormControl(new Date(), { nonNullable: true }), + menuCategory: new FormControl('', { nonNullable: true }), + prices: new FormArray< + FormGroup<{ + newPrice: FormControl; + }> + >([]), }); } @@ -66,23 +73,20 @@ export class UpdateProductPricesComponent implements OnInit { loadData(info: UpdateProductPrices) { this.info = info; - (this.form.get('date') as AbstractControl).setValue( - moment(this.info.date, 'DD-MMM-YYYY').toDate(), - ); - (this.form.get('menuCategory') as AbstractControl).setValue( + this.form.controls.date.setValue(moment(this.info.date, 'DD-MMM-YYYY').toDate()); + this.form.controls.menuCategory.setValue( this.info.menuCategoryId !== undefined ? this.info.menuCategoryId : '', ); - this.form.setControl( - 'prices', - this.fb.array( - this.info.items.map((x) => - this.fb.group({ - newPrice: x.newPrice, - }), - ), + this.form.controls.prices.clear(); + + this.info.items.forEach((x) => + this.form.controls.prices.push( + new FormGroup({ + newPrice: new FormControl(x.newPrice), + }), ), - ); - this.dataSource = new UpdateProductPricesDataSource(this.info.items); + ), + (this.dataSource = new UpdateProductPricesDataSource(this.info.items)); } show() { @@ -100,9 +104,9 @@ export class UpdateProductPricesComponent implements OnInit { getInfo(): UpdateProductPrices { const formModel = this.form.value; - const array = this.form.get('prices') as UntypedFormArray; + const array = this.form.controls.prices; this.info.items.forEach((item, index) => { - item.newPrice = array.controls[index].value.newPrice; + item.newPrice = array.controls[index].value.newPrice ?? null; }); return { diff --git a/bookie/src/app/users/user-detail/user-detail.component.ts b/bookie/src/app/users/user-detail/user-detail.component.ts index 8055215..9650b9d 100644 --- a/bookie/src/app/users/user-detail/user-detail.component.ts +++ b/bookie/src/app/users/user-detail/user-detail.component.ts @@ -1,10 +1,5 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { - AbstractControl, - UntypedFormArray, - UntypedFormBuilder, - UntypedFormGroup, -} from '@angular/forms'; +import { FormArray, FormControl, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; @@ -20,25 +15,34 @@ import { UserService } from '../user.service'; }) export class UserDetailComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; - form: UntypedFormGroup; + form: FormGroup<{ + name: FormControl; + password: FormControl; + lockedOut: FormControl; + roles: FormArray< + FormGroup<{ + role: FormControl; + }> + >; + }>; + item: User = new User(); hide: boolean; constructor( private route: ActivatedRoute, private router: Router, - private fb: UntypedFormBuilder, private toaster: ToasterService, private dialog: MatDialog, private ser: UserService, ) { this.hide = true; // Create form - this.form = this.fb.group({ - name: '', - password: '', - lockedOut: '', - roles: this.fb.array([]), + this.form = new FormGroup({ + name: new FormControl(null), + password: new FormControl(null), + lockedOut: new FormControl(false, { nonNullable: true }), + roles: new FormArray }>>([]), }); } @@ -51,17 +55,15 @@ export class UserDetailComponent implements OnInit, AfterViewInit { showItem(item: User) { this.item = item; - (this.form.get('name') as AbstractControl).setValue(item.name); - (this.form.get('password') as AbstractControl).setValue(''); - (this.form.get('lockedOut') as AbstractControl).setValue(item.lockedOut); - this.form.setControl( - 'roles', - this.fb.array( - item.roles.map((x) => - this.fb.group({ - role: x.enabled, - }), - ), + this.form.controls.name.setValue(item.name); + this.form.controls.password.setValue(''); + this.form.controls.lockedOut.setValue(item.lockedOut); + this.form.controls.roles.clear(); + this.item.roles.forEach((x) => + this.form.controls.roles.push( + new FormGroup({ + role: new FormControl(x.enabled, { nonNullable: true }), + }), ), ); } @@ -118,15 +120,13 @@ export class UserDetailComponent implements OnInit, AfterViewInit { getItem(): User { const formModel = this.form.value; - this.item.name = formModel.name; - this.item.password = formModel.password; - this.item.lockedOut = formModel.lockedOut; - const array = this.form.get('roles') as UntypedFormArray; - if (this.item.roles !== undefined) { - this.item.roles.forEach((item, index) => { - item.enabled = array.controls[index].value.role; - }); - } + this.item.name = formModel.name ?? ''; + this.item.password = formModel.password ?? ''; + this.item.lockedOut = formModel.lockedOut ?? true; + const array = this.form.controls.roles; + this.item.roles.forEach((item, index) => { + item.enabled = array.controls[index].value.role ?? false; + }); return this.item; } } diff --git a/lint.sh b/lint.sh index 726ade0..ca784c3 100755 --- a/lint.sh +++ b/lint.sh @@ -4,6 +4,6 @@ cd "$parent_path/bookie" || exit npx prettier --write src/app npx ng lint --fix cd "$parent_path/barker" || exit -poetry run isort barker -poetry run black barker -poetry run flake8 barker +isort barker +black barker +flake8 barker