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>
170 lines
5.2 KiB
YAML
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 <"
|