Feature: Table Booking in guestbook.

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

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

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

@ -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):

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

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

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

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

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

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

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

@ -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_,
)

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

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

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

@ -13,6 +13,7 @@ export class GuestBookDetailResolver {
resolve(route: ActivatedRouteSnapshot): Observable<GuestBook> {
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);
}
}

@ -41,10 +41,22 @@
</div>
<div class="flex flex-row justify-around content-start items-start">
<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>
<input matInput formControlName="hours" />
</mat-form-field>
<mat-form-field class="flex-auto">
<mat-form-field class="flex-auto basis-1/4">
<mat-label>Minutes</mat-label>
<input matInput formControlName="minutes" />
</mat-form-field>

@ -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<string | Customer>;
pax: FormControl<number>;
address: FormControl<string | null>;
notes: FormControl<string | null>;
date: FormControl<Date>;
hours: FormControl<number>;
minutes: FormControl<number>;
}>;
@ -49,6 +52,8 @@ export class GuestBookDetailComponent implements OnInit, AfterViewInit {
}),
pax: new FormControl(0, { validators: Validators.required, nonNullable: true }),
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 }),
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;
}
}

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

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

@ -22,10 +22,11 @@ export class GuestBookService {
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}`;
return this.http
.get<GuestBook>(getUrl)
.get<GuestBook>(getUrl, options)
.pipe(catchError(this.log.handleError(serviceName, `get id=${id}`))) as Observable<GuestBook>;
}

@ -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<GuestBook>) {
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);
}
}

@ -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) {