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:
2025-12-20 20:33:48 +01:00
parent 7f0d32c18d
commit a19c84ea4e
56 changed files with 6155 additions and 447 deletions

View File

@@ -238,50 +238,105 @@ class TestVendorLetzshopOrdersAPI:
self, client, db, vendor_user_headers, test_vendor_with_vendor_user
):
"""Test listing orders with status filter."""
from models.database.letzshop import LetzshopOrder
from models.database.order import Order
# Create test orders
order1 = LetzshopOrder(
# Create test orders using unified Order model with all required fields
order1 = Order(
vendor_id=test_vendor_with_vendor_user.id,
letzshop_order_id="order_1",
letzshop_state="unconfirmed",
sync_status="pending",
customer_id=1,
order_number=f"LS-{test_vendor_with_vendor_user.id}-order_1",
channel="letzshop",
external_order_id="order_1",
status="pending",
customer_first_name="Test",
customer_last_name="User",
customer_email="test1@example.com",
ship_first_name="Test",
ship_last_name="User",
ship_address_line_1="123 Test Street",
ship_city="Luxembourg",
ship_postal_code="1234",
ship_country_iso="LU",
bill_first_name="Test",
bill_last_name="User",
bill_address_line_1="123 Test Street",
bill_city="Luxembourg",
bill_postal_code="1234",
bill_country_iso="LU",
total_amount_cents=10000,
currency="EUR",
)
order2 = LetzshopOrder(
order2 = Order(
vendor_id=test_vendor_with_vendor_user.id,
letzshop_order_id="order_2",
letzshop_state="confirmed",
sync_status="confirmed",
customer_id=1,
order_number=f"LS-{test_vendor_with_vendor_user.id}-order_2",
channel="letzshop",
external_order_id="order_2",
status="processing",
customer_first_name="Test",
customer_last_name="User",
customer_email="test2@example.com",
ship_first_name="Test",
ship_last_name="User",
ship_address_line_1="456 Test Avenue",
ship_city="Luxembourg",
ship_postal_code="5678",
ship_country_iso="LU",
bill_first_name="Test",
bill_last_name="User",
bill_address_line_1="456 Test Avenue",
bill_city="Luxembourg",
bill_postal_code="5678",
bill_country_iso="LU",
total_amount_cents=20000,
currency="EUR",
)
db.add_all([order1, order2])
db.commit()
# List pending only
response = client.get(
"/api/v1/vendor/letzshop/orders?sync_status=pending",
"/api/v1/vendor/letzshop/orders?status=pending",
headers=vendor_user_headers,
)
assert response.status_code == 200
data = response.json()
assert data["total"] == 1
assert data["orders"][0]["sync_status"] == "pending"
assert data["orders"][0]["status"] == "pending"
def test_get_order_detail(
self, client, db, vendor_user_headers, test_vendor_with_vendor_user
):
"""Test getting order detail."""
from models.database.letzshop import LetzshopOrder
from models.database.order import Order
order = LetzshopOrder(
order = Order(
vendor_id=test_vendor_with_vendor_user.id,
letzshop_order_id="order_detail_test",
letzshop_shipment_id="shipment_1",
letzshop_state="unconfirmed",
customer_id=1,
order_number=f"LS-{test_vendor_with_vendor_user.id}-order_detail_test",
channel="letzshop",
external_order_id="order_detail_test",
external_shipment_id="shipment_1",
status="pending",
customer_first_name="Test",
customer_last_name="User",
customer_email="test@example.com",
total_amount="99.99",
sync_status="pending",
raw_order_data={"test": "data"},
ship_first_name="Test",
ship_last_name="User",
ship_address_line_1="123 Test Street",
ship_city="Luxembourg",
ship_postal_code="1234",
ship_country_iso="LU",
bill_first_name="Test",
bill_last_name="User",
bill_address_line_1="123 Test Street",
bill_city="Luxembourg",
bill_postal_code="1234",
bill_country_iso="LU",
total_amount_cents=9999, # €99.99
currency="EUR",
external_data={"test": "data"},
)
db.add(order)
db.commit()
@@ -293,9 +348,9 @@ class TestVendorLetzshopOrdersAPI:
assert response.status_code == 200
data = response.json()
assert data["letzshop_order_id"] == "order_detail_test"
assert data["external_order_id"] == "order_detail_test"
assert data["customer_email"] == "test@example.com"
assert data["raw_order_data"] == {"test": "data"}
assert data["external_data"] == {"test": "data"}
def test_get_order_not_found(
self, client, vendor_user_headers, test_vendor_with_vendor_user
@@ -393,18 +448,50 @@ class TestVendorLetzshopFulfillmentAPI:
test_vendor_with_vendor_user,
):
"""Test confirming an order."""
from models.database.letzshop import LetzshopOrder
from models.database.order import Order, OrderItem
# Create test order
order = LetzshopOrder(
# Create test order using unified Order model with all required fields
order = Order(
vendor_id=test_vendor_with_vendor_user.id,
letzshop_order_id="order_confirm",
letzshop_shipment_id="shipment_1",
letzshop_state="unconfirmed",
sync_status="pending",
inventory_units=[{"id": "unit_1", "state": "unconfirmed"}],
customer_id=1,
order_number=f"LS-{test_vendor_with_vendor_user.id}-order_confirm",
channel="letzshop",
external_order_id="order_confirm",
external_shipment_id="shipment_1",
status="pending",
customer_first_name="Test",
customer_last_name="User",
customer_email="test@example.com",
ship_first_name="Test",
ship_last_name="User",
ship_address_line_1="123 Test Street",
ship_city="Luxembourg",
ship_postal_code="1234",
ship_country_iso="LU",
bill_first_name="Test",
bill_last_name="User",
bill_address_line_1="123 Test Street",
bill_city="Luxembourg",
bill_postal_code="1234",
bill_country_iso="LU",
total_amount_cents=10000,
currency="EUR",
)
db.add(order)
db.flush()
# Add order item
item = OrderItem(
order_id=order.id,
product_id=1,
product_name="Test Product",
quantity=1,
unit_price_cents=10000,
total_price_cents=10000,
external_item_id="unit_1",
item_state="unconfirmed",
)
db.add(item)
db.commit()
# Save credentials
@@ -447,14 +534,33 @@ class TestVendorLetzshopFulfillmentAPI:
test_vendor_with_vendor_user,
):
"""Test setting tracking information."""
from models.database.letzshop import LetzshopOrder
from models.database.order import Order
order = LetzshopOrder(
order = Order(
vendor_id=test_vendor_with_vendor_user.id,
letzshop_order_id="order_tracking",
letzshop_shipment_id="shipment_track",
letzshop_state="confirmed",
sync_status="confirmed",
customer_id=1,
order_number=f"LS-{test_vendor_with_vendor_user.id}-order_tracking",
channel="letzshop",
external_order_id="order_tracking",
external_shipment_id="shipment_track",
status="processing", # confirmed state
customer_first_name="Test",
customer_last_name="User",
customer_email="test@example.com",
ship_first_name="Test",
ship_last_name="User",
ship_address_line_1="123 Test Street",
ship_city="Luxembourg",
ship_postal_code="1234",
ship_country_iso="LU",
bill_first_name="Test",
bill_last_name="User",
bill_address_line_1="123 Test Street",
bill_city="Luxembourg",
bill_postal_code="1234",
bill_country_iso="LU",
total_amount_cents=10000,
currency="EUR",
)
db.add(order)
db.commit()