feat: integer cents money handling, order page fixes, and vendor filter persistence
Money Handling Architecture: - Store all monetary values as integer cents (€105.91 = 10591) - Add app/utils/money.py with Money class and conversion helpers - Add static/shared/js/money.js for frontend formatting - Update all database models to use _cents columns (Product, Order, etc.) - Update CSV processor to convert prices to cents on import - Add Alembic migration for Float to Integer conversion - Create .architecture-rules/money.yaml with 7 validation rules - Add docs/architecture/money-handling.md documentation Order Details Page Fixes: - Fix customer name showing 'undefined undefined' - use flat field names - Fix vendor info empty - add vendor_name/vendor_code to OrderDetailResponse - Fix shipping address using wrong nested object structure - Enrich order detail API response with vendor info Vendor Filter Persistence Fixes: - Fix orders.js: restoreSavedVendor now sets selectedVendor and filters - Fix orders.js: init() only loads orders if no saved vendor to restore - Fix marketplace-letzshop.js: restoreSavedVendor calls selectVendor() - Fix marketplace-letzshop.js: clearVendorSelection clears TomSelect dropdown - Align vendor selector placeholder text between pages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,65 @@
|
||||
# tests/unit/models/database/test_order.py
|
||||
"""Unit tests for Order and OrderItem database models."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from models.database.order import Order, OrderItem
|
||||
|
||||
|
||||
def create_order_with_snapshots(
|
||||
db,
|
||||
vendor,
|
||||
customer,
|
||||
customer_address,
|
||||
order_number,
|
||||
status="pending",
|
||||
subtotal=99.99,
|
||||
total_amount=99.99,
|
||||
**kwargs
|
||||
):
|
||||
"""Helper to create Order with required address snapshots."""
|
||||
# Remove channel from kwargs if present (we set it explicitly)
|
||||
channel = kwargs.pop("channel", "direct")
|
||||
|
||||
order = Order(
|
||||
vendor_id=vendor.id,
|
||||
customer_id=customer.id,
|
||||
order_number=order_number,
|
||||
status=status,
|
||||
channel=channel,
|
||||
subtotal=subtotal,
|
||||
total_amount=total_amount,
|
||||
currency="EUR",
|
||||
order_date=datetime.now(timezone.utc),
|
||||
# Customer snapshot
|
||||
customer_first_name=customer.first_name,
|
||||
customer_last_name=customer.last_name,
|
||||
customer_email=customer.email,
|
||||
# Shipping address snapshot
|
||||
ship_first_name=customer_address.first_name,
|
||||
ship_last_name=customer_address.last_name,
|
||||
ship_address_line_1=customer_address.address_line_1,
|
||||
ship_city=customer_address.city,
|
||||
ship_postal_code=customer_address.postal_code,
|
||||
ship_country_iso="LU",
|
||||
# Billing address snapshot
|
||||
bill_first_name=customer_address.first_name,
|
||||
bill_last_name=customer_address.last_name,
|
||||
bill_address_line_1=customer_address.address_line_1,
|
||||
bill_city=customer_address.city,
|
||||
bill_postal_code=customer_address.postal_code,
|
||||
bill_country_iso="LU",
|
||||
**kwargs
|
||||
)
|
||||
db.add(order)
|
||||
db.commit()
|
||||
db.refresh(order)
|
||||
return order
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.database
|
||||
class TestOrderModel:
|
||||
@@ -16,60 +69,37 @@ class TestOrderModel:
|
||||
self, db, test_vendor, test_customer, test_customer_address
|
||||
):
|
||||
"""Test Order model with customer relationship."""
|
||||
order = Order(
|
||||
vendor_id=test_vendor.id,
|
||||
customer_id=test_customer.id,
|
||||
order = create_order_with_snapshots(
|
||||
db, test_vendor, test_customer, test_customer_address,
|
||||
order_number="ORD-001",
|
||||
status="pending",
|
||||
subtotal=99.99,
|
||||
total_amount=99.99,
|
||||
currency="EUR",
|
||||
shipping_address_id=test_customer_address.id,
|
||||
billing_address_id=test_customer_address.id,
|
||||
)
|
||||
|
||||
db.add(order)
|
||||
db.commit()
|
||||
db.refresh(order)
|
||||
|
||||
assert order.id is not None
|
||||
assert order.vendor_id == test_vendor.id
|
||||
assert order.customer_id == test_customer.id
|
||||
assert order.order_number == "ORD-001"
|
||||
assert order.status == "pending"
|
||||
assert float(order.total_amount) == 99.99
|
||||
# Verify snapshots
|
||||
assert order.customer_first_name == test_customer.first_name
|
||||
assert order.ship_city == test_customer_address.city
|
||||
assert order.ship_country_iso == "LU"
|
||||
|
||||
def test_order_number_uniqueness(
|
||||
self, db, test_vendor, test_customer, test_customer_address
|
||||
):
|
||||
"""Test order_number unique constraint."""
|
||||
order1 = Order(
|
||||
vendor_id=test_vendor.id,
|
||||
customer_id=test_customer.id,
|
||||
create_order_with_snapshots(
|
||||
db, test_vendor, test_customer, test_customer_address,
|
||||
order_number="UNIQUE-ORD-001",
|
||||
status="pending",
|
||||
subtotal=50.00,
|
||||
total_amount=50.00,
|
||||
shipping_address_id=test_customer_address.id,
|
||||
billing_address_id=test_customer_address.id,
|
||||
)
|
||||
db.add(order1)
|
||||
db.commit()
|
||||
|
||||
# Duplicate order number should fail
|
||||
with pytest.raises(IntegrityError):
|
||||
order2 = Order(
|
||||
vendor_id=test_vendor.id,
|
||||
customer_id=test_customer.id,
|
||||
create_order_with_snapshots(
|
||||
db, test_vendor, test_customer, test_customer_address,
|
||||
order_number="UNIQUE-ORD-001",
|
||||
status="pending",
|
||||
subtotal=75.00,
|
||||
total_amount=75.00,
|
||||
shipping_address_id=test_customer_address.id,
|
||||
billing_address_id=test_customer_address.id,
|
||||
)
|
||||
db.add(order2)
|
||||
db.commit()
|
||||
|
||||
def test_order_status_values(
|
||||
self, db, test_vendor, test_customer, test_customer_address
|
||||
@@ -77,49 +107,32 @@ class TestOrderModel:
|
||||
"""Test Order with different status values."""
|
||||
statuses = [
|
||||
"pending",
|
||||
"confirmed",
|
||||
"processing",
|
||||
"shipped",
|
||||
"delivered",
|
||||
"cancelled",
|
||||
"refunded",
|
||||
]
|
||||
|
||||
for i, status in enumerate(statuses):
|
||||
order = Order(
|
||||
vendor_id=test_vendor.id,
|
||||
customer_id=test_customer.id,
|
||||
order = create_order_with_snapshots(
|
||||
db, test_vendor, test_customer, test_customer_address,
|
||||
order_number=f"STATUS-ORD-{i:03d}",
|
||||
status=status,
|
||||
subtotal=50.00,
|
||||
total_amount=50.00,
|
||||
shipping_address_id=test_customer_address.id,
|
||||
billing_address_id=test_customer_address.id,
|
||||
)
|
||||
db.add(order)
|
||||
db.commit()
|
||||
db.refresh(order)
|
||||
|
||||
assert order.status == status
|
||||
|
||||
def test_order_amounts(self, db, test_vendor, test_customer, test_customer_address):
|
||||
"""Test Order amount fields."""
|
||||
order = Order(
|
||||
vendor_id=test_vendor.id,
|
||||
customer_id=test_customer.id,
|
||||
order = create_order_with_snapshots(
|
||||
db, test_vendor, test_customer, test_customer_address,
|
||||
order_number="AMOUNTS-ORD-001",
|
||||
status="pending",
|
||||
subtotal=100.00,
|
||||
tax_amount=20.00,
|
||||
shipping_amount=10.00,
|
||||
discount_amount=5.00,
|
||||
total_amount=125.00,
|
||||
currency="EUR",
|
||||
shipping_address_id=test_customer_address.id,
|
||||
billing_address_id=test_customer_address.id,
|
||||
)
|
||||
db.add(order)
|
||||
db.commit()
|
||||
db.refresh(order)
|
||||
|
||||
assert float(order.subtotal) == 100.00
|
||||
assert float(order.tax_amount) == 20.00
|
||||
@@ -131,25 +144,32 @@ class TestOrderModel:
|
||||
self, db, test_vendor, test_customer, test_customer_address
|
||||
):
|
||||
"""Test Order relationships."""
|
||||
order = Order(
|
||||
vendor_id=test_vendor.id,
|
||||
customer_id=test_customer.id,
|
||||
order = create_order_with_snapshots(
|
||||
db, test_vendor, test_customer, test_customer_address,
|
||||
order_number="REL-ORD-001",
|
||||
status="pending",
|
||||
subtotal=50.00,
|
||||
total_amount=50.00,
|
||||
shipping_address_id=test_customer_address.id,
|
||||
billing_address_id=test_customer_address.id,
|
||||
)
|
||||
db.add(order)
|
||||
db.commit()
|
||||
db.refresh(order)
|
||||
|
||||
assert order.vendor is not None
|
||||
assert order.customer is not None
|
||||
assert order.vendor.id == test_vendor.id
|
||||
assert order.customer.id == test_customer.id
|
||||
|
||||
def test_order_channel_field(
|
||||
self, db, test_vendor, test_customer, test_customer_address
|
||||
):
|
||||
"""Test Order channel field for marketplace support."""
|
||||
order = create_order_with_snapshots(
|
||||
db, test_vendor, test_customer, test_customer_address,
|
||||
order_number="CHANNEL-ORD-001",
|
||||
channel="letzshop",
|
||||
external_order_id="LS-12345",
|
||||
external_shipment_id="SHIP-67890",
|
||||
)
|
||||
|
||||
assert order.channel == "letzshop"
|
||||
assert order.external_order_id == "LS-12345"
|
||||
assert order.external_shipment_id == "SHIP-67890"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.database
|
||||
@@ -249,3 +269,29 @@ class TestOrderItemModel:
|
||||
assert item1.order_id == item2.order_id
|
||||
assert item1.id != item2.id
|
||||
assert item1.product_id == item2.product_id # Same product, different items
|
||||
|
||||
def test_order_item_needs_product_match(self, db, test_order, test_product):
|
||||
"""Test OrderItem needs_product_match flag for exceptions."""
|
||||
order_item = OrderItem(
|
||||
order_id=test_order.id,
|
||||
product_id=test_product.id,
|
||||
product_name="Unmatched Product",
|
||||
product_sku="UNMATCHED-001",
|
||||
quantity=1,
|
||||
unit_price=50.00,
|
||||
total_price=50.00,
|
||||
needs_product_match=True,
|
||||
)
|
||||
|
||||
db.add(order_item)
|
||||
db.commit()
|
||||
db.refresh(order_item)
|
||||
|
||||
assert order_item.needs_product_match is True
|
||||
|
||||
# Resolve the match
|
||||
order_item.needs_product_match = False
|
||||
db.commit()
|
||||
db.refresh(order_item)
|
||||
|
||||
assert order_item.needs_product_match is False
|
||||
|
||||
Reference in New Issue
Block a user