Product update sort order working

Beer Sale report should work. But it has all the products in it now.
All reports working
This commit is contained in:
2026-01-28 03:53:52 +00:00
parent 0a7ffb4a5c
commit 61f5f2f1af
10 changed files with 196 additions and 116 deletions

View File

@ -13,11 +13,10 @@ from ..core.config import settings
from ..models.device import Device
from ..models.printer import Printer
from ..models.section_printer import SectionPrinter
from ..schemas.menu_engineering_report import MeReport
from ..schemas.product_sale_report import ProductSaleReport
def print_product_sale_report(report: ProductSaleReport | MeReport, device_id: uuid.UUID, db: Session) -> None:
def print_product_sale_report(report: ProductSaleReport, device_id: uuid.UUID, db: Session) -> None:
locale.setlocale(locale.LC_MONETARY, "en_IN")
data = design_product_sale_report(report)
section_id = db.execute(select(Device.section_id).where(Device.id == device_id)).scalar_one()

View File

@ -35,38 +35,18 @@ from . import effective_date
router = APIRouter()
@router.post("/list", response_model=list[schemas.Product])
@router.post("/list/{id_}", response_model=list[schemas.Product])
def sort_order(
id_: uuid.UUID,
data: list[schemas.Product],
date_: date = Depends(effective_date),
user: UserToken = Security(get_user, scopes=["products"]),
) -> list[schemas.Product]:
raise NotImplementedError("Sorting products is not yet implemented.")
try:
with SessionFuture() as db:
indexes: dict[uuid.UUID, int] = {}
for item in data:
if item.menu_category.id_ in indexes:
indexes[item.menu_category.id_] += 1
else:
indexes[item.menu_category.id_] = 0
db.execute(
update(ProductVersion)
.where(
and_(
ProductVersion.product_id == item.id_,
or_(
ProductVersion.valid_from == None, # noqa: E711
ProductVersion.valid_from <= date_,
),
or_(
ProductVersion.valid_till == None, # noqa: E711
ProductVersion.valid_till >= date_,
),
)
)
.values(sort_order=indexes[item.menu_category.id_])
)
skus = [sku for product in data for sku in product.skus if sku.menu_category.id_ == id_]
for i, sku in enumerate(skus):
db.execute(update(StockKeepingUnit).where(StockKeepingUnit.id == sku.id_).values(sort_order=i))
db.commit()
return product_list(date_, db)
except SQLAlchemyError as e:

View File

@ -5,6 +5,9 @@ from fastapi import APIRouter, Depends, Security
from sqlalchemy import Date, and_
from sqlalchemy.sql.expression import func, select
from barker.models.sku_version import SkuVersion
from barker.models.stock_keeping_unit import StockKeepingUnit
from ...core.config import settings
from ...core.security import get_current_active_user as get_user
from ...db.session import SessionFuture
@ -36,7 +39,7 @@ def beer_consumption(
day = func.cast(
Voucher.date + timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES - settings.NEW_DAY_OFFSET_MINUTES), Date
).label("day")
sum_ = func.sum(Inventory.quantity * ProductVersion.quantity).label("sum")
sum_ = func.sum(Inventory.quantity * SkuVersion.fraction).label("sum")
product_version_onclause = and_(
ProductVersion.product_id == Product.id,
or_(
@ -48,11 +51,18 @@ def beer_consumption(
ProductVersion.valid_till >= day,
),
)
sku_version_onclause = and_(
SkuVersion.sku_id == StockKeepingUnit.id,
or_(SkuVersion.valid_from == None, SkuVersion.valid_from <= day), # noqa: E711
or_(SkuVersion.valid_till == None, SkuVersion.valid_till >= day), # noqa: E711
)
query = (
select(day, ProductVersion.name, sum_)
select(day, ProductVersion.name, sum_, ProductVersion.fraction_units)
.join(Voucher.kots)
.join(Kot.inventories)
.join(Inventory.product)
.join(Inventory.sku)
.join(SkuVersion, onclause=sku_version_onclause)
.join(StockKeepingUnit.product)
.join(ProductVersion, onclause=product_version_onclause)
.where(
day >= start_date,
@ -74,21 +84,21 @@ def beer_consumption(
with SessionFuture() as db:
list_ = db.execute(
query.where(Voucher.voucher_type.in_(vt))
.group_by(day, ProductVersion.name)
.group_by(day, ProductVersion.name, ProductVersion.fraction_units)
.having(sum_ != 0)
.order_by(day, ProductVersion.name)
).all()
headers: list[str] = []
data: list[BeerConsumptionReportItem] = []
for date_, name, quantity in list_:
if name not in headers:
headers.append(name)
for date_, name, quantity, units in list_:
if f"{name} ({units})" not in headers:
headers.append(f"{name} ({units})")
old = next((d for d in data if d.date_ == date_), None)
if old:
old[name] = quantity
old[f"{name} ({units})"] = quantity
else:
item = BeerConsumptionReportItem(date_=date_)
item[name] = quantity
item[f"{name} ({units})"] = quantity
data.append(item)
return BeerConsumptionReport(
start_date=start_date,

View File

@ -6,6 +6,9 @@ from fastapi import APIRouter, Cookie, Depends, Security
from sqlalchemy import Date, and_, func, or_, select
from sqlalchemy.orm import Session
from barker.models.sku_version import SkuVersion
from barker.models.stock_keeping_unit import StockKeepingUnit
from ...core.config import settings
from ...core.security import get_current_active_user as get_user
from ...db.session import SessionFuture
@ -56,11 +59,18 @@ def get_discount_report(start_date: date, finish_date: date, db: Session) -> lis
ProductVersion.valid_till >= day,
),
)
sku_version_onclause = and_(
SkuVersion.sku_id == StockKeepingUnit.id,
or_(SkuVersion.valid_from == None, SkuVersion.valid_from <= day), # noqa: E711
or_(SkuVersion.valid_till == None, SkuVersion.valid_till >= day), # noqa: E711
)
list_ = db.execute(
select(SaleCategory.name, amount)
.join(Voucher.kots)
.join(Kot.inventories)
.join(Inventory.product)
.join(Inventory.sku)
.join(SkuVersion, onclause=sku_version_onclause)
.join(StockKeepingUnit.product)
.join(ProductVersion, onclause=product_version_onclause)
.join(ProductVersion.sale_category)
.where(

View File

@ -3,10 +3,13 @@ import uuid
from datetime import date, timedelta
from decimal import Decimal
from fastapi import APIRouter, Cookie, Depends, Security
from fastapi import APIRouter, Depends, Security
from sqlalchemy import Date, and_, func, nulls_last, or_, select
from sqlalchemy.orm import Session
from barker.models.sku_version import SkuVersion
from barker.models.stock_keeping_unit import StockKeepingUnit
from ...core.config import settings
from ...core.security import get_current_active_user as get_user
from ...db.session import SessionFuture
@ -18,7 +21,6 @@ from ...models.product_version import ProductVersion
from ...models.sale_category import SaleCategory
from ...models.voucher import Voucher
from ...models.voucher_type import VoucherType
from ...printing.product_sale_report import print_product_sale_report
from ...schemas.menu_engineering_report import MeItem, MeReport
from ...schemas.user_token import UserToken
from . import check_audit_permission, report_finish_date, report_start_date
@ -47,8 +49,7 @@ def menu_engineering_report(start_date: date, finish_date: date, db: Session) ->
day = func.cast(
Voucher.date + timedelta(minutes=settings.TIMEZONE_OFFSET_MINUTES - settings.NEW_DAY_OFFSET_MINUTES), Date
).label("day")
product_version_onclause = and_(
ProductVersion.product_id == Product.id,
product_version_valid = and_(
or_(
ProductVersion.valid_from == None, # noqa: E711
ProductVersion.valid_from <= day,
@ -58,45 +59,53 @@ def menu_engineering_report(start_date: date, finish_date: date, db: Session) ->
ProductVersion.valid_till >= day,
),
)
sku_version_valid = and_(
or_(SkuVersion.valid_from == None, SkuVersion.valid_from <= day), # noqa: E711
or_(SkuVersion.valid_till == None, SkuVersion.valid_till >= day), # noqa: E711
)
list_ = db.execute(
select(
SaleCategory.name,
MenuCategory.name,
ProductVersion.id,
ProductVersion.full_name,
ProductVersion.price,
ProductVersion.name,
SkuVersion.id,
SkuVersion.units,
SkuVersion.sale_price,
func.sum(Inventory.quantity),
func.sum(Inventory.net),
)
.select_from(ProductVersion)
.join(ProductVersion.sale_category)
.join(ProductVersion.menu_category)
.join(Product, onclause=product_version_onclause)
.join(Product.inventories, isouter=True)
.join(ProductVersion.product)
.join(Product.skus)
.join(StockKeepingUnit.versions)
.join(SkuVersion.menu_category)
.join(StockKeepingUnit.inventories, isouter=True)
.join(Inventory.kot, isouter=True)
.join(Kot.voucher, isouter=True)
.where(
or_(
day == None, # noqa: E711
and_(
day >= start_date,
day <= finish_date,
Voucher.voucher_type == VoucherType.REGULAR_BILL,
),
),
day >= start_date,
day <= finish_date,
Voucher.voucher_type == VoucherType.REGULAR_BILL,
product_version_valid,
sku_version_valid,
)
.group_by(
SaleCategory.name,
MenuCategory.name,
ProductVersion.id,
ProductVersion.full_name,
ProductVersion.name,
SkuVersion.id,
SkuVersion.units,
SkuVersion.sale_price,
)
.order_by(SaleCategory.name, nulls_last(func.sum(Inventory.net).desc()))
).all()
info: list[MeItem] = []
sc_sales: dict[str, Decimal] = {}
sc_quantity: dict[str, Decimal] = {}
for sc, mc, id_, name, price, quantity, sales in list_:
for sc, mc, p_id, name, s_id, units, price, quantity, sales in list_:
if sc not in sc_sales:
sc_sales[sc] = Decimal(0)
if sales is not None:
@ -107,8 +116,8 @@ def menu_engineering_report(start_date: date, finish_date: date, db: Session) ->
sc_quantity[sc] += quantity
info.append(
MeItem(
id_=id_,
name=name,
id_=uuid.uuid5(uuid.NAMESPACE_DNS, f"{p_id}_{s_id}"),
name=f"{name} ({units})",
price=price,
average=round(sales / quantity) if sales and quantity else price,
sale_category=sc,
@ -126,20 +135,20 @@ def menu_engineering_report(start_date: date, finish_date: date, db: Session) ->
return info
@router.get("/print", response_model=bool)
def print_report(
start_date: date = Depends(report_start_date),
finish_date: date = Depends(report_finish_date),
device_id: uuid.UUID = Cookie(None),
user: UserToken = Security(get_user, scopes=["product-sale-report"]),
) -> bool:
check_audit_permission(start_date, user.permissions)
with SessionFuture() as db:
report = MeReport(
user_name=user.name,
start_date=start_date,
finish_date=finish_date,
amounts=menu_engineering_report(start_date, finish_date, db),
)
print_product_sale_report(report, device_id, db)
return True
# @router.get("/print", response_model=bool)
# def print_report(
# start_date: date = Depends(report_start_date),
# finish_date: date = Depends(report_finish_date),
# device_id: uuid.UUID = Cookie(None),
# user: UserToken = Security(get_user, scopes=["product-sale-report"]),
# ) -> bool:
# check_audit_permission(start_date, user.permissions)
# with SessionFuture() as db:
# report = MeReport(
# user_name=user.name,
# start_date=start_date,
# finish_date=finish_date,
# amounts=menu_engineering_report(start_date, finish_date, db),
# )
# print_product_sale_report(report, device_id, db)
# return True

View File

@ -7,6 +7,9 @@ from fastapi import APIRouter, Cookie, Depends, Query, Security
from sqlalchemy import Date, and_, func, or_, select
from sqlalchemy.orm import Session
from barker.models.sku_version import SkuVersion
from barker.models.stock_keeping_unit import StockKeepingUnit
from ...core.config import settings
from ...core.security import get_current_active_user as get_user
from ...db.session import SessionFuture
@ -63,20 +66,28 @@ def product_sale_report(
ProductVersion.valid_till >= day,
),
)
sku_version_onclause = and_(
SkuVersion.sku_id == StockKeepingUnit.id,
or_(SkuVersion.valid_from == None, SkuVersion.valid_from <= day), # noqa: E711
or_(SkuVersion.valid_till == None, SkuVersion.valid_till >= day), # noqa: E711
)
query = (
select(
ProductVersion.id,
ProductVersion.full_name,
ProductVersion.name,
SkuVersion.units,
Voucher.voucher_type,
Inventory.is_happy_hour,
func.sum(Inventory.quantity),
)
.join(Inventory.kot)
.join(Kot.voucher)
.join(Inventory.product)
.join(Inventory.sku)
.join(SkuVersion, onclause=sku_version_onclause)
.join(SkuVersion.menu_category)
.join(StockKeepingUnit.product)
.join(ProductVersion, onclause=product_version_onclause)
.join(ProductVersion.sale_category)
.join(ProductVersion.menu_category)
.join(Voucher.food_table)
.where(
day >= start_date,
@ -89,20 +100,21 @@ def product_sale_report(
SaleCategory.name,
MenuCategory.name,
ProductVersion.id,
ProductVersion.full_name,
ProductVersion.name,
SkuVersion.units,
Voucher.voucher_type,
Inventory.is_happy_hour,
).order_by(SaleCategory.name, MenuCategory.name, ProductVersion.full_name)
).order_by(SaleCategory.name, MenuCategory.name, ProductVersion.name, SkuVersion.units)
list_ = db.execute(query).all()
info: list[ProductSaleReportItem] = []
for product_version_id, name, v_type, hh, quantity in list_:
for product_version_id, name, units, v_type, hh, quantity in list_:
type_ = VoucherType(v_type).name
old = next((i for i in info if i.product_version_id == product_version_id and i.is_happy_hour == hh), None)
if old:
old[type_] = old[type_] + quantity
else:
item = ProductSaleReportItem(
product_version_id=product_version_id, name=f"{'H H ' if hh else ''}{name}", is_happy_hour=hh
product_version_id=product_version_id, name=f"{'H H ' if hh else ''}{name} ({units})", is_happy_hour=hh
)
item[type_] = quantity
info.append(item)

View File

@ -1,9 +1,13 @@
from datetime import date, timedelta
from fastapi import APIRouter, Depends, Security
from sqlalchemy import and_, or_, select
from sqlalchemy import and_, nullsfirst, or_, select
from sqlalchemy.orm import Session, contains_eager
from barker.models.product import Product
from barker.models.sku_version import SkuVersion
from barker.models.stock_keeping_unit import StockKeepingUnit
from ...core.security import get_current_active_user as get_user
from ...db.session import SessionFuture
from ...models.menu_category import MenuCategory
@ -21,19 +25,32 @@ def product_updates_report_view(
finish_date: date = Depends(report_finish_date),
user: UserToken = Security(get_user, scopes=["product-sale-report"]),
):
with SessionFuture() as db:
return {
"startDate": start_date.strftime("%d-%b-%Y"),
"finishDate": finish_date.strftime("%d-%b-%Y"),
"report": product_updates_report(start_date, finish_date, db),
}
try:
with SessionFuture() as db:
return {
"startDate": start_date.strftime("%d-%b-%Y"),
"finishDate": finish_date.strftime("%d-%b-%Y"),
"report": product_updates_report(start_date, finish_date, db),
}
except Exception as e:
return {"error": str(e)}
def product_updates_report(start_date: date, finish_date: date, db: Session) -> list[str]:
list_ = (
db.execute(
select(ProductVersion)
.join(ProductVersion.menu_category)
select(Product)
.join(Product.versions)
.join(ProductVersion.sale_category)
.join(Product.skus)
.join(StockKeepingUnit.versions)
.join(SkuVersion.menu_category)
.order_by(MenuCategory.sort_order)
.order_by(MenuCategory.name)
.order_by(Product.sort_order)
.order_by(ProductVersion.name)
.order_by(nullsfirst(ProductVersion.valid_from))
.order_by(nullsfirst(SkuVersion.valid_from))
.where(
or_(
and_(
@ -44,33 +61,76 @@ def product_updates_report(start_date: date, finish_date: date, db: Session) ->
ProductVersion.valid_till >= start_date - timedelta(days=1),
ProductVersion.valid_till <= finish_date,
),
and_(
SkuVersion.valid_from >= start_date,
SkuVersion.valid_from <= finish_date,
),
and_(
SkuVersion.valid_till >= start_date - timedelta(days=1),
SkuVersion.valid_till <= finish_date,
),
)
)
.order_by(
MenuCategory.sort_order,
MenuCategory.name,
ProductVersion.sort_order,
ProductVersion.name,
ProductVersion.valid_from.nullsfirst(),
)
.options(
contains_eager(ProductVersion.menu_category),
contains_eager(Product.versions).contains_eager(ProductVersion.sale_category),
contains_eager(Product.skus)
.contains_eager(StockKeepingUnit.versions)
.contains_eager(SkuVersion.menu_category),
)
)
.unique()
.scalars()
.all()
)
report = {}
for item in list_:
if item.product_id not in report:
report[item.product_id] = ""
report[item.product_id] += (
"From: "
+ (f"{item.valid_from:%d-%b-%Y}" if item.valid_from is not None else "\u221e")
+ " "
+ "Till: "
+ (f"{item.valid_till:%d-%b-%Y}" if item.valid_till is not None else "\u221e")
+ "\n"
+ f"{item.full_name} @ {item.price: .2f} - {'Happy Hour' if item.has_happy_hour else 'No Happy Hour'}\n"
)
for product in list_:
dates: set[date] = set()
for p in product.versions:
dates.add(p.valid_from or date.min)
for sk in product.skus:
for s in sk.versions:
dates.add(s.valid_from or date.min)
sku_versions = [s for sk in product.skus for s in sk.versions]
if product.id not in report:
report[product.id] = ""
for d in sorted(dates):
ap = active_at_p(product.versions, d)
ass = active_at_s(sku_versions, d)
if ap is None or ass is None:
continue
v_from: date | None = (
max(ap.valid_from, ass.valid_from)
if ap.valid_from and ass.valid_from
else ap.valid_from or ass.valid_from
)
v_till: date | None = (
min(ap.valid_till, ass.valid_till)
if ap.valid_till and ass.valid_till
else ap.valid_till or ass.valid_till
)
report[product.id] += (
"From: "
+ (f"{v_from:%d-%b-%Y}" if v_from is not None else "\u221e")
+ " "
+ "Till: "
+ (f"{v_till:%d-%b-%Y}" if v_till is not None else "\u221e")
+ "\n"
+ f"{ap.name} ({ass.units}) @ {ass.sale_price: .2f} - {'Happy Hour' if ass.has_happy_hour else 'No Happy Hour'}\n"
)
return list(report.values())
def active_at_p(items: list[ProductVersion], t: date) -> ProductVersion | None:
for it in items:
if (it.valid_from or date.min) <= t and (it.valid_till is None or t < it.valid_till):
return it
return None
def active_at_s(items: list[SkuVersion], t: date) -> SkuVersion | None:
for it in items:
if (it.valid_from or date.min) <= t and (it.valid_till is None or t < it.valid_till):
return it
return None

View File

@ -51,8 +51,8 @@ class Product(BaseModel):
class StockKeepingUnit(BaseModel):
id_: uuid.UUID = None
version_id: uuid.UUID = None
id_: uuid.UUID
version_id: uuid.UUID
units: str = Field(..., min_length=1)
fraction: Daf = Field(ge=Decimal(1), default=Decimal(1))
product_yield: Daf = Field(gt=Decimal(0), le=Decimal(1), default=Decimal(1))

View File

@ -98,7 +98,7 @@ export class ProductListComponent implements OnInit {
}
updateSortOrder() {
this.ser.updateSortOrder(this.dataSource.filteredData).subscribe({
this.ser.updateSortOrder(this.menuCategoryFilter.value, this.dataSource.filteredData).subscribe({
next: (result: Product[]) => {
this.snackBar.open('', 'Success');
this.loadData(result, this.menuCategories);

View File

@ -57,9 +57,9 @@ export class ProductService {
.pipe(catchError(this.log.handleError(serviceName, 'update'))) as Observable<void>;
}
updateSortOrder(list: Product[]): Observable<Product[]> {
updateSortOrder(menuCategoryId: string, list: Product[]): Observable<Product[]> {
return this.http
.post<Product[]>(`${url}/list`, list, httpOptions)
.post<Product[]>(`${url}/list/${menuCategoryId}`, list, httpOptions)
.pipe(catchError(this.log.handleError(serviceName, 'updateSortOrder'))) as Observable<Product[]>;
}