feat(catalog): supplier catalog cleanup, fuzzy matching, and navigation fixes
- Cleaned 137+ fake engine-displacement models from supplier imports (v3/v4 scripts: Chevrolet, Ford, Chrysler, Dodge, Jeep, Nissan, etc.) - Removed 1,251+ corrupted models (INT. prefixes, year-suffix, torque specs, empty names, trailing-year variants) - Migrated supplier tables to master DB (supplier_catalog, supplier_catalog_compat, supplier_catalog_interchange) - Fixed _get_mye_ids_with_parts() to query supplier_catalog_compat from master DB so supplier-only vehicles appear for all tenants - Added fuzzy model matcher with parenthesis stripping, noise suffix removal, compact matching, prefix/substring fallback, model aliases, and ±3 year proximity - Matched compat rows: KEEP GREEN +14,152, KNADIAN +3,021, VAZLO +127,500, LUK +477, RAYBESTOS +1,743 - Added KNADIAN catalog importer with year-range expansion and future-year filtering - Added VAZLO catalog importer with position parsing and SKU-in-model cleanup - Added Keep Green, LUK, Yokomitsu, Raybestos catalog importers - Cache clearing after cleanups (_classify_cache_*, nexus:mye_ids:*, nexus:brand_mye_counts:*) Final match rates: - KEEP GREEN: 90.3% - VAZLO: 93.6% - YOKOMITSU: 100.0% - KNADIAN: 57.4% - LUK: 51.0% - RAYBESTOS: 55.9%
This commit is contained in:
65
pos/services/webhook_service.py
Normal file
65
pos/services/webhook_service.py
Normal file
@@ -0,0 +1,65 @@
|
||||
"""Webhook dispatch service for dropshipping and external integrations.
|
||||
|
||||
Sends POST requests to configured target URLs with retry logic.
|
||||
Can be called synchronously or enqueued via Celery.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
import threading
|
||||
from typing import Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _send_post(url: str, payload: dict, headers: Optional[dict] = None, timeout: int = 10):
|
||||
"""Send a POST request and return (success, status_code, response_text)."""
|
||||
default_headers = {"Content-Type": "application/json"}
|
||||
if headers:
|
||||
default_headers.update(headers)
|
||||
try:
|
||||
resp = requests.post(url, json=payload, headers=default_headers, timeout=timeout)
|
||||
success = 200 <= resp.status_code < 300
|
||||
if not success:
|
||||
logger.warning("Webhook %s returned %s: %s", url, resp.status_code, resp.text[:200])
|
||||
return success, resp.status_code, resp.text
|
||||
except requests.exceptions.Timeout:
|
||||
logger.warning("Webhook %s timed out after %ss", url, timeout)
|
||||
return False, 0, "timeout"
|
||||
except Exception as e:
|
||||
logger.warning("Webhook %s failed: %s", url, e)
|
||||
return False, 0, str(e)
|
||||
|
||||
|
||||
def dispatch_webhook_sync(target_url: str, event_type: str, payload: dict, secret: Optional[str] = None):
|
||||
"""Send webhook synchronously (use inside Celery tasks for async)."""
|
||||
full_payload = {
|
||||
"event": event_type,
|
||||
"data": payload,
|
||||
}
|
||||
headers = {}
|
||||
if secret:
|
||||
headers["X-Webhook-Secret"] = secret
|
||||
success, status, body = _send_post(target_url, full_payload, headers=headers)
|
||||
return {"success": success, "status": status, "body": body[:500]}
|
||||
|
||||
|
||||
def dispatch_webhooks_bulk(target_urls: list[str], event_type: str, payload: dict, secret: Optional[str] = None):
|
||||
"""Dispatch to multiple URLs concurrently using threads."""
|
||||
results = []
|
||||
threads = []
|
||||
|
||||
def _send(url):
|
||||
result = dispatch_webhook_sync(url, event_type, payload, secret=secret)
|
||||
results.append({"url": url, **result})
|
||||
|
||||
for url in target_urls:
|
||||
t = threading.Thread(target=_send, args=(url,))
|
||||
t.start()
|
||||
threads.append(t)
|
||||
|
||||
for t in threads:
|
||||
t.join(timeout=15)
|
||||
|
||||
return results
|
||||
Reference in New Issue
Block a user