From b3fb2ef40c24efcf7c6c29dfd2841f550f588c67 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Wed, 1 Apr 2026 11:38:37 -0400 Subject: [PATCH] feat: add Cost and Margin % columns with inline editing Cost column shows Odoo standard_price (editable). Margin % is calculated from cost and sale price. Editing margin auto-calculates the sale price using: price = cost / (1 - margin/100). All cells are inline-editable. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../controllers/product_search.py | 1 + .../static/src/css/woo_styles.css | 5 ++ .../static/src/js/product_mapping.js | 51 +++++++++++++++++++ .../static/src/xml/product_mapping.xml | 24 +++++++++ 4 files changed, 81 insertions(+) diff --git a/fusion-woo-odoo/fusion_woocommerce/controllers/product_search.py b/fusion-woo-odoo/fusion_woocommerce/controllers/product_search.py index 763a74cf..5ce89524 100644 --- a/fusion-woo-odoo/fusion_woocommerce/controllers/product_search.py +++ b/fusion-woo-odoo/fusion_woocommerce/controllers/product_search.py @@ -158,6 +158,7 @@ class WooProductSearchController(http.Controller): 'odoo_product_name': m.product_id.name if m.product_id else '', 'odoo_default_code': m.product_id.default_code or '' if m.product_id else '', 'odoo_price': m.product_id.list_price if m.product_id else 0.0, + 'odoo_cost': m.product_id.standard_price if m.product_id else 0.0, 'woo_regular_price': m.woo_regular_price or 0.0, 'woo_sale_price': m.woo_sale_price or 0.0, 'sync_price': m.sync_price, 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 90fcaf67..30c2a387 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 @@ -626,6 +626,11 @@ html[style*="color-scheme: dark"] { .woo-editable-cell:hover { background: var(--woo-bg-hover) !important; } +.woo-margin-cell { + font-weight: 600; + color: var(--woo-success); +} + .woo-edit-input { width: 90px; padding: 2px 6px; 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 b1256ca4..1cdf5847 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 @@ -226,6 +226,19 @@ export class ProductMapping extends Component { return "$" + n.toFixed(2); } + calcMargin(cost, price) { + const c = parseFloat(cost) || 0; + const p = parseFloat(price) || 0; + if (p <= 0 || c <= 0) return null; + return ((p - c) / p) * 100; + } + + formatMargin(cost, price) { + const m = this.calcMargin(cost, price); + if (m === null) return "—"; + return m.toFixed(1) + "%"; + } + // ------------------------------------------------------------------------- // Tab handlers // ------------------------------------------------------------------------- @@ -606,6 +619,44 @@ export class ProductMapping extends Component { kwargs: {}, }); } + } else if (cell.field === 'odoo_cost') { + 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], { standard_price: value }], + kwargs: {}, + }); + } + } else if (cell.field === 'margin') { + // Margin edit: calculate new sale price from cost and desired margin + const product = this.state.mappedProducts.find(p => p.id === cell.mapId); + if (product && product.odoo_product_id) { + const cost = parseFloat(product.odoo_cost) || 0; + if (cost <= 0) { + this.notification.add("Cannot calculate price: cost is zero.", { type: "danger" }); + await this._loadMapped(); + return; + } + if (value >= 100) { + this.notification.add("Margin cannot be 100% or more.", { type: "danger" }); + await this._loadMapped(); + return; + } + // price = cost / (1 - margin/100) + const newPrice = cost / (1 - value / 100); + const rounded = Math.round(newPrice * 100) / 100; + await rpc("/web/dataset/call_kw", { + model: "product.product", + method: "write", + args: [[product.odoo_product_id], { list_price: rounded }], + kwargs: {}, + }); + this.notification.add("Sale price set to $" + rounded.toFixed(2) + " (" + value.toFixed(1) + "% margin).", { type: "success" }); + await this._loadMapped(); + return; + } } this.notification.add("Price updated.", { type: "success" }); } catch (err) { 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 4f341b78..e91aa9fa 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 @@ -144,6 +144,8 @@ WC Sale Odoo Price + Cost + Margin % Instance Price Sync Inventory Sync @@ -216,6 +218,28 @@ + + + + + + + + + + + +