- 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
83 lines
2.7 KiB
Python
83 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Sync Strada inventory to marketplace warehouse_inventory.
|
|
Matches by OEM part_number and cross-references.
|
|
"""
|
|
import os, sys, io
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'pos'))
|
|
from tenant_db import get_tenant_conn, get_master_conn
|
|
|
|
BODEGA_ID = 7
|
|
USER_ID = 1 # admin user in master DB
|
|
CURRENCY = 'MXN'
|
|
|
|
def main():
|
|
tenant_conn = get_tenant_conn(28)
|
|
tenant_cur = tenant_conn.cursor()
|
|
|
|
# Get active inventory with stock
|
|
tenant_cur.execute("""
|
|
SELECT i.id, i.part_number, i.price_1, i.cost, COALESCE(iss.stock, 0) as stock
|
|
FROM inventory i
|
|
LEFT JOIN inventory_stock_summary iss ON iss.inventory_id = i.id
|
|
WHERE i.is_active = true
|
|
""")
|
|
items = {r[0]: {'pn': r[1], 'price': r[2], 'cost': r[3], 'stock': r[4]} for r in tenant_cur.fetchall()}
|
|
print(f"Active inventory: {len(items)}")
|
|
|
|
tenant_cur.close()
|
|
tenant_conn.close()
|
|
|
|
# Get catalog mappings from master
|
|
master = get_master_conn()
|
|
master_cur = master.cursor()
|
|
|
|
master_cur.execute("SELECT id_part, oem_part_number FROM parts WHERE oem_part_number IS NOT NULL")
|
|
oem_map = {r[1]: r[0] for r in master_cur.fetchall()}
|
|
|
|
master_cur.execute("SELECT part_id, cross_reference_number FROM part_cross_references")
|
|
cross_map = {r[1]: r[0] for r in master_cur.fetchall()}
|
|
|
|
print(f"OEM parts: {len(oem_map)}, Cross-references: {len(cross_map)}")
|
|
|
|
# Build match list
|
|
matched = []
|
|
seen_parts = set()
|
|
for item_id, data in items.items():
|
|
pn = data['pn']
|
|
raw = pn.split('-', 1)[1] if '-' in pn else pn
|
|
|
|
part_id = oem_map.get(raw) or oem_map.get(pn) or cross_map.get(raw) or cross_map.get(pn)
|
|
if part_id and part_id not in seen_parts:
|
|
seen_parts.add(part_id)
|
|
price = data['price'] or data['cost'] or 0
|
|
stock = data['stock'] or 0
|
|
matched.append((USER_ID, part_id, price, stock, 1, 'Principal', BODEGA_ID, CURRENCY))
|
|
|
|
print(f"Matched items: {len(matched)}")
|
|
|
|
if not matched:
|
|
print("Nothing to sync")
|
|
return
|
|
|
|
# Bulk insert via COPY
|
|
csv_buffer = io.StringIO()
|
|
for row in matched:
|
|
csv_buffer.write(','.join(str(c) for c in row) + '\n')
|
|
csv_buffer.seek(0)
|
|
|
|
master_cur.copy_expert(
|
|
"""COPY warehouse_inventory (user_id, part_id, price, stock_quantity, min_order_quantity, warehouse_location, bodega_id, currency)
|
|
FROM STDIN WITH (FORMAT CSV)""",
|
|
csv_buffer
|
|
)
|
|
|
|
master.commit()
|
|
master_cur.close()
|
|
master.close()
|
|
|
|
print(f"Synced {len(matched)} items to warehouse_inventory")
|
|
|
|
if __name__ == '__main__':
|
|
main()
|