Yoklayıcı Örnek Kodları▾
Talep ile Aktarım

Yoklayıcı Örnek Kodları

Node.js, C# ve Python ile yoklama döngüsü: GET, claim, complete, fail.

Son güncelleme: 21 Eylül 2026 · Sözleşme sürümü 1.0.0

Üç dilde aynı döngü: GET → claim → complete {veri} ya da fail (ekstrede complete'ten önce statement/rows parçaları). Anahtar MAI_API_KEY ortam değişkeninden okunur; kodda tutmayın. Önce test ortamında mai_test_ anahtarıyla çalıştırın; talebi panelden bırakıp kartın "Geldi" demesini bekleyin.

Node.js — yoklama döngüsü (dört işlem: GET · claim · complete{veri} · fail)▾
const BASE = "https://mutabakat.ai/api/v1";
const H = { Authorization: `Bearer ${process.env.MAI_API_KEY}`, "Content-Type": "application/json" };
const api = (method, path, body, idem) =>
  fetch(BASE + path, { method, headers: { ...H, ...(idem ? { "Idempotency-Key": idem } : {}) }, body: body ? JSON.stringify(body) : undefined })
    .then(async (r) => ({ status: r.status, retryAfter: Number(r.headers.get("retry-after") ?? 0), body: await r.json().catch(() => null) }));
const transient = (status) => status === 429 || status >= 500;   // geçici: talebi KAPATMAYIN, sonraki turda yeniden deneyin

// ERP'den veriyi hazırla → tek çağrıyla teslim et; dönem, cari ve başlık talepten türetilir.
async function fulfil(req) {
  const claim = await api("POST", `/erp-requests/${req.id}/claim`);                              // "aldım, hazırlıyorum" (isteğe bağlı)
  if (claim.status !== 200) { console.warn(req.id, "claim", claim.status, claim.body?.error?.code, "— atlandı"); return; } // 409: başka program aldı / süresi doldu; 429/5xx: sonraki tur
  let body;
  try {
    if (req.kind === "statement") {
      const { counterparty, period_start, period_end } = req.params;                   // { external_id, name, tax_number }
      const rows = await erp.lineItems(counterparty.external_id, period_start, period_end); // [{ date, description, reference_no, amount | debit/credit }]
      // Ekstre satırları PARÇA PARÇA gider (≤20.000 satır/parça, toplam ≤250.000); sonra complete bitirir.
      const PART = 20000, total_parts = Math.max(1, Math.ceil(rows.length / PART));
      for (let part = 1; part <= total_parts; part++) {
        const chunk = await api("POST", `/erp-requests/${req.id}/statement/rows`, { part, total_parts, rows: rows.slice((part - 1) * PART, part * PART) }, `part:${req.id}:${part}`); // Idempotency-Key parça başına
        if (chunk.status !== 200) {
          if (transient(chunk.status)) console.warn(req.id, "parça", part, "geçici hata", chunk.status, "→ sonraki turda aynı parçadan devam"); // rezervasyon pending'e döner; fail ÇAĞIRMAYIN
          else console.error(req.id, "parça", part, "kalıcı ret", chunk.status, chunk.body?.error);                                  // 400/413/422: sunucu talebi failed yaptı
          return;
        }
      }
      body = { statement: { total_parts } };
    } else if (req.kind === "balances") {
      const items = await erp.openBalances(req.params.period_date);
      body = { balances: { records: items.map((b) => ({ counterparty: { external_id: b.code, name: b.name, tax_number: b.taxNo }, amount: Math.abs(b.balance).toFixed(2), direction: b.balance >= 0 ? "debit" : "credit" })) } };
    } else {
      body = { counterparties: await erp.allVendors() };                                // [{ external_id, name, tax_number, email }] ≤ 10.000
    }
  } catch (err) {
    // Veri ÜRETİLEMEDİ (cari yok, ERP kapalı…): kalıcı iş hatası — talebi kapatın, sebep panelde görünür.
    await api("POST", `/erp-requests/${req.id}/fail`, { reason: String(err.message).slice(0, 300) });
    return;
  }
  const res = await api("POST", `/erp-requests/${req.id}/complete`, body, `complete:${req.id}`); // Idempotency-Key: talep başına SABİT
  if (res.status === 200) { console.log(req.kind, "teslim edildi", res.body.delivery); return; }   // { row_count | campaign_id | accepted… }
  if (transient(res.status)) { console.warn(req.id, "geçici hata", res.status, "→ sonraki turda yeniden (Retry-After:", res.retryAfter, "sn)"); return; } // rezervasyon pending'e döner; fail ÇAĞIRMAYIN
  console.error(req.id, "kalıcı ret", res.status, res.body?.error);                     // 400/402/413/422: sunucu talebi zaten failed yaptı; aynı veriyi yeniden göndermeyin
}

async function loop() {
  for (;;) {
    let wait = 20;
    try {
      const { status, body, retryAfter } = await api("GET", "/erp-requests");
      if (status === 200) {
        if (body.fetch_mode !== "request") console.warn("Firma 'Talep ile aktarım' seçmemiş; talep gelmeyecek:", body.fetch_mode);
        for (const req of body.data) await fulfil(req).catch((err) => console.error(req.id, "ağ hatası; sonraki turda yeniden denenecek:", err.message));
        wait = body.poll_after_seconds ?? 20;
      } else if (status === 429) wait = retryAfter || 60;
    } catch (err) { console.error("yoklama başarısız (ağ?):", err.message); }          // döngü ölmez
    await new Promise((r) => setTimeout(r, wait * 1000));
  }
}
loop();
C# — yoklama döngüsü (Windows servisi / zamanlanmış görev)▾
var http = new HttpClient { BaseAddress = new Uri("https://mutabakat.ai/api/v1/") };
http.DefaultRequestHeaders.Authorization = new("Bearer", Environment.GetEnvironmentVariable("MAI_API_KEY"));
static bool Transient(HttpStatusCode s) => (int)s == 429 || (int)s >= 500;   // geçici: talebi KAPATMAYIN, sonraki turda yeniden deneyin

async Task Fulfil(ErpRequest req)
{
    var claim = await http.PostAsync($"erp-requests/{req.Id}/claim", null);          // "aldım, hazırlıyorum" (isteğe bağlı)
    if (!claim.IsSuccessStatusCode) { Log.Warn($"{req.Id} claim {(int)claim.StatusCode} — atlandı"); return; } // 409 başka program aldı; 429/5xx sonraki tur
    object body;
    try
    {
        if (req.Kind == "statement")
        {
            // Ekstre satırları PARÇA PARÇA gider (≤20.000 satır/parça, toplam ≤250.000); sonra complete bitirir.
            var rows = Erp.LineItems(req.Params.Counterparty.ExternalId, req.Params.PeriodStart, req.Params.PeriodEnd);
            const int Part = 20000; var totalParts = Math.Max(1, (rows.Count + Part - 1) / Part);
            for (var part = 1; part <= totalParts; part++)
            {
                var chunk = new HttpRequestMessage(HttpMethod.Post, $"erp-requests/{req.Id}/statement/rows") { Content = JsonContent.Create(new { part, total_parts = totalParts, rows = rows.Skip((part - 1) * Part).Take(Part) }) };
                chunk.Headers.Add("Idempotency-Key", $"part:{req.Id}:{part}");                   // parça başına
                var sent = await http.SendAsync(chunk);
                if (!sent.IsSuccessStatusCode) { Log.Warn($"{req.Id} parça {part} {(int)sent.StatusCode} — {(Transient(sent.StatusCode) ? "sonraki turda aynı parçadan devam" : "kalıcı ret, talep failed")}"); return; }
            }
            body = new { statement = new { total_parts = totalParts } };
        }
        else
        {
            body = req.Kind switch
            {
                "balances" => new { balances = new { records = Erp.OpenBalances(req.Params.PeriodDate) } },
                _          => new { counterparties = Erp.AllVendors() },
            };
        }
    }
    catch (Exception ex)
    {
        // Veri ÜRETİLEMEDİ (ERP kapalı, cari yok): kalıcı iş hatası — talebi kapatın, sebep panelde görünür.
        await http.PostAsJsonAsync($"erp-requests/{req.Id}/fail", new { reason = ex.Message[..Math.Min(300, ex.Message.Length)] });
        return;
    }
    var deliver = new HttpRequestMessage(HttpMethod.Post, $"erp-requests/{req.Id}/complete") { Content = JsonContent.Create(body) };
    deliver.Headers.Add("Idempotency-Key", $"complete:{req.Id}");   // zorunlu; talep başına SABİT (tekrar deneme ikinci kez işlenmez)
    var res = await http.SendAsync(deliver);
    if (res.IsSuccessStatusCode) { Log.Info($"{req.Kind} teslim edildi"); return; }   // 200 → { data, delivery: { row_count | campaign_id | accepted… } }
    if (Transient(res.StatusCode)) { Log.Warn($"{req.Id} geçici hata {(int)res.StatusCode}; sonraki turda yeniden (Retry-After: {res.Headers.RetryAfter})"); return; } // rezervasyon pending'e döner; fail ÇAĞIRMAYIN
    Log.Error($"{req.Id} kalıcı ret {(int)res.StatusCode}: {await res.Content.ReadAsStringAsync()}");   // 400/402/413/422: sunucu talebi failed yaptı; aynı veriyi yeniden göndermeyin
}

while (true)
{
    var wait = 20;
    try
    {
        var list = await http.GetFromJsonAsync<ErpRequestList>("erp-requests");
        if (list!.FetchMode != "request") Log.Warn($"Firma 'Talep ile aktarım' seçmemiş ({list.FetchMode}); talep gelmeyecek.");
        foreach (var req in list.Data)
        {
            try { await Fulfil(req); }
            catch (HttpRequestException ex) { Log.Warn($"{req.Id} ağ hatası; sonraki turda yeniden: {ex.Message}"); }
        }
        wait = list.PollAfterSeconds;
    }
    catch (Exception ex) { Log.Error($"yoklama başarısız (ağ?): {ex.Message}"); }   // döngü ölmez
    await Task.Delay(TimeSpan.FromSeconds(wait));
}
Python — yoklama döngüsü▾
import os, time, requests

BASE = "https://mutabakat.ai/api/v1"
H = {"Authorization": f"Bearer {os.environ['MAI_API_KEY']}"}

def api(method, path, json=None, idem=None):
    headers = {**H, **({"Idempotency-Key": idem} if idem else {})}
    return requests.request(method, BASE + path, json=json, headers=headers, timeout=120)

def transient(status):  # geçici: talebi KAPATMAYIN, sonraki turda yeniden deneyin
    return status == 429 or status >= 500

def fulfil(req):
    claim = api("POST", f"/erp-requests/{req['id']}/claim")  # "aldım, hazırlıyorum" (isteğe bağlı)
    if claim.status_code != 200:
        print(req["id"], "claim", claim.status_code, "— atlandı")  # 409 başka program aldı; 429/5xx sonraki tur
        return
    try:
        p = req["params"]
        if req["kind"] == "statement":
            # Ekstre satırları PARÇA PARÇA gider (≤20.000 satır/parça, toplam ≤250.000); sonra complete bitirir.
            rows = erp.line_items(p["counterparty"]["external_id"], p["period_start"], p["period_end"])
            PART = 20000
            total_parts = max(1, -(-len(rows) // PART))
            for part in range(1, total_parts + 1):
                chunk = api("POST", f"/erp-requests/{req['id']}/statement/rows", {"part": part, "total_parts": total_parts, "rows": rows[(part - 1) * PART: part * PART]}, f"part:{req['id']}:{part}")  # Idempotency-Key parça başına
                if chunk.status_code != 200:
                    print(req["id"], "parça", part, chunk.status_code, "— sonraki turda aynı parçadan devam" if transient(chunk.status_code) else "— kalıcı ret, talep failed")
                    return
            body = {"statement": {"total_parts": total_parts}}
        elif req["kind"] == "balances":
            body = {"balances": {"records": erp.open_balances(p["period_date"])}}
        else:
            body = {"counterparties": erp.all_vendors()}
    except Exception as exc:
        # Veri ÜRETİLEMEDİ (ERP kapalı, cari yok): kalıcı iş hatası — talebi kapatın, sebep panelde görünür.
        api("POST", f"/erp-requests/{req['id']}/fail", {"reason": str(exc)[:300]})
        return
    r = api("POST", f"/erp-requests/{req['id']}/complete", body, f"complete:{req['id']}")  # Idempotency-Key talep başına SABİT
    if r.status_code == 200:
        print(req["kind"], "teslim edildi", r.json()["delivery"])  # {"row_count" | "campaign_id" | "accepted"...}
    elif transient(r.status_code):
        print(req["id"], "geçici hata", r.status_code, "→ sonraki turda yeniden (Retry-After:", r.headers.get("Retry-After"), ")")  # fail ÇAĞIRMAYIN
    else:
        print(req["id"], "kalıcı ret", r.status_code, r.text[:300])  # sunucu talebi failed yaptı; aynı veriyi yeniden göndermeyin

while True:
    wait = 20
    try:
        res = api("GET", "/erp-requests")
        if res.status_code == 200:
            body = res.json()
            if body.get("fetch_mode") != "request":
                print("Firma 'Talep ile aktarım' seçmemiş; talep gelmeyecek:", body.get("fetch_mode"))
            for req in body.get("data", []):
                try:
                    fulfil(req)
                except requests.RequestException as exc:
                    print(req["id"], "ağ hatası; sonraki turda yeniden:", exc)
            wait = body.get("poll_after_seconds", 20)
        elif res.status_code == 429:
            wait = int(res.headers.get("Retry-After", 60))
    except requests.RequestException as exc:
        print("yoklama başarısız (ağ?):", exc)  # döngü ölmez
    time.sleep(wait)