181 lines
5.0 KiB
Python
181 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Pobiera dane e-commerce ze Shoper API (zamówienia, przychody, AOV).
|
|
|
|
Użycie:
|
|
python scripts/reports/fetch_shoper_data.py --domain innsi.pl --month 2026-02
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
import io
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
|
|
if __name__ == "__main__":
|
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
|
|
|
|
import requests
|
|
ROOT = Path(__file__).parent.parent.parent
|
|
sys.path.insert(0, str(ROOT))
|
|
from src.gads_v2.config import load_env
|
|
|
|
load_env(ROOT / ".env")
|
|
|
|
|
|
def _shoper_auth(domain):
|
|
"""Authenticate to Shoper API, return (base_url, headers)."""
|
|
base = os.environ[f"SHOPER_API_URL_{domain}"].rstrip("/")
|
|
login = os.environ[f"SHOPER_API_LOGIN_{domain}"]
|
|
password = os.environ[f"SHOPER_API_PASSWORD_{domain}"]
|
|
|
|
r = requests.post(f"{base}/auth", auth=(login, password), timeout=15)
|
|
r.raise_for_status()
|
|
token = r.json()["access_token"]
|
|
return base, {"Authorization": f"Bearer {token}"}
|
|
|
|
|
|
def _collect_orders_for_month(base, headers, target_month):
|
|
"""Paginate orders (newest first) and collect all for target_month."""
|
|
orders = []
|
|
page = 1
|
|
found = False
|
|
|
|
while page < 100:
|
|
r = requests.get(
|
|
f"{base}/orders?limit=50&page={page}&order=date+desc",
|
|
headers=headers, timeout=20,
|
|
)
|
|
r.raise_for_status()
|
|
data = r.json()
|
|
page_orders = data.get("list", [])
|
|
if not page_orders:
|
|
break
|
|
|
|
for o in page_orders:
|
|
month = o["date"][:7]
|
|
if month == target_month:
|
|
found = True
|
|
orders.append(o)
|
|
elif found and month < target_month:
|
|
return orders
|
|
|
|
page += 1
|
|
|
|
return orders
|
|
|
|
|
|
def fetch_shoper_ecommerce(domain, month, prev_month):
|
|
"""Fetch Shoper e-commerce data for month and previous month.
|
|
|
|
Returns dict compatible with ga4.ecommerce structure.
|
|
"""
|
|
base, headers = _shoper_auth(domain)
|
|
|
|
# Collect orders for both months in one pass
|
|
current_orders = []
|
|
prev_orders = []
|
|
page = 1
|
|
found_current = False
|
|
found_prev = False
|
|
passed_prev = False
|
|
|
|
while page < 200 and not passed_prev:
|
|
r = requests.get(
|
|
f"{base}/orders?limit=50&page={page}&order=date+desc",
|
|
headers=headers, timeout=20,
|
|
)
|
|
r.raise_for_status()
|
|
data = r.json()
|
|
page_orders = data.get("list", [])
|
|
if not page_orders:
|
|
break
|
|
|
|
for o in page_orders:
|
|
m = o["date"][:7]
|
|
if m == month:
|
|
found_current = True
|
|
current_orders.append(o)
|
|
elif m == prev_month:
|
|
found_prev = True
|
|
prev_orders.append(o)
|
|
elif found_prev and m < prev_month:
|
|
passed_prev = True
|
|
break
|
|
|
|
page += 1
|
|
|
|
def _summarize(orders):
|
|
if not orders:
|
|
return {"transactions": 0, "revenue": 0.0, "aov": 0.0}
|
|
total = sum(float(o["sum"]) for o in orders)
|
|
count = len(orders)
|
|
return {
|
|
"transactions": count,
|
|
"revenue": round(total, 2),
|
|
"aov": round(total / count, 2),
|
|
}
|
|
|
|
current = _summarize(current_orders)
|
|
previous = _summarize(prev_orders)
|
|
|
|
# Daily breakdown
|
|
daily_map = defaultdict(lambda: {"revenue": 0.0, "transactions": 0})
|
|
for o in current_orders:
|
|
day = o["date"][:10]
|
|
daily_map[day]["revenue"] += float(o["sum"])
|
|
daily_map[day]["transactions"] += 1
|
|
|
|
daily = []
|
|
for day in sorted(daily_map.keys()):
|
|
daily.append({
|
|
"date": day,
|
|
"revenue": round(daily_map[day]["revenue"], 2),
|
|
"transactions": daily_map[day]["transactions"],
|
|
})
|
|
|
|
# MoM change
|
|
def _pct(cur, prev):
|
|
if prev == 0:
|
|
return 100.0 if cur > 0 else 0.0
|
|
return round(((cur - prev) / prev) * 100, 1)
|
|
|
|
mom = {
|
|
"transactions_pct": _pct(current["transactions"], previous["transactions"]),
|
|
"revenue_pct": _pct(current["revenue"], previous["revenue"]),
|
|
"aov_pct": _pct(current["aov"], previous["aov"]),
|
|
}
|
|
|
|
return {
|
|
"source": "shoper",
|
|
"current": current,
|
|
"previous": previous,
|
|
"mom_change": mom,
|
|
"daily": daily,
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Pobierz dane e-commerce ze Shoper")
|
|
parser.add_argument("--domain", required=True)
|
|
parser.add_argument("--month", required=True, help="YYYY-MM")
|
|
args = parser.parse_args()
|
|
|
|
# Calculate prev month
|
|
y, m = map(int, args.month.split("-"))
|
|
if m == 1:
|
|
prev = f"{y-1}-12"
|
|
else:
|
|
prev = f"{y}-{m-1:02d}"
|
|
|
|
data = fetch_shoper_ecommerce(args.domain, args.month, prev)
|
|
print(json.dumps(data, indent=2, ensure_ascii=False))
|
|
cur = data["current"]
|
|
print(f"\n{args.month}: {cur['transactions']} zamówień, {cur['revenue']:.2f} PLN, AOV {cur['aov']:.2f} PLN")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|