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 key | table.close |
| Scope | hooks:table.close (consent-gated) |
| Manifest | hooks: [{ action, blocking, ui, webhookPath?, includeData? }] |
| Hedef | manifest webhookUrl (veya webhookPath) |
| Method | POST (senkron, blocking) |
| Yanıt bütçesi | timeoutMs (varsayılan ~10s); aşımda failMode |
{
"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
}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.
{
"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-…"
}| Alan | Açıklama |
|---|---|
| target.id | Masa id’si. Dolu hesabı tables/get?id= ile çekin (gate yalnız id taşır). |
| data | Yalnı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. |
| timeoutMs | Cevap 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. |
| formData | Yalnı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. |
| actor | Kapatmayı başlatan kullanıcı {userId, role} — imzalı gövdede, güvenilir. |
{ "decision": "allow" }{ "decision": "deny", "message": "Önce fişi yazdırın." }{ "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" }
] }| message | Anlam |
|---|---|
| plugin.hook.targetNotFound | Masa bulunamadı. |
| plugin.hook.notRegistered | Manifest’inizde bu hook yok. |
| plugin.scope.denied | hooks:table.close onaylı değil. |
| plugin.hook.inactive | Kurulum pasif (kill-switch / billing / connect). |