feat(shopping): alışverişi tamamla transaction'ı ve karşılaştırma motoru

- tek transaction: stok alımı, fiş kaynaklı fiyat, mağaza başına bölünmüş gider, fiş ekleri, kalanların taşınması
- muadil, markasız ürün tipi ve plansız ekstra satırları; stok → gider kategorisi eşlemesi ve yedek kategori
- compareShopping saf fonksiyonu: aynı/muadil/alınmadı/ekstra, farkın nedenlere tam ayrışması
- tamamlanan listede rapor görüntüsü; testler

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-06 23:57:50 +03:00
parent 3dd9834a3d
commit bd5a20ce64
12 changed files with 1291 additions and 4 deletions
+89
View File
@@ -1491,6 +1491,95 @@ export const tr = {
notActive: "Bu liste tamamlandı; değiştirilemez.",
sameList: "Kalem zaten bu listede.",
suggestionGone: "Öneri artık geçerli değil (listede ya da stok yenilenmiş).",
amount: "Geçerli bir tutar girin (ör. 89,90)",
noLines: "Alınan en az bir ürün seçin",
duplicateLine: "Bir liste kalemi yalnızca bir kez girilebilir",
duplicateStore: "Mağaza iki kez girilmiş",
account: "Her mağaza için ödeme hesabı seçin",
targetList: "Alınmayanlar için hedef liste seçin",
futureDate: "Gelecek tarihli alışveriş girilemez",
itemGone: "Listede olmayan bir kalem seçilmiş; sayfayı yenileyin",
substituteType: "Muadil aynı ürün tipinden olmalı",
textNoProduct: "Serbest metin kalemine ürün seçilemez",
conversion: (name: string) => `"${name}" ürününün tip birimine dönüşümü yok`,
fallbackCategory: "Eşlenmemiş kalemler için gider kategorisi seçin",
tooManySplits: "Bir mağazada çok fazla gider kategorisi var",
},
complete: {
action: "Alışverişi tamamla",
title: "Alışverişi tamamla",
description:
"Aldıklarınızı seçin: aynı ürün ya da muadili, paket adedi, ödenen tutar ve mağaza. Kaydedince stok, fiyatlar, mağaza başına gider ve fişler tek seferde işlenir.",
date: "Alışveriş tarihi",
bought: "Alındı",
itemsTitle: "Listedekiler",
extrasTitle: "Listede olmayanlar",
addExtra: "Listede olmayan ürün ekle",
removeExtra: "Ekstrayı çıkar",
product: "Alınan ürün",
store: "Mağaza",
packages: "Paket",
quantity: "Miktar",
generic: (unit: string) => `Marka belirtmeden (${unit})`,
amount: "Tutar (₺)",
amountHint: "Fişteki satır tutarı.",
planned: (text: string) => `Plan: ${text}`,
substitute: "Muadil",
noPlan: "Planda fiyat yoktu",
textItem: "Serbest metin: stok ve fiyata işlenmez.",
storesTitle: "Ödeme ve fiş",
storesDescription:
"Her mağaza ayrı gider işlemi olur; kalemler stok kategorisinin gider kategorisine bölünür.",
account: "Ödeme hesabı",
receipts: "Fiş görselleri",
addReceipt: "Fiş ekle",
receiptHint: "Fiş fotoğrafı; işlemin ekine kaydedilir.",
subtotal: (n: number) => `${n} kalem`,
fallbackCategory: "Eşlenmemiş kalemlerin gider kategorisi",
fallbackHint:
"Stok kategorisi bir gider kategorisine eşlenmemiş ürünler ve serbest metin kalemleri buraya yazılır.",
leftoverTitle: "Alınmayanlar",
leftoverDescription: (n: number) => `${n} kalem alınmadı.`,
leftoverActions: {
move: "Başka aktif listeye taşı",
new: "Yeni liste aç",
drop: "Bırak (listeyle kapanır)",
},
newListName: "Yeni liste adı",
newListDefault: (name: string) => `${name} – kalanlar`,
total: "Toplam",
plannedTotal: "Planlanan",
submit: "Alışverişi tamamla",
saved: "Alışveriş tamamlandı; stok, fiyatlar ve giderler işlendi.",
txDescription: (list: string) => `Alışveriş: ${list}`,
splitNote: (n: number) => `${n} kalem`,
noStores: "Mağaza yok. Önce bir mağaza ekleyin.",
},
report: {
title: "Karşılaştırma raporu",
description: (date: string) =>
`Alışveriş tarihi ${date}. Plan, tamamlama anındaki akıllı öneridir.`,
planned: "Planlanan",
actual: "Gerçekleşen",
diff: "Fark",
reasonsTitle: "Farkın nedenleri",
reasons: {
price: "Fiyat farkı (aynı ürün)",
quantity: "Miktar farkı",
substitute: "Muadil ürün",
skipped: "Alınmayanlar",
extra: "Plansız ekstralar",
unplanned: "Planda fiyatı olmayanlar",
},
matches: { same: "Aynı", substitute: "Muadil", skipped: "Alınmadı", extra: "Plansız ekstra" },
item: "Kalem",
plan: "Plan",
bought: "Alınan",
expenses: "Mağaza giderleri",
expenseLine: (account: string, receipts: number) =>
receipts ? `${account} · ${receipts} fiş` : account,
movedTo: (name: string) => `Alınmayanlar "${name}" listesine taşındı.`,
noPlanCell: "—",
},
dashboard: {
title: "Alışveriş",