From c5b519f8f46b04fa1f184fd10f0b48378dbf22f3 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Wed, 1 Apr 2026 11:23:23 -0400 Subject: [PATCH] feat: inline-editable prices in product mapping UI Click any price cell (WC Standard, WC Sale, Odoo Price) to edit inline. Enter or click away saves and syncs to the source. Escape cancels. Validation: sale price cannot exceed standard price. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../models/woo_product_map.py | 20 ++++ .../static/src/css/woo_styles.css | 23 +++++ .../static/src/js/product_mapping.js | 94 +++++++++++++++++++ .../static/src/xml/product_mapping.xml | 42 +++++++-- 4 files changed, 173 insertions(+), 6 deletions(-) diff --git a/fusion-woo-odoo/fusion_woocommerce/models/woo_product_map.py b/fusion-woo-odoo/fusion_woocommerce/models/woo_product_map.py index 0c379943..cd5407fb 100644 --- a/fusion-woo-odoo/fusion_woocommerce/models/woo_product_map.py +++ b/fusion-woo-odoo/fusion_woocommerce/models/woo_product_map.py @@ -135,6 +135,26 @@ class WooProductMap(models.Model): 'Standard price set to $%.2f' % price, ) + def action_set_sale_price(self, price): + """Set the WC sale price directly.""" + self.ensure_one() + if not self.instance_id: + return + client = self.instance_id._get_client() + # Sale price cannot exceed regular price + if self.woo_regular_price and price > self.woo_regular_price + 0.01: + raise UserError( + 'Sale price ($%.2f) cannot exceed the standard price ($%.2f).' + % (price, self.woo_regular_price) + ) + update_data = {'sale_price': str(price) if price > 0 else ''} + client.update_product(self.woo_product_id, update_data) + self.woo_sale_price = price + self.instance_id._log_sync( + 'product', 'odoo_to_woo', self.woo_product_name, 'success', + 'Sale price set to $%.2f' % price, + ) + # ------------------------------------------------------------------ # Image Sync (Task 22) # ------------------------------------------------------------------ diff --git a/fusion-woo-odoo/fusion_woocommerce/static/src/css/woo_styles.css b/fusion-woo-odoo/fusion_woocommerce/static/src/css/woo_styles.css index 3ed6f689..90fcaf67 100644 --- a/fusion-woo-odoo/fusion_woocommerce/static/src/css/woo_styles.css +++ b/fusion-woo-odoo/fusion_woocommerce/static/src/css/woo_styles.css @@ -615,3 +615,26 @@ html[style*="color-scheme: dark"] { width: 60px; white-space: nowrap; } + +/* ---------------------------------------------------------- + Editable price cells + ---------------------------------------------------------- */ +.woo-editable-cell { + cursor: pointer; + position: relative; +} +.woo-editable-cell:hover { + background: var(--woo-bg-hover) !important; +} +.woo-edit-input { + width: 90px; + padding: 2px 6px; + border: 1px solid var(--woo-accent); + border-radius: 4px; + font-size: 0.85rem; + text-align: right; + background: var(--woo-input-bg); + color: var(--woo-text-primary); + outline: none; + box-shadow: 0 0 0 2px var(--woo-accent-glow); +} diff --git a/fusion-woo-odoo/fusion_woocommerce/static/src/js/product_mapping.js b/fusion-woo-odoo/fusion_woocommerce/static/src/js/product_mapping.js index 1f247630..b1256ca4 100644 --- a/fusion-woo-odoo/fusion_woocommerce/static/src/js/product_mapping.js +++ b/fusion-woo-odoo/fusion_woocommerce/static/src/js/product_mapping.js @@ -43,6 +43,10 @@ export class ProductMapping extends Component { mappedPage: 1, mappedTotal: 0, + // Inline price editing + editingCell: null, // { mapId: int, field: 'woo_regular'|'woo_sale'|'odoo_price' } + editValue: '', + // Unmatched tab odooProducts: [], wooProducts: [], @@ -522,6 +526,96 @@ export class ProductMapping extends Component { } } + // ------------------------------------------------------------------------- + // Inline price editing + // ------------------------------------------------------------------------- + + startEdit(mapId, field, currentValue) { + this.state.editingCell = { mapId, field }; + this.state.editValue = currentValue !== null && currentValue !== undefined ? String(currentValue) : ''; + // Focus the input after OWL re-renders + setTimeout(() => { + const input = document.querySelector('.woo-edit-input'); + if (input) { input.focus(); input.select(); } + }, 50); + } + + cancelEdit() { + this.state.editingCell = null; + this.state.editValue = ''; + } + + onEditInput(ev) { + this.state.editValue = ev.target.value; + } + + async onEditKeydown(ev) { + if (ev.key === 'Enter') { + await this.saveEdit(); + } else if (ev.key === 'Escape') { + this.cancelEdit(); + } + } + + async onEditBlur() { + await this.saveEdit(); + } + + isEditing(mapId, field) { + return this.state.editingCell && + this.state.editingCell.mapId === mapId && + this.state.editingCell.field === field; + } + + async saveEdit() { + const cell = this.state.editingCell; + if (!cell) return; + + const value = parseFloat(this.state.editValue); + if (isNaN(value) || value < 0) { + this.notification.add("Invalid price value.", { type: "danger" }); + this.cancelEdit(); + return; + } + + // Cancel first so blur doesn't fire a second save after Enter + this.cancelEdit(); + + try { + if (cell.field === 'woo_regular') { + await rpc("/web/dataset/call_kw", { + model: "woo.product.map", + method: "action_set_regular_price", + args: [[cell.mapId], value], + kwargs: {}, + }); + } else if (cell.field === 'woo_sale') { + await rpc("/web/dataset/call_kw", { + model: "woo.product.map", + method: "action_set_sale_price", + args: [[cell.mapId], value], + kwargs: {}, + }); + } else if (cell.field === 'odoo_price') { + const product = this.state.mappedProducts.find(p => p.id === cell.mapId); + if (product && product.odoo_product_id) { + await rpc("/web/dataset/call_kw", { + model: "product.product", + method: "write", + args: [[product.odoo_product_id], { list_price: value }], + kwargs: {}, + }); + } + } + this.notification.add("Price updated.", { type: "success" }); + } catch (err) { + console.error("[ProductMapping] saveEdit error:", err); + this.notification.add(err.message || "Failed to update price.", { type: "danger" }); + } + + await this._loadMapped(); + } + // ------------------------------------------------------------------------- // Individual price sync // ------------------------------------------------------------------------- diff --git a/fusion-woo-odoo/fusion_woocommerce/static/src/xml/product_mapping.xml b/fusion-woo-odoo/fusion_woocommerce/static/src/xml/product_mapping.xml index 3ca68364..4f341b78 100644 --- a/fusion-woo-odoo/fusion_woocommerce/static/src/xml/product_mapping.xml +++ b/fusion-woo-odoo/fusion_woocommerce/static/src/xml/product_mapping.xml @@ -168,12 +168,32 @@ - - - - + + + + + + + + + + + + + + + - - + + + + + +