Productos: allowed_order_units + fix sync que pisaba overrides
Introduce un campo nuevo allowed_order_units por producto (array kg/unit) que separa "cómo se vende" de "cómo se puede pedir". Productos sell_unit=unit solo aceptan unit (regla dura aplicada por código y handler). Productos sell_unit=kg defaultean a ambos, editable desde la UI con checkboxes. Fixea bug crítico de wooSnapshot.upsertSnapshotItems que pisaba raw entero en cada sync, perdiendo overrides locales (_sell_unit_override, _categories_override y el nuevo _allowed_order_units). El UPDATE ahora mergea preservando los overrides. Las tools del agente (addToCart, setQuantity, selectCandidate) validan unit contra allowed_order_units y devuelven unit_not_allowed para que el LLM re-pregunte. workingMemory expone allowed_order_units y needs_unit_choice. buildLineItems agrega meta_data unit/sell_unit_product/unit_mode_mismatch y omite subtotal/total cuando hay mismatch para que Woo recalcule. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -458,6 +458,24 @@ class ProductsCrud extends HTMLElement {
|
||||
Define si este producto se vende por peso o por unidad
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Unidades aceptadas al pedir</label>
|
||||
<div id="allowedUnitsBox" style="display:flex;gap:14px;align-items:center;">
|
||||
<label style="display:inline-flex;gap:6px;align-items:center;font-size:13px;">
|
||||
<input type="checkbox" id="cbAllowKg" ${this.computeAllowedUI(p, currentUnit).includes("kg") ? "checked" : ""} ${currentUnit === "unit" ? "disabled" : ""}>
|
||||
Permitir pedir por kg
|
||||
</label>
|
||||
<label style="display:inline-flex;gap:6px;align-items:center;font-size:13px;">
|
||||
<input type="checkbox" id="cbAllowUnit" ${this.computeAllowedUI(p, currentUnit).includes("unit") ? "checked" : ""} ${currentUnit === "unit" ? "disabled" : ""}>
|
||||
Permitir pedir por unidad
|
||||
</label>
|
||||
</div>
|
||||
<div style="font-size:11px;color:var(--text-muted);margin-top:4px;" id="allowedUnitsHint">
|
||||
${currentUnit === "unit"
|
||||
? 'Producto por unidad: solo se acepta pedir por unidad.'
|
||||
: 'Para productos vendidos por kg, permitir también pedido por unidad (ej: milanesas, hamburguesas).'}
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Categorías actuales</label>
|
||||
<div id="currentCategories" style="display:flex;flex-wrap:wrap;gap:6px;min-height:30px;margin-bottom:8px;">
|
||||
@@ -504,7 +522,40 @@ class ProductsCrud extends HTMLElement {
|
||||
|
||||
// Bind save button
|
||||
this.shadowRoot.getElementById("saveProduct").onclick = () => this.saveProduct();
|
||||
|
||||
|
||||
// Bind sell_unit select: si cambia a "unit", forzar checks; si pasa a "kg", rehabilitar.
|
||||
const sellUnitSel = this.shadowRoot.getElementById("sellUnit");
|
||||
const cbKg = this.shadowRoot.getElementById("cbAllowKg");
|
||||
const cbUnit = this.shadowRoot.getElementById("cbAllowUnit");
|
||||
const hintEl = this.shadowRoot.getElementById("allowedUnitsHint");
|
||||
sellUnitSel.onchange = () => {
|
||||
if (sellUnitSel.value === "unit") {
|
||||
cbKg.checked = false;
|
||||
cbUnit.checked = true;
|
||||
cbKg.disabled = true;
|
||||
cbUnit.disabled = true;
|
||||
hintEl.textContent = 'Producto por unidad: solo se acepta pedir por unidad.';
|
||||
} else {
|
||||
cbKg.disabled = false;
|
||||
cbUnit.disabled = false;
|
||||
// Default permisivo al pasar a kg
|
||||
if (!cbKg.checked && !cbUnit.checked) {
|
||||
cbKg.checked = true;
|
||||
cbUnit.checked = true;
|
||||
}
|
||||
hintEl.textContent = 'Para productos vendidos por kg, permitir también pedido por unidad (ej: milanesas, hamburguesas).';
|
||||
}
|
||||
};
|
||||
const validateAtLeastOne = (e) => {
|
||||
if (!cbKg.checked && !cbUnit.checked) {
|
||||
modal.warn("Tenés que dejar al menos una unidad aceptada.");
|
||||
// Revertir: marcar el que se acaba de desmarcar
|
||||
e.target.checked = true;
|
||||
}
|
||||
};
|
||||
cbKg.addEventListener("change", validateAtLeastOne);
|
||||
cbUnit.addEventListener("change", validateAtLeastOne);
|
||||
|
||||
// Bind add category button
|
||||
this.shadowRoot.getElementById("addCatBtn").onclick = () => this.addCategoryToProduct();
|
||||
|
||||
@@ -558,22 +609,25 @@ class ProductsCrud extends HTMLElement {
|
||||
|
||||
async autoSaveCategories() {
|
||||
if (this.selectedItems.length !== 1) return;
|
||||
|
||||
|
||||
const p = this.selectedItems[0];
|
||||
const categories = this.getCurrentProductCategories();
|
||||
const sellUnitSelect = this.shadowRoot.getElementById("sellUnit");
|
||||
const sell_unit = sellUnitSelect?.value || p.sell_unit || 'kg';
|
||||
|
||||
const allowed_order_units = this.getCurrentAllowedOrderUnits();
|
||||
|
||||
try {
|
||||
await api.updateProduct(p.woo_product_id, { sell_unit, categories });
|
||||
|
||||
await api.updateProduct(p.woo_product_id, { sell_unit, categories, allowed_order_units });
|
||||
|
||||
// Actualizar localmente
|
||||
p.categories = JSON.stringify(categories.map(name => ({ name })));
|
||||
if (allowed_order_units) p.allowed_order_units = allowed_order_units;
|
||||
const idx = this.items.findIndex(i => i.woo_product_id === p.woo_product_id);
|
||||
if (idx >= 0) {
|
||||
this.items[idx].categories = p.categories;
|
||||
if (allowed_order_units) this.items[idx].allowed_order_units = allowed_order_units;
|
||||
}
|
||||
|
||||
|
||||
// Mostrar feedback breve
|
||||
const status = this.shadowRoot.getElementById("saveStatus");
|
||||
if (status) {
|
||||
@@ -604,31 +658,52 @@ class ProductsCrud extends HTMLElement {
|
||||
return [...tags].map(t => t.dataset.category);
|
||||
}
|
||||
|
||||
computeAllowedUI(p, currentUnit) {
|
||||
if (currentUnit === "unit") return ["unit"];
|
||||
if (Array.isArray(p?.allowed_order_units) && p.allowed_order_units.length > 0) {
|
||||
return p.allowed_order_units;
|
||||
}
|
||||
return ["kg", "unit"];
|
||||
}
|
||||
|
||||
getCurrentAllowedOrderUnits() {
|
||||
const cbKg = this.shadowRoot.getElementById("cbAllowKg");
|
||||
const cbUnit = this.shadowRoot.getElementById("cbAllowUnit");
|
||||
if (!cbKg || !cbUnit) return undefined;
|
||||
const list = [];
|
||||
if (cbKg.checked) list.push("kg");
|
||||
if (cbUnit.checked) list.push("unit");
|
||||
return list.length ? list : ["unit"];
|
||||
}
|
||||
|
||||
async saveProduct() {
|
||||
if (this.selectedItems.length !== 1) return;
|
||||
|
||||
|
||||
const p = this.selectedItems[0];
|
||||
const btn = this.shadowRoot.getElementById("saveProduct");
|
||||
const sellUnitSelect = this.shadowRoot.getElementById("sellUnit");
|
||||
|
||||
|
||||
const sell_unit = sellUnitSelect.value;
|
||||
const categories = this.getCurrentProductCategories();
|
||||
|
||||
const allowed_order_units = this.getCurrentAllowedOrderUnits();
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = "Guardando...";
|
||||
|
||||
|
||||
try {
|
||||
await api.updateProduct(p.woo_product_id, { sell_unit, categories });
|
||||
|
||||
await api.updateProduct(p.woo_product_id, { sell_unit, categories, allowed_order_units });
|
||||
|
||||
// Actualizar localmente
|
||||
p.sell_unit = sell_unit;
|
||||
p.categories = JSON.stringify(categories.map(name => ({ name })));
|
||||
if (allowed_order_units) p.allowed_order_units = sell_unit === "unit" ? ["unit"] : allowed_order_units;
|
||||
const idx = this.items.findIndex(i => i.woo_product_id === p.woo_product_id);
|
||||
if (idx >= 0) {
|
||||
this.items[idx].sell_unit = sell_unit;
|
||||
this.items[idx].categories = p.categories;
|
||||
if (allowed_order_units) this.items[idx].allowed_order_units = p.allowed_order_units;
|
||||
}
|
||||
|
||||
|
||||
btn.textContent = "Guardado!";
|
||||
this.renderList();
|
||||
setTimeout(() => { btn.textContent = "Guardar cambios"; btn.disabled = false; }, 1500);
|
||||
|
||||
Reference in New Issue
Block a user