From ecea277e46886356bcfcf98b27c323c9e38cb1f7 Mon Sep 17 00:00:00 2001 From: Amritanshu Date: Mon, 28 Aug 2023 07:14:24 +0530 Subject: [PATCH] Feature: Table Booking in guestbook. Feature: Guest book list shows the running cover count --- .../alembic/versions/7e9944b430d6_booking.py | 180 ++++++++++++++++++ barker/barker/db/base.py | 1 + barker/barker/models/food_table.py | 4 +- barker/barker/models/guest_book.py | 50 ++++- barker/barker/models/guest_book_status.py | 10 + barker/barker/models/guest_book_type.py | 7 + barker/barker/models/inventory.py | 2 +- barker/barker/models/overview.py | 7 +- barker/barker/models/overview_status.py | 6 + barker/barker/models/voucher.py | 4 +- barker/barker/routers/guest_book.py | 75 ++++++-- barker/barker/routers/voucher/__init__.py | 8 +- barker/barker/routers/voucher/change.py | 6 +- barker/barker/schemas/guest_book.py | 100 ++++++++-- .../guest-book-detail-resolver.service.ts | 3 +- .../guest-book-detail.component.html | 14 +- .../guest-book-detail.component.ts | 27 ++- .../guest-book-list.component.html | 17 +- .../guest-book-list.component.sass | 4 + .../src/app/guest-book/guest-book.service.ts | 5 +- bookie/src/app/guest-book/guest-book.ts | 13 +- bookie/src/app/shared/local-time.pipe.ts | 2 +- 22 files changed, 481 insertions(+), 64 deletions(-) create mode 100644 barker/alembic/versions/7e9944b430d6_booking.py create mode 100644 barker/barker/models/guest_book_status.py create mode 100644 barker/barker/models/guest_book_type.py create mode 100644 barker/barker/models/overview_status.py diff --git a/barker/alembic/versions/7e9944b430d6_booking.py b/barker/alembic/versions/7e9944b430d6_booking.py new file mode 100644 index 0000000..6b18c4e --- /dev/null +++ b/barker/alembic/versions/7e9944b430d6_booking.py @@ -0,0 +1,180 @@ +"""booking + +Revision ID: 7e9944b430d6 +Revises: 1d95eb10ea20 +Create Date: 2023-08-19 14:25:40.206187 + +""" +import sqlalchemy as sa + +from alembic import op +from sqlalchemy.dialects import postgresql + + +# revision identifiers, used by Alembic. +revision = "7e9944b430d6" +down_revision = "1d95eb10ea20" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column("customers", "name", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False) + op.alter_column( + "customers", "phone", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False + ) + op.alter_column( + "customers", "address", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=True + ) + op.alter_column("devices", "name", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False) + op.alter_column( + "food_tables", "name", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False + ) + op.alter_column( + "menu_categories", "name", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False + ) + op.alter_column( + "modifier_categories", "name", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False + ) + op.alter_column("modifiers", "name", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False) + overviewstatus = sa.Enum("running", "printed", name="overviewstatus") + overviewstatus.create(op.get_bind()) + op.execute("ALTER TABLE overview ALTER COLUMN status TYPE overviewstatus USING status::text::overviewstatus;") + op.alter_column( + "overview", + "status", + existing_type=sa.VARCHAR(length=255), + type_=overviewstatus, + existing_nullable=False, + ) + op.alter_column( + "permissions", "name", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False + ) + op.alter_column("printers", "name", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False) + op.alter_column( + "printers", "address", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False + ) + op.alter_column( + "printers", "cut_code", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False + ) + op.alter_column( + "product_versions", "name", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False + ) + op.alter_column( + "product_versions", "units", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False + ) + op.alter_column("regimes", "name", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False) + op.alter_column("regimes", "header", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False) + op.alter_column("regimes", "prefix", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False) + op.alter_column("roles", "name", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False) + op.alter_column( + "sale_categories", "name", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False + ) + op.alter_column("sections", "name", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False) + op.alter_column("settings", "name", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False) + op.alter_column("settings", "data", existing_type=postgresql.JSON(astext_type=sa.Text()), nullable=False) + op.alter_column( + "settle_options", "name", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False + ) + op.alter_column("taxes", "name", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False) + op.alter_column("users", "name", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=False) + op.alter_column("users", "password", existing_type=sa.VARCHAR(length=60), type_=sa.Text(), existing_nullable=False) + op.alter_column( + "vouchers", "narration", existing_type=sa.VARCHAR(length=1000), type_=sa.Text(), existing_nullable=True + ) + op.alter_column("vouchers", "reason", existing_type=sa.VARCHAR(length=255), type_=sa.Text(), existing_nullable=True) + + guestbooktype = sa.Enum("walk_in", "booking", "arrived", name="guestbooktype") + guestbooktype.create(op.get_bind()) + gbt = sa.table( + "guest_book", + sa.column("last_edit_date", sa.DateTime()), + sa.column("arrival_date", sa.DateTime()), + sa.column("type", guestbooktype), + ) + + with op.batch_alter_table("guest_book") as batch_op: + batch_op.add_column(sa.Column("booking_date", sa.DateTime(), nullable=True)) + batch_op.alter_column("creation_date", new_column_name="last_edit_date") + batch_op.add_column(sa.Column("arrival_date", sa.DateTime(), nullable=True)) + batch_op.add_column(sa.Column("notes", sa.Text(), nullable=True)) + batch_op.add_column(sa.Column("type", guestbooktype, nullable=True)) + op.execute(gbt.update().values(arrival_date=sa.text("last_edit_date"))) + op.execute(gbt.update().values(type="walk_in")) + with op.batch_alter_table("guest_book") as batch_op: + batch_op.alter_column("type", existing_type=guestbooktype, nullable=False) + batch_op.create_check_constraint("ck_guest_book_nulls", sa.text("num_nonnulls(booking_date, arrival_date) > 0")) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column("vouchers", "reason", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=True) + op.alter_column( + "vouchers", "narration", existing_type=sa.Text(), type_=sa.VARCHAR(length=1000), existing_nullable=True + ) + op.alter_column("users", "password", existing_type=sa.Text(), type_=sa.VARCHAR(length=60), existing_nullable=False) + op.alter_column("users", "name", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False) + op.alter_column("taxes", "name", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False) + op.alter_column( + "settle_options", "name", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False + ) + op.alter_column("settings", "data", existing_type=postgresql.JSON(astext_type=sa.Text()), nullable=True) + op.alter_column("settings", "name", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False) + op.alter_column("sections", "name", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False) + op.alter_column( + "sale_categories", "name", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False + ) + op.alter_column("roles", "name", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False) + op.alter_column("regimes", "prefix", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False) + op.alter_column("regimes", "header", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False) + op.alter_column("regimes", "name", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False) + op.alter_column( + "product_versions", "units", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False + ) + op.alter_column( + "product_versions", "name", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False + ) + op.alter_column( + "printers", "cut_code", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False + ) + op.alter_column( + "printers", "address", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False + ) + op.alter_column("printers", "name", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False) + op.alter_column( + "permissions", "name", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False + ) + op.alter_column( + "overview", + "status", + existing_type=sa.Enum("running", "printed", name="overviewstatus"), + type_=sa.VARCHAR(length=255), + existing_nullable=False, + ) + op.alter_column("modifiers", "name", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False) + op.alter_column( + "modifier_categories", "name", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False + ) + op.alter_column( + "menu_categories", "name", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False + ) + op.add_column("guest_book", sa.Column("creation_date", postgresql.TIMESTAMP(), autoincrement=False, nullable=False)) + op.drop_column("guest_book", "type_") + op.drop_column("guest_book", "notes") + op.drop_column("guest_book", "last_edit_date") + op.drop_column("guest_book", "date") + op.alter_column( + "food_tables", "name", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False + ) + op.alter_column("devices", "name", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False) + op.alter_column( + "customers", "address", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=True + ) + op.alter_column( + "customers", "phone", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False + ) + op.alter_column("customers", "name", existing_type=sa.Text(), type_=sa.VARCHAR(length=255), existing_nullable=False) + # ### end Alembic commands ### diff --git a/barker/barker/db/base.py b/barker/barker/db/base.py index fe0cde3..61d8c49 100644 --- a/barker/barker/db/base.py +++ b/barker/barker/db/base.py @@ -16,6 +16,7 @@ from ..models.modifier import Modifier # noqa: F401 from ..models.modifier_category import ModifierCategory # noqa: F401 from ..models.modifier_category_product import ModifierCategoryProduct # noqa: F401 from ..models.overview import Overview # noqa: F401 +from ..models.overview_status import OverviewStatus # noqa: F401 from ..models.permission import Permission # noqa: F401 from ..models.printer import Printer # noqa: F401 from ..models.product import Product # noqa: F401 diff --git a/barker/barker/models/food_table.py b/barker/barker/models/food_table.py index 912d129..36d11f2 100644 --- a/barker/barker/models/food_table.py +++ b/barker/barker/models/food_table.py @@ -1,6 +1,6 @@ import uuid -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from sqlalchemy import Boolean, ForeignKey, Integer, Text, Uuid, text from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -25,7 +25,7 @@ class FoodTable: sort_order: Mapped[bool] = mapped_column(Integer, nullable=False) section: Mapped["Section"] = relationship("Section", foreign_keys=section_id) - status: Mapped["Overview" | None] = relationship(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 29ff509..1458020 100644 --- a/barker/barker/models/guest_book.py +++ b/barker/barker/models/guest_book.py @@ -1,9 +1,19 @@ import uuid from datetime import datetime -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional -from sqlalchemy import DateTime, ForeignKey, Integer, Uuid, text +from barker.models.guest_book_type import GuestBookType +from sqlalchemy import ( + CheckConstraint, + DateTime, + Enum, + ForeignKey, + Integer, + Text, + Uuid, + text, +) from sqlalchemy.orm import Mapped, mapped_column, relationship from ..db.base_class import reg @@ -17,20 +27,44 @@ if TYPE_CHECKING: @reg.mapped_as_dataclass(unsafe_hash=True) class GuestBook: __tablename__ = "guest_book" + __table_args__ = (CheckConstraint("num_nonnulls(booking_date, arrival_date) > 0", name="ck_guest_book_nulls"),) + id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, server_default=text("gen_random_uuid()")) customer_id: Mapped[uuid.UUID] = mapped_column(Uuid, ForeignKey("customers.id"), nullable=False) pax: Mapped[int] = mapped_column(Integer, nullable=False) - date: Mapped[datetime] = mapped_column("creation_date", DateTime(), nullable=False) + booking_date: Mapped[Optional[datetime]] = mapped_column(DateTime(), nullable=True) + arrival_date: Mapped[Optional[datetime]] = mapped_column(DateTime(), nullable=True) + last_edit_date: Mapped[datetime] = mapped_column(DateTime(), nullable=False) + notes: Mapped[str | None] = mapped_column(Text, nullable=True) + type_: Mapped[GuestBookType] = mapped_column("type", Enum(GuestBookType), nullable=False) customer: Mapped["Customer"] = relationship("Customer") - status: Mapped["Overview" | None] = relationship(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, date_=None): - self.customer_id = customer_id + def __init__( + self, + customer_id=None, + pax=None, + booking_date=None, + arrival_date=None, + notes=None, + type_=None, + customer=None, + id_=None, + ): + if customer_id is not None: + self.customer_id = customer_id self.pax = pax - self.id = id_ - self.date = datetime.utcnow() if date_ is None else date_ + if booking_date is None and arrival_date is None: + raise Exception("Both arrival and booking date cannot be null") + + self.booking_date = booking_date + self.arrival_date = arrival_date + self.last_edit_date = datetime.utcnow() + self.notes = notes + self.type_ = type_ if customer is None: self.customer_id = customer_id else: self.customer = customer + self.id = id_ diff --git a/barker/barker/models/guest_book_status.py b/barker/barker/models/guest_book_status.py new file mode 100644 index 0000000..53b59bb --- /dev/null +++ b/barker/barker/models/guest_book_status.py @@ -0,0 +1,10 @@ +import enum + + +class GuestBookStatus(str, enum.Enum): + running = "running" + printed = "printed" + walk_in = "walk_in" + booking = "booking" + arrived = "arrived" + old = "old" diff --git a/barker/barker/models/guest_book_type.py b/barker/barker/models/guest_book_type.py new file mode 100644 index 0000000..b1e89c0 --- /dev/null +++ b/barker/barker/models/guest_book_type.py @@ -0,0 +1,7 @@ +import enum + + +class GuestBookType(str, enum.Enum): + walk_in = "walk_in" + booking = "booking" + arrived = "arrived" diff --git a/barker/barker/models/inventory.py b/barker/barker/models/inventory.py index 7ec2fff..f6da297 100644 --- a/barker/barker/models/inventory.py +++ b/barker/barker/models/inventory.py @@ -83,7 +83,7 @@ class Inventory: @hybrid_property def effective_price(self) -> Decimal: - return 0 if self.is_happy_hour else self.price + return Decimal(0) if self.is_happy_hour else self.price @effective_price.inplace.expression @classmethod diff --git a/barker/barker/models/overview.py b/barker/barker/models/overview.py index 6f9d381..6ada808 100644 --- a/barker/barker/models/overview.py +++ b/barker/barker/models/overview.py @@ -1,8 +1,11 @@ +from __future__ import annotations + import uuid from typing import TYPE_CHECKING -from sqlalchemy import ForeignKey, Text, Uuid, text +from barker.models.overview_status import OverviewStatus +from sqlalchemy import Enum, ForeignKey, Uuid, text from sqlalchemy.orm import Mapped, mapped_column, relationship from ..db.base_class import reg @@ -31,7 +34,7 @@ class Overview: unique=True, ) guest_book_id: Mapped[uuid.UUID] = mapped_column(Uuid, ForeignKey("guest_book.id"), unique=True, nullable=True) - status: Mapped[str] = mapped_column(Text, nullable=False) + status: Mapped[OverviewStatus] = mapped_column(Enum(OverviewStatus), nullable=False) voucher: Mapped["Voucher"] = relationship(back_populates="status") food_table: Mapped["FoodTable"] = relationship(back_populates="status") diff --git a/barker/barker/models/overview_status.py b/barker/barker/models/overview_status.py new file mode 100644 index 0000000..498d2b2 --- /dev/null +++ b/barker/barker/models/overview_status.py @@ -0,0 +1,6 @@ +import enum + + +class OverviewStatus(str, enum.Enum): + running = "running" + printed = "printed" diff --git a/barker/barker/models/voucher.py b/barker/barker/models/voucher.py index 97a6f18..22e4ff5 100644 --- a/barker/barker/models/voucher.py +++ b/barker/barker/models/voucher.py @@ -1,7 +1,7 @@ import uuid from datetime import datetime -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, List, Optional from sqlalchemy import DateTime, Enum, ForeignKey, Integer, Text, Uuid, text from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -63,7 +63,7 @@ class Voucher: back_populates="voucher", cascade="delete, delete-orphan", ) - status: Mapped["Overview" | None] = relationship(back_populates="voucher", uselist=False) + status: Mapped[Optional["Overview"]] = relationship(back_populates="voucher", uselist=False) def __init__( self, diff --git a/barker/barker/routers/guest_book.py b/barker/barker/routers/guest_book.py index 106d84e..ff8df79 100644 --- a/barker/barker/routers/guest_book.py +++ b/barker/barker/routers/guest_book.py @@ -4,8 +4,10 @@ from datetime import date, datetime, timedelta import barker.schemas.guest_book as schemas +from barker.models.guest_book_status import GuestBookStatus +from barker.models.guest_book_type import GuestBookType from fastapi import APIRouter, Depends, HTTPException, Security, status -from sqlalchemy import desc, select +from sqlalchemy import and_, desc, func, or_, select from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import joinedload @@ -47,8 +49,16 @@ def save( else: customer.name = data.name or customer.name customer.address = data.address or customer.address + td = timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES) + booking_date = None if data.booking_date is None else data.booking_date - td + arrival_date = None if data.arrival_date is None else data.arrival_date - td item = GuestBook( - pax=data.pax, customer=customer, date_=data.date_ - timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES) + pax=data.pax, + customer=customer, + booking_date=booking_date, + arrival_date=arrival_date, + notes=data.notes, + type_=data.type_, ) db.add(item) db.commit() @@ -73,7 +83,14 @@ def update_route( item.customer.phone = data.phone item.customer.address = data.address item.pax = data.pax - item.date = data.date_ - timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES) + td = timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES) + booking_date = None if data.booking_date is None else data.booking_date - td + arrival_date = None if data.arrival_date is None else data.arrival_date - td + item.booking_date = booking_date + item.arrival_date = arrival_date + item.last_edit_date = datetime.now() + item.notes = data.notes + item.type_ = data.type_ db.commit() return guest_book_info(item) except SQLAlchemyError as e: @@ -103,9 +120,10 @@ def delete_route( @router.get("", response_model=schemas.GuestBookIn) def show_blank( + t: GuestBookStatus | None, user: UserToken = Security(get_user, scopes=["customers"]), ) -> schemas.GuestBookIn: - return blank_guest_book_info() + return blank_guest_book_info(t or GuestBookStatus.walk_in) @router.get("/list", response_model=schemas.GuestBookList) @@ -117,33 +135,39 @@ def show_list( d = date.today() else: d = datetime.strptime(q, "%d-%b-%Y") + s = d + timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES - settings.TIMEZONE_OFFSET_MINUTES) + f = d + timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES - settings.TIMEZONE_OFFSET_MINUTES, days=1) list_ = ( select(GuestBook) .where( - GuestBook.date >= d + timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES - settings.TIMEZONE_OFFSET_MINUTES) - ) - .where( - GuestBook.date - < d - + timedelta( - minutes=settings.NEW_DAY_OFFSET_MINUTES - settings.TIMEZONE_OFFSET_MINUTES, - days=1, + or_( + and_(GuestBook.arrival_date >= s, GuestBook.arrival_date < f), + and_(GuestBook.booking_date >= s, GuestBook.booking_date < f), ) ) - .order_by(GuestBook.date) + .order_by(func.coalesce(GuestBook.arrival_date, GuestBook.booking_date)) ) guest_book: list[schemas.GuestBookListItem] = [] with SessionFuture() as db: + count = 0 for i, item in enumerate(db.execute(list_).scalars().all()): + status = ( + GuestBookStatus(item.type_.name) if item.status is None else GuestBookStatus(item.status.status.name) + ) + if item.type_ != GuestBookType.booking: + count += item.pax gbli = schemas.GuestBookListItem( id_=item.id, serial=i + 1, name=item.customer.name, phone=item.customer.phone, pax=item.pax, - date_=item.date, - status=None if item.status is None else item.status.status, + count=count, + booking_date=item.booking_date, + arrival_date=item.arrival_date, + last_edit_date=item.last_edit_date, + status=status, table_id=None if item.status is None else item.status.food_table.id, voucher_id=None if item.status is None else item.status.voucher_id, table_name=None if item.status is None else item.status.food_table.name, @@ -160,7 +184,7 @@ def show_list( .first() ) if last is not None: - gbli.status = "old" + gbli.status = GuestBookStatus.old gbli.voucher_id = last.id gbli.table_name = last.food_table.name guest_book.insert(0, gbli) @@ -178,21 +202,34 @@ def show_id( def guest_book_info(item: GuestBook) -> schemas.GuestBook: + td = timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES) + booking_date = None if item.booking_date is None else item.booking_date + td + arrival_date = None if item.arrival_date is None else item.arrival_date + td return schemas.GuestBook( id_=item.id, name=item.customer.name, phone=item.customer.phone, pax=item.pax, address=item.customer.address, - date_=item.date + timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES), + booking_date=booking_date, + arrival_date=arrival_date, + last_edit_date=item.last_edit_date + td, + notes=item.notes, + type_=GuestBookStatus(item.type_.name), ) -def blank_guest_book_info() -> schemas.GuestBookIn: +def blank_guest_book_info(type_: GuestBookStatus) -> schemas.GuestBookIn: + now = datetime.utcnow() + timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES) + booking_date = None if type_ != GuestBookStatus.booking else now + arrival_date = None if type_ != GuestBookStatus.walk_in else now return schemas.GuestBookIn( name="", phone="", pax=0, address="", - date_=datetime.utcnow() + timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES), + booking_date=booking_date, + arrival_date=arrival_date, + notes=None, + type_=type_, ) diff --git a/barker/barker/routers/voucher/__init__.py b/barker/barker/routers/voucher/__init__.py index bbd6028..73462cf 100644 --- a/barker/barker/routers/voucher/__init__.py +++ b/barker/barker/routers/voucher/__init__.py @@ -1,10 +1,13 @@ import uuid +from datetime import datetime from decimal import Decimal from typing import Set import barker.schemas.voucher as schemas +from barker.models.guest_book_type import GuestBookType +from barker.models.overview_status import OverviewStatus from fastapi import HTTPException, status from sqlalchemy import func from sqlalchemy.orm import Session @@ -34,7 +37,7 @@ def get_tax(tax, voucher_type): def do_update_table(item: Voucher, guest_book: GuestBook | None, db: Session): - status_ = "running" if item.voucher_type == VoucherType.KOT else "printed" + status_ = OverviewStatus.running if item.voucher_type == VoucherType.KOT else OverviewStatus.printed if item.status is None: item.status = Overview( voucher_id=item.id, @@ -42,6 +45,9 @@ def do_update_table(item: Voucher, guest_book: GuestBook | None, db: Session): guest_book_id=guest_book.id if guest_book is not None else None, status=status_, ) + if guest_book is not None and guest_book.type_ == GuestBookType.booking: + guest_book.type_ = GuestBookType.arrived + guest_book.arrival_date = datetime.now() db.add(item.status) else: item.status.status = status_ diff --git a/barker/barker/routers/voucher/change.py b/barker/barker/routers/voucher/change.py index e47e7ed..02245f8 100644 --- a/barker/barker/routers/voucher/change.py +++ b/barker/barker/routers/voucher/change.py @@ -2,6 +2,7 @@ import uuid import barker.schemas.voucher as schemas +from barker.models.overview_status import OverviewStatus from fastapi import APIRouter, HTTPException, Security, status from sqlalchemy import select, update from sqlalchemy.exc import SQLAlchemyError @@ -101,7 +102,10 @@ def void_and_issue_new_bill( if old.status is None: guest_book_id = None if guest_book is None else guest_book.id item.status = Overview( - voucher_id=None, food_table_id=item.food_table_id, guest_book_id=guest_book_id, status="printed" + voucher_id=None, + food_table_id=item.food_table_id, + guest_book_id=guest_book_id, + status=OverviewStatus.printed, ) db.add(item.status) else: diff --git a/barker/barker/schemas/guest_book.py b/barker/barker/schemas/guest_book.py index 190a351..cca781b 100644 --- a/barker/barker/schemas/guest_book.py +++ b/barker/barker/schemas/guest_book.py @@ -2,7 +2,15 @@ import uuid from datetime import date, datetime -from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator +from barker.models.guest_book_status import GuestBookStatus +from pydantic import ( + BaseModel, + ConfigDict, + Field, + field_serializer, + field_validator, + model_validator, +) from . import to_camel @@ -12,51 +20,117 @@ class GuestBookIn(BaseModel): phone: str address: str | None = None pax: int = Field(ge=0) - date_: datetime + booking_date: datetime | None = None + arrival_date: datetime | None = None + notes: str | None = None + type_: GuestBookStatus model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True) - @field_validator("date_", mode="before") + @field_validator("booking_date", mode="before") @classmethod - def parse_date(cls, value): + def parse_booking_date(cls, value): + if value is None: + return None if isinstance(value, datetime): return value return datetime.strptime(value, "%d-%b-%Y %H:%M") - @field_serializer("date_") - def serialize_date(self, value: datetime, _info): - return value.strftime("%d-%b-%Y %H:%M") + @field_serializer("booking_date") + def serialize_booking_date(self, value: datetime | None, _info): + return None if value is None else value.strftime("%d-%b-%Y %H:%M") + + @field_validator("arrival_date", mode="before") + @classmethod + def parse_arrival_date(cls, value): + if value is None: + return None + if isinstance(value, datetime): + return value + return datetime.strptime(value, "%d-%b-%Y %H:%M") + + @field_serializer("arrival_date") + def serialize_arrival_date(self, value: datetime | None, _info): + return None if value is None else value.strftime("%d-%b-%Y %H:%M") + + @model_validator(mode="after") + def nulls(self) -> "GuestBookIn": + if self.arrival_date is None and self.booking_date is None: + raise ValueError("Both arrival and booking date cannot be null") + return self class GuestBook(GuestBookIn): id_: uuid.UUID + last_edit_date: datetime model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True) + @field_validator("last_edit_date", mode="before") + @classmethod + def parse_last_edit_date(cls, value): + if isinstance(value, datetime): + return value + return datetime.strptime(value, "%d-%b-%Y %H:%M") + + @field_serializer("last_edit_date") + def serialize_last_edit_date(self, value: datetime, _info): + return value.strftime("%d-%b-%Y %H:%M") + class GuestBookListItem(BaseModel): id_: uuid.UUID serial: int + count: int name: str phone: str pax: int - date_: datetime - status: str | None = None + booking_date: datetime | None = None + arrival_date: datetime | None = None + last_edit_date: datetime + + status: GuestBookStatus table_id: uuid.UUID | None = None voucher_id: uuid.UUID | None = None table_name: str | None = None model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True) - @field_validator("date_", mode="before") + @field_validator("booking_date", mode="before") @classmethod - def parse_date(cls, value): + def parse_booking_date(cls, value): + if value is None: + return None if isinstance(value, datetime): return value return datetime.strptime(value, "%d-%b-%Y %H:%M") - @field_serializer("date_") - def serialize_date(self, value: datetime, _info): + @field_serializer("booking_date") + def serialize_booking_date(self, value: datetime | None, _info): + return None if value is None else value.strftime("%d-%b-%Y %H:%M") + + @field_validator("arrival_date", mode="before") + @classmethod + def parse_arrival_date(cls, value): + if value is None: + return None + if isinstance(value, datetime): + return value + return datetime.strptime(value, "%d-%b-%Y %H:%M") + + @field_serializer("arrival_date") + def serialize_arrival_date(self, value: datetime | None, _info): + return None if value is None else value.strftime("%d-%b-%Y %H:%M") + + @field_validator("last_edit_date", mode="before") + @classmethod + def parse_last_edit_date(cls, value): + if isinstance(value, datetime): + return value + return datetime.strptime(value, "%d-%b-%Y %H:%M") + + @field_serializer("last_edit_date") + def serialize_last_edit_date(self, value: datetime, _info): return value.strftime("%d-%b-%Y %H:%M") diff --git a/bookie/src/app/guest-book/guest-book-detail-resolver.service.ts b/bookie/src/app/guest-book/guest-book-detail-resolver.service.ts index f40dac9..6a35f2a 100644 --- a/bookie/src/app/guest-book/guest-book-detail-resolver.service.ts +++ b/bookie/src/app/guest-book/guest-book-detail-resolver.service.ts @@ -13,6 +13,7 @@ export class GuestBookDetailResolver { resolve(route: ActivatedRouteSnapshot): Observable { const id = route.paramMap.get('id'); - return this.ser.get(id); + const account = route.queryParamMap.get('t') || 'walk_in'; + return this.ser.get(id, account); } } diff --git a/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.html b/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.html index 96effa4..9590868 100644 --- a/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.html +++ b/bookie/src/app/guest-book/guest-book-detail/guest-book-detail.component.html @@ -41,10 +41,22 @@
+ Notes + + +
+
+ + Date + + + + + Hours - + Minutes 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 d7ac127..d70f159 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 @@ -3,6 +3,7 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; +import * as moment from 'moment'; import { Observable, of as observableOf } from 'rxjs'; import { debounceTime, distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators'; @@ -25,6 +26,8 @@ export class GuestBookDetailComponent implements OnInit, AfterViewInit { phone: FormControl; pax: FormControl; address: FormControl; + notes: FormControl; + date: FormControl; hours: FormControl; minutes: FormControl; }>; @@ -49,6 +52,8 @@ export class GuestBookDetailComponent implements OnInit, AfterViewInit { }), pax: new FormControl(0, { validators: Validators.required, nonNullable: true }), address: new FormControl(null), + notes: new FormControl(null), + date: new FormControl({ value: new Date(), disabled: false }, { nonNullable: true }), hours: new FormControl(0, { validators: [Validators.min(0), Validators.max(23)], nonNullable: true }), minutes: new FormControl(0, { validators: [Validators.min(0), Validators.max(59)], nonNullable: true }), }); @@ -79,13 +84,22 @@ export class GuestBookDetailComponent implements OnInit, AfterViewInit { showItem(item: GuestBook) { this.item = item; + const dt = (this.item.arrivalDate || this.item.bookingDate) as string; + if (item.type === 'booking') { + this.form.controls.date.enable(); + } else { + this.form.controls.date.disable(); + } + this.form.setValue({ name: item.name, phone: item.phone, pax: item.pax, address: item.address, - hours: +item.date.substring(12, 14), - minutes: +item.date.substring(15, 17), + notes: item.notes, + date: new Date(dt), + hours: +dt.substring(12, 14), + minutes: +dt.substring(15, 17), }); } @@ -149,9 +163,16 @@ export class GuestBookDetailComponent implements OnInit, AfterViewInit { } this.item.pax = formModel.pax ?? 0; this.item.address = formModel.address ?? null; + this.item.notes = formModel.notes ?? null; const hours = ('' + (formModel.hours as number)).padStart(2, '0'); const minutes = ('' + (formModel.minutes as number)).padStart(2, '0'); - this.item.date = this.item.date.substring(0, 12) + hours + ':' + minutes; + if (this.item.type === 'booking') { + console.log(formModel.date); + this.item.bookingDate = moment(formModel.date).format('DD-MMM-YYYY') + ' ' + hours + ':' + minutes; + } else { + this.item.arrivalDate = (this.item.arrivalDate as string).substring(0, 12) + hours + ':' + minutes; + } + return this.item; } } diff --git a/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.html b/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.html index dad4b5e..08ff957 100644 --- a/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.html +++ b/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.html @@ -2,9 +2,13 @@ Guest List - + add_box - Add + Table Booking + + + add_box + Walk-in @@ -22,8 +26,8 @@ - S. No - {{ row.serial }} + S. No / Total + {{ row.serial }} / {{ row.count }} @@ -49,7 +53,9 @@ Time - {{ row.date | localTime }} + {{ row.bookingDate | localTime }} // {{ row.arrivalDate | localTime }} @@ -88,6 +94,7 @@ diff --git a/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.sass b/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.sass index ab956a3..c16f1d0 100644 --- a/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.sass +++ b/bookie/src/app/guest-book/guest-book-list/guest-book-list.component.sass @@ -19,3 +19,7 @@ table /* Read the 700 hue from the primary color palete.*/ color: mat.get-color-from-palette($my-accent, '700-contrast') background: mat.get-color-from-palette($my-accent, 700) + +.grey300 + background-color: #ede7f6 + color: #000000 diff --git a/bookie/src/app/guest-book/guest-book.service.ts b/bookie/src/app/guest-book/guest-book.service.ts index 35d5bfa..3dff50c 100644 --- a/bookie/src/app/guest-book/guest-book.service.ts +++ b/bookie/src/app/guest-book/guest-book.service.ts @@ -22,10 +22,11 @@ export class GuestBookService { private log: ErrorLoggerService, ) {} - get(id: string | null): Observable { + get(id: string | null, type: string): Observable { + const options = { params: new HttpParams().set('t', type) }; const getUrl: string = id === null ? url : `${url}/${id}`; return this.http - .get(getUrl) + .get(getUrl, options) .pipe(catchError(this.log.handleError(serviceName, `get id=${id}`))) as Observable; } diff --git a/bookie/src/app/guest-book/guest-book.ts b/bookie/src/app/guest-book/guest-book.ts index 5f63aef..0b2d4a8 100644 --- a/bookie/src/app/guest-book/guest-book.ts +++ b/bookie/src/app/guest-book/guest-book.ts @@ -1,11 +1,16 @@ export class GuestBook { id: string | undefined; serial: number; + count: number; name: string; phone: string; pax: number; address: string | null; - date: string; + notes: string | null; + bookingDate: string | null; + arrivalDate: string | null; + type: string | undefined; + lastEditDate: string | undefined; status?: string; tableId?: string; voucherId?: string; @@ -14,11 +19,15 @@ export class GuestBook { public constructor(init?: Partial) { this.id = undefined; this.serial = 0; + this.count = 0; this.name = ''; this.phone = ''; this.pax = 0; this.address = null; - this.date = ''; + this.notes = null; + this.bookingDate = null; + this.arrivalDate = null; + this.lastEditDate = ''; Object.assign(this, init); } } diff --git a/bookie/src/app/shared/local-time.pipe.ts b/bookie/src/app/shared/local-time.pipe.ts index 7bcf150..bc364a1 100644 --- a/bookie/src/app/shared/local-time.pipe.ts +++ b/bookie/src/app/shared/local-time.pipe.ts @@ -6,7 +6,7 @@ import * as moment from 'moment'; }) export class LocalTimePipe implements PipeTransform { transform(value: string): string { - if (value === undefined) { + if (value === undefined || value === null) { return ''; } if (value.length === 5) {