Files
orion/.architecture-rules/money.yaml
Samir Boulahtit a19c84ea4e 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>
2025-12-20 20:33:48 +01:00

170 lines
5.2 KiB
YAML

# Architecture Rules - Money Handling
# Rules for monetary value handling across the codebase
#
# Reference: docs/architecture/money-handling.md
money_handling_rules:
- id: "MON-001"
name: "Database columns for money must use Integer cents"
severity: "error"
description: |
All monetary values in database models MUST be stored as integers representing
cents (or smallest currency unit). This prevents floating-point precision errors.
CORRECT:
price_cents = Column(Integer)
total_amount_cents = Column(Integer)
sale_price_cents = Column(Integer, nullable=True)
INCORRECT:
price = Column(Float)
total_amount = Column(Numeric(10, 2))
Column naming convention: Use `_cents` suffix for all monetary columns.
pattern:
file_pattern: "models/database/**/*.py"
required_patterns:
- "_cents = Column(Integer"
anti_patterns:
- "price = Column(Float"
- "amount = Column(Float"
- "total = Column(Float"
- "Column(Numeric"
- id: "MON-002"
name: "Use Money utility for conversions"
severity: "error"
description: |
All euro/cents conversions MUST use the Money utility class or
the euros_to_cents/cents_to_euros helper functions.
Never use manual multiplication/division for money conversions.
CORRECT:
from app.utils.money import Money, euros_to_cents, cents_to_euros
price_cents = euros_to_cents(19.99)
display_price = cents_to_euros(price_cents)
formatted = Money.format(price_cents, "EUR")
INCORRECT:
price_cents = int(price * 100)
display_price = price_cents / 100
pattern:
file_pattern:
- "app/**/*.py"
- "models/**/*.py"
required_imports:
- "from app.utils.money import"
anti_patterns:
- "* 100" # Manual cents conversion
- "/ 100" # Manual euros conversion
exceptions:
- "app/utils/money.py" # The utility itself
- id: "MON-003"
name: "API responses use euros for display"
severity: "error"
description: |
Pydantic response schemas MUST expose monetary values as floats (euros)
for frontend consumption. Use computed properties to convert from _cents.
CORRECT (in schema):
price_cents: int # Internal
@computed_field
@property
def price(self) -> float:
return cents_to_euros(self.price_cents)
Or use model validators to convert before response serialization.
pattern:
file_pattern: "models/schema/**/*.py"
check: "money_response_format"
- id: "MON-004"
name: "Frontend must use Money.format() for display"
severity: "error"
description: |
All monetary value display in frontend MUST use the Money utility.
This ensures consistent formatting across locales.
CORRECT (JavaScript):
Money.format(order.total_amount_cents, 'EUR')
Money.formatEuros(order.total_amount)
INCORRECT:
`${order.total_amount} EUR`
order.total_amount.toFixed(2)
pattern:
file_pattern:
- "static/**/*.js"
- "app/templates/**/*.html"
required_patterns:
- "Money.format"
- "formatPrice" # Helper function wrapping Money.format
discouraged_patterns:
- "toFixed(2)"
- "${.*} EUR"
- id: "MON-005"
name: "All arithmetic on money uses integers"
severity: "error"
description: |
All monetary calculations MUST be performed on integer cents values.
Convert to euros ONLY for display purposes, never for calculations.
CORRECT:
subtotal_cents = sum(item.unit_price_cents * item.quantity for item in items)
tax_cents = int(subtotal_cents * Decimal("0.17"))
total_cents = subtotal_cents + tax_cents
INCORRECT:
subtotal = sum(item.price * item.quantity for item in items)
tax = subtotal * 0.17 # Floating point!
total = subtotal + tax
pattern:
file_pattern: "app/services/**/*.py"
check: "money_arithmetic"
- id: "MON-006"
name: "CSV/External imports must convert to cents"
severity: "error"
description: |
When importing monetary values from CSV files or external APIs,
always convert to cents immediately upon parsing.
CORRECT:
price_cents = euros_to_cents(parsed_price)
product.price_cents = price_cents
INCORRECT:
product.price = parsed_price # Float assignment
pattern:
file_pattern:
- "app/utils/csv_processor.py"
- "app/services/**/import*.py"
required_patterns:
- "euros_to_cents"
- "_cents"
- id: "MON-007"
name: "Test assertions use cents"
severity: "warning"
description: |
Test assertions for monetary values should use integer cents
to avoid floating-point comparison issues.
CORRECT:
assert order.total_amount_cents == 10591
assert product.price_cents == 1999
INCORRECT:
assert order.total_amount == 105.91 # Float comparison
pattern:
file_pattern: "tests/**/*.py"
encouraged_patterns:
- "_cents =="
- "_cents >"
- "_cents <"