Release v0.1.22
This commit is contained in:
@@ -101,6 +101,29 @@ def _coerce_bool(value: object) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def _coerce_import_bool(value: object, *, default: bool = False) -> bool:
|
||||
"""Conservative boolean parsing for ad-hoc imports.
|
||||
|
||||
Uploaded CSV/XLSX rows often leave destination columns blank, or use text
|
||||
like "stock" / "order" elsewhere in the row. Those should not silently
|
||||
become True. Only explicit truthy markers opt in.
|
||||
"""
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
if value is None:
|
||||
return default
|
||||
if isinstance(value, (int, float)):
|
||||
return bool(value)
|
||||
text = str(value).strip().lower()
|
||||
if not text:
|
||||
return default
|
||||
if text in {"yes", "y", "true", "1", "pass", "ok", "x", "checked"}:
|
||||
return True
|
||||
if text in {"no", "n", "false", "0", "fail"}:
|
||||
return False
|
||||
return default
|
||||
|
||||
|
||||
def _coerce_float(value: object) -> float | None:
|
||||
if value is None or value == "":
|
||||
return None
|
||||
@@ -631,8 +654,8 @@ def import_entries_from_file(
|
||||
by_item[item_id] = product
|
||||
by_name[product_name.lower()] = product
|
||||
|
||||
for_order = _coerce_bool(cell(row, "for_order")) if field_index.get("for_order") is not None else False
|
||||
for_stock = _coerce_bool(cell(row, "for_stock")) if field_index.get("for_stock") is not None else False
|
||||
for_order = _coerce_import_bool(cell(row, "for_order")) if field_index.get("for_order") is not None else False
|
||||
for_stock = _coerce_import_bool(cell(row, "for_stock")) if field_index.get("for_stock") is not None else False
|
||||
stock_quantity = _coerce_float(cell(row, "stock_quantity")) if for_stock else None
|
||||
|
||||
calculated = calculate_kg(quantity, quantity_type, bag_size)
|
||||
|
||||
@@ -13,6 +13,7 @@ from app.models.throughput import ProductionThroughput, ThroughputProduct
|
||||
from app.seed import seed_throughput_products_from_costing
|
||||
from app.services.throughput_service import (
|
||||
calculate_kg,
|
||||
import_entries_from_file,
|
||||
import_names_sheet,
|
||||
import_production_sheet,
|
||||
normalise_staff_name,
|
||||
@@ -240,3 +241,48 @@ def test_seed_throughput_products_from_costing_updates_existing_by_item_id():
|
||||
assert products[0].name == "Updated Wheat 25kg"
|
||||
assert products[0].default_bag_size == 25
|
||||
assert products[0].active is True
|
||||
|
||||
|
||||
def test_upload_import_keeps_blank_destination_flags_false():
|
||||
db = _session()
|
||||
csv_bytes = (
|
||||
"Date,Product,Quantity,Type,Bag Size,For Order,For Stock,Job Number\n"
|
||||
"2026-06-12,Specialty Pigeon Breeder,40,bags,20,,,\n"
|
||||
).encode("utf-8")
|
||||
|
||||
result = import_entries_from_file(
|
||||
db,
|
||||
filename="throughput-import.csv",
|
||||
content=csv_bytes,
|
||||
tenant_id="test-tenant",
|
||||
created_by="tester@example.com",
|
||||
)
|
||||
|
||||
assert result["entries_imported"] == 1
|
||||
entry = db.scalar(select(ProductionThroughput))
|
||||
assert entry is not None
|
||||
assert entry.for_order is False
|
||||
assert entry.for_stock is False
|
||||
assert entry.job_number is None
|
||||
|
||||
|
||||
def test_upload_import_does_not_treat_unknown_destination_text_as_true():
|
||||
db = _session()
|
||||
csv_bytes = (
|
||||
"Date,Product,Quantity,Type,Bag Size,For Order,For Stock\n"
|
||||
"2026-06-12,Specialty Pigeon Breeder,40,bags,20,stock,\n"
|
||||
).encode("utf-8")
|
||||
|
||||
result = import_entries_from_file(
|
||||
db,
|
||||
filename="throughput-import.csv",
|
||||
content=csv_bytes,
|
||||
tenant_id="test-tenant",
|
||||
created_by="tester@example.com",
|
||||
)
|
||||
|
||||
assert result["entries_imported"] == 1
|
||||
entry = db.scalar(select(ProductionThroughput))
|
||||
assert entry is not None
|
||||
assert entry.for_order is False
|
||||
assert entry.for_stock is False
|
||||
|
||||
Reference in New Issue
Block a user