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>
194 lines
5.6 KiB
JavaScript
194 lines
5.6 KiB
JavaScript
// static/shared/js/money.js
|
|
/**
|
|
* Money handling utilities using integer cents.
|
|
*
|
|
* All monetary values are stored as integers representing cents in the database.
|
|
* The API returns euros (converted from cents on the backend), but these utilities
|
|
* can be used if cents are passed to the frontend.
|
|
*
|
|
* Example:
|
|
* 105.91 EUR is stored as 10591 (integer cents)
|
|
*
|
|
* Usage:
|
|
* Money.format(10591) // Returns "105.91"
|
|
* Money.format(10591, 'EUR') // Returns "105,91 EUR"
|
|
* Money.toCents(105.91) // Returns 10591
|
|
* Money.toEuros(10591) // Returns 105.91
|
|
* Money.formatEuros(105.91, 'EUR') // Returns "105,91 EUR"
|
|
*
|
|
* See docs/architecture/money-handling.md for full documentation.
|
|
*/
|
|
|
|
const Money = {
|
|
/**
|
|
* Format cents as a currency string.
|
|
*
|
|
* @param {number} cents - Amount in cents
|
|
* @param {string} currency - Currency code (default: '', no currency shown)
|
|
* @param {string} locale - Locale for formatting (default: 'de-DE')
|
|
* @returns {string} Formatted price string
|
|
*
|
|
* @example
|
|
* Money.format(10591) // "105.91"
|
|
* Money.format(10591, 'EUR') // "105,91 EUR" (German locale)
|
|
* Money.format(1999) // "19.99"
|
|
*/
|
|
format(cents, currency = '', locale = 'de-DE') {
|
|
if (cents === null || cents === undefined) {
|
|
cents = 0;
|
|
}
|
|
|
|
const euros = cents / 100;
|
|
|
|
if (currency) {
|
|
return new Intl.NumberFormat(locale, {
|
|
style: 'currency',
|
|
currency: currency
|
|
}).format(euros);
|
|
}
|
|
|
|
return new Intl.NumberFormat(locale, {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
}).format(euros);
|
|
},
|
|
|
|
/**
|
|
* Convert euros to cents.
|
|
*
|
|
* @param {number|string} euros - Amount in euros
|
|
* @returns {number} Amount in cents (integer)
|
|
*
|
|
* @example
|
|
* Money.toCents(105.91) // 10591
|
|
* Money.toCents('19.99') // 1999
|
|
* Money.toCents(null) // 0
|
|
*/
|
|
toCents(euros) {
|
|
if (euros === null || euros === undefined || euros === '') {
|
|
return 0;
|
|
}
|
|
return Math.round(parseFloat(euros) * 100);
|
|
},
|
|
|
|
/**
|
|
* Convert cents to euros.
|
|
*
|
|
* @param {number} cents - Amount in cents
|
|
* @returns {number} Amount in euros
|
|
*
|
|
* @example
|
|
* Money.toEuros(10591) // 105.91
|
|
* Money.toEuros(1999) // 19.99
|
|
* Money.toEuros(null) // 0
|
|
*/
|
|
toEuros(cents) {
|
|
if (cents === null || cents === undefined) {
|
|
return 0;
|
|
}
|
|
return cents / 100;
|
|
},
|
|
|
|
/**
|
|
* Format a euro amount for display.
|
|
*
|
|
* Use this when the value is already in euros (e.g., from API response).
|
|
*
|
|
* @param {number} euros - Amount in euros
|
|
* @param {string} currency - Currency code (default: 'EUR')
|
|
* @param {string} locale - Locale for formatting (default: 'de-DE')
|
|
* @returns {string} Formatted price string
|
|
*
|
|
* @example
|
|
* Money.formatEuros(105.91, 'EUR') // "105,91 EUR"
|
|
* Money.formatEuros(19.99) // "19.99"
|
|
*/
|
|
formatEuros(euros, currency = '', locale = 'de-DE') {
|
|
if (euros === null || euros === undefined) {
|
|
euros = 0;
|
|
}
|
|
|
|
if (currency) {
|
|
return new Intl.NumberFormat(locale, {
|
|
style: 'currency',
|
|
currency: currency
|
|
}).format(euros);
|
|
}
|
|
|
|
return new Intl.NumberFormat(locale, {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
}).format(euros);
|
|
},
|
|
|
|
/**
|
|
* Parse a price string to cents.
|
|
*
|
|
* Handles various formats:
|
|
* - "19.99 EUR"
|
|
* - "19,99"
|
|
* - 19.99
|
|
*
|
|
* @param {string|number} priceStr - Price string or number
|
|
* @returns {number} Amount in cents
|
|
*
|
|
* @example
|
|
* Money.parse("19.99 EUR") // 1999
|
|
* Money.parse("19,99") // 1999
|
|
* Money.parse(19.99) // 1999
|
|
*/
|
|
parse(priceStr) {
|
|
if (priceStr === null || priceStr === undefined || priceStr === '') {
|
|
return 0;
|
|
}
|
|
|
|
if (typeof priceStr === 'number') {
|
|
return Math.round(priceStr * 100);
|
|
}
|
|
|
|
// Remove currency symbols and spaces
|
|
let cleaned = priceStr.toString().replace(/[^\d,.-]/g, '');
|
|
|
|
// Handle European decimal comma
|
|
cleaned = cleaned.replace(',', '.');
|
|
|
|
try {
|
|
return Math.round(parseFloat(cleaned) * 100);
|
|
} catch {
|
|
return 0;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Calculate line total (unit price * quantity).
|
|
*
|
|
* @param {number} unitPriceCents - Price per unit in cents
|
|
* @param {number} quantity - Number of units
|
|
* @returns {number} Total in cents
|
|
*/
|
|
calculateLineTotal(unitPriceCents, quantity) {
|
|
return unitPriceCents * quantity;
|
|
},
|
|
|
|
/**
|
|
* Calculate order total.
|
|
*
|
|
* @param {number} subtotalCents - Sum of line items in cents
|
|
* @param {number} taxCents - Tax amount in cents (default: 0)
|
|
* @param {number} shippingCents - Shipping cost in cents (default: 0)
|
|
* @param {number} discountCents - Discount amount in cents (default: 0)
|
|
* @returns {number} Total in cents
|
|
*/
|
|
calculateOrderTotal(subtotalCents, taxCents = 0, shippingCents = 0, discountCents = 0) {
|
|
return subtotalCents + taxCents + shippingCents - discountCents;
|
|
}
|
|
};
|
|
|
|
// Make available globally
|
|
window.Money = Money;
|
|
|
|
// Export for modules
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = Money;
|
|
}
|