Feature: Table Booking in guestbook.

Feature: Guest book list shows the running cover count
This commit is contained in:
2023-08-28 07:14:24 +05:30
parent 1cb2677ad1
commit ecea277e46
22 changed files with 481 additions and 64 deletions

View File

@ -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 ###

View File

@ -16,6 +16,7 @@ from ..models.modifier import Modifier # noqa: F401
from ..models.modifier_category import ModifierCategory # noqa: F401 from ..models.modifier_category import ModifierCategory # noqa: F401
from ..models.modifier_category_product import ModifierCategoryProduct # noqa: F401 from ..models.modifier_category_product import ModifierCategoryProduct # noqa: F401
from ..models.overview import Overview # 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.permission import Permission # noqa: F401
from ..models.printer import Printer # noqa: F401 from ..models.printer import Printer # noqa: F401
from ..models.product import Product # noqa: F401 from ..models.product import Product # noqa: F401

View File

@ -1,6 +1,6 @@
import uuid import uuid
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Optional
from sqlalchemy import Boolean, ForeignKey, Integer, Text, Uuid, text from sqlalchemy import Boolean, ForeignKey, Integer, Text, Uuid, text
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
@ -25,7 +25,7 @@ class FoodTable:
sort_order: Mapped[bool] = mapped_column(Integer, nullable=False) sort_order: Mapped[bool] = mapped_column(Integer, nullable=False)
section: Mapped["Section"] = relationship("Section", foreign_keys=section_id) 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 @property
def __name__(self): def __name__(self):

View File

@ -1,9 +1,19 @@
import uuid import uuid
from datetime import datetime 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 sqlalchemy.orm import Mapped, mapped_column, relationship
from ..db.base_class import reg from ..db.base_class import reg
@ -17,20 +27,44 @@ if TYPE_CHECKING:
@reg.mapped_as_dataclass(unsafe_hash=True) @reg.mapped_as_dataclass(unsafe_hash=True)
class GuestBook: class GuestBook:
__tablename__ = "guest_book" __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()")) 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) customer_id: Mapped[uuid.UUID] = mapped_column(Uuid, ForeignKey("customers.id"), nullable=False)
pax: Mapped[int] = mapped_column(Integer, 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") 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): def __init__(
self.customer_id = customer_id 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.pax = pax
self.id = id_ if booking_date is None and arrival_date is None:
self.date = datetime.utcnow() if date_ is None else date_ 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: if customer is None:
self.customer_id = customer_id self.customer_id = customer_id
else: else:
self.customer = customer self.customer = customer
self.id = id_

View File

@ -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"

View File

@ -0,0 +1,7 @@
import enum
class GuestBookType(str, enum.Enum):
walk_in = "walk_in"
booking = "booking"
arrived = "arrived"

View File

@ -83,7 +83,7 @@ class Inventory:
@hybrid_property @hybrid_property
def effective_price(self) -> Decimal: 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 @effective_price.inplace.expression
@classmethod @classmethod

View File

@ -1,8 +1,11 @@
from __future__ import annotations
import uuid import uuid
from typing import TYPE_CHECKING 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 sqlalchemy.orm import Mapped, mapped_column, relationship
from ..db.base_class import reg from ..db.base_class import reg
@ -31,7 +34,7 @@ class Overview:
unique=True, unique=True,
) )
guest_book_id: Mapped[uuid.UUID] = mapped_column(Uuid, ForeignKey("guest_book.id"), unique=True, nullable=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") voucher: Mapped["Voucher"] = relationship(back_populates="status")
food_table: Mapped["FoodTable"] = relationship(back_populates="status") food_table: Mapped["FoodTable"] = relationship(back_populates="status")

View File

@ -0,0 +1,6 @@
import enum
class OverviewStatus(str, enum.Enum):
running = "running"
printed = "printed"

View File

@ -1,7 +1,7 @@
import uuid import uuid
from datetime import datetime 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 import DateTime, Enum, ForeignKey, Integer, Text, Uuid, text
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
@ -63,7 +63,7 @@ class Voucher:
back_populates="voucher", back_populates="voucher",
cascade="delete, delete-orphan", 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__( def __init__(
self, self,

View File

@ -4,8 +4,10 @@ from datetime import date, datetime, timedelta
import barker.schemas.guest_book as schemas 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 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.exc import SQLAlchemyError
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
@ -47,8 +49,16 @@ def save(
else: else:
customer.name = data.name or customer.name customer.name = data.name or customer.name
customer.address = data.address or customer.address 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( 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.add(item)
db.commit() db.commit()
@ -73,7 +83,14 @@ def update_route(
item.customer.phone = data.phone item.customer.phone = data.phone
item.customer.address = data.address item.customer.address = data.address
item.pax = data.pax 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() db.commit()
return guest_book_info(item) return guest_book_info(item)
except SQLAlchemyError as e: except SQLAlchemyError as e:
@ -103,9 +120,10 @@ def delete_route(
@router.get("", response_model=schemas.GuestBookIn) @router.get("", response_model=schemas.GuestBookIn)
def show_blank( def show_blank(
t: GuestBookStatus | None,
user: UserToken = Security(get_user, scopes=["customers"]), user: UserToken = Security(get_user, scopes=["customers"]),
) -> schemas.GuestBookIn: ) -> schemas.GuestBookIn:
return blank_guest_book_info() return blank_guest_book_info(t or GuestBookStatus.walk_in)
@router.get("/list", response_model=schemas.GuestBookList) @router.get("/list", response_model=schemas.GuestBookList)
@ -117,33 +135,39 @@ def show_list(
d = date.today() d = date.today()
else: else:
d = datetime.strptime(q, "%d-%b-%Y") 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_ = ( list_ = (
select(GuestBook) select(GuestBook)
.where( .where(
GuestBook.date >= d + timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES - settings.TIMEZONE_OFFSET_MINUTES) or_(
) and_(GuestBook.arrival_date >= s, GuestBook.arrival_date < f),
.where( and_(GuestBook.booking_date >= s, GuestBook.booking_date < f),
GuestBook.date
< d
+ timedelta(
minutes=settings.NEW_DAY_OFFSET_MINUTES - settings.TIMEZONE_OFFSET_MINUTES,
days=1,
) )
) )
.order_by(GuestBook.date) .order_by(func.coalesce(GuestBook.arrival_date, GuestBook.booking_date))
) )
guest_book: list[schemas.GuestBookListItem] = [] guest_book: list[schemas.GuestBookListItem] = []
with SessionFuture() as db: with SessionFuture() as db:
count = 0
for i, item in enumerate(db.execute(list_).scalars().all()): 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( gbli = schemas.GuestBookListItem(
id_=item.id, id_=item.id,
serial=i + 1, serial=i + 1,
name=item.customer.name, name=item.customer.name,
phone=item.customer.phone, phone=item.customer.phone,
pax=item.pax, pax=item.pax,
date_=item.date, count=count,
status=None if item.status is None else item.status.status, 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, 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, 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, table_name=None if item.status is None else item.status.food_table.name,
@ -160,7 +184,7 @@ def show_list(
.first() .first()
) )
if last is not None: if last is not None:
gbli.status = "old" gbli.status = GuestBookStatus.old
gbli.voucher_id = last.id gbli.voucher_id = last.id
gbli.table_name = last.food_table.name gbli.table_name = last.food_table.name
guest_book.insert(0, gbli) guest_book.insert(0, gbli)
@ -178,21 +202,34 @@ def show_id(
def guest_book_info(item: GuestBook) -> schemas.GuestBook: 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( return schemas.GuestBook(
id_=item.id, id_=item.id,
name=item.customer.name, name=item.customer.name,
phone=item.customer.phone, phone=item.customer.phone,
pax=item.pax, pax=item.pax,
address=item.customer.address, 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( return schemas.GuestBookIn(
name="", name="",
phone="", phone="",
pax=0, pax=0,
address="", address="",
date_=datetime.utcnow() + timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES), booking_date=booking_date,
arrival_date=arrival_date,
notes=None,
type_=type_,
) )

View File

@ -1,10 +1,13 @@
import uuid import uuid
from datetime import datetime
from decimal import Decimal from decimal import Decimal
from typing import Set from typing import Set
import barker.schemas.voucher as schemas 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 fastapi import HTTPException, status
from sqlalchemy import func from sqlalchemy import func
from sqlalchemy.orm import Session 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): 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: if item.status is None:
item.status = Overview( item.status = Overview(
voucher_id=item.id, 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, guest_book_id=guest_book.id if guest_book is not None else None,
status=status_, 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) db.add(item.status)
else: else:
item.status.status = status_ item.status.status = status_

View File

@ -2,6 +2,7 @@ import uuid
import barker.schemas.voucher as schemas import barker.schemas.voucher as schemas
from barker.models.overview_status import OverviewStatus
from fastapi import APIRouter, HTTPException, Security, status from fastapi import APIRouter, HTTPException, Security, status
from sqlalchemy import select, update from sqlalchemy import select, update
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
@ -101,7 +102,10 @@ def void_and_issue_new_bill(
if old.status is None: if old.status is None:
guest_book_id = None if guest_book is None else guest_book.id guest_book_id = None if guest_book is None else guest_book.id
item.status = Overview( 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) db.add(item.status)
else: else:

View File

@ -2,7 +2,15 @@ import uuid
from datetime import date, datetime 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 from . import to_camel
@ -12,51 +20,117 @@ class GuestBookIn(BaseModel):
phone: str phone: str
address: str | None = None address: str | None = None
pax: int = Field(ge=0) 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) 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 @classmethod
def parse_date(cls, value): def parse_booking_date(cls, value):
if value is None:
return None
if isinstance(value, datetime): if isinstance(value, datetime):
return value return value
return datetime.strptime(value, "%d-%b-%Y %H:%M") return datetime.strptime(value, "%d-%b-%Y %H:%M")
@field_serializer("date_") @field_serializer("booking_date")
def serialize_date(self, value: datetime, _info): def serialize_booking_date(self, value: datetime | None, _info):
return value.strftime("%d-%b-%Y %H:%M") 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): class GuestBook(GuestBookIn):
id_: uuid.UUID id_: uuid.UUID
last_edit_date: datetime
model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True) 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): class GuestBookListItem(BaseModel):
id_: uuid.UUID id_: uuid.UUID
serial: int serial: int
count: int
name: str name: str
phone: str phone: str
pax: int pax: int
date_: datetime booking_date: datetime | None = None
status: str | None = None arrival_date: datetime | None = None
last_edit_date: datetime
status: GuestBookStatus
table_id: uuid.UUID | None = None table_id: uuid.UUID | None = None
voucher_id: uuid.UUID | None = None voucher_id: uuid.UUID | None = None
table_name: str | None = None table_name: str | None = None
model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True) 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 @classmethod
def parse_date(cls, value): def parse_booking_date(cls, value):
if value is None:
return None
if isinstance(value, datetime): if isinstance(value, datetime):
return value return value
return datetime.strptime(value, "%d-%b-%Y %H:%M") return datetime.strptime(value, "%d-%b-%Y %H:%M")
@field_serializer("date_") @field_serializer("booking_date")
def serialize_date(self, value: datetime, _info): 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") return value.strftime("%d-%b-%Y %H:%M")

View File

@ -13,6 +13,7 @@ export class GuestBookDetailResolver {
resolve(route: ActivatedRouteSnapshot): Observable<GuestBook> { resolve(route: ActivatedRouteSnapshot): Observable<GuestBook> {
const id = route.paramMap.get('id'); 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);
} }
} }

View File

@ -41,10 +41,22 @@
</div> </div>
<div class="flex flex-row justify-around content-start items-start"> <div class="flex flex-row justify-around content-start items-start">
<mat-form-field class="flex-auto"> <mat-form-field class="flex-auto">
<mat-label>Notes</mat-label>
<textarea matInput formControlName="notes"></textarea>
</mat-form-field>
</div>
<div class="flex flex-row justify-around content-start items-start">
<mat-form-field class="flex-auto basis-1/2 mr-5">
<mat-label>Date</mat-label>
<input matInput [matDatepicker]="date" (focus)="date.open()" formControlName="date" autocomplete="off" />
<mat-datepicker-toggle matSuffix [for]="date"></mat-datepicker-toggle>
<mat-datepicker #date></mat-datepicker>
</mat-form-field>
<mat-form-field class="flex-auto basis-1/4 mr-5">
<mat-label>Hours</mat-label> <mat-label>Hours</mat-label>
<input matInput formControlName="hours" /> <input matInput formControlName="hours" />
</mat-form-field> </mat-form-field>
<mat-form-field class="flex-auto"> <mat-form-field class="flex-auto basis-1/4">
<mat-label>Minutes</mat-label> <mat-label>Minutes</mat-label>
<input matInput formControlName="minutes" /> <input matInput formControlName="minutes" />
</mat-form-field> </mat-form-field>

View File

@ -3,6 +3,7 @@ import { FormControl, FormGroup, Validators } from '@angular/forms';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import * as moment from 'moment';
import { Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { debounceTime, distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators'; import { debounceTime, distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators';
@ -25,6 +26,8 @@ export class GuestBookDetailComponent implements OnInit, AfterViewInit {
phone: FormControl<string | Customer>; phone: FormControl<string | Customer>;
pax: FormControl<number>; pax: FormControl<number>;
address: FormControl<string | null>; address: FormControl<string | null>;
notes: FormControl<string | null>;
date: FormControl<Date>;
hours: FormControl<number>; hours: FormControl<number>;
minutes: FormControl<number>; minutes: FormControl<number>;
}>; }>;
@ -49,6 +52,8 @@ export class GuestBookDetailComponent implements OnInit, AfterViewInit {
}), }),
pax: new FormControl(0, { validators: Validators.required, nonNullable: true }), pax: new FormControl(0, { validators: Validators.required, nonNullable: true }),
address: new FormControl<string | null>(null), address: new FormControl<string | null>(null),
notes: new FormControl<string | null>(null),
date: new FormControl({ value: new Date(), disabled: false }, { nonNullable: true }),
hours: new FormControl(0, { validators: [Validators.min(0), Validators.max(23)], 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 }), 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) { showItem(item: GuestBook) {
this.item = item; 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({ this.form.setValue({
name: item.name, name: item.name,
phone: item.phone, phone: item.phone,
pax: item.pax, pax: item.pax,
address: item.address, address: item.address,
hours: +item.date.substring(12, 14), notes: item.notes,
minutes: +item.date.substring(15, 17), 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.pax = formModel.pax ?? 0;
this.item.address = formModel.address ?? null; this.item.address = formModel.address ?? null;
this.item.notes = formModel.notes ?? null;
const hours = ('' + (formModel.hours as number)).padStart(2, '0'); const hours = ('' + (formModel.hours as number)).padStart(2, '0');
const minutes = ('' + (formModel.minutes 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; return this.item;
} }
} }

View File

@ -2,9 +2,13 @@
<mat-card-header> <mat-card-header>
<mat-card-title-group> <mat-card-title-group>
<mat-card-title>Guest List</mat-card-title> <mat-card-title>Guest List</mat-card-title>
<a mat-button [routerLink]="['/guest-book', 'new']"> <a mat-button [routerLink]="['/guest-book', 'new']" [queryParams]="{ t: 'booking' }">
<mat-icon>add_box</mat-icon> <mat-icon>add_box</mat-icon>
Add Table Booking
</a>
<a mat-button [routerLink]="['/guest-book', 'new']" [queryParams]="{ t: 'walk_in' }">
<mat-icon>add_box</mat-icon>
Walk-in
</a> </a>
</mat-card-title-group> </mat-card-title-group>
</mat-card-header> </mat-card-header>
@ -22,8 +26,8 @@
<mat-table [dataSource]="dataSource" aria-label="Elements"> <mat-table [dataSource]="dataSource" aria-label="Elements">
<!-- SNo Column --> <!-- SNo Column -->
<ng-container matColumnDef="sno"> <ng-container matColumnDef="sno">
<mat-header-cell *matHeaderCellDef>S. No</mat-header-cell> <mat-header-cell *matHeaderCellDef>S. No / Total</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.serial }}</mat-cell> <mat-cell *matCellDef="let row">{{ row.serial }} / {{ row.count }}</mat-cell>
</ng-container> </ng-container>
<!-- Name Column --> <!-- Name Column -->
@ -49,7 +53,9 @@
<!-- Time Column --> <!-- Time Column -->
<ng-container matColumnDef="date"> <ng-container matColumnDef="date">
<mat-header-cell *matHeaderCellDef>Time</mat-header-cell> <mat-header-cell *matHeaderCellDef>Time</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.date | localTime }}</mat-cell> <mat-cell *matCellDef="let row"
>{{ row.bookingDate | localTime }} // {{ row.arrivalDate | localTime }}</mat-cell
>
</ng-container> </ng-container>
<!-- Action Column --> <!-- Action Column -->
@ -88,6 +94,7 @@
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row <mat-row
*matRowDef="let row; columns: displayedColumns" *matRowDef="let row; columns: displayedColumns"
[class.grey300]="row.status === 'booking'"
[class.accent]="row.status === 'running'" [class.accent]="row.status === 'running'"
[class.strong-accent]="row.status === 'printed'" [class.strong-accent]="row.status === 'printed'"
></mat-row> ></mat-row>

View File

@ -19,3 +19,7 @@ table
/* Read the 700 hue from the primary color palete.*/ /* Read the 700 hue from the primary color palete.*/
color: mat.get-color-from-palette($my-accent, '700-contrast') color: mat.get-color-from-palette($my-accent, '700-contrast')
background: mat.get-color-from-palette($my-accent, 700) background: mat.get-color-from-palette($my-accent, 700)
.grey300
background-color: #ede7f6
color: #000000

View File

@ -22,10 +22,11 @@ export class GuestBookService {
private log: ErrorLoggerService, private log: ErrorLoggerService,
) {} ) {}
get(id: string | null): Observable<GuestBook> { get(id: string | null, type: string): Observable<GuestBook> {
const options = { params: new HttpParams().set('t', type) };
const getUrl: string = id === null ? url : `${url}/${id}`; const getUrl: string = id === null ? url : `${url}/${id}`;
return this.http return this.http
.get<GuestBook>(getUrl) .get<GuestBook>(getUrl, options)
.pipe(catchError(this.log.handleError(serviceName, `get id=${id}`))) as Observable<GuestBook>; .pipe(catchError(this.log.handleError(serviceName, `get id=${id}`))) as Observable<GuestBook>;
} }

View File

@ -1,11 +1,16 @@
export class GuestBook { export class GuestBook {
id: string | undefined; id: string | undefined;
serial: number; serial: number;
count: number;
name: string; name: string;
phone: string; phone: string;
pax: number; pax: number;
address: string | null; address: string | null;
date: string; notes: string | null;
bookingDate: string | null;
arrivalDate: string | null;
type: string | undefined;
lastEditDate: string | undefined;
status?: string; status?: string;
tableId?: string; tableId?: string;
voucherId?: string; voucherId?: string;
@ -14,11 +19,15 @@ export class GuestBook {
public constructor(init?: Partial<GuestBook>) { public constructor(init?: Partial<GuestBook>) {
this.id = undefined; this.id = undefined;
this.serial = 0; this.serial = 0;
this.count = 0;
this.name = ''; this.name = '';
this.phone = ''; this.phone = '';
this.pax = 0; this.pax = 0;
this.address = null; this.address = null;
this.date = ''; this.notes = null;
this.bookingDate = null;
this.arrivalDate = null;
this.lastEditDate = '';
Object.assign(this, init); Object.assign(this, init);
} }
} }

View File

@ -6,7 +6,7 @@ import * as moment from 'moment';
}) })
export class LocalTimePipe implements PipeTransform { export class LocalTimePipe implements PipeTransform {
transform(value: string): string { transform(value: string): string {
if (value === undefined) { if (value === undefined || value === null) {
return ''; return '';
} }
if (value.length === 5) { if (value.length === 5) {