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:
@@ -78,73 +78,86 @@
|
||||
<!-- Error Message -->
|
||||
{{ error_state('Error', show_condition='error && !loading') }}
|
||||
|
||||
<!-- Vendor Required Warning -->
|
||||
<div x-show="!selectedVendor && !loading" class="mb-8 p-6 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg border border-yellow-200 dark:border-yellow-800">
|
||||
<!-- Cross-vendor info banner (shown when no vendor selected) -->
|
||||
<div x-show="!selectedVendor && !loading" class="mb-6 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
|
||||
<div class="flex items-center">
|
||||
<span x-html="$icon('exclamation', 'w-6 h-6 text-yellow-500 mr-3')"></span>
|
||||
<span x-html="$icon('information-circle', 'w-6 h-6 text-blue-500 mr-3')"></span>
|
||||
<div>
|
||||
<h3 class="font-medium text-yellow-800 dark:text-yellow-200">Select a Vendor</h3>
|
||||
<p class="text-sm text-yellow-700 dark:text-yellow-300">Please select a vendor from the dropdown above to manage their Letzshop integration.</p>
|
||||
<h3 class="font-medium text-blue-800 dark:text-blue-200">All Vendors View</h3>
|
||||
<p class="text-sm text-blue-700 dark:text-blue-300">Showing data across all vendors. Select a vendor above to manage products, import orders, or access settings.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content (shown when vendor selected) -->
|
||||
<div x-show="selectedVendor" x-transition x-cloak>
|
||||
<!-- Vendor Info Bar -->
|
||||
<div class="mb-6 p-4 bg-white dark:bg-gray-800 rounded-lg shadow-xs flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-full bg-purple-100 dark:bg-purple-900 flex items-center justify-center">
|
||||
<span class="text-lg font-semibold text-purple-600 dark:text-purple-300" x-text="selectedVendor?.name?.charAt(0).toUpperCase()"></span>
|
||||
<!-- Main Content -->
|
||||
<div x-show="!loading" x-transition x-cloak>
|
||||
<!-- Selected Vendor Filter (same pattern as orders page) -->
|
||||
<div x-show="selectedVendor" x-transition class="mb-6 p-3 bg-purple-50 dark:bg-purple-900/20 rounded-lg border border-purple-200 dark:border-purple-800">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded-full bg-purple-100 dark:bg-purple-900 flex items-center justify-center">
|
||||
<span class="text-sm font-semibold text-purple-600 dark:text-purple-300" x-text="selectedVendor?.name?.charAt(0).toUpperCase()"></span>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<div>
|
||||
<span class="font-medium text-purple-800 dark:text-purple-200" x-text="selectedVendor?.name"></span>
|
||||
<span class="ml-2 text-xs text-purple-600 dark:text-purple-400 font-mono" x-text="selectedVendor?.vendor_code"></span>
|
||||
</div>
|
||||
<!-- Status badges -->
|
||||
<span class="px-2 py-0.5 text-xs font-medium rounded-full"
|
||||
:class="letzshopStatus.is_configured ? 'bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300' : 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400'"
|
||||
x-text="letzshopStatus.is_configured ? 'Configured' : 'Not Configured'">
|
||||
</span>
|
||||
<span x-show="letzshopStatus.auto_sync_enabled" class="px-2 py-0.5 text-xs font-medium rounded-full bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300">
|
||||
Auto-sync
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-800 dark:text-gray-200" x-text="selectedVendor?.name"></h3>
|
||||
<p class="text-sm text-gray-500" x-text="selectedVendor?.vendor_code"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Status Badge -->
|
||||
<span class="px-3 py-1 text-sm font-medium rounded-full"
|
||||
:class="letzshopStatus.is_configured ? 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300' : 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300'"
|
||||
x-text="letzshopStatus.is_configured ? 'Configured' : 'Not Configured'">
|
||||
</span>
|
||||
<!-- Auto-sync indicator -->
|
||||
<span x-show="letzshopStatus.auto_sync_enabled" class="px-3 py-1 text-sm font-medium rounded-full bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300">
|
||||
Auto-sync
|
||||
</span>
|
||||
<button @click="clearVendorSelection()" class="text-purple-600 dark:text-purple-400 hover:text-purple-800 dark:hover:text-purple-200 text-sm flex items-center gap-1">
|
||||
<span x-html="$icon('x', 'w-4 h-4')"></span>
|
||||
Clear filter
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
{% call tabs_nav(tab_var='activeTab') %}
|
||||
{{ tab_button('products', 'Products', tab_var='activeTab', icon='cube') }}
|
||||
<template x-if="selectedVendor">
|
||||
<span>{{ tab_button('products', 'Products', tab_var='activeTab', icon='cube') }}</span>
|
||||
</template>
|
||||
{{ tab_button('orders', 'Orders', tab_var='activeTab', icon='shopping-cart', count_var='orderStats.pending') }}
|
||||
{{ tab_button('exceptions', 'Exceptions', tab_var='activeTab', icon='exclamation-circle', count_var='exceptionStats.pending') }}
|
||||
{{ tab_button('settings', 'Settings', tab_var='activeTab', icon='cog') }}
|
||||
<template x-if="selectedVendor">
|
||||
<span>{{ tab_button('settings', 'Settings', tab_var='activeTab', icon='cog') }}</span>
|
||||
</template>
|
||||
{% endcall %}
|
||||
|
||||
<!-- Products Tab (Import + Export) -->
|
||||
{{ tab_panel('products', tab_var='activeTab') }}
|
||||
{% include 'admin/partials/letzshop-products-tab.html' %}
|
||||
{{ endtab_panel() }}
|
||||
<!-- Products Tab (Import + Export) - Vendor only -->
|
||||
<template x-if="selectedVendor">
|
||||
{{ tab_panel('products', tab_var='activeTab') }}
|
||||
{% include 'admin/partials/letzshop-products-tab.html' %}
|
||||
{{ endtab_panel() }}
|
||||
</template>
|
||||
|
||||
<!-- Orders Tab -->
|
||||
{{ tab_panel('orders', tab_var='activeTab') }}
|
||||
{% include 'admin/partials/letzshop-orders-tab.html' %}
|
||||
{{ endtab_panel() }}
|
||||
|
||||
<!-- Settings Tab -->
|
||||
{{ tab_panel('settings', tab_var='activeTab') }}
|
||||
{% include 'admin/partials/letzshop-settings-tab.html' %}
|
||||
{{ endtab_panel() }}
|
||||
<!-- Settings Tab - Vendor only -->
|
||||
<template x-if="selectedVendor">
|
||||
{{ tab_panel('settings', tab_var='activeTab') }}
|
||||
{% include 'admin/partials/letzshop-settings-tab.html' %}
|
||||
{{ endtab_panel() }}
|
||||
</template>
|
||||
|
||||
<!-- Exceptions Tab -->
|
||||
{{ tab_panel('exceptions', tab_var='activeTab') }}
|
||||
{% include 'admin/partials/letzshop-exceptions-tab.html' %}
|
||||
{{ endtab_panel() }}
|
||||
|
||||
<!-- Unified Jobs Table (below all tabs) -->
|
||||
<div class="mt-8">
|
||||
<!-- Unified Jobs Table (below all tabs) - Vendor only -->
|
||||
<div x-show="selectedVendor" class="mt-8">
|
||||
{% include 'admin/partials/letzshop-jobs-table.html' %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -361,7 +374,7 @@
|
||||
(<span x-text="selectedOrder?.items?.length"></span> item<span x-show="selectedOrder?.items?.length > 1">s</span>)
|
||||
</span>
|
||||
</h4>
|
||||
<div class="space-y-2">
|
||||
<div class="space-y-2 max-h-64 overflow-y-auto">
|
||||
<template x-for="(item, index) in selectedOrder?.items || []" :key="item.id">
|
||||
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
|
||||
<div class="flex justify-between items-start">
|
||||
|
||||
Reference in New Issue
Block a user