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) <noreply@anthropic.com>
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user