Added the funtionality to update product name and units when importing sales.

This commit is contained in:
2026-09-12 14:46:09 +00:00
parent b425b299bf
commit 7be26ad9da
2 changed files with 98 additions and 17 deletions
@@ -16,7 +16,7 @@ from alembic import op
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision = "51013f52f0fb" revision = "51013f52f0fb"
down_revision = "9b4f7c2e81a3" down_revision = "c7e1a94b2d30"
branch_labels = None branch_labels = None
depends_on = None depends_on = None
@@ -3,7 +3,7 @@
The mapping, once made, is standing truth: a Barker SKU whose mapping row exists is served The mapping, once made, is standing truth: a Barker SKU whose mapping row exists is served
from it verbatim, forever. The slugified product name is only the resolver for a Barker from it verbatim, forever. The slugified product name is only the resolver for a Barker
product never mapped before. Barker renames and units changes after first mapping are product never mapped before. Barker renames and units changes after first mapping are
ignored — brewman master data is edited by hand when that matters. captured by creating a new product version or SKU version.
Resolution for one Barker line on business date ``date_``: Resolution for one Barker line on business date ``date_``:
@@ -27,7 +27,7 @@ import re
import uuid import uuid
from dataclasses import dataclass from dataclasses import dataclass
from datetime import date from datetime import date, timedelta
from decimal import Decimal from decimal import Decimal
from typing import Any, Literal from typing import Any, Literal
@@ -87,6 +87,8 @@ def resolve_product(db: Session, line: BarkerSaleLine, date_: date) -> Provision
""" """
binding = db.execute(select(BarkerProduct).where(BarkerProduct.barker_sku_id == line.sku_id)).scalar_one_or_none() binding = db.execute(select(BarkerProduct).where(BarkerProduct.barker_sku_id == line.sku_id)).scalar_one_or_none()
if binding is not None: if binding is not None:
_update_name_if_needed(db, binding.brewman_product_id, line.name, date_)
_update_units_if_needed(db, binding.brewman_sku_id, line.units, date_)
return ProvisionedProduct( return ProvisionedProduct(
product_id=binding.brewman_product_id, sku_id=binding.brewman_sku_id, disposition="mapped" product_id=binding.brewman_product_id, sku_id=binding.brewman_sku_id, disposition="mapped"
) )
@@ -100,7 +102,9 @@ def resolve_product(db: Session, line: BarkerSaleLine, date_: date) -> Provision
.first() .first()
) )
if known_product_id is not None: if known_product_id is not None:
_update_name_if_needed(db, known_product_id, line.name, date_)
sku_id = _find_or_create_sku(db, known_product_id, line, date_) sku_id = _find_or_create_sku(db, known_product_id, line, date_)
_update_units_if_needed(db, sku_id, line.units, date_)
_write_mapping(db, line, known_product_id, sku_id) _write_mapping(db, line, known_product_id, sku_id)
return ProvisionedProduct(product_id=known_product_id, sku_id=sku_id, disposition="linked") return ProvisionedProduct(product_id=known_product_id, sku_id=sku_id, disposition="linked")
@@ -109,7 +113,10 @@ def resolve_product(db: Session, line: BarkerSaleLine, date_: date) -> Provision
if product_id is None: if product_id is None:
product_id = _create_product(db, line, date_) product_id = _create_product(db, line, date_)
disposition = "created" disposition = "created"
else:
_update_name_if_needed(db, product_id, line.name, date_)
sku_id = _find_or_create_sku(db, product_id, line, date_) sku_id = _find_or_create_sku(db, product_id, line, date_)
_update_units_if_needed(db, sku_id, line.units, date_)
_write_mapping(db, line, product_id, sku_id) _write_mapping(db, line, product_id, sku_id)
return ProvisionedProduct(product_id=product_id, sku_id=sku_id, disposition=disposition) return ProvisionedProduct(product_id=product_id, sku_id=sku_id, disposition=disposition)
@@ -178,8 +185,7 @@ def _find_product_id_by_name(db: Session, name: str, date_: date) -> uuid.UUID |
""" """
wanted = _normalize(name) wanted = _normalize(name)
normalized_name = _pg_normalize(ProductVersion.name) normalized_name = _pg_normalize(ProductVersion.name)
rows = ( rows = db.execute(
db.execute(
select(ProductVersion.product_id, ProductVersion.name).where( select(ProductVersion.product_id, ProductVersion.name).where(
normalized_name == wanted, normalized_name == wanted,
(ProductVersion.valid_from == None) # noqa: E711 (ProductVersion.valid_from == None) # noqa: E711
@@ -187,15 +193,12 @@ def _find_product_id_by_name(db: Session, name: str, date_: date) -> uuid.UUID |
(ProductVersion.valid_till == None) # noqa: E711 (ProductVersion.valid_till == None) # noqa: E711
| (ProductVersion.valid_till >= date_), | (ProductVersion.valid_till >= date_),
) )
) ).all()
.scalars() if len(rows) > 1:
.all()
)
if rows.count > 1:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, status_code=status.HTTP_409_CONFLICT,
detail=f"Cannot import: '{name}' matches multiple products: " detail=f"Cannot import: '{name}' matches multiple products: "
+ ", ".join(f"'{name}'" for (id_, name) in rows), + ", ".join(f"'{p_name}'" for (id_, p_name) in rows),
) )
return rows[0][0] if rows else None return rows[0][0] if rows else None
@@ -323,3 +326,81 @@ def _write_mapping(db: Session, line: BarkerSaleLine, product_id: uuid.UUID, sku
row.brewman_product_id = product_id row.brewman_product_id = product_id
row.brewman_sku_id = sku_id row.brewman_sku_id = sku_id
db.flush() # autoflush is off — later lookups must see this row db.flush() # autoflush is off — later lookups must see this row
def _update_name_if_needed(db: Session, product_id: uuid.UUID, new_name: str, date_: date) -> None:
wanted = _normalize(new_name)
active_pv = db.execute(
select(ProductVersion).where(
ProductVersion.product_id == product_id,
(ProductVersion.valid_from == None) | (ProductVersion.valid_from <= date_), # noqa: E711
(ProductVersion.valid_till == None) | (ProductVersion.valid_till >= date_), # noqa: E711
)
).scalar_one_or_none()
if active_pv is not None and _normalize(active_pv.name) != wanted:
if active_pv.valid_from == date_:
active_pv.name = new_name
active_pv.handle = ProductVersion.slugify(new_name)
db.flush()
else:
active_pv.valid_till = date_ - timedelta(days=1)
new_pv = ProductVersion(
product_id=active_pv.product_id,
name=new_name,
description=active_pv.description,
handle=ProductVersion.slugify(new_name),
fraction_units=active_pv.fraction_units,
product_group_id=active_pv.product_group_id,
account_id=active_pv.account_id,
is_purchased=active_pv.is_purchased,
is_sold=active_pv.is_sold,
allergen=active_pv.allergen,
protein=active_pv.protein,
carbohydrate=active_pv.carbohydrate,
total_sugar=active_pv.total_sugar,
added_sugar=active_pv.added_sugar,
total_fat=active_pv.total_fat,
saturated_fat=active_pv.saturated_fat,
trans_fat=active_pv.trans_fat,
cholestrol=active_pv.cholestrol,
sodium=active_pv.sodium,
msnf=active_pv.msnf,
other_solids=active_pv.other_solids,
total_solids=active_pv.total_solids,
water=active_pv.water,
valid_from=date_,
valid_till=None,
)
db.add(new_pv)
db.flush()
def _update_units_if_needed(db: Session, sku_id: uuid.UUID, new_units: str, date_: date) -> None:
wanted = _normalize(new_units)
active_sv = db.execute(
select(SkuVersion).where(
SkuVersion.sku_id == sku_id,
(SkuVersion.valid_from == None) | (SkuVersion.valid_from <= date_), # noqa: E711
(SkuVersion.valid_till == None) | (SkuVersion.valid_till >= date_), # noqa: E711
)
).scalar_one_or_none()
if active_sv is not None and _normalize(active_sv.units) != wanted:
if active_sv.valid_from == date_:
active_sv.units = new_units
db.flush()
else:
active_sv.valid_till = date_ - timedelta(days=1)
new_sv = SkuVersion(
sku_id=active_sv.sku_id,
units=new_units,
fraction=active_sv.fraction,
product_yield=active_sv.product_yield,
cost_price=active_sv.cost_price,
sale_price=active_sv.sale_price,
valid_from=date_,
valid_till=None,
)
db.add(new_sv)
db.flush()