audit and sync
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { handleSearchProducts, handleListProducts, handleGetProduct, handleSyncProducts, handleUpdateProductUnit, handleBulkUpdateProductUnit, handleUpdateProduct } from "../handlers/products.js";
|
||||
import { handleSearchProducts, handleListProducts, handleGetProduct, handleSyncProducts, handleSyncFromWoo, handleUpdateProductUnit, handleBulkUpdateProductUnit, handleUpdateProduct } from "../handlers/products.js";
|
||||
|
||||
export const makeSearchProducts = (tenantIdOrFn) => async (req, res) => {
|
||||
try {
|
||||
@@ -54,6 +54,20 @@ export const makeSyncProducts = (tenantIdOrFn) => async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync manual de emergencia: reimporta todos los productos de WooCommerce
|
||||
*/
|
||||
export const makeSyncFromWoo = (tenantIdOrFn) => async (req, res) => {
|
||||
try {
|
||||
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
|
||||
const result = await handleSyncFromWoo({ tenantId });
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ ok: false, error: "internal_error", message: err.message });
|
||||
}
|
||||
};
|
||||
|
||||
export const makeUpdateProductUnit = (tenantIdOrFn) => async (req, res) => {
|
||||
try {
|
||||
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
|
||||
|
||||
@@ -624,3 +624,63 @@ export async function countQtyRulesByProduct({ tenantId }) {
|
||||
const { rows } = await pool.query(sql, [tenantId]);
|
||||
return rows;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Audit Log
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Inserta un registro en el audit log
|
||||
* @param {Object} params
|
||||
* @param {string} params.tenantId - UUID del tenant
|
||||
* @param {string} params.entityType - 'product' | 'user' | 'order'
|
||||
* @param {string} params.entityId - ID de la entidad (woo_product_id, wa_chat_id, etc)
|
||||
* @param {string} params.action - 'create' | 'update' | 'delete' | 'sync_from_woo' | 'push_to_woo'
|
||||
* @param {Object} params.changes - { field: { old, new } }
|
||||
* @param {string} params.actor - 'system' | 'webhook' | 'ui'
|
||||
*/
|
||||
export async function insertAuditLog({ tenantId, entityType, entityId, action, changes, actor = 'system' }) {
|
||||
const sql = `
|
||||
insert into audit_log (tenant_id, entity_type, entity_id, action, changes, actor)
|
||||
values ($1, $2, $3, $4, $5::jsonb, $6)
|
||||
returning id, created_at
|
||||
`;
|
||||
const { rows } = await pool.query(sql, [
|
||||
tenantId,
|
||||
entityType,
|
||||
String(entityId),
|
||||
action,
|
||||
changes ? JSON.stringify(changes) : null,
|
||||
actor
|
||||
]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el historial de cambios de una entidad
|
||||
*/
|
||||
export async function getAuditLog({ tenantId, entityType, entityId, limit = 50 }) {
|
||||
const sql = `
|
||||
select id, entity_type, entity_id, action, changes, actor, created_at
|
||||
from audit_log
|
||||
where tenant_id = $1
|
||||
and ($2::text is null or entity_type = $2)
|
||||
and ($3::text is null or entity_id = $3)
|
||||
order by created_at desc
|
||||
limit $4
|
||||
`;
|
||||
const { rows } = await pool.query(sql, [tenantId, entityType || null, entityId || null, limit]);
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el historial de cambios recientes de productos
|
||||
*/
|
||||
export async function getProductAuditLog({ tenantId, wooProductId, limit = 20 }) {
|
||||
return getAuditLog({
|
||||
tenantId,
|
||||
entityType: 'product',
|
||||
entityId: wooProductId ? String(wooProductId) : null,
|
||||
limit
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { searchSnapshotItems } from "../../shared/wooSnapshot.js";
|
||||
import { listProducts, getProductByWooId, updateProductSellUnit, bulkUpdateProductSellUnit, updateProduct } from "../db/repo.js";
|
||||
import { searchSnapshotItems, syncFromWoo, pushProductToWoo } from "../../shared/wooSnapshot.js";
|
||||
import { listProducts, getProductByWooId, updateProductSellUnit, bulkUpdateProductSellUnit, updateProduct, insertAuditLog } from "../db/repo.js";
|
||||
|
||||
export async function handleSearchProducts({ tenantId, q = "", limit = "10", forceWoo = "0" }) {
|
||||
const { items, source } = await searchSnapshotItems({
|
||||
@@ -20,9 +20,34 @@ export async function handleGetProduct({ tenantId, wooProductId }) {
|
||||
}
|
||||
|
||||
export async function handleSyncProducts({ tenantId }) {
|
||||
// This is a placeholder - actual sync would fetch from Woo API
|
||||
// For now, just return success
|
||||
return { ok: true, message: "Sync triggered (use import script for full sync)" };
|
||||
// Placeholder legacy - redirigir a syncFromWoo
|
||||
return handleSyncFromWoo({ tenantId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sincroniza todos los productos desde WooCommerce (solo para emergencias)
|
||||
*/
|
||||
export async function handleSyncFromWoo({ tenantId }) {
|
||||
console.log("[products] handleSyncFromWoo starting...");
|
||||
|
||||
try {
|
||||
const result = await syncFromWoo({ tenantId });
|
||||
|
||||
// Registrar en audit_log
|
||||
await insertAuditLog({
|
||||
tenantId,
|
||||
entityType: 'product',
|
||||
entityId: 'all',
|
||||
action: 'sync_from_woo',
|
||||
changes: { synced_count: result.synced, ms: result.ms },
|
||||
actor: 'ui'
|
||||
});
|
||||
|
||||
return { ok: true, synced: result.synced, ms: result.ms };
|
||||
} catch (err) {
|
||||
console.error("[products] handleSyncFromWoo error:", err);
|
||||
return { ok: false, error: err.message };
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleUpdateProductUnit({ tenantId, wooProductId, sell_unit }) {
|
||||
@@ -35,8 +60,43 @@ export async function handleBulkUpdateProductUnit({ tenantId, wooProductIds, sel
|
||||
return { ok: true, updated_count: wooProductIds.length, sell_unit };
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza un producto localmente y automáticamente lo pushea a WooCommerce
|
||||
*/
|
||||
export async function handleUpdateProduct({ tenantId, wooProductId, sell_unit, categories }) {
|
||||
// 1. Guardar cambios localmente
|
||||
await updateProduct({ tenantId, wooProductId, sell_unit, categories });
|
||||
return { ok: true, woo_product_id: wooProductId, sell_unit, categories };
|
||||
|
||||
// 2. Auto-push a WooCommerce (best effort - no falla si Woo no responde)
|
||||
let wooPushResult = null;
|
||||
try {
|
||||
wooPushResult = await pushProductToWoo({ tenantId, wooProductId, categories, sellUnit: sell_unit });
|
||||
console.log("[products] handleUpdateProduct: pushed to Woo", { wooProductId, updated: wooPushResult?.updated });
|
||||
} catch (err) {
|
||||
console.error("[products] handleUpdateProduct: Woo push failed (continuing anyway)", err.message);
|
||||
// No lanzamos el error - el guardado local fue exitoso
|
||||
}
|
||||
|
||||
// 3. Registrar en audit_log
|
||||
await insertAuditLog({
|
||||
tenantId,
|
||||
entityType: 'product',
|
||||
entityId: String(wooProductId),
|
||||
action: 'update',
|
||||
changes: {
|
||||
sell_unit: { new: sell_unit },
|
||||
categories: { new: categories },
|
||||
woo_synced: wooPushResult?.updated ?? false
|
||||
},
|
||||
actor: 'ui'
|
||||
});
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
woo_product_id: wooProductId,
|
||||
sell_unit,
|
||||
categories,
|
||||
woo_synced: wooPushResult?.updated ?? false
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user