diff --git a/barker/alembic/versions/1e0daf6bc1ae_combine_skus.py b/barker/alembic/versions/1e0daf6bc1ae_combine_skus.py new file mode 100644 index 00000000..fb6d411a --- /dev/null +++ b/barker/alembic/versions/1e0daf6bc1ae_combine_skus.py @@ -0,0 +1,286 @@ +"""combine skus + +Revision ID: 1e0daf6bc1ae +Revises: c116517977af +Create Date: 2026-01-23 15:56:01.354574 + +""" + +import sqlalchemy as sa + +from alembic import op + + +# revision identifiers, used by Alembic. +revision = "1e0daf6bc1ae" +down_revision = "c116517977af" +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute( + sa.text( + """ + -- Merge contiguous identical sku_versions (same business fields) into single rows. + + -- 0) Work table: compute vf/vt sentinels + run_id (islands) + CREATE TEMP TABLE tmp_sku_work ON COMMIT DROP AS + WITH base AS ( + SELECT + sv.id, + sv.sku_id, + sv.units, + sv.fraction, + sv.product_yield, + sv.cost_price, + sv.sale_price, + sv.has_happy_hour, + sv.menu_category_id, + sv.valid_from, + sv.valid_till, + COALESCE(sv.valid_from, DATE '1900-01-01') AS vf, + COALESCE(sv.valid_till, DATE '9999-12-31') AS vt + FROM sku_versions sv + ), + ordered AS ( + SELECT + b.*, + LAG(vt) OVER ( + PARTITION BY sku_id, units, fraction, product_yield, cost_price, sale_price, has_happy_hour, menu_category_id + ORDER BY vf, vt, id + ) AS prev_vt + FROM base b + ), + flagged AS ( + SELECT + o.*, + CASE + WHEN o.prev_vt IS NULL THEN 1 + WHEN o.vf = (o.prev_vt + INTERVAL '1 day')::date THEN 0 + ELSE 1 + END AS is_new_run + FROM ordered o + ), + runs AS ( + SELECT + f.*, + SUM(is_new_run) OVER ( + PARTITION BY sku_id, units, fraction, product_yield, cost_price, sale_price, has_happy_hour, menu_category_id + ORDER BY vf, vt, id + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) AS run_id + FROM flagged f + ) + SELECT + id, + sku_id, units, fraction, product_yield, cost_price, sale_price, has_happy_hour, menu_category_id, + valid_from, valid_till, + vf, vt, + run_id + FROM runs; + + -- 1) Survivors + merged validity (only runs with >1 row) + CREATE TEMP TABLE tmp_sku_merge ON COMMIT DROP AS + WITH run_stats AS ( + SELECT + sku_id, units, fraction, product_yield, cost_price, sale_price, has_happy_hour, menu_category_id, + run_id, + MIN(vf) AS min_vf, + MAX(vt) AS max_vt, + COUNT(*) AS cnt + FROM tmp_sku_work + GROUP BY + sku_id, units, fraction, product_yield, cost_price, sale_price, has_happy_hour, menu_category_id, run_id + HAVING COUNT(*) > 1 + ), + survivors AS ( + SELECT DISTINCT ON ( + w.sku_id, w.units, w.fraction, w.product_yield, w.cost_price, w.sale_price, w.has_happy_hour, w.menu_category_id, w.run_id + ) + w.sku_id, w.units, w.fraction, w.product_yield, w.cost_price, w.sale_price, w.has_happy_hour, w.menu_category_id, w.run_id, + w.id AS survivor_id + FROM tmp_sku_work w + JOIN run_stats s + ON s.sku_id = w.sku_id + AND s.units = w.units + AND s.fraction = w.fraction + AND s.product_yield = w.product_yield + AND s.cost_price = w.cost_price + AND s.sale_price = w.sale_price + AND s.has_happy_hour = w.has_happy_hour + AND s.menu_category_id = w.menu_category_id + AND s.run_id = w.run_id + ORDER BY + w.sku_id, w.units, w.fraction, w.product_yield, w.cost_price, w.sale_price, w.has_happy_hour, w.menu_category_id, w.run_id, + w.vf, w.vt, w.id + ) + SELECT + sv.survivor_id, + sv.sku_id, sv.units, sv.fraction, sv.product_yield, sv.cost_price, sv.sale_price, sv.has_happy_hour, sv.menu_category_id, sv.run_id, + CASE WHEN s.min_vf = DATE '1900-01-01' THEN NULL ELSE s.min_vf END AS new_valid_from, + CASE WHEN s.max_vt = DATE '9999-12-31' THEN NULL ELSE s.max_vt END AS new_valid_till + FROM survivors sv + JOIN run_stats s + ON s.sku_id = sv.sku_id + AND s.units = sv.units + AND s.fraction = sv.fraction + AND s.product_yield = sv.product_yield + AND s.cost_price = sv.cost_price + AND s.sale_price = sv.sale_price + AND s.has_happy_hour = sv.has_happy_hour + AND s.menu_category_id = sv.menu_category_id + AND s.run_id = sv.run_id; + + -- 2) Rows to delete = everything in a merged run except the survivor + CREATE TEMP TABLE tmp_sku_delete ON COMMIT DROP AS + SELECT w.id + FROM tmp_sku_work w + JOIN tmp_sku_merge m + ON m.sku_id = w.sku_id + AND m.units = w.units + AND m.fraction = w.fraction + AND m.product_yield = w.product_yield + AND m.cost_price = w.cost_price + AND m.sale_price = w.sale_price + AND m.has_happy_hour = w.has_happy_hour + AND m.menu_category_id = w.menu_category_id + AND m.run_id = w.run_id + WHERE w.id <> m.survivor_id; + + -- 3) Delete first (avoid exclusion overlap), then update survivor validity + DELETE FROM sku_versions + WHERE id IN (SELECT id FROM tmp_sku_delete); + + UPDATE sku_versions sv + SET valid_from = m.new_valid_from, + valid_till = m.new_valid_till + FROM tmp_sku_merge m + WHERE sv.id = m.survivor_id; + """ + ) + ) + op.execute( + sa.text( + """ + -- Merge contiguous identical product_versions (ignoring fraction_units) + + -- 0) Work table: compute vf/vt sentinels + run_id (islands) + CREATE TEMP TABLE tmp_pv_work ON COMMIT DROP AS + WITH base AS ( + SELECT + pv.id, + pv.product_id, + pv.name, + pv.valid_from, + pv.valid_till, + COALESCE(pv.valid_from, DATE '1900-01-01') AS vf, + COALESCE(pv.valid_till, DATE '9999-12-31') AS vt + FROM product_versions pv + ), + ordered AS ( + SELECT + b.*, + LAG(vt) OVER ( + PARTITION BY product_id, name + ORDER BY vf, vt, id + ) AS prev_vt + FROM base b + ), + flagged AS ( + SELECT + o.*, + CASE + WHEN o.prev_vt IS NULL THEN 1 + WHEN o.vf = (o.prev_vt + INTERVAL '1 day')::date THEN 0 + ELSE 1 + END AS is_new_run + FROM ordered o + ), + runs AS ( + SELECT + f.*, + SUM(is_new_run) OVER ( + PARTITION BY product_id, name + ORDER BY vf, vt, id + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) AS run_id + FROM flagged f + ) + SELECT + id, + product_id, + name, + valid_from, + valid_till, + vf, vt, + run_id + FROM runs; + + -- 1) Survivors + merged validity (only runs with >1 row) + CREATE TEMP TABLE tmp_pv_merge ON COMMIT DROP AS + WITH run_stats AS ( + SELECT + product_id, + name, + run_id, + MIN(vf) AS min_vf, + MAX(vt) AS max_vt, + COUNT(*) AS cnt + FROM tmp_pv_work + GROUP BY product_id, name, run_id + HAVING COUNT(*) > 1 + ), + survivors AS ( + -- deterministic survivor: earliest vf, then earliest vt, then lowest id + SELECT DISTINCT ON (w.product_id, w.name, w.run_id) + w.product_id, + w.name, + w.run_id, + w.id AS survivor_id + FROM tmp_pv_work w + JOIN run_stats s + ON s.product_id = w.product_id + AND s.name = w.name + AND s.run_id = w.run_id + ORDER BY w.product_id, w.name, w.run_id, w.vf, w.vt, w.id + ) + SELECT + sv.survivor_id, + sv.product_id, + sv.name, + sv.run_id, + CASE WHEN s.min_vf = DATE '1900-01-01' THEN NULL ELSE s.min_vf END AS new_valid_from, + CASE WHEN s.max_vt = DATE '9999-12-31' THEN NULL ELSE s.max_vt END AS new_valid_till + FROM survivors sv + JOIN run_stats s + ON s.product_id = sv.product_id + AND s.name = sv.name + AND s.run_id = sv.run_id; + + -- 2) Rows to delete = everything in a merged run except the survivor + CREATE TEMP TABLE tmp_pv_delete ON COMMIT DROP AS + SELECT w.id + FROM tmp_pv_work w + JOIN tmp_pv_merge m + ON m.product_id = w.product_id + AND m.name = w.name + AND m.run_id = w.run_id + WHERE w.id <> m.survivor_id; + + -- 3) Delete first (avoid exclusion overlap), then update survivor validity + DELETE FROM product_versions + WHERE id IN (SELECT id FROM tmp_pv_delete); + + UPDATE product_versions pv + SET valid_from = m.new_valid_from, + valid_till = m.new_valid_till + FROM tmp_pv_merge m + WHERE pv.id = m.survivor_id; + """ + ) + ) + + +def downgrade(): + pass diff --git a/barker/alembic/versions/32c508eed4df_skus.py b/barker/alembic/versions/32c508eed4df_skus.py index 720c160e..99e29962 100644 --- a/barker/alembic/versions/32c508eed4df_skus.py +++ b/barker/alembic/versions/32c508eed4df_skus.py @@ -15,6 +15,14 @@ depends_on = None def upgrade(): + # --------------------------------------------------------------------- + # 0) Add NON-VERSIONED sort_order columns (but do NOT remove versioned ones here) + # --------------------------------------------------------------------- + op.add_column( + "products", + sa.Column("sort_order", sa.Integer(), nullable=False, server_default=sa.text("0")), + ) + # --------------------------------------------------------------------- # 1) stock_keeping_units # --------------------------------------------------------------------- @@ -23,6 +31,7 @@ def upgrade(): sa.Column("id", sa.Uuid(), server_default=sa.text("gen_random_uuid()"), nullable=False), sa.Column("product_id", sa.Uuid(), nullable=False), sa.Column("is_not_available", sa.Boolean(), nullable=False, server_default=sa.text("false")), + sa.Column("sort_order", sa.Integer(), nullable=False), sa.ForeignKeyConstraint( ["product_id"], ["products.id"], name=op.f("fk_stock_keeping_units_product_id_products") ), @@ -42,7 +51,6 @@ def upgrade(): sa.Column("cost_price", sa.Numeric(precision=15, scale=2), nullable=False), sa.Column("sale_price", sa.Numeric(precision=15, scale=2), nullable=False), sa.Column("has_happy_hour", sa.Boolean(), nullable=False), - sa.Column("sort_order", sa.Integer(), nullable=False), sa.Column("menu_category_id", sa.Uuid(), nullable=False), sa.Column("valid_from", sa.Date(), nullable=True), sa.Column("valid_till", sa.Date(), nullable=True), @@ -77,15 +85,17 @@ def upgrade(): sa.column("id", postgresql.UUID(as_uuid=True)), sa.column("product_id", postgresql.UUID(as_uuid=True)), sa.column("is_not_available", sa.Boolean()), + sa.column("sort_order", sa.Integer()), ) op.execute( stock_keeping_units.insert().from_select( - ["id", "product_id", "is_not_available"], + ["id", "product_id", "is_not_available", "sort_order"], sa.select( sa.func.gen_random_uuid(), products.c.id, sa.false(), + sa.literal(0), ), ) ) @@ -141,7 +151,8 @@ def upgrade(): sa.text( """ UPDATE stock_keeping_units sku - SET is_not_available = pv.is_not_available + SET is_not_available = pv.is_not_available, + sort_order = pv.sort_order FROM product_versions pv WHERE pv.product_id = sku.product_id AND pv.valid_till IS NULL @@ -160,7 +171,6 @@ def upgrade(): "cost_price", "sale_price", "has_happy_hour", - "sort_order", "menu_category_id", "valid_from", "valid_till", @@ -168,13 +178,12 @@ def upgrade(): sa.select( sa.func.gen_random_uuid(), stock_keeping_units.c.id, - pv.c.units, + sa.func.coalesce(sa.func.nullif(sa.func.btrim(pv.c.units), ""), sa.literal("pc")), sa.cast(1.0, sa.Numeric(15, 5)), sa.cast(1.0, sa.Numeric(15, 5)), sa.cast(0.0, sa.Numeric(15, 2)), sa.cast(pv.c.price, sa.Numeric(15, 2)), pv.c.has_happy_hour, - pv.c.sort_order, pv.c.menu_category_id, pv.c.valid_from, pv.c.valid_till, @@ -206,7 +215,7 @@ def upgrade(): # --------------------------------------------------------------------- op.alter_column("product_versions", "units", new_column_name="fraction_units") op.drop_constraint(op.f("fk_products_menu_category_id_menu_categories"), "product_versions", type_="foreignkey") - for col in ["price", "quantity", "has_happy_hour", "is_not_available", "menu_category_id"]: + for col in ["price", "quantity", "has_happy_hour", "is_not_available", "menu_category_id", "sort_order"]: op.drop_column("product_versions", col) diff --git a/barker/alembic/versions/c116517977af_normalize_sku.py b/barker/alembic/versions/c116517977af_normalize_sku.py new file mode 100644 index 00000000..9cbfb4a0 --- /dev/null +++ b/barker/alembic/versions/c116517977af_normalize_sku.py @@ -0,0 +1,146 @@ +"""normalize sku + +Revision ID: c116517977af +Revises: 32c508eed4df +Create Date: 2026-01-23 15:33:39.682395 + +""" + +import sqlalchemy as sa + +from alembic import op + + +# revision identifiers, used by Alembic. +revision = "c116517977af" +down_revision = "32c508eed4df" +branch_labels = None +depends_on = None + + +def upgrade(): + # --------------------------------------------------------------------- + # 1) Build mapping: old_product_id -> canonical_product_id + # Canonical = product whose CURRENT pv (valid_till IS NULL) has + # latest valid_from (prefer newer). Tie-break: product_id. + # --------------------------------------------------------------------- + op.execute( + sa.text( + """ + CREATE TEMP TABLE tmp_product_canon ( + old_product_id uuid PRIMARY KEY, + canonical_product_id uuid NOT NULL + ) ON COMMIT DROP; + + WITH current_pv AS ( + SELECT + pv.product_id, + pv.name, + pv.valid_from + FROM product_versions pv + WHERE pv.valid_till IS NULL + ), + canon AS ( + SELECT DISTINCT ON (name) + name, + product_id AS canonical_product_id + FROM current_pv + ORDER BY name, valid_from DESC NULLS LAST, product_id + ) + INSERT INTO tmp_product_canon (old_product_id, canonical_product_id) + SELECT + cp.product_id AS old_product_id, + c.canonical_product_id + FROM current_pv cp + JOIN canon c ON c.name = cp.name; + """ + ) + ) + + # --------------------------------------------------------------------- + # 2) Move SKUs to canonical product + # --------------------------------------------------------------------- + op.execute( + sa.text( + """ + UPDATE stock_keeping_units sku + SET product_id = m.canonical_product_id + FROM tmp_product_canon m + WHERE sku.product_id = m.old_product_id + AND m.old_product_id <> m.canonical_product_id; + """ + ) + ) + + # --------------------------------------------------------------------- + # 3) Merge modifier_categories_products: + # (A) delete duplicates that would conflict after update + # (B) update remaining rows to canonical_product_id + # --------------------------------------------------------------------- + op.execute( + sa.text( + """ + DELETE FROM modifier_categories_products mcp + USING tmp_product_canon m + WHERE m.old_product_id <> m.canonical_product_id + AND mcp.product_id = m.old_product_id + AND EXISTS ( + SELECT 1 + FROM modifier_categories_products mcp2 + WHERE mcp2.product_id = m.canonical_product_id + AND mcp2.modifier_category_id = mcp.modifier_category_id + ); + """ + ) + ) + + op.execute( + sa.text( + """ + UPDATE modifier_categories_products mcp + SET product_id = m.canonical_product_id + FROM tmp_product_canon m + WHERE m.old_product_id <> m.canonical_product_id + AND mcp.product_id = m.old_product_id; + """ + ) + ) + + # --------------------------------------------------------------------- + # 4) Delete duplicate product_versions and products. + # Since you said "newer products mean more than old ones", + # we keep canonical product and delete other products in the group. + # + # IMPORTANT: + # - inventories already point to SKUs, so safe + # - SKUs already moved to canonical product + # - modifier categories already moved + # --------------------------------------------------------------------- + + # Delete product_versions for old (non-canonical) products + op.execute( + sa.text( + """ + DELETE FROM product_versions pv + USING tmp_product_canon m + WHERE pv.product_id = m.old_product_id + AND m.old_product_id <> m.canonical_product_id; + """ + ) + ) + + # Delete products for old (non-canonical) products + op.execute( + sa.text( + """ + DELETE FROM products p + USING tmp_product_canon m + WHERE p.id = m.old_product_id + AND m.old_product_id <> m.canonical_product_id; + """ + ) + ) + + +def downgrade(): + pass diff --git a/barker/barker/models/product.py b/barker/barker/models/product.py index 7faff96a..1fd8059d 100644 --- a/barker/barker/models/product.py +++ b/barker/barker/models/product.py @@ -4,7 +4,7 @@ import uuid from typing import TYPE_CHECKING -from sqlalchemy import Uuid, text +from sqlalchemy import Integer, Uuid, text from sqlalchemy.orm import Mapped, mapped_column, relationship from ..db.base_class import reg @@ -21,6 +21,7 @@ if TYPE_CHECKING: class Product: __tablename__ = "products" id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, server_default=text("gen_random_uuid()")) + sort_order: Mapped[int] = mapped_column(Integer, nullable=False) versions: Mapped[list[ProductVersion]] = relationship(back_populates="product") skus: Mapped[list[StockKeepingUnit]] = relationship("StockKeepingUnit", back_populates="product") modifier_categories: Mapped[list[ModifierCategory]] = relationship( @@ -30,6 +31,7 @@ class Product: back_populates="products", ) - def __init__(self, id_: uuid.UUID | None = None): + def __init__(self, sort_order: int = 0, id_: uuid.UUID | None = None): + self.sort_order = sort_order if id_ is not None: self.id = id_ diff --git a/barker/barker/models/product_version.py b/barker/barker/models/product_version.py index a0178c17..bb4ba19b 100644 --- a/barker/barker/models/product_version.py +++ b/barker/barker/models/product_version.py @@ -8,7 +8,6 @@ from typing import TYPE_CHECKING from sqlalchemy import ( Date, ForeignKey, - Integer, Text, Uuid, func, @@ -37,7 +36,6 @@ class ProductVersion: ForeignKey("sale_categories.id"), nullable=False, ) - sort_order: Mapped[int] = mapped_column(Integer, nullable=False) valid_from: Mapped[date | None] = mapped_column(Date(), nullable=True) valid_till: Mapped[date | None] = mapped_column(Date(), nullable=True) @@ -65,7 +63,6 @@ class ProductVersion: sale_category_id: uuid.UUID | None = None, valid_from: date | None = None, valid_till: date | None = None, - sort_order: int = 0, id_: uuid.UUID | None = None, ): self.product_id = product_id @@ -75,6 +72,5 @@ class ProductVersion: self.sale_category_id = sale_category_id self.valid_from = valid_from self.valid_till = valid_till - self.sort_order = sort_order if id_ is not None: self.id = id_ diff --git a/barker/barker/models/sku_version.py b/barker/barker/models/sku_version.py index 900971b4..300ef9e4 100644 --- a/barker/barker/models/sku_version.py +++ b/barker/barker/models/sku_version.py @@ -6,7 +6,7 @@ from datetime import date from decimal import Decimal from typing import TYPE_CHECKING -from sqlalchemy import Boolean, Date, ForeignKey, Integer, Numeric, Unicode, Uuid, func, text +from sqlalchemy import Boolean, Date, ForeignKey, Numeric, Unicode, Uuid, func, text from sqlalchemy.dialects import postgresql from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -34,7 +34,6 @@ class SkuVersion: cost_price: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=2), nullable=False) sale_price: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=2), nullable=False) has_happy_hour: Mapped[bool] = mapped_column(Boolean, nullable=False) - sort_order: Mapped[int] = mapped_column(Integer, nullable=False) menu_category_id: Mapped[uuid.UUID] = mapped_column( Uuid, ForeignKey("menu_categories.id"), @@ -66,7 +65,6 @@ class SkuVersion: cost_price: Decimal = Decimal("0.0"), sale_price: Decimal = Decimal("0.0"), has_happy_hour: bool = False, - sort_order: int = 0, menu_category_id: uuid.UUID | None = None, sku_id: uuid.UUID | None = None, sku: StockKeepingUnit | None = None, @@ -82,7 +80,6 @@ class SkuVersion: self.cost_price = cost_price self.sale_price = sale_price self.has_happy_hour = has_happy_hour - self.sort_order = sort_order if menu_category_id is not None: self.menu_category_id = menu_category_id if id_ is not None: diff --git a/barker/barker/models/stock_keeping_unit.py b/barker/barker/models/stock_keeping_unit.py index 96592595..94e1372a 100644 --- a/barker/barker/models/stock_keeping_unit.py +++ b/barker/barker/models/stock_keeping_unit.py @@ -4,7 +4,7 @@ import uuid from typing import TYPE_CHECKING -from sqlalchemy import Boolean, ForeignKey, Uuid, text +from sqlalchemy import Boolean, ForeignKey, Integer, Uuid, text from sqlalchemy.orm import Mapped, mapped_column, relationship from ..db.base_class import reg @@ -25,6 +25,7 @@ class StockKeepingUnit: ) product_id: Mapped[uuid.UUID] = mapped_column(Uuid, ForeignKey("products.id"), nullable=False) is_not_available: Mapped[bool] = mapped_column(Boolean, nullable=False) + sort_order: Mapped[int] = mapped_column(Integer, nullable=False) product: Mapped[Product] = relationship("Product", back_populates="skus") inventories: Mapped[list[Inventory]] = relationship(back_populates="sku") @@ -33,11 +34,13 @@ class StockKeepingUnit: def __init__( self, is_not_available: bool = False, + sort_order: int = 0, product_id: uuid.UUID | None = None, product: Product | None = None, id_: uuid.UUID | None = None, ) -> None: self.is_not_available = is_not_available + self.sort_order = sort_order if product_id is not None: self.product_id = product_id if id_ is not None: diff --git a/barker/barker/routers/product.py b/barker/barker/routers/product.py index 324585be..112da54a 100644 --- a/barker/barker/routers/product.py +++ b/barker/barker/routers/product.py @@ -83,7 +83,7 @@ def save( ) -> None: try: with SessionFuture() as db: - product = Product() + product = Product(sort_order=data.sort_order) db.add(product) db.flush() # so product.id is available version = ProductVersion( @@ -91,7 +91,6 @@ def save( name=data.name, fraction_units=data.fraction_units, sale_category_id=data.sale_category.id_, - sort_order=data.sort_order, valid_from=date_, valid_till=None, ) @@ -216,13 +215,13 @@ def update_route( or version.fraction_units != data.fraction_units or version.sale_category_id != data.sale_category.id_ ) + version.product.sort_order = data.sort_order if pv_changed: if version.valid_from == date_: # Safe to mutate current version version.name = data.name version.fraction_units = data.fraction_units version.sale_category_id = data.sale_category.id_ - version.sort_order = data.sort_order else: # Close current and create a new version effective today version.valid_till = date_ - timedelta(days=1) @@ -231,7 +230,6 @@ def update_route( name=data.name, fraction_units=data.fraction_units, sale_category_id=data.sale_category.id_, - sort_order=data.sort_order, valid_from=date_, valid_till=None, ) @@ -251,7 +249,7 @@ def update_route( contains_eager(SkuVersion.menu_category), ) .where(StockKeepingUnit.product_id == id_) - .order_by(SkuVersion.sort_order, SkuVersion.units) + .order_by(StockKeepingUnit.sort_order, SkuVersion.units) ) .scalars() .all() @@ -274,6 +272,8 @@ def update_route( or new_data_sku.menu_category.id_ != sku.menu_category_id ) print(f"SKU Changed: {sku_changed} for {sku.id}") + sku.sku.sort_order = new_data_sku.sort_order + sku.sku.is_not_available = new_data_sku.is_not_available if sku_changed: if sku.valid_from == date_: # Safe to mutate current version @@ -295,7 +295,6 @@ def update_route( cost_price=round(new_data_sku.cost_price, 2), sale_price=round(new_data_sku.sale_price, 2), has_happy_hour=new_data_sku.has_happy_hour, - sort_order=sku.sort_order, menu_category_id=new_data_sku.menu_category.id_, ) db.add(new_sku) @@ -431,9 +430,9 @@ def product_list(date_: date, db: Session) -> list[schemas.Product]: .order_by( MenuCategory.sort_order, MenuCategory.name, - ProductVersion.sort_order, + Product.sort_order, ProductVersion.name, - SkuVersion.sort_order, + StockKeepingUnit.sort_order, SkuVersion.units, ) .options( @@ -507,10 +506,16 @@ def show_term( if sc is not None: query = query.join(SkuVersion.menu_category).join(ProductVersion.sale_category).join(SaleCategory.tax) if mc is not None: - query = query.where(SkuVersion.menu_category_id == mc).order_by(ProductVersion.sort_order, ProductVersion.name) + query = query.where(SkuVersion.menu_category_id == mc).order_by( + Product.sort_order, ProductVersion.name, StockKeepingUnit.sort_order, SkuVersion.units + ) if sc is not None: query = query.where(ProductVersion.sale_category_id == sc).order_by( - MenuCategory.sort_order, ProductVersion.sort_order, ProductVersion.name + MenuCategory.sort_order, + Product.sort_order, + ProductVersion.name, + StockKeepingUnit.sort_order, + SkuVersion.units, ) if mc is not None: @@ -605,7 +610,7 @@ def show_id( .join(SkuVersion.menu_category) .where(pv_active) .order_by( - SkuVersion.sort_order, + StockKeepingUnit.sort_order, SkuVersion.units, ) .options( @@ -639,7 +644,7 @@ def query_product_info(item: SkuVersion, happy_hour: bool) -> ProductQuery: price=item.sale_price, has_happy_hour=happy_hour, is_not_available=item.sku.is_not_available, - sort_order=item.sort_order, + sort_order=item.sku.sort_order, ) @@ -666,12 +671,12 @@ def product_info(version: ProductVersion) -> schemas.Product: menu_category=MenuCategoryLink( id_=sku_version.menu_category_id, name=sku_version.menu_category.name, products=[] ), - sort_order=sku_version.sort_order, + sort_order=sku_version.sku.sort_order, ) for sku in version.product.skus for sku_version in sku.versions ], - sort_order=version.sort_order, + sort_order=version.product.sort_order, ) @@ -683,23 +688,3 @@ def product_blank() -> schemas.ProductBlank: sort_order=0, enabled=True, ) - - -# def product_info(item: ProductVersion) -> schemas.Product: -# return schemas.Product( -# id_=item.product_id, -# units=item.units, - -# sale_category=schemas.SaleCategoryLink( -# id_=item.sale_category_id, -# name=item.sale_category.name, -# ), -# price=item.price, -# has_happy_hour=item.has_happy_hour, -# is_not_available=item.is_not_available, -# quantity=item.quantity, -# is_active=True, -# sort_order=item.sort_order, -# valid_from=item.valid_from, -# valid_till=item.valid_till, -# )