Product list / detail / router fully working.

Need to test the sale / reports
This commit is contained in:
Amritanshu Agrawal 2020-11-09 12:43:18 +05:30
parent 4aaa3fc72b
commit a92726f5e6
22 changed files with 370 additions and 271 deletions

View File

@ -21,10 +21,10 @@ branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
def update_sale_categories():
product = table(
"products",
column("product_id", postgresql.UUID(as_uuid=True)),
column("sale_category_id", postgresql.UUID(as_uuid=True)),
column("sale_category_name", sa.Unicode(length=255)),
)
@ -40,7 +40,12 @@ def upgrade():
}
)
)
with op.batch_alter_table("products") as batch_op:
batch_op.alter_column("sale_category_id", nullable=False)
batch_op.drop_column("sale_category_name")
def update_section_printers():
section_printer = table(
"section_printers",
column("section_id", postgresql.UUID(as_uuid=True)),
@ -66,75 +71,8 @@ def upgrade():
batch_op.alter_column("printer_id", nullable=False)
batch_op.drop_column("printer_name")
with op.batch_alter_table("products") as batch_op:
batch_op.alter_column("sale_category_id", nullable=False)
batch_op.drop_column("sale_category_name")
# Migration to temporal products
op.rename_table("products", "product_histories")
with op.batch_alter_table("product_histories") as batch_op:
batch_op.add_column(sa.Column("valid_from", sa.Date(), nullable=True))
batch_op.add_column(sa.Column("valid_till", sa.Date(), nullable=True))
batch_op.drop_column("is_active")
op.create_table(
"product_versions",
sa.Column("id", postgresql.UUID(as_uuid=True), server_default=sa.text("gen_random_uuid()"), nullable=False),
sa.Column("version_id", postgresql.UUID(as_uuid=True), nullable=False),
sa.ForeignKeyConstraint(
["version_id"], ["product_histories.id"], name=op.f("fk_product_versions_version_id_product_histories")
),
sa.PrimaryKeyConstraint("id", name=op.f("pk_product_versions")),
sa.UniqueConstraint("id", "version_id", name=op.f("uq_product_versions_id")),
)
op.drop_constraint("fk_inventories_product_id_products", "inventories", type_="foreignkey")
op.create_foreign_key(
op.f("fk_inventories_product_id_product_versions"), "inventories", "product_versions", ["product_id"], ["id"]
)
pv = table(
"product_versions",
column("id", postgresql.UUID(as_uuid=True)),
column("version_id", postgresql.UUID(as_uuid=True)),
)
prod = table(
"product_histories",
column("id", postgresql.UUID(as_uuid=True)),
column("name", sa.Unicode(length=255)),
column("units", sa.Unicode(length=255)),
column("valid_from", sa.Date()),
column("valid_till", sa.Date()),
)
op.execute(pv.insert().from_select([pv.c.id, pv.c.version_id], select([prod.c.id, prod.c.id.label("vid")])))
op.alter_column("modifier_categories_products", "modifier_categories_id", new_column_name="modifier_category_id")
op.drop_constraint("uq_modifier_categories_products_product_id", "modifier_categories_products", type_="unique")
op.create_unique_constraint(
op.f("uq_modifier_categories_products_product_id"),
"modifier_categories_products",
["product_id", "modifier_category_id"],
)
op.drop_constraint(
"fk_modifier_categories_products_product_id_products", "modifier_categories_products", type_="foreignkey"
)
op.create_foreign_key(
op.f("fk_modifier_categories_products_product_id_product_versions"),
"modifier_categories_products",
"product_versions",
["product_id"],
["id"],
)
op.create_exclude_constraint(
"uq_product_histories_name",
"product_histories",
(prod.c.name, "="),
(prod.c.units, "="),
(func.daterange(prod.c.valid_from, prod.c.valid_till, text("'[]'")), "&&"),
)
op.drop_constraint("uq_products_name", "product_histories", type_="unique")
def add_all_roles_to_owner():
r = table(
"roles",
column("id", postgresql.UUID(as_uuid=True)),
@ -159,6 +97,92 @@ def upgrade():
)
.on_conflict_do_nothing(),
)
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
update_sale_categories()
update_section_printers()
# Migration to temporal products
op.drop_constraint("fk_inventories_product_id_products", "inventories", type_="foreignkey")
op.drop_constraint(
"fk_modifier_categories_products_product_id_products", "modifier_categories_products", type_="foreignkey"
)
op.drop_constraint("uq_products_name", "products", type_="unique")
op.drop_constraint("pk_products", "products", type_="primary")
op.rename_table("products", "product_versions")
with op.batch_alter_table("product_versions") as batch_op:
batch_op.add_column(sa.Column("product_id", postgresql.UUID(as_uuid=True), nullable=True)),
batch_op.add_column(sa.Column("valid_from", sa.Date(), nullable=True))
batch_op.add_column(sa.Column("valid_till", sa.Date(), nullable=True))
batch_op.drop_column("is_active")
batch_op.create_primary_key("pk_product_versions", ["id"])
prod_v = table(
"product_versions",
column("id", postgresql.UUID(as_uuid=True)),
column("product_id", postgresql.UUID(as_uuid=True)),
)
op.execute(prod_v.update(values={"product_id": prod_v.c.id}))
op.create_table(
"products",
sa.Column("id", postgresql.UUID(as_uuid=True), server_default=sa.text("gen_random_uuid()"), nullable=False),
# sa.Column("version_id", postgresql.UUID(as_uuid=True), nullable=False),
# sa.ForeignKeyConstraint(
# ["version_id"], ["product_versions.id"], name=op.f("fk_products_version_id_product_versions")
# ),
sa.PrimaryKeyConstraint("id", name=op.f("pk_products")),
# sa.UniqueConstraint("id", "version_id", name=op.f("uq_products_id")),
)
op.alter_column("product_versions", "product_id", nullable=False)
prod = table(
"product_versions",
column("id", postgresql.UUID(as_uuid=True)),
column("name", sa.Unicode(length=255)),
column("units", sa.Unicode(length=255)),
column("valid_from", sa.Date()),
column("valid_till", sa.Date()),
)
pv = table(
"products",
column("id", postgresql.UUID(as_uuid=True)),
)
op.execute(pv.insert().from_select([pv.c.id], select([prod.c.id])))
op.create_foreign_key(
op.f("fk_product_versions_product_id_products"), "product_versions", "products", ["product_id"], ["id"]
)
op.create_foreign_key(op.f("fk_inventories_product_id_products"), "inventories", "products", ["product_id"], ["id"])
op.create_foreign_key(
op.f("fk_modifier_categories_products_product_id_products"),
"modifier_categories_products",
"products",
["product_id"],
["id"],
)
op.alter_column("modifier_categories_products", "modifier_categories_id", new_column_name="modifier_category_id")
op.drop_constraint("uq_modifier_categories_products_product_id", "modifier_categories_products", type_="unique")
op.create_unique_constraint(
op.f("uq_modifier_categories_products_product_id"),
"modifier_categories_products",
["product_id", "modifier_category_id"],
)
op.create_exclude_constraint(
"uq_product_versions_name",
"product_versions",
(prod.c.name, "="),
(prod.c.units, "="),
(func.daterange(prod.c.valid_from, prod.c.valid_till, text("'[]'")), "&&"),
)
add_all_roles_to_owner()
# ### end Alembic commands ###

View File

@ -10,8 +10,8 @@ from .master import (
Modifier,
ModifierCategory,
Printer,
ProductHistory,
ProductVersions,
ProductNew,
ProductVersion,
SaleCategory,
Section,
SectionPrinter,

View File

@ -138,21 +138,23 @@ class SaleCategory(Base):
self.id = id_
class ProductVersions(Base):
__tablename__ = "product_versions"
__table_args__ = (UniqueConstraint("id", "version_id"),)
class ProductNew(Base):
__tablename__ = "products"
id = Column(
"id", UUID(as_uuid=True), primary_key=True, server_default=text("gen_random_uuid()"), default=uuid.uuid4
)
version_id = Column("version_id", UUID(as_uuid=True), ForeignKey("product_histories.id"), nullable=False)
histories = relationship("ProductHistory")
versions = relationship("ProductVersion")
def __init__(self, id_=None, version_id=None):
self.id = id_
self.version_id = version_id
modifier_categories_products = Table(
"modifier_categories_products",
Base.metadata,
Column("id", UUID(as_uuid=True), primary_key=True, server_default=text("gen_random_uuid()"), default=uuid.uuid4),
Column("product_id", UUID(as_uuid=True), ForeignKey("product_versions.id"), nullable=False),
Column("product_id", UUID(as_uuid=True), ForeignKey("products.id"), nullable=False),
Column(
"modifier_category_id",
UUID(as_uuid=True),
@ -163,11 +165,12 @@ modifier_categories_products = Table(
)
class ProductHistory(Base):
__tablename__ = "product_histories"
class ProductVersion(Base):
__tablename__ = "product_versions"
id = Column(
"id", UUID(as_uuid=True), primary_key=True, server_default=text("gen_random_uuid()"), default=uuid.uuid4
)
product_id = Column("product_id", UUID(as_uuid=True), ForeignKey("products.id"), nullable=False)
name = Column("name", Unicode(255), nullable=False)
units = Column("units", Unicode(255), nullable=False)
menu_category_id = Column(
@ -193,15 +196,16 @@ class ProductHistory(Base):
menu_category = relationship("MenuCategory", backref="products")
sale_category = relationship("SaleCategory", backref="products")
modifier_categories = relationship(
"ModifierCategory",
secondary="join(product_versions, modifier_categories_products, "
"ProductVersions.id==modifier_categories_products.c.product_id)",
primaryjoin="ProductHistory.id==ProductVersions.version_id",
secondaryjoin="modifier_categories_products.c.modifier_category_id==ModifierCategory.id",
order_by="ModifierCategory.sort_order",
backref="products",
)
# modifier_categories = relationship(
# "ModifierCategory",
# secondary="join(products, modifier_categories_products, "
# "ProductNew.id==modifier_categories_products.c.product_id)",
# primaryjoin="ProductVersion.id==ProductNew.version_id",
# secondaryjoin="modifier_categories_products.c.modifier_category_id==ModifierCategory.id",
# order_by="ModifierCategory.sort_order",
# backref="products",
# )
product = relationship("ProductNew")
__table_args__ = (
postgresql.ExcludeConstraint(
@ -213,6 +217,7 @@ class ProductHistory(Base):
def __init__(
self,
product_id=None,
name=None,
units=None,
menu_category_id=None,
@ -221,10 +226,12 @@ class ProductHistory(Base):
has_happy_hour=None,
is_not_available=None,
quantity=None,
is_active=None,
valid_from=None,
valid_till=None,
sort_order=0,
id_=None,
):
self.product_id = product_id
self.name = name
self.units = units
self.menu_category_id = menu_category_id
@ -233,7 +240,8 @@ class ProductHistory(Base):
self.has_happy_hour = has_happy_hour
self.is_not_available = is_not_available
self.quantity = quantity
self.is_active = is_active
self.valid_from = valid_from
self.valid_till = valid_till
self.sort_order = sort_order
self.id = id_

View File

@ -21,7 +21,7 @@ from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import backref, relationship, synonym
from .master import ProductVersions
from .master import ProductNew
from .meta import Base
@ -311,7 +311,7 @@ class Inventory(Base):
"id", UUID(as_uuid=True), primary_key=True, server_default=text("gen_random_uuid()"), default=uuid.uuid4
)
kot_id = Column("kot_id", UUID(as_uuid=True), ForeignKey("kots.id"), nullable=False, index=True)
product_id = Column("product_id", UUID(as_uuid=True), ForeignKey("product_versions.id"), nullable=False)
product_id = Column("product_id", UUID(as_uuid=True), ForeignKey("products.id"), nullable=False)
quantity = Column("quantity", Numeric(precision=15, scale=2))
price = Column("price", Numeric(precision=15, scale=2))
is_happy_hour = Column("is_happy_hour", Boolean, nullable=False)
@ -322,7 +322,7 @@ class Inventory(Base):
kot = relationship("Kot", backref="inventories")
tax = relationship("Tax", foreign_keys=tax_id)
product = relationship("ProductHistory", secondary=ProductVersions.__table__, backref="inventories")
product = relationship("ProductVersion", secondary=ProductNew.__table__, backref="inventories")
def __init__(
self,

View File

@ -10,7 +10,7 @@ from sqlalchemy.orm import Session
from ..core.security import get_current_active_user as get_user
from ..db.session import SessionLocal
from ..models.master import MenuCategory, ProductVersions
from ..models.master import MenuCategory, ProductNew
from ..schemas.auth import UserToken

View File

@ -11,7 +11,7 @@ from sqlalchemy.orm import Session
from ..core.security import get_current_active_user as get_user
from ..db.session import SessionLocal
from ..models.master import MenuCategory, ModifierCategory, ProductVersions
from ..models.master import MenuCategory, ModifierCategory, ProductNew
from ..schemas.auth import UserToken

View File

@ -1,9 +1,10 @@
import uuid
from datetime import date, datetime, timedelta
from typing import List, Optional
from typing import List
import barker.schemas.master as schemas
import barker.schemas.product as product_schemas
from fastapi import APIRouter, Depends, HTTPException, Security, status
from sqlalchemy import and_, or_
@ -13,7 +14,7 @@ from sqlalchemy.orm import Session, contains_eager, joinedload
from ..core.config import settings
from ..core.security import get_current_active_user as get_user
from ..db.session import SessionLocal
from ..models.master import MenuCategory, ProductHistory, ProductVersions, SaleCategory
from ..models.master import MenuCategory, ProductNew, ProductVersion, SaleCategory
from ..schemas.auth import UserToken
@ -29,9 +30,18 @@ def get_db():
db.close()
def effective_date(d: str = None) -> date:
return (
(datetime.now() - timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES)).date()
if d is None
else datetime.strptime(d, "%d-%b-%Y").date()
)
@router.post("/list", response_model=List[schemas.Product])
def sort_order(
data: List[schemas.Product],
date_: date = Depends(effective_date),
db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["products"]),
):
@ -42,19 +52,15 @@ def sort_order(
indexes[item.menu_category.id_] += 1
else:
indexes[item.menu_category.id_] = 0
db.query(Product).filter(Product.id == item.id_).update(
{Product.sort_order: indexes[item.menu_category.id_]}
)
db.query(ProductVersion).filter(
and_(
ProductVersion.product_id == item.id_,
or_(ProductVersion.valid_from == None, ProductVersion.valid_from <= date_),
or_(ProductVersion.valid_till == None, ProductVersion.valid_till >= date_),
)
).update({ProductVersion.sort_order: indexes[item.menu_category.id_]})
db.commit()
return [
product_info(item)
for item in db.query(Product)
.order_by(Product.menu_category_id)
.order_by(Product.sort_order)
.order_by(Product.name)
.options(joinedload(Product.menu_category), joinedload(Product.sale_category))
.all()
]
return product_list(date_, db)
except SQLAlchemyError as e:
db.rollback()
raise HTTPException(
@ -69,11 +75,16 @@ def sort_order(
@router.post("", response_model=schemas.Product)
def save(
data: schemas.ProductIn,
date_: date = Depends(effective_date),
db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["products"]),
):
try:
item = ProductHistory(
item = ProductNew()
db.add(item)
db.flush()
product_version = ProductVersion(
product_id=item.id,
name=data.name,
units=data.units,
menu_category_id=data.menu_category.id_,
@ -82,11 +93,12 @@ def save(
has_happy_hour=data.has_happy_hour,
is_not_available=data.is_not_available,
quantity=data.quantity,
is_active=data.is_active,
valid_from=date_,
valid_till=None,
)
db.add(item)
db.add(product_version)
db.commit()
return product_info(item)
return product_info(product_version)
except SQLAlchemyError as e:
db.rollback()
raise HTTPException(
@ -102,22 +114,59 @@ def save(
def update(
id_: uuid.UUID,
data: schemas.ProductIn,
date_: date = Depends(effective_date),
db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["products"]),
):
) -> schemas.Product:
try:
item: Product = db.query(Product).filter(Product.id == id_).first()
item.name = data.name
item.units = data.units
item.menu_category_id = data.menu_category.id_
item.sale_category_id = data.sale_category.id_
item.price = data.price
item.has_happy_hour = data.has_happy_hour
item.is_not_available = data.is_not_available
item.quantity = data.quantity
item.is_active = data.is_active
db.commit()
return product_info(item)
item: ProductVersion = (
db.query(ProductVersion)
.join(ProductVersion.menu_category)
.filter(
and_(
ProductVersion.product_id == id_,
or_(ProductVersion.valid_from == None, ProductVersion.valid_from <= date_),
or_(ProductVersion.valid_till == None, ProductVersion.valid_till >= date_),
)
)
.first()
)
if item.valid_till != None:
# Allow adding a product here splitting the valid from and to, but not implemented right now
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Product has been invalidated",
)
if item.valid_from == date_: # Update the product as valid from the the same
item.name = data.name
item.units = data.units
item.menu_category_id = data.menu_category.id_
item.sale_category_id = data.sale_category.id_
item.price = data.price
item.has_happy_hour = data.has_happy_hour
item.is_not_available = data.is_not_available
item.quantity = data.quantity
db.commit()
return product_info(item)
else: # Create a new version of the product from the new details
item.valid_till = date_ - timedelta(days=1)
product_version = ProductVersion(
product_id=item.product_id,
name=data.name,
units=data.units,
menu_category_id=data.menu_category.id_,
sale_category_id=data.sale_category.id_,
price=data.price,
has_happy_hour=data.has_happy_hour,
is_not_available=data.is_not_available,
quantity=data.quantity,
valid_from=date_,
valid_till=None,
)
db.add(product_version)
db.commit()
return product_info(product_version)
except SQLAlchemyError as e:
db.rollback()
raise HTTPException(
@ -132,21 +181,32 @@ def update(
@router.delete("/{id_}")
def delete(
id_: uuid.UUID,
date_: date = Depends(effective_date),
db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["products"]),
):
try:
item: Product = db.query(Product).filter(Product.id == id_).first()
item: ProductVersion = (
db.query(ProductVersion)
.filter(
and_(
ProductVersion.product_id == id_,
or_(ProductVersion.valid_from == None, ProductVersion.valid_from <= date_),
or_(ProductVersion.valid_till == None, ProductVersion.valid_till >= date_),
)
)
.first()
)
if item is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Product not found",
)
elif item.valid_from == date_:
db.delete(item)
else:
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
detail="Product deletion not implemented",
)
item.valid_till = date_ - timedelta(days=1)
db.commit()
except Exception:
db.rollback()
raise
@ -157,38 +217,47 @@ def show_blank(
db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["products"]),
):
return product_info(None)
@router.get("/list")
def show_list(d: str = None, db: Session = Depends(get_db), user: UserToken = Depends(get_user)):
date_ = (
(datetime.now() - timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES)).date()
if d is None
else datetime.strptime(d, "%d-%b-%Y").date()
return schemas.Product(
name="",
units="",
menuCategory={},
saleCategory={},
price=0,
hasHappyHour=False,
isNotAvailable=False,
isActive=True,
sortOrder=0,
)
@router.get("/list", response_model=List[schemas.Product])
def show_list(
date_: date = Depends(effective_date), db: Session = Depends(get_db), user: UserToken = Depends(get_user)
) -> List[schemas.Product]:
return product_list(date_, db)
def product_list(date_: date, db: Session) -> List[schemas.Product]:
return [
product_info(item.histories)
for item in db.query(ProductVersions)
.join(ProductVersions.histories)
.join(ProductHistory.menu_category)
product_info(item)
for item in db.query(ProductVersion)
.join(ProductVersion.menu_category)
.join(ProductVersion.sale_category)
.filter(
and_(
or_(ProductHistory.valid_from == None, ProductHistory.valid_from <= date_),
or_(ProductHistory.valid_till == None, ProductHistory.valid_till >= date_),
or_(ProductVersion.valid_from == None, ProductVersion.valid_from <= date_),
or_(ProductVersion.valid_till == None, ProductVersion.valid_till >= date_),
)
)
.order_by(MenuCategory.sort_order)
.order_by(MenuCategory.name)
.order_by(ProductHistory.sort_order)
.order_by(ProductHistory.name)
.order_by(ProductVersion.sort_order)
.order_by(ProductVersion.name)
.options(
joinedload(ProductVersions.histories, innerjoin=True),
joinedload(ProductVersions.histories, ProductHistory.menu_category, innerjoin=True),
joinedload(ProductVersions.histories, ProductHistory.sale_category, innerjoin=True),
contains_eager(ProductVersions.histories),
contains_eager(ProductVersions.histories, ProductHistory.menu_category),
contains_eager(ProductVersions.histories, ProductHistory.sale_category),
joinedload(ProductVersion.menu_category, innerjoin=True),
joinedload(ProductVersion.sale_category, innerjoin=True),
contains_eager(ProductVersion.menu_category),
contains_eager(ProductVersion.sale_category),
)
.all()
]
@ -197,56 +266,68 @@ def show_list(d: str = None, db: Session = Depends(get_db), user: UserToken = De
@router.get("/query")
async def show_term(
mc: uuid.UUID = None,
d: str = None,
date_: date = Depends(effective_date),
db: Session = Depends(get_db),
current_user: UserToken = Depends(get_user),
):
date_ = (
(datetime.now() - timedelta(minutes=settings.NEW_DAY_OFFSET_MINUTES)).date()
if d is None
else datetime.strptime(d, "%d-%b-%Y").date()
)
list_ = []
for item in (
db.query(ProductVersions)
.join(ProductVersions.histories)
.join(ProductVersions.histories, ProductHistory.sale_category)
.join(ProductVersions.histories, ProductHistory.sale_category, SaleCategory.tax)
db.query(ProductVersion)
.join(ProductVersion.sale_category)
.join(ProductVersion.sale_category, SaleCategory.tax)
.filter(
and_(
ProductHistory.menu_category_id == mc,
or_(ProductHistory.valid_from == None, ProductHistory.valid_from <= date_),
or_(ProductHistory.valid_till == None, ProductHistory.valid_till >= date_),
ProductVersion.menu_category_id == mc,
or_(ProductVersion.valid_from == None, ProductVersion.valid_from <= date_),
or_(ProductVersion.valid_till == None, ProductVersion.valid_till >= date_),
)
)
.order_by(ProductHistory.sort_order, ProductHistory.name)
.order_by(ProductVersion.sort_order, ProductVersion.name)
.options(
joinedload(ProductVersions.histories, innerjoin=True),
joinedload(ProductVersions.histories, ProductHistory.sale_category, innerjoin=True),
joinedload(ProductVersions.histories, ProductHistory.sale_category, SaleCategory.tax, innerjoin=True),
contains_eager(ProductVersions.histories),
contains_eager(ProductVersions.histories, ProductHistory.sale_category),
contains_eager(ProductVersions.histories, ProductHistory.sale_category, SaleCategory.tax),
joinedload(ProductVersion.sale_category, innerjoin=True),
joinedload(ProductVersion.sale_category, SaleCategory.tax, innerjoin=True),
contains_eager(ProductVersion.sale_category),
contains_eager(ProductVersion.sale_category, SaleCategory.tax),
)
.all()
):
list_.append(query_product_info(item.histories, False))
if item.histories.has_happy_hour:
list_.append(query_product_info(item.histories, True))
list_.append(query_product_info(item, False))
if item.has_happy_hour:
list_.append(query_product_info(item, True))
return list_
@router.get("/{id_}")
@router.get("/{id_}", response_model=schemas.Product)
def show_id(
id_: uuid.UUID,
date_: date = Depends(effective_date),
db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["products"]),
):
item: Product = db.query(Product).filter(Product.id == id_).first()
) -> schemas.Product:
item: ProductVersion = (
db.query(ProductVersion)
.join(ProductVersion.sale_category)
.join(ProductVersion.sale_category, SaleCategory.tax)
.filter(
and_(
ProductVersion.product_id == id_,
or_(ProductVersion.valid_from == None, ProductVersion.valid_from <= date_),
or_(ProductVersion.valid_till == None, ProductVersion.valid_till >= date_),
)
)
.order_by(ProductVersion.sort_order, ProductVersion.name)
.options(
joinedload(ProductVersion.sale_category, innerjoin=True),
joinedload(ProductVersion.sale_category, SaleCategory.tax, innerjoin=True),
contains_eager(ProductVersion.sale_category),
contains_eager(ProductVersion.sale_category, SaleCategory.tax),
)
.first()
)
return product_info(item)
def query_product_info(item: ProductHistory, happy_hour: bool):
def query_product_info(item: ProductVersion, happy_hour: bool):
return {
"id": item.id,
"name": ("H H " if happy_hour else "") + item.full_name,
@ -266,31 +347,19 @@ def query_product_info(item: ProductHistory, happy_hour: bool):
}
def product_info(item: Optional[ProductHistory]):
if item is None:
return {
"name": "",
"units": "",
"menuCategory": {},
"saleCategory": {},
"price": 0,
"hasHappyHour": False,
"isNotAvailable": False,
"isActive": True,
"sortOrder": 0,
}
return {
"id": item.id,
"name": item.name,
"units": item.units,
"menuCategory": {"id": item.menu_category_id, "name": item.menu_category.name},
"saleCategory": {
"id": item.sale_category_id,
"name": item.sale_category.name,
},
"price": item.price,
"hasHappyHour": item.has_happy_hour,
"isNotAvailable": item.is_not_available,
"isActive": True,
"sortOrder": item.sort_order,
}
def product_info(item: ProductVersion) -> schemas.Product:
return schemas.Product(
id=item.product_id,
name=item.name,
units=item.units,
menuCategory=product_schemas.MenuCategoryLink(id=item.menu_category_id, name=item.menu_category.name),
saleCategory=product_schemas.SaleCategoryLink(
id=item.sale_category_id,
name=item.sale_category.name,
),
price=item.price,
hasHappyHour=item.has_happy_hour,
isNotAvailable=item.is_not_available,
isActive=True,
sortOrder=item.sort_order,
)

View File

@ -7,7 +7,7 @@ from sqlalchemy.sql.expression import func
from ...core.config import settings
from ...core.security import get_current_active_user as get_user
from ...db.session import SessionLocal
from ...models import Inventory, Kot, ProductVersions, Voucher, VoucherType
from ...models import Inventory, Kot, ProductNew, Voucher, VoucherType
from ...schemas.auth import UserToken

View File

@ -10,14 +10,7 @@ from sqlalchemy.orm import Session
from ...core.config import settings
from ...core.security import get_current_active_user as get_user
from ...db.session import SessionLocal
from ...models import (
Inventory,
Kot,
ProductVersions,
SaleCategory,
Voucher,
VoucherType,
)
from ...models import Inventory, Kot, ProductNew, SaleCategory, Voucher, VoucherType
from ...printing.discount_report import print_discount_report
from ...schemas.auth import UserToken
from ...schemas.discount_report import DiscountReport, DiscountReportItem

View File

@ -11,7 +11,7 @@ from ...models import (
Inventory,
Kot,
MenuCategory,
ProductVersions,
ProductNew,
SaleCategory,
Voucher,
VoucherType,

View File

@ -13,7 +13,7 @@ from ...db.session import SessionLocal
from ...models import (
Inventory,
Kot,
ProductVersions,
ProductNew,
SaleCategory,
Settlement,
SettleOption,

View File

@ -17,7 +17,7 @@ from ...models import (
Inventory,
InventoryModifier,
Kot,
ProductVersions,
ProductNew,
Voucher,
VoucherType,
)

View File

@ -16,7 +16,7 @@ from ...models import (
Inventory,
InventoryModifier,
Kot,
ProductVersions,
ProductNew,
Voucher,
VoucherType,
)

View File

@ -20,6 +20,7 @@ class ProductLink(BaseModel):
class MenuCategoryLink(BaseModel): # Hack to prevent circular error
id_: uuid.UUID = Field(...)
name: Optional[str]
products: Optional[List[ProductLink]]
class Config:

View File

@ -1,5 +1,7 @@
import uuid
from typing import Optional
from pydantic import BaseModel, Field
from . import to_camel
@ -27,6 +29,7 @@ class SaleCategory(SaleCategoryIn):
class SaleCategoryLink(BaseModel):
id_: uuid.UUID = Field(...)
name: Optional[str]
class Config:
fields = {"id_": "id"}

View File

@ -58,7 +58,6 @@
>
<mat-checkbox formControlName="hasHappyHour">Has Happy Hour?</mat-checkbox>
<mat-checkbox formControlName="isNotAvailable">Is Not Available?</mat-checkbox>
<mat-checkbox formControlName="isActive">Is Active?</mat-checkbox>
</div>
<div
fxLayout="row"

View File

@ -44,7 +44,6 @@ export class ProductDetailComponent implements OnInit, AfterViewInit {
hasHappyHour: '',
isNotAvailable: '',
quantity: '',
isActive: '',
});
}
@ -70,7 +69,6 @@ export class ProductDetailComponent implements OnInit, AfterViewInit {
hasHappyHour: this.item.hasHappyHour,
isNotAvailable: this.item.isNotAvailable,
quantity: this.item.quantity || '',
isActive: this.item.isActive,
});
}
@ -127,7 +125,6 @@ export class ProductDetailComponent implements OnInit, AfterViewInit {
this.item.hasHappyHour = formModel.hasHappyHour;
this.item.isNotAvailable = formModel.isNotAvailable;
this.item.quantity = +formModel.quantity;
this.item.isActive = formModel.isActive;
return this.item;
}
}

View File

@ -6,8 +6,8 @@ import { Product } from '../../core/product';
export class ProductListDataSource extends DataSource<Product> {
public data: Product[];
public viewData: Product[];
private filterValue: string;
public filteredData: Product[];
public filterValue: string;
constructor(
private readonly filter: Observable<string>,
@ -15,39 +15,38 @@ export class ProductListDataSource extends DataSource<Product> {
) {
super();
this.data = [];
this.viewData = [];
this.filter = filter.pipe(
tap((x) => {
this.filterValue = x;
}),
);
this.dataObs = dataObs.pipe(
tap((x) => {
this.data = x;
}),
);
this.filteredData = [];
this.filterValue = '';
}
connect(): Observable<Product[]> {
const dataMutations = [this.dataObs, this.filter];
const dataMutations = [
this.dataObs.pipe(
tap((x) => {
this.data = x;
}),
),
this.filter.pipe(
tap((x) => {
this.filterValue = x;
}),
),
];
return merge(...dataMutations).pipe(
map(() => {
this.viewData = this.getFilteredData([...this.data]);
return this.viewData;
map(() => this.getFilteredData(this.data, this.filterValue)),
tap((x) => {
this.filteredData = x;
}),
);
}
disconnect() {}
private getFilteredData(data: Product[]): Product[] {
const filter = this.filterValue === undefined ? '' : this.filterValue;
if (filter === '') {
// eslint-disable-next-line class-methods-use-this
private getFilteredData(data: Product[], filter: string): Product[] {
if (filter === undefined) {
return data;
}
return data.filter((x) => {
return x.menuCategory.id === filter;
});
return data.filter((x) => x.menuCategory.id === filter);
}
}

View File

@ -0,0 +1,4 @@
.right {
display: flex;
justify-content: flex-end;
}

View File

@ -4,7 +4,8 @@
<button mat-button (click)="updateSortOrder()" [disabled]="!(filter | async)">
Update Order
</button>
<button mat-button mat-icon-button *ngIf="dataSource.viewData.length" (click)="exportCsv()">
<!-- This should check filtered data and not data-->
<button mat-button mat-icon-button (click)="exportCsv()" [disabled]="!((data | async).length)">
<mat-icon>save_alt</mat-icon>
</button>
<a mat-button [routerLink]="['/products', 'new']">
@ -26,7 +27,7 @@
<mat-select
placeholder="Menu Category"
formControlName="menuCategory"
(selectionChange)="filterOn($event)"
(selectionChange)="filterOn($event.value)"
>
<mat-option>-- All Products --</mat-option>
<mat-option *ngFor="let mc of menuCategories" [value]="mc.id">
@ -54,8 +55,8 @@
<!-- Price Column -->
<ng-container matColumnDef="price">
<mat-header-cell *matHeaderCellDef>Price</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.price | currency: 'INR' }}</mat-cell>
<mat-header-cell *matHeaderCellDef class="right">Price</mat-header-cell>
<mat-cell *matCellDef="let row" class="right">{{ row.price | currency: 'INR' }}</mat-cell>
</ng-container>
<!-- Menu Category Column -->
@ -87,20 +88,14 @@
</mat-icon>
<b> {{ row.isNotAvailable ? 'Not Available' : 'Available' }}</b>
</div>
<div flex>
<mat-icon>
{{ row.isActive ? 'visibility' : 'visibility_off' }}
</mat-icon>
<b> {{ row.isActive ? 'Active' : 'Deactivated' }}</b>
</div>
</div>
</mat-cell>
</ng-container>
<!-- Yield Column -->
<ng-container matColumnDef="quantity">
<mat-header-cell *matHeaderCellDef>Quantity</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.quantity | currency: 'INR' }}</mat-cell>
<mat-header-cell *matHeaderCellDef class="right">Quantity</mat-header-cell>
<mat-cell *matCellDef="let row" class="right">{{ row.quantity | number: '1.2-2' }}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>

View File

@ -44,15 +44,15 @@ export class ProductListComponent implements OnInit {
this.form = this.fb.group({
menuCategory: '',
});
this.filter = new BehaviorSubject('');
this.filter = new BehaviorSubject(undefined);
this.data = new BehaviorSubject([]);
this.data.subscribe((data: Product[]) => {
this.list = data;
});
}
filterOn(val: any) {
this.filter.next(val.value);
filterOn(val: string) {
this.filter.next(val);
}
ngOnInit() {
@ -68,7 +68,7 @@ export class ProductListComponent implements OnInit {
}
updateSortOrder() {
this.ser.updateSortOrder(this.dataSource.viewData).subscribe(
this.ser.updateSortOrder(this.dataSource.filteredData).subscribe(
(result: Product[]) => {
this.toaster.show('Success', '');
this.loadData(result, this.menuCategories);
@ -80,8 +80,15 @@ export class ProductListComponent implements OnInit {
}
dropTable(event: CdkDragDrop<ProductListDataSource>) {
const prevIndex = this.list.indexOf(event.item.data);
moveItemInArray(this.list, prevIndex, event.currentIndex);
const prevIndex = this.dataSource.filteredData.indexOf(event.item.data);
moveItemInArray(this.dataSource.filteredData, prevIndex, event.currentIndex);
if (this.dataSource.filterValue === undefined) {
this.list = this.dataSource.filteredData;
} else {
this.list = this.list
.filter((x) => x.menuCategory.id !== this.dataSource.filterValue)
.concat(this.dataSource.filteredData);
}
this.data.next(this.list);
}
@ -95,7 +102,7 @@ export class ProductListComponent implements OnInit {
SaleCategory: 'saleCategory',
};
const csvData = new Blob([this.toCsv.toCsv(headers, this.dataSource.viewData)], {
const csvData = new Blob([this.toCsv.toCsv(headers, this.dataSource.filteredData)], {
type: 'text/csv;charset=utf-8;',
});
const link = document.createElement('a');

View File

@ -35,7 +35,7 @@ export class ProductService {
}
listIsActiveOfCategory(id: string): Observable<Product[]> {
const options = { params: new HttpParams().set('mc', id).set('a', 'true') };
const options = { params: new HttpParams().set('mc', id) };
return <Observable<Product[]>>(
this.http
.get<Product[]>(`${url}/query`, options)