lineChanges ✓ Canlıtable.updated ve packet.updated olayları satır düzeyi delta taşır: hangi kalem değişti, ne kadarı iptal edildi, hangi yeni satır hangisinin devamı. Tam durum (orders[]) sözleşmesi değişmedi — bu blok onun üzerine, satır bazında defter tutan tüketiciler için gelir. Alan opsiyoneldir: yalnız satır düzeyinde bir şey değiştiğinde konur.
← Event Kataloğu · Hesap Yaşam Döngüsü
lineChanges yalnız satır düzeyinde bir şey değiştiğinde konur; ödeme eklenmesi gibi olaylarda hiç gelmez. data.lineChanges ?? [] ile oku ve gerçeğin kaynağı olarak her zaman orders[] tam durumunu kullan — blok bir optimizasyondur, kaynak değildir.changed tipine değil, satır dizilerindeki gerçek deltaya bağlıdır — delta boşsa alan hiç konmaz. Ödeme değişimleri delta üretmez: tahsilat ekleme/iptali satır dizilerini değiştirmediği sürece lineChanges gelmez. Aşağıdaki kapsam listesi bloğun hesaplandığı akışları sayar — her akışta mutlaka geleceğini değil.Satış olayları bugün tam durum yayınlıyor: orders[] değişimden sonraki hesabın tamamı. Bu upsert sözleşmesi kayıp veya çift teslimde bile yakınsadığı için doğru tasarım ve değişmiyor.
Ama satır bazında defter tutan bir tüketici (her kalemi ayrı imzalayan mali eklenti gibi) "hangi satır değişti"yi kendi diff'iyle bulmak zorunda kalıyor — ve diff iki yerde yanılıyor:
lineId veriyor. Diff bunu "eski satır gitti + yeni satır geldi" olarak okuyor; tüketici imzaladığı kaydı haksız yere storno ediyor ve aynı ciroyu iki kez sayabiliyor.cancels[]'a aynı lineId ile yazılıyor ve satır azaltılmış adetle orders[]'da kalmaya devam ediyor. "Satır iptal edildi" diye okuyan tüketici hâlâ duran bir satırı siliyor.lineChanges ikisini de açık alanlarla ifade ediyor: soy (splitFrom / movedFrom / movedTo) ve adet kapsamlı iptal.
data.lineChanges — yalnız satır düzeyinde bir şey değiştiğinde gelir. Ödeme eklenmesi gibi olaylarda alan hiç konmaz.
{
"type": "table.updated",
"sequence": 7, "sequenceScope": "sq_9f3ab27c…",
"data": {
"tableId": "masa-5",
"changed": "order_updated", // NE oldu — mevcut alan, değişmiyor
"orders": [ /* tam durum — mevcut, değişmiyor */ ],
"lineChanges": [ // YENİ — hangi satırda ne oldu
{ "op": "updated", "lineId": "masa-5-a1f2",
"before": { "quantity": 2, "amounts": { … } },
"after": { "quantity": 1, "amounts": { … } } },
{ "op": "created", "lineId": "masa-5-9z8y",
"splitFrom": "masa-5-a1f2", // aynı ciro, yeniden kimliklendirildi
"after": { "quantity": 1, "amounts": { … } } },
{ "op": "cancelled", "lineId": "masa-5-c3d4",
"quantity": 1, // ← İPTAL EDİLEN adet; satır DURUYOR olabilir
"before": { "quantity": 3, "amounts": { … } } },
{ "op": "removed", "lineId": "masa-5-e5f6",
"removeReason": "moved", // 'moved' → DEVİR (storno YOK) · 'deleted' → STORNO
"movedTo": { "type": "table", "id": "masa-7" }, // satırın TAMAMI ayrıldı
"before": { … } },
// ⏳ KISMİ DEVİR — satır KALIYOR, adedi düşüyor: 'removed' HİÇ GELMEZ
{ "op": "updated", "lineId": "masa-5-f220",
"movedTo": { "type": "table", "id": "masa-7" },
"movedQuantity": 4, // ← devredilen adet; bu adede STORNO YAZMA
"after": { "quantity": 1 } }
]
}
}before / after satır şekli. İkisi de orders[] içindeki satırla birebir aynıdır (aynı allowlist): lineId, productId, title, quantity, options, lineTotalDecimal, vatRate, amounts, metadata. Yeni bir satır tipi öğrenmene gerek yok — mevcut ayrıştırıcın çalışır.integration/removeOrder) · e-fatura/e-adisyon senkronu.splitFrom/movedFrom/movedTo alanları orders[] allowlist'inden geçmez. Devirde zincir kapanır: kaynakta removed + movedTo, hedefte created + movedFrom. (Tam taşımada kaynak dokümanı silindiği için delta yoktur — *.deleted zaten movedTo taşır.)movedTo iki girdide gelir: satırın tamamı ayrıldıysa removed + removeReason:"moved"; ⏳ yalnız bir kısmı devredildiyse updated + movedQuantity — satır kalan adetle satışta durmaya devam eder.orders[]'dan düşüyor ama artık cancels[]'a void kaydı yazılıyor (kayda girmiş bir pozisyonun izsiz kaybolması DSFinV-K Tz 4.2.3 ve AO §146a ile bağdaşmıyordu). Zarfta görünen fark: eskiden { op: "removed", removeReason: "deleted" }, şimdi { op: "cancelled", quantity: … }. Kuver satırı türev olduğu için dışlanır; void kaydı plugin:<pluginId> aktör damgası taşır.| op | Ne oldu | Taşıdığı alanlar | Mali karşılığı |
|---|---|---|---|
| created | Bu satışta yeni satır | after · varsa splitFrom / movedFrom | Yeni kayıt — soy alanı varsa devamdır, yeni satış değil |
| updated | Aynı lineId, alanlar değişti | before + after · ⏳ kısmi devirde movedTo + movedQuantity | Fark kaydedilir. movedTo varsa düşüşün movedQuantity kadarı devirdir, iptal değildir → o adede storno yazma |
| cancelled | cancels[]'a taşındı — adet kapsamlı | quantity (iptal edilen) + before | Negatif kayıt, yalnız o adet kadar |
| removed | Satır listeden çıktı — sebebi removeReason söyler | removeReason ("moved" | "deleted") + moved ise movedTo (zorunlu) + before | removeReason !== "moved" → STORNO; "moved" → devir, storno yok |
moved_out kaldırıldı → removed + removeReason. Eski ad iki farklı olguyu aynı kelimeyle söylüyordu: hedefi belli devir (storno etme — hedef zinciri sürdürüyor) ile izsiz silme (storno et — başka hiçbir yer bu satırı hesaba katmayacak). Fark yalnız opsiyonel bir alanın varlığındaydı ve adın kendisi tüketiciyi yanlış tarafa itiyordu. removed dalının kuralı tek satırdır:if (c.op === 'removed' && c.removeReason !== 'moved') → STORNOremoveReason çağıranın beyanı değil, movedTo'dan türetilir → hedefsiz bir "moved" yayınlanması yapısal olarak imkânsız. Belirsizlik daima storno tarafına düşer: fazladan storno mali olarak zararsız, eksik storno Unveränderbarkeit ihlalidir.updated dalı da var. Kısmi devir removed üretmez: satır satışta kalır (adedi düşer) ve girdi updated + movedTo + movedQuantity olarak gelir. Yalnız tek satırlık kurala bakan tüketici bu düşüşü "iptal edildi" okur — sahada tam olarak bu yaşandı ("4 adet iptal edildi"). Doğru kural:updated girdisinde movedTo varsa düşüşün movedQuantity kadarı devirdir (hedef satışta created + movedFrom ile karşılanır). Storno yalnız movedTo olmayan düşüşlere yazılır; movedTo olup movedQuantity gelmeyen durumda (aynı yazımda iptal de var) düşüşün tamamını iptal sayma — iptal edilen adet ayrı bir cancelled girdisinin quantity'sindedir.shouldReverse(change) dört dalı da tek yerde kapatır.reason adı verilmez. Platformun redaction katmanı reason anahtarını serbest-metin PII sayıp her derinlikte siler; alan önce reason adıyla yazıldı ve PII rızası olmayan kurulumlarda (yani tipik mali eklentide) undefined geliyordu → kural gereği taşınan her satır storno edilirdi. Bu yüzden kök seviyede deleteReason, satır seviyesinde removeReason kullanılır.Satır kimliğinin değiştiği her yerde nereden geldiği söyleniyor. Bu alanlar mali doğruluğun taşıyıcısıdır: onlar olmadan yeniden kimliklenmiş bir satır "sil + ekle" görünür.
| Alan | Tip | Zorunlu | Açıklama |
|---|---|---|---|
| splitFrom | string? | – | created ile. Satır aynı satışta bölündü; değer kaynak lineId. Ciro bu satışta kalıyor → storno yazma, kaydı devam ettir. |
| movedFrom | object? | – | created ile. Satır başka bir satıştan geldi — { type, id, lineId } (kaynak hesap + kaynak satır). Kaynak satışta karşılığı bir removed + movedTo girdisidir. |
| movedTo | object? | – | removed + removeReason: "moved" ile (zorunlu; aksi hâlde alan hiç konmaz). Satır bu satıştan çıktı — { type, id } (hedef hesap). Ciro iadesi değil, sahip değişimi. |
movedTo / movedFrom şekli yeni değil — *.deleted olaylarında zaten kullanılan yapı; aynı ayrıştırıcıyı kullanabilirsin.
lineChanges bir optimizasyondur, kaynak değildir. Gerçeğin kaynağı orders[] tam durumudur. Blok gelmezse, eksikse ya da anlamadığın bir op içeriyorsa tam durumdan yakınsamaya devam et — bugünkü davranışın zaten doğrudur.orders[] ile güncelle; lineChanges'i "ne oldu"yu anlamak ve defterine kayıt düşmek için kullan.created görüp splitFrom/movedFrom yoksa gerçekten yeni bir satırdır. Varsa, önceki kaydının devamıdır — storno yazma.cancelled'ı adetle sınırla. quantity alanı iptal edilen adettir; aynı lineId azaltılmış adetle hâlâ aktif olabilir. Satırı komple silme.removed'ı körü körüne iade sayma — removeReason'a bak. "moved" ise ciro kaybolmadı, sahip değişti (hedef hesapta movedFrom ile aynı satırı bulacaksın); "deleted" ise satır izsiz silindi → storno et.op'u yok say. Sözlük ileride genişleyebilir; tanımadığın değeri atla ve tam durumdan yakınsa — bloğun tamamını atma.import { isKnownLineChangeOp, shouldReverse } from '@restomenum/plugin-sdk';
// 1) Kendi kopyanı DAİMA tam durumdan güncelle — delta kaynak değildir.
store.set(accountKey(data), data);
// 2) lineChanges yalnız "ne oldu"yu anlamak ve deftere kayıt düşmek için.
for (const change of data.lineChanges ?? []) { // alan yoksa döngü hiç dönmez
if (!isKnownLineChangeOp(change.op)) continue; // sözlük büyüyebilir → tanımadığını ATLA
switch (change.op) {
case 'created':
// ⚠️ SOY ALANI VARSA YENİ SATIŞ DEĞİL → storno YAZMA, kaydı devam ettir.
if (change.splitFrom || change.movedFrom) ledger.continue(change);
else ledger.open(change);
break;
case 'updated':
// ⚠️ ADET DÜŞÜŞÜ HER ZAMAN İPTAL DEĞİL: movedTo varsa düşüşün movedQuantity kadarı DEVİRDİR.
if (change.movedTo) ledger.transfer(change.lineId, change.movedTo, change.movedQuantity);
ledger.adjust(change.before, change.after);
break;
case 'cancelled': ledger.reverse(change.lineId, change.quantity); break; // ⚠️ YALNIZ bu adet
case 'removed':
if (change.removeReason === 'moved') ledger.transfer(change.lineId, change.movedTo); // devir
else ledger.reverse(change.lineId); // izsiz silme
break;
}
// Aynı kararı tek çağrıda isteyen için: dört dalı da kapatan yardımcı.
// if (shouldReverse(change)) ledger.reverse(change.lineId, change.quantity);
}sequence satış düzeyindedir, satır düzeyinde değil. Tek bir mutasyondan çıkan tüm lineChanges girdileri aynı sequence'a aittir ve aralarında sıra yoktur — hepsi o numaradaki durumun parçasıdır. Girdiler arasında zamansal sıra çıkarma.Tipler 3.0.1+ sürümünde: LineChange · LineChangeOp · KnownLineChangeOp · LineMovedFrom · LINE_CHANGE_OPS · isKnownLineChangeOp.
before/after mevcut satır tipini yeniden kullanır (OrderLine) — ayrı bir tip üretilmedi.op genişletilebilir bir tiptir: bilinen değerler otomatik tamamlanır, tanınmayan değer ayrıştırmayı kırmaz.data üzerinde katı şema doğrulaması yapma: payload additive büyüyor, bilinmeyen alanı reddeden bir doğrulayıcı canlı entegrasyonu sessizce kırar. SDK'nın parseEnvelope'u da tanımadığı üst-seviye alanları düşürmez (3.0.0+).Bu tamamen additive bir değişiklik. Hiçbir alan kaldırılmıyor, hiçbir tipin anlamı değişmiyor.
table.updated / packet.updated içinde geliyor.orders[] tam durumu — şekli, anlamı, upsert sözleşmesi aynı.changed alanı — "ne oldu" özeti aynen duruyor; lineChanges onun yerine geçmiyor, ayrıntısını veriyor.id, type, version, tenantId, occurredAt, actor, origin, sequence, sequenceScope.lineChanges satırları orders[] ile aynı allowlist'ten geçer (personel kimliği, maliyet ve reçete verisi bu blokta da yok).