feat: robust ML publish with pre-flight, preview, validation, async

- Add /inventory-check endpoint for local pre-flight validation
- Add /listings/validate endpoint using ML /items/validate API
- Add /categories/<id>/attributes endpoint for required attrs
- Add /listings/async + polling for background publishing via Celery
- Editable preview: title (0/60 counter), price, stock per item
- Pre-flight checks: image, stock, price, duplicate detection
- Image upload directly from publish modal (uses existing /items/<id>/image)
- Dynamic required attributes form based on selected ML category
- Frontend: validate button, async polling with progress, detailed error display
- Backend: build_item_payload supports custom_title, extra_attributes
This commit is contained in:
2026-05-26 04:37:05 +00:00
parent 4866823ba9
commit b314a781a1
8 changed files with 706 additions and 108 deletions

View File

@@ -13,7 +13,7 @@
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
<meta name="theme-color" content="#F5A623" />
<link rel="stylesheet" href="/pos/static/css/inventory.css?v=4">
<link rel="stylesheet" href="/pos/static/css/inventory.css?v=6">
</head>
<body>
@@ -846,9 +846,12 @@
</div>
<div class="inv-modal__body">
<div id="meliPublishSelectedCount" style="font-size:var(--text-caption);color:var(--color-text-muted);margin-bottom:var(--space-3);">0 productos seleccionados</div>
<div id="meliPublishItemsPreview" style="max-height:200px;overflow-y:auto;border:1px solid var(--color-border);border-radius:var(--radius-md);padding:var(--space-3);margin-bottom:var(--space-4);">
<p style="color:var(--color-text-muted);font-size:var(--text-caption);">Selecciona productos del inventario para ver el preview.</p>
<!-- Pre-flight checks & editable preview -->
<div id="meliPublishItemsPreview" style="max-height:320px;overflow-y:auto;margin-bottom:var(--space-4);">
<p style="color:var(--color-text-muted);font-size:var(--text-caption);">Cargando preview...</p>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-bottom:var(--space-4);">
<div class="inv-field">
<label>Categoría ML *</label>
@@ -874,10 +877,20 @@
<small style="color:var(--color-text-muted);font-size:var(--text-caption);">Tu cuenta requiere ME2 obligatoriamente.</small>
</div>
</div>
<div id="meliPublishResult" style="min-height:1.5em;"></div>
<!-- Dynamic required attributes section -->
<div id="meliAttrsSection" style="display:none;">
<div class="meli-attrs-section">
<strong style="font-size:var(--text-body-sm);">Atributos requeridos para esta categoría</strong>
<div id="meliAttrsGrid" class="meli-attrs-grid"></div>
</div>
</div>
<div id="meliPublishResult" style="min-height:1.5em;margin-top:var(--space-3);"></div>
</div>
<div class="inv-modal__footer">
<button class="btn btn--ghost" onclick="closeMeliPublishModal()">Cancelar</button>
<button class="btn btn--secondary" id="meliValidateBtn" onclick="validateMeliPublish()">Validar con ML</button>
<button class="btn btn--meli" id="meliPublishBtn" onclick="executeMeliPublish()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg> Publicar</button>
</div>
</div>
@@ -895,7 +908,7 @@
<script src="/pos/static/js/pos-utils.js" defer></script>
<script src="/pos/static/js/sidebar.js" defer></script>
<script src="/pos/static/js/virtual-scroll.js" defer></script>
<script src="/pos/static/js/inventory.js?v=10" defer></script>
<script src="/pos/static/js/inventory.js?v=12" defer></script>
<script src="/pos/static/js/offline-banner.js" defer></script>
<script src="/pos/static/js/sync-engine.js" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>