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:
@@ -104,7 +104,7 @@ const Customers = (() => {
|
||||
const creditClass = usedPct >= 80 ? 'none' : usedPct >= 60 ? 'low' : '';
|
||||
const num = String(c.id).padStart(5, '0');
|
||||
const selClass = (currentCustomer && currentCustomer.id === c.id) ? 'selected' : '';
|
||||
return '<tr class="' + selClass + '" onclick="selectCustomer(' + c.id + ')">' +
|
||||
return '<tr class="' + selClass + '" onclick="Customers.selectCustomer(' + c.id + ')">' +
|
||||
'<td class="cell-num">' + num + '</td>' +
|
||||
'<td>' +
|
||||
'<div class="cell-name">' + (c.name || '') + '</div>' +
|
||||
@@ -263,6 +263,13 @@ const Customers = (() => {
|
||||
bar.className = `progress-bar__fill ${pct < 40 ? 'low' : pct > 75 ? 'high' : ''}`;
|
||||
}
|
||||
|
||||
// Discount
|
||||
const discountEl = document.getElementById('detailMaxDiscount');
|
||||
if (discountEl) discountEl.textContent = (c.max_discount_pct || 0) + '%';
|
||||
|
||||
// Re-wire action buttons after detail panel is visible
|
||||
wireActionButtons();
|
||||
|
||||
// Purchase History
|
||||
const hbody = document.getElementById('historyBody');
|
||||
if (hbody) {
|
||||
@@ -363,7 +370,7 @@ const Customers = (() => {
|
||||
const btns = document.querySelectorAll('.quick-actions .action-btn');
|
||||
// Order: Nueva Venta, Editar, Estado de Cuenta, Historial
|
||||
if (btns.length >= 1) btns[0].onclick = () => {
|
||||
if (currentCustomer) window.location.href = '/pos/?customer=' + currentCustomer.id;
|
||||
if (currentCustomer) window.location.href = '/pos/sale?customer=' + currentCustomer.id;
|
||||
};
|
||||
if (btns.length >= 2) btns[1].onclick = () => editCurrent();
|
||||
if (btns.length >= 3) btns[2].onclick = () => showStatement();
|
||||
@@ -378,17 +385,19 @@ const Customers = (() => {
|
||||
if (!modal) return;
|
||||
document.getElementById('modalTitle').textContent = 'Nuevo Cliente';
|
||||
document.getElementById('editId').value = '';
|
||||
document.getElementById('fName').value = '';
|
||||
document.getElementById('fRfc').value = '';
|
||||
document.getElementById('fRazonSocial').value = '';
|
||||
document.getElementById('fRegimenFiscal').value = '';
|
||||
document.getElementById('fUsoCfdi').value = 'G03';
|
||||
document.getElementById('fCp').value = '';
|
||||
document.getElementById('fPhone').value = '';
|
||||
document.getElementById('fEmail').value = '';
|
||||
document.getElementById('fAddress').value = '';
|
||||
document.getElementById('fPriceTier').value = '1';
|
||||
document.getElementById('fCreditLimit').value = '0';
|
||||
const safeSet = (id, v) => { const el = document.getElementById(id); if (el) el.value = v; };
|
||||
safeSet('fName', '');
|
||||
safeSet('fRfc', '');
|
||||
safeSet('fRazonSocial', '');
|
||||
safeSet('fRegimenFiscal', '');
|
||||
safeSet('fUsoCfdi', 'G03');
|
||||
safeSet('fCp', '');
|
||||
safeSet('fPhone', '');
|
||||
safeSet('fEmail', '');
|
||||
safeSet('fAddress', '');
|
||||
safeSet('fPriceTier', '1');
|
||||
safeSet('fCreditLimit', '0');
|
||||
safeSet('fMaxDiscountPct', '0');
|
||||
modal.classList.add('active');
|
||||
document.getElementById('fName').focus();
|
||||
}
|
||||
@@ -400,17 +409,19 @@ const Customers = (() => {
|
||||
if (!modal) return;
|
||||
document.getElementById('modalTitle').textContent = 'Editar Cliente';
|
||||
document.getElementById('editId').value = c.id;
|
||||
document.getElementById('fName').value = c.name || '';
|
||||
document.getElementById('fRfc').value = c.rfc || '';
|
||||
document.getElementById('fRazonSocial').value = c.razon_social || '';
|
||||
document.getElementById('fRegimenFiscal').value = c.regimen_fiscal || '';
|
||||
document.getElementById('fUsoCfdi').value = c.uso_cfdi || 'G03';
|
||||
document.getElementById('fCp').value = c.cp || '';
|
||||
document.getElementById('fPhone').value = c.phone || '';
|
||||
document.getElementById('fEmail').value = c.email || '';
|
||||
document.getElementById('fAddress').value = c.address || '';
|
||||
document.getElementById('fPriceTier').value = c.price_tier || '1';
|
||||
document.getElementById('fCreditLimit').value = c.credit_limit || 0;
|
||||
const safeSet = (id, v) => { const el = document.getElementById(id); if (el) el.value = v; };
|
||||
safeSet('fName', c.name || '');
|
||||
safeSet('fRfc', c.rfc || '');
|
||||
safeSet('fRazonSocial', c.razon_social || '');
|
||||
safeSet('fRegimenFiscal', c.regimen_fiscal || '');
|
||||
safeSet('fUsoCfdi', c.uso_cfdi || 'G03');
|
||||
safeSet('fCp', c.cp || '');
|
||||
safeSet('fPhone', c.phone || '');
|
||||
safeSet('fEmail', c.email || '');
|
||||
safeSet('fAddress', c.address || '');
|
||||
safeSet('fPriceTier', c.price_tier || '1');
|
||||
safeSet('fCreditLimit', c.credit_limit || 0);
|
||||
safeSet('fMaxDiscountPct', c.max_discount_pct || 0);
|
||||
modal.classList.add('active');
|
||||
}
|
||||
|
||||
@@ -438,6 +449,7 @@ const Customers = (() => {
|
||||
address: val('fAddress') || null,
|
||||
price_tier: parseInt(val('fPriceTier')) || 1,
|
||||
credit_limit: parseFloat(val('fCreditLimit')) || 0,
|
||||
max_discount_pct: parseFloat(val('fMaxDiscountPct')) || 0,
|
||||
};
|
||||
|
||||
const editId = val('editId');
|
||||
@@ -586,7 +598,9 @@ const Customers = (() => {
|
||||
|
||||
function injectModals() {
|
||||
// Customer Create/Edit Modal
|
||||
if (!document.getElementById('customerModal')) {
|
||||
// Always remove and re-inject to ensure latest fields are present
|
||||
const existingModal = document.getElementById('customerModal');
|
||||
if (existingModal) existingModal.remove();
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = `
|
||||
<div id="customerModal" class="modal-overlay" style="display:none;">
|
||||
@@ -646,6 +660,7 @@ const Customers = (() => {
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group"><label>Limite de Credito</label><input type="number" id="fCreditLimit" class="form-input" value="0" min="0" step="1000" /></div>
|
||||
<div class="form-group"><label>Descuento Max (%)</label><input type="number" id="fMaxDiscountPct" class="form-input" value="0" min="0" max="100" step="0.5" /></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@@ -655,9 +670,10 @@ const Customers = (() => {
|
||||
</div>
|
||||
</div>`;
|
||||
document.body.appendChild(div);
|
||||
}
|
||||
|
||||
// Statement Modal
|
||||
const existingStatement = document.getElementById('statementModal');
|
||||
if (existingStatement) existingStatement.remove();
|
||||
if (!document.getElementById('statementModal')) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = `
|
||||
@@ -772,11 +788,15 @@ const Customers = (() => {
|
||||
// Run init
|
||||
init();
|
||||
|
||||
return {
|
||||
const publicApi = {
|
||||
search, goToPage, loadCustomers,
|
||||
showDetail, selectCustomer, closeDetail,
|
||||
showCreateModal, editCurrent, closeModal, save,
|
||||
showStatement, closeStatement,
|
||||
showPaymentModal, closePayment, recordPayment,
|
||||
};
|
||||
|
||||
// Expose globally for inline HTML onclick handlers
|
||||
window.Customers = publicApi;
|
||||
return publicApi;
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user