feat: MercadoLibre integration + inventory bulk publish + WhatsApp bridge fixes
- Add MercadoLibre OAuth, listings, orders, webhooks and category search - New marketplace_external_bp.py, meli_service.py, marketplace_external_service.py - New marketplace_external.html/js with ML management UI - Inventory: bulk publish to ML with category autocomplete, listing type and shipping selectors - Inventory: new .btn--meli styles, select/label CSS fixes - WhatsApp bridge: rate limiting, 440/515/408 error handling, stale watchdog - DB migration v3.4_meli_integration.sql for marketplace_listings, orders, sync_queue - Add Celery tasks for ML sync and webhook processing - Sidebar: MercadoLibre navigation link
This commit is contained in:
@@ -123,6 +123,8 @@ const POS = (() => {
|
||||
currentRegister = null;
|
||||
document.getElementById('registerInfo').innerHTML =
|
||||
'<span style="color:var(--color-error);cursor:pointer;" onclick="POS.showOpenRegisterModal()" title="Clic para abrir caja">⚠ Sin caja abierta — Clic para abrir</span>';
|
||||
// Force open register modal on first load
|
||||
showOpenRegisterModal();
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Register check failed:', e);
|
||||
@@ -253,6 +255,9 @@ const POS = (() => {
|
||||
discount_pct: parseFloat(item.discount_pct || 0),
|
||||
tax_rate: parseFloat(item.tax_rate || 0.16),
|
||||
stock: item.stock || 0,
|
||||
price_1: parseFloat(item.price_1 || 0),
|
||||
price_2: parseFloat(item.price_2 || 0),
|
||||
price_3: parseFloat(item.price_3 || 0),
|
||||
});
|
||||
|
||||
renderCart();
|
||||
@@ -265,6 +270,67 @@ const POS = (() => {
|
||||
renderCart();
|
||||
}
|
||||
|
||||
function clearCart() {
|
||||
cart.length = 0;
|
||||
selectedRow = -1;
|
||||
renderCart();
|
||||
}
|
||||
|
||||
function openCancelModal() {
|
||||
const overlay = document.getElementById('overlay-cancelar-venta');
|
||||
const dialog = document.getElementById('modal-cancelar-venta');
|
||||
if (overlay) overlay.classList.add('active');
|
||||
if (dialog) dialog.classList.add('active');
|
||||
}
|
||||
|
||||
function closeCancelModal() {
|
||||
const overlay = document.getElementById('overlay-cancelar-venta');
|
||||
const dialog = document.getElementById('modal-cancelar-venta');
|
||||
if (overlay) overlay.classList.remove('active');
|
||||
if (dialog) dialog.classList.remove('active');
|
||||
}
|
||||
|
||||
function changeQuantity() {
|
||||
if (selectedRow < 0 || selectedRow >= cart.length) {
|
||||
showToast('Selecciona un articulo primero', 'warn');
|
||||
return;
|
||||
}
|
||||
const q = prompt('Nueva cantidad:', cart[selectedRow].quantity);
|
||||
if (q !== null) {
|
||||
const n = parseInt(q);
|
||||
if (n > 0) {
|
||||
cart[selectedRow].quantity = n;
|
||||
renderCart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyDiscount() {
|
||||
if (selectedRow < 0 || selectedRow >= cart.length) {
|
||||
showToast('Selecciona un articulo primero', 'warn');
|
||||
return;
|
||||
}
|
||||
const d = prompt('Descuento %:', cart[selectedRow].discount_pct);
|
||||
if (d !== null) {
|
||||
const n = parseFloat(d);
|
||||
if (n >= 0 && n <= 100) {
|
||||
cart[selectedRow].discount_pct = n;
|
||||
renderCart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wire confirm-cancel button
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var btn = document.getElementById('btnConfirmCancel');
|
||||
if (btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
clearCart();
|
||||
closeCancelModal();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function renderCart() {
|
||||
const tbody = document.getElementById('cartBody');
|
||||
const table = document.getElementById('cartTable');
|
||||
@@ -472,6 +538,9 @@ const POS = (() => {
|
||||
cost: item.cost,
|
||||
tax_rate: item.tax_rate,
|
||||
stock: item.stock,
|
||||
price_1: item.price_1,
|
||||
price_2: item.price_2,
|
||||
price_3: item.price_3,
|
||||
});
|
||||
hideSearchResults();
|
||||
document.getElementById('itemSearch').value = '';
|
||||
@@ -530,11 +599,22 @@ const POS = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
function recalcCartPrices() {
|
||||
const tier = currentCustomer ? (currentCustomer.price_tier || 1) : 1;
|
||||
cart.forEach(item => {
|
||||
if (item.price_1 > 0) {
|
||||
item.unit_price = tier === 3 ? item.price_3 : tier === 2 ? item.price_2 : item.price_1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function selectCustomer(customer) {
|
||||
currentCustomer = customer;
|
||||
document.getElementById('customerAutocomplete').style.display = 'none';
|
||||
document.getElementById('customerSearchWrap').querySelector('input').style.display = 'none';
|
||||
|
||||
recalcCartPrices();
|
||||
|
||||
const tiers = { 1: 'P1 Mostrador', 2: 'P2 Taller', 3: 'P3 Mayoreo' };
|
||||
document.getElementById('customerName').textContent = customer.name;
|
||||
document.getElementById('customerTier').textContent = tiers[customer.price_tier] || 'P1';
|
||||
@@ -1255,7 +1335,7 @@ const POS = (() => {
|
||||
init();
|
||||
|
||||
return {
|
||||
addToCart, removeFromCart, selectRow,
|
||||
addToCart, removeFromCart, clearCart, selectRow,
|
||||
updateQty, updateDiscount,
|
||||
addFromSearch, hideSearchResults,
|
||||
selectCustomer, clearCustomer,
|
||||
@@ -1268,5 +1348,6 @@ const POS = (() => {
|
||||
connectThermal, thermalPrint,
|
||||
showOpenRegisterModal, closeOpenRegisterModal, openRegister,
|
||||
showCutZModal, closeCutZModal, loadCutX, confirmCutZ,
|
||||
openCancelModal, closeCancelModal, changeQuantity, applyDiscount,
|
||||
};
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user