feat(sync): add GoMag API client with Phase 0 auto-download
- New gomag_client.py service: async httpx client that downloads orders from GoMag API with full pagination and 1s rate-limit sleep - config.py: add GOMAG_API_KEY, GOMAG_API_SHOP, GOMAG_ORDER_DAYS_BACK, GOMAG_LIMIT, GOMAG_API_URL settings - sync_service.py: Phase 0 downloads fresh orders before reading JSONs; graceful skip if API keys not configured - start.sh: auto-detect INSTANTCLIENTPATH from .env, fallback to thin mode - .env.example: document GoMag API variables Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
88
api/app/services/gomag_client.py
Normal file
88
api/app/services/gomag_client.py
Normal file
@@ -0,0 +1,88 @@
|
||||
"""GoMag API client - downloads orders and saves them as JSON files."""
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
import httpx
|
||||
|
||||
from ..config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def download_orders(
|
||||
json_dir: str,
|
||||
days_back: int = None,
|
||||
log_fn: Callable[[str], None] = None,
|
||||
) -> dict:
|
||||
"""Download orders from GoMag API and save as JSON files.
|
||||
|
||||
Returns dict with keys: pages, total, files (list of saved file paths).
|
||||
If API keys are not configured, returns immediately with empty result.
|
||||
"""
|
||||
def _log(msg: str):
|
||||
logger.info(msg)
|
||||
if log_fn:
|
||||
log_fn(msg)
|
||||
|
||||
if not settings.GOMAG_API_KEY or not settings.GOMAG_API_SHOP:
|
||||
_log("GoMag API keys neconfigurați, skip download")
|
||||
return {"pages": 0, "total": 0, "files": []}
|
||||
|
||||
if days_back is None:
|
||||
days_back = settings.GOMAG_ORDER_DAYS_BACK
|
||||
|
||||
start_date = (datetime.now() - timedelta(days=days_back)).strftime("%Y-%m-%d")
|
||||
out_dir = Path(json_dir)
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
headers = {
|
||||
"Apikey": settings.GOMAG_API_KEY,
|
||||
"ApiShop": settings.GOMAG_API_SHOP,
|
||||
"User-Agent": "Mozilla/5.0",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
saved_files = []
|
||||
total_orders = 0
|
||||
total_pages = 1
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
async with httpx.AsyncClient(timeout=30) as client:
|
||||
page = 1
|
||||
while page <= total_pages:
|
||||
params = {
|
||||
"startDate": start_date,
|
||||
"page": page,
|
||||
"limit": settings.GOMAG_LIMIT,
|
||||
}
|
||||
try:
|
||||
response = await client.get(settings.GOMAG_API_URL, headers=headers, params=params)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
except httpx.HTTPError as e:
|
||||
_log(f"GoMag API eroare pagina {page}: {e}")
|
||||
break
|
||||
except Exception as e:
|
||||
_log(f"GoMag eroare neașteptată pagina {page}: {e}")
|
||||
break
|
||||
|
||||
# Update totals from first page response
|
||||
if page == 1:
|
||||
total_orders = int(data.get("total", 0))
|
||||
total_pages = int(data.get("pages", 1))
|
||||
_log(f"GoMag: {total_orders} comenzi în {total_pages} pagini (startDate={start_date})")
|
||||
|
||||
filename = out_dir / f"gomag_orders_page{page}_{timestamp}.json"
|
||||
filename.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
saved_files.append(str(filename))
|
||||
_log(f"GoMag: pagina {page}/{total_pages} salvată → {filename.name}")
|
||||
|
||||
page += 1
|
||||
if page <= total_pages:
|
||||
await asyncio.sleep(1)
|
||||
|
||||
return {"pages": total_pages, "total": total_orders, "files": saved_files}
|
||||
@@ -4,7 +4,7 @@ import logging
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from . import order_reader, validation_service, import_service, sqlite_service, invoice_service
|
||||
from . import order_reader, validation_service, import_service, sqlite_service, invoice_service, gomag_client
|
||||
from ..config import settings
|
||||
from .. import database
|
||||
|
||||
@@ -122,9 +122,22 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
|
||||
f"Inceput: {started_dt.strftime('%d.%m.%Y %H:%M:%S')}",
|
||||
""
|
||||
]
|
||||
_log_line(run_id, "Citire fisiere JSON...")
|
||||
|
||||
json_dir = settings.JSON_OUTPUT_DIR
|
||||
|
||||
try:
|
||||
# Phase 0: Download orders from GoMag API
|
||||
_update_progress("downloading", "Descărcare comenzi din GoMag API...")
|
||||
_log_line(run_id, "Descărcare comenzi din GoMag API...")
|
||||
dl_result = await gomag_client.download_orders(
|
||||
json_dir, log_fn=lambda msg: _log_line(run_id, msg)
|
||||
)
|
||||
if dl_result["files"]:
|
||||
_log_line(run_id, f"GoMag: {dl_result['total']} comenzi în {dl_result['pages']} pagini → {len(dl_result['files'])} fișiere")
|
||||
|
||||
_update_progress("reading", "Citire fisiere JSON...")
|
||||
_log_line(run_id, "Citire fisiere JSON...")
|
||||
|
||||
# Step 1: Read orders and sort chronologically (oldest first - R3)
|
||||
orders, json_count = order_reader.read_json_orders()
|
||||
orders.sort(key=lambda o: o.date or '')
|
||||
|
||||
Reference in New Issue
Block a user