mejoras en el modelo de clarificacion de productos

This commit is contained in:
Lucas Tettamanti
2026-01-17 06:31:49 -03:00
parent 63b9ecef61
commit 204403560e
24 changed files with 1940 additions and 873 deletions

View File

@@ -1,4 +1,4 @@
import { handleSearchProducts, handleListProducts, handleGetProduct, handleSyncProducts } from "../handlers/products.js";
import { handleSearchProducts, handleListProducts, handleGetProduct, handleSyncProducts, handleUpdateProductUnit, handleBulkUpdateProductUnit, handleUpdateProduct } from "../handlers/products.js";
export const makeSearchProducts = (tenantIdOrFn) => async (req, res) => {
try {
@@ -54,3 +54,60 @@ export const makeSyncProducts = (tenantIdOrFn) => async (req, res) => {
}
};
export const makeUpdateProductUnit = (tenantIdOrFn) => async (req, res) => {
try {
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
const wooProductId = req.params.id;
const { sell_unit } = req.body || {};
if (!sell_unit || !["kg", "unit"].includes(sell_unit)) {
return res.status(400).json({ ok: false, error: "invalid_sell_unit" });
}
const result = await handleUpdateProductUnit({ tenantId, wooProductId, sell_unit });
res.json(result);
} catch (err) {
console.error(err);
res.status(500).json({ ok: false, error: "internal_error" });
}
};
export const makeBulkUpdateProductUnit = (tenantIdOrFn) => async (req, res) => {
try {
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
const { woo_product_ids, sell_unit } = req.body || {};
if (!sell_unit || !["kg", "unit"].includes(sell_unit)) {
return res.status(400).json({ ok: false, error: "invalid_sell_unit" });
}
if (!Array.isArray(woo_product_ids) || !woo_product_ids.length) {
return res.status(400).json({ ok: false, error: "invalid_woo_product_ids" });
}
const result = await handleBulkUpdateProductUnit({ tenantId, wooProductIds: woo_product_ids, sell_unit });
res.json(result);
} catch (err) {
console.error(err);
res.status(500).json({ ok: false, error: "internal_error" });
}
};
export const makeUpdateProduct = (tenantIdOrFn) => async (req, res) => {
try {
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
const wooProductId = req.params.id;
const { sell_unit, categories } = req.body || {};
if (sell_unit && !["kg", "unit"].includes(sell_unit)) {
return res.status(400).json({ ok: false, error: "invalid_sell_unit" });
}
const result = await handleUpdateProduct({ tenantId, wooProductId, sell_unit, categories });
res.json(result);
} catch (err) {
console.error(err);
res.status(500).json({ ok: false, error: "internal_error" });
}
};

View File

@@ -22,7 +22,8 @@ export async function listProducts({ tenantId, q = "", limit = 2000, offset = 0
categories,
attributes_normalized,
updated_at as refreshed_at,
raw as payload
raw as payload,
raw->>'_sell_unit_override' as sell_unit
from woo_products_snapshot
where tenant_id = $1
and (name ilike $2 or coalesce(slug,'') ilike $2)
@@ -41,7 +42,8 @@ export async function listProducts({ tenantId, q = "", limit = 2000, offset = 0
categories,
attributes_normalized,
updated_at as refreshed_at,
raw as payload
raw as payload,
raw->>'_sell_unit_override' as sell_unit
from woo_products_snapshot
where tenant_id = $1
order by name asc
@@ -65,7 +67,8 @@ export async function getProductByWooId({ tenantId, wooProductId }) {
categories,
attributes_normalized,
updated_at as refreshed_at,
raw as payload
raw as payload,
raw->>'_sell_unit_override' as sell_unit
from woo_products_snapshot
where tenant_id = $1 and woo_id = $2
limit 1
@@ -74,6 +77,66 @@ export async function getProductByWooId({ tenantId, wooProductId }) {
return rows[0] || null;
}
export async function updateProductSellUnit({ tenantId, wooProductId, sell_unit }) {
const sql = `
update woo_products_snapshot
set raw = jsonb_set(coalesce(raw, '{}'::jsonb), '{_sell_unit_override}', $3::jsonb)
where tenant_id = $1 and woo_id = $2
returning woo_id as woo_product_id
`;
const { rows } = await pool.query(sql, [tenantId, wooProductId, JSON.stringify(sell_unit)]);
return rows[0] || null;
}
export async function bulkUpdateProductSellUnit({ tenantId, wooProductIds, sell_unit }) {
if (!wooProductIds || !wooProductIds.length) return { updated: 0 };
const sql = `
update woo_products_snapshot
set raw = jsonb_set(coalesce(raw, '{}'::jsonb), '{_sell_unit_override}', $3::jsonb)
where tenant_id = $1 and woo_id = ANY($2::int[])
`;
const result = await pool.query(sql, [tenantId, wooProductIds, JSON.stringify(sell_unit)]);
return { updated: result.rowCount };
}
export async function updateProduct({ tenantId, wooProductId, sell_unit, categories }) {
// Build the JSONB update dynamically
let updates = [];
let params = [tenantId, wooProductId];
let paramIdx = 3;
if (sell_unit) {
updates.push(`raw = jsonb_set(coalesce(raw, '{}'::jsonb), '{_sell_unit_override}', $${paramIdx}::jsonb)`);
params.push(JSON.stringify(sell_unit));
paramIdx++;
}
if (categories) {
// Also update the categories column if it exists
updates.push(`categories = $${paramIdx}::jsonb`);
params.push(JSON.stringify(categories.map(name => ({ name }))));
paramIdx++;
// Also store in raw for persistence
updates.push(`raw = jsonb_set(coalesce(raw, '{}'::jsonb), '{_categories_override}', $${paramIdx}::jsonb)`);
params.push(JSON.stringify(categories));
paramIdx++;
}
if (!updates.length) return null;
const sql = `
update woo_products_snapshot
set ${updates.join(", ")}
where tenant_id = $1 and woo_id = $2
returning woo_id as woo_product_id
`;
const { rows } = await pool.query(sql, params);
return rows[0] || null;
}
// ─────────────────────────────────────────────────────────────
// Aliases
// ─────────────────────────────────────────────────────────────
@@ -188,7 +251,8 @@ export async function listRecommendations({ tenantId, q = "", limit = 200 }) {
if (query) {
const like = `%${query}%`;
sql = `
select id, rule_key, trigger, queries, boosts, ask_slots, active, priority, created_at, updated_at
select id, rule_key, trigger, queries, boosts, ask_slots, active, priority,
trigger_product_ids, recommended_product_ids, created_at, updated_at
from product_reco_rules
where tenant_id = $1 and rule_key ilike $2
order by priority desc, rule_key asc
@@ -197,7 +261,8 @@ export async function listRecommendations({ tenantId, q = "", limit = 200 }) {
params = [tenantId, like, lim];
} else {
sql = `
select id, rule_key, trigger, queries, boosts, ask_slots, active, priority, created_at, updated_at
select id, rule_key, trigger, queries, boosts, ask_slots, active, priority,
trigger_product_ids, recommended_product_ids, created_at, updated_at
from product_reco_rules
where tenant_id = $1
order by priority desc, rule_key asc
@@ -212,7 +277,8 @@ export async function listRecommendations({ tenantId, q = "", limit = 200 }) {
export async function getRecommendationById({ tenantId, id }) {
const sql = `
select id, rule_key, trigger, queries, boosts, ask_slots, active, priority, created_at, updated_at
select id, rule_key, trigger, queries, boosts, ask_slots, active, priority,
trigger_product_ids, recommended_product_ids, created_at, updated_at
from product_reco_rules
where tenant_id = $1 and id = $2
limit 1
@@ -230,11 +296,13 @@ export async function insertRecommendation({
ask_slots = [],
active = true,
priority = 100,
trigger_product_ids = [],
recommended_product_ids = [],
}) {
const sql = `
insert into product_reco_rules (tenant_id, rule_key, trigger, queries, boosts, ask_slots, active, priority)
values ($1, $2, $3, $4, $5, $6, $7, $8)
returning id, rule_key, trigger, queries, boosts, ask_slots, active, priority, created_at, updated_at
insert into product_reco_rules (tenant_id, rule_key, trigger, queries, boosts, ask_slots, active, priority, trigger_product_ids, recommended_product_ids)
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
returning id, rule_key, trigger, queries, boosts, ask_slots, active, priority, trigger_product_ids, recommended_product_ids, created_at, updated_at
`;
const { rows } = await pool.query(sql, [
@@ -246,6 +314,8 @@ export async function insertRecommendation({
JSON.stringify(ask_slots || []),
active !== false,
priority || 100,
trigger_product_ids || [],
recommended_product_ids || [],
]);
return rows[0];
@@ -260,6 +330,8 @@ export async function updateRecommendation({
ask_slots,
active,
priority,
trigger_product_ids,
recommended_product_ids,
}) {
const sql = `
update product_reco_rules
@@ -270,9 +342,11 @@ export async function updateRecommendation({
ask_slots = $6,
active = $7,
priority = $8,
trigger_product_ids = $9,
recommended_product_ids = $10,
updated_at = now()
where tenant_id = $1 and id = $2
returning id, rule_key, trigger, queries, boosts, ask_slots, active, priority, created_at, updated_at
returning id, rule_key, trigger, queries, boosts, ask_slots, active, priority, trigger_product_ids, recommended_product_ids, created_at, updated_at
`;
const { rows } = await pool.query(sql, [
@@ -284,6 +358,8 @@ export async function updateRecommendation({
JSON.stringify(ask_slots || []),
active !== false,
priority || 100,
trigger_product_ids || [],
recommended_product_ids || [],
]);
return rows[0] || null;

View File

@@ -1,5 +1,5 @@
import { searchSnapshotItems } from "../../shared/wooSnapshot.js";
import { listProducts, getProductByWooId } from "../db/repo.js";
import { listProducts, getProductByWooId, updateProductSellUnit, bulkUpdateProductSellUnit, updateProduct } from "../db/repo.js";
export async function handleSearchProducts({ tenantId, q = "", limit = "10", forceWoo = "0" }) {
const { items, source } = await searchSnapshotItems({
@@ -25,3 +25,18 @@ export async function handleSyncProducts({ tenantId }) {
return { ok: true, message: "Sync triggered (use import script for full sync)" };
}
export async function handleUpdateProductUnit({ tenantId, wooProductId, sell_unit }) {
await updateProductSellUnit({ tenantId, wooProductId, sell_unit });
return { ok: true, woo_product_id: wooProductId, sell_unit };
}
export async function handleBulkUpdateProductUnit({ tenantId, wooProductIds, sell_unit }) {
await bulkUpdateProductSellUnit({ tenantId, wooProductIds, sell_unit });
return { ok: true, updated_count: wooProductIds.length, sell_unit };
}
export async function handleUpdateProduct({ tenantId, wooProductId, sell_unit, categories }) {
await updateProduct({ tenantId, wooProductId, sell_unit, categories });
return { ok: true, woo_product_id: wooProductId, sell_unit, categories };
}

View File

@@ -24,8 +24,10 @@ export async function handleCreateRecommendation({
ask_slots = [],
active = true,
priority = 100,
trigger_product_ids = [],
recommended_product_ids = [],
}) {
return insertRecommendation({ tenantId, rule_key, trigger, queries, boosts, ask_slots, active, priority });
return insertRecommendation({ tenantId, rule_key, trigger, queries, boosts, ask_slots, active, priority, trigger_product_ids, recommended_product_ids });
}
export async function handleUpdateRecommendation({
@@ -37,8 +39,10 @@ export async function handleUpdateRecommendation({
ask_slots,
active,
priority,
trigger_product_ids,
recommended_product_ids,
}) {
return updateRecommendation({ tenantId, id, trigger, queries, boosts, ask_slots, active, priority });
return updateRecommendation({ tenantId, id, trigger, queries, boosts, ask_slots, active, priority, trigger_product_ids, recommended_product_ids });
}
export async function handleDeleteRecommendation({ tenantId, id }) {