Masa Kapatma Gate’i — table.close ✓ Canlı

Masa (dine-in) hesabı kapatılmadan ÖNCE araya giren before-hook. Restoran "Hesabı Kapat" dediğinde eklentinize senkron sorulur; allow/deny dönersiniz (deny → kapatma durur, message personele gösterilir). Ödeme/sadakat/fatura entegrasyonları için kapanış anını yakalar.

← Hook'lar

Genel

Hook keytable.close
Scopehooks:table.close (consent-gated)
Manifesthooks: [{ action, blocking, ui, webhookPath?, includeData? }]
Hedefmanifest webhookUrl (veya webhookPath)
MethodPOST (senkron, blocking)
Yanıt bütçesitimeoutMs (varsayılan ~10s); aşımda failMode

Manifest hook config

{
  "action": "table.close",
  "blocking": true,                // akışı durdurur (tüm hook'lar blocking)
  "ui": { "kind": "none" },        // none → doğrudan webhook'a sorulur; form/iframe da olur
  "webhookPath": "/hooks/table-close"  // ops — boşsa ana webhookUrl
}

failMode

table.close varsayılanı closed: cevap gelmeden masa kapatma DURUR. open opt-in’dir (h.failMode:"open"). Sunucunuz güvenilir değilse open düşünün.

Restomenum → eklenti (request body)

{
  "type": "hook", "event": "table.close", "stage": "before",
  "tenantId": "gSN3AOhdjzMhb47pYE4bY1qlJbH3",
  "target": { "type": "table", "id": "masa-1" },
  "data": {                                     // YALNIZ includeData:true ise — kapanış-anı snapshot'ı
    "tableId": "masa-1", "tableName": "Masa 1", "docNo": 4, "desing": "Bahçe",
    "location": "Masa 1", "personCount": 0,
    "orders": [ { "id": "masa-1-5be4", "title": "Frozen", "quantity": 1, "lineTotal": 14 } ],
    "payments": [ { "methodId": "29-cash", "title": "nakit", "amount": 14, "isDiscount": false } ],
    "total": 14, "paid": 14, "totalDiscount": 0
  },
  "formData": { "<sizin-field-keyiniz>": "<personel-degeri>" },   // DİNAMİK: formunuzdaki key'ler; yalnız ui.kind form/iframe ise
  "actor": { "userId": "test-uid", "role": "staff" },
  "timeoutMs": 8000, "occurredAt": 1785514833229, "hookId": "hk_058f707e-…"
}
AlanAçıklama
target.idMasa id’si. Dolu hesabı tables/get?id= ile çekin (gate yalnız id taşır).
dataYalnız includeData:true ise. Kapanış-anı snapshot’ı: tableId/tableName/docNo/desing/location/personCount + orders[] + payments[] + total/paid/totalDiscount. DİKKAT: tables/get’ten FARKLI olarak ödeme kırılımı (payments[]) da gelir — kapanışta okuma ucundan çekemeyeceğin veri (masa kapanınca silinir).
data.orders[]Adisyon satırları: { id, title, quantity, lineTotal, … } — lineTotal’ı Restomenum hesaplar.
data.payments[]Ödeme kırılımı: { methodId, title, amount, isDiscount }. methodId tenant’ın ödeme yöntemi id’sidir → payment-methods/list ile eşleyip cash bayrağını oradan oku (nakit/nakit-dışı ayrımını methodId metninden ÇIKARMA). isDiscount:true satırları indirimdir, tahsilat değil.
timeoutMsCevap bütçen (manifest’ten; table.close varsayılanı 10 sn, 1–30 sn arasına clamp’lenir). Aşarsan yanıtın değerlendirilmez → failMode.
formDataYalnız ui.kind form/iframe ise gelir. İçeriği DİNAMİKTİR: anahtarlar SİZİN formunuzda tanımladığınız field key’ler, değerler personelin girdiği değerlerdir (her eklentiye özel; sabit şema yok). ui.kind none ise hiç gelmez.
actorKapatmayı başlatan kullanıcı {userId, role} — imzalı gövdede, güvenilir.

Eklenti → Restomenum (response)

allow — kapatma devam
{ "decision": "allow" }
deny — engelle (message personele gösterilir)
{ "decision": "deny", "message": "Önce fişi yazdırın." }
allow + receiptExtras — mali blok fişe (⏳ yakında)
{ "decision": "allow",
  "receiptExtras": [
    { "key": "tse.qr",               "type": "qr",   "value": "V0;KassenSN;Kassenbeleg-V1;…" },
    { "key": "tse.txNumber",         "type": "text", "label": "Beleg-Nr",   "value": "366" },
    { "key": "tse.signatureCounter", "type": "text", "label": "Sig-Zähler", "value": "774" }
  ] }
  • decision: allow (kapatma devam) / deny (durdur; message personele gösterilir).
  • Geçersiz/boş cevap veya timeout → failMode uygulanır.
  • receiptExtras (⏳ yakında): allow yanıtında dönen öğeler kapanış yazılmadan ÖNCE adisyona işlenir ve fişe basılabilir (KassenSichV/TSE mali bloğu, e-arşiv belge no, sadakat satırı) — karar ile veri aynı yanıtta gelir. deny/pending’de HİÇBİR ŞEY yazılmaz; aynı key üzerine yazılmaz; tse.* için capability:fiscal.de:provide şart. Detay: Fişe Ek Alanlar.

Hata mesajları (hook çağrısı)

messageAnlam
plugin.hook.targetNotFoundMasa bulunamadı.
plugin.hook.notRegisteredManifest’inizde bu hook yok.
plugin.scope.deniedhooks:table.close onaylı değil.
plugin.hook.inactiveKurulum pasif (kill-switch / billing / connect).

Güvenlik

  • İmza: X-Restomenum-Signature (HMAC, ±5dk) — webhook/action ile aynı şema; type:"hook" ile ayırt edin.
  • Gate yalnız target.id taşır → dolu hesabı tables/get ile çekin; cross-tenant kontrolü yapın.
  • Bir action başına tenant’ta yalnız bir blocking hook (çakışma → ilk aktif kurulum sahiplenir).