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

@@ -6,6 +6,9 @@ This module provides:
- Session-based cart management
- Cart item operations (add, update, remove)
- Cart total calculations
All monetary calculations use integer cents internally for precision.
See docs/architecture/money-handling.md for details.
"""
import logging
@@ -19,6 +22,7 @@ from app.exceptions import (
InvalidCartQuantityException,
ProductNotFoundException,
)
from app.utils.money import cents_to_euros
from models.database.cart import CartItem
from models.database.product import Product
@@ -62,21 +66,23 @@ class CartService:
extra={"item_count": len(cart_items)},
)
# Build response
# Build response - calculate totals in cents, return euros
items = []
subtotal = 0.0
subtotal_cents = 0
for cart_item in cart_items:
product = cart_item.product
line_total = cart_item.line_total
line_total_cents = cart_item.line_total_cents
items.append(
{
"product_id": product.id,
"product_name": product.marketplace_product.title,
"product_name": product.marketplace_product.get_title("en")
if product.marketplace_product
else str(product.id),
"quantity": cart_item.quantity,
"price": cart_item.price_at_add,
"line_total": line_total,
"price": cart_item.price_at_add, # Returns euros via property
"line_total": cents_to_euros(line_total_cents),
"image_url": (
product.marketplace_product.image_link
if product.marketplace_product
@@ -85,8 +91,10 @@ class CartService:
}
)
subtotal += line_total
subtotal_cents += line_total_cents
# Convert to euros for API response
subtotal = cents_to_euros(subtotal_cents)
cart_data = {
"vendor_id": vendor_id,
"session_id": session_id,
@@ -166,8 +174,12 @@ class CartService:
},
)
# Get current price (use sale_price if available, otherwise regular price)
current_price = product.sale_price if product.sale_price else product.price
# Get current price in cents (use sale_price if available, otherwise regular price)
current_price_cents = (
product.effective_sale_price_cents
or product.effective_price_cents
or 0
)
# Check if item already exists in cart
existing_item = (
@@ -236,13 +248,13 @@ class CartService:
available=product.available_inventory,
)
# Create new cart item
# Create new cart item (price stored in cents)
cart_item = CartItem(
vendor_id=vendor_id,
session_id=session_id,
product_id=product_id,
quantity=quantity,
price_at_add=current_price,
price_at_add_cents=current_price_cents,
)
db.add(cart_item)
db.flush()
@@ -253,7 +265,7 @@ class CartService:
extra={
"cart_item_id": cart_item.id,
"quantity": quantity,
"price": current_price,
"price_cents": current_price_cents,
},
)