feat(shopping): alışveriş listeleri, stok eşiği önerileri ve akıllı mağaza önerisi motoru

- shoppingLists koleksiyonu (ürün tipi / marka / serbest metin kalemleri), migration 015
- planShopping saf fonksiyonu: paket yuvarlama, zahmet ve hacimli cezası, teslimat, yemek kartı önceliği, ek mağaza eşiği
- eşik önerileri onay/red (red stok yenilenene kadar), ürün tipine hacimli işareti
- fiyat servisinde ortak loadTypeOffers; seed listeleri ve testler

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-06 23:28:54 +03:00
parent 0b7de17b5c
commit 8ab2244755
21 changed files with 2325 additions and 16 deletions
+147
View File
@@ -1012,6 +1012,7 @@ export const tr = {
title: "Alınması önerilenler",
description: "Kritik eşiğin altına düşen ürünler ve hedefe tamamlamak için gereken miktar.",
empty: "Eşiğin altına düşen ürün yok.",
toShopping: "Alışveriş listelerinde onayla veya reddet",
suggested: (q: string) => `Öneri: ${q}`,
wasteReduced: (purchases: number, pct: number) =>
`Son ${purchases} alımda %${pct} fire, miktar azaltıldı.`,
@@ -1055,6 +1056,9 @@ export const tr = {
stepHint: "Hızlı azaltma düğmesinin düşeceği miktar.",
countEveryDays: "Sayım sıklığı (gün)",
countEveryDaysHint: "Boş bırakılırsa kategoriden devralınır.",
bulky: "Ağır / hacimli",
bulkyHint:
"Su, deterjan gibi taşıması zor ürünler. Akıllı öneri bunları yakın ya da online mağazaya yönlendirir.",
note: "Not",
initialQuantity: "Evdeki mevcut miktar",
initialHint: "Tahmini veya ambalaj üzerinden. Boş bırakılırsa 0 kabul edilir.",
@@ -1352,6 +1356,149 @@ export const tr = {
archived: "Ürün arşivli.",
},
},
shopping: {
title: "Alışveriş listeleri",
description:
"Aile üyeleri aynı listeleri kullanır. Stokta azalan ürünler öneri olarak gelir; akıllı öneri en uygun mağaza ve markayı seçer.",
readOnly: "Görüntüleme yetkiniz var; listeleri değiştiremezsiniz.",
newList: "Yeni liste",
createTitle: "Yeni alışveriş listesi",
editTitle: "Listeyi düzenle",
active: "Aktif listeler",
completed: "Tamamlanan alışverişler",
emptyActive: "Aktif liste yok. Yeni bir liste açın.",
itemCount: (n: number) => `${n} kalem`,
checkedCount: (done: number, total: number) => `${done}/${total} alındı`,
updated: (when: string) => `Güncellendi: ${when}`,
completedAt: (when: string) => `Tamamlandı: ${when}`,
emptyList: "Liste boş.",
suggestionsBanner: (n: number) =>
`Stokta eşiğin altına düşen ${n} ürün var. Bir listeyi açıp öneriyi onaylayın ya da reddedin.`,
fields: {
name: "Liste adı",
namePlaceholder: "ör. Haftalık market, Ay başı toptan",
note: "Not",
kind: "Kalem türü",
productType: "Ürün tipi",
product: "Ürün (marka/paket)",
text: "Kalem",
textPlaceholder: "ör. Doğum günü mumu",
quantity: "Miktar",
packages: "Paket adedi",
quantityHint: "Boş bırakılırsa en az bir paket.",
unit: "Birim",
unitPlaceholder: "ör. adet",
itemNote: "Not",
itemNotePlaceholder: "ör. laktozsuz olsun",
},
kinds: { type: "Ürün tipi", product: "Belirli marka", text: "Serbest metin" },
kindHints: {
type: "Markayı akıllı öneri seçer.",
product: "Yalnızca bu ürün önerilir.",
text: "Stokla ve fiyatlarla bağlanmaz.",
},
packageUnit: "paket",
stockLine: (current: string, target: string) => `Evde ${current} / hedef ${target}`,
fromSuggestion: "Stok önerisi",
archivedRef: "Arşivlendi",
actions: {
more: "Diğer işlemler",
addItem: "Kalem ekle",
edit: "Düzenle",
editList: "Listeyi düzenle",
deleteList: "Listeyi sil",
remove: "Listeden çıkar",
moveTo: "Başka listeye taşı",
goShopping: "Alışverişe çık",
back: "Alışveriş listeleri",
backToList: "Listeye dön",
},
editItemTitle: "Kalemi düzenle",
addItemTitle: "Kalem ekle",
moveTitle: "Başka listeye taşı",
moveDescription: (name: string) => `"${name}" seçilen aktif listeye taşınır.`,
moveTarget: "Hedef liste",
noOtherLists: "Taşınabilecek başka aktif liste yok.",
deleteTitle: "Liste silinsin mi?",
deleteDescription: (name: string) => `"${name}" listesi ve kalemleri silinir.`,
suggestions: {
title: "Stok önerileri",
description: "Eşiğin altına düşen ürünler. Onaylananlar önerilen miktarla listeye eklenir.",
addAll: "Tümünü ekle",
add: "Ekle",
dismiss: "Reddet",
dismissHint: "Stok yenilenene (yeni alım girilene) kadar önerilmez.",
line: (current: string, target: string, pct: number | null) =>
`Evde ${current} / hedef ${target}${pct === null ? "" : ` (%${pct})`}`,
suggested: (q: string) => `Önerilen: ${q}`,
},
plan: {
title: "Akıllı mağaza önerisi",
description:
"Son bilinen fiyatlarla. Uzaklık ve taşıma güçlüğü, online teslimat ücreti ve yemek kartı bakiyesi hesaba katılır.",
recommended: "Önerilen dağılım",
single: "Tek mağaza",
singleBest: (store: string, total: string) => `Tek mağaza en iyi seçenek: ${store}, ${total}`,
split: (parts: string, total: string) => `Önerilen dağılım: ${parts}: ${total}`,
part: (store: string, n: number) => `${store} (${n} ürün)`,
saving: (amount: string) => `${amount} tasarruf`,
singleMissing: (n: number) => `Tek mağazada ${n} kalem bulunmuyor.`,
threshold: (amount: string) =>
`Ek mağaza ancak ${amount} üstü tasarruf sağlarsa önerilir (hane ayarı).`,
mealCard: (amount: string) => `Yemek kartıyla ödenebilir: ${amount}`,
mealCardBalance: (amount: string) => `Yemek kartı bakiyeniz: ${amount}`,
delivery: (amount: string) => `Teslimat ${amount}`,
freeDelivery: "Teslimat ücretsiz",
subtotal: "Ara toplam",
total: "Tahmini toplam",
line: (packages: string, price: string) => `${packages} × ${price}`,
unpriced: "Fiyatı bilinmeyenler",
unpricedHint: "Bu kalemler için mağaza fiyatı girilmemiş.",
cheapestAt: (store: string, unitPrice: string) =>
`En ucuz birim fiyat: ${store}, ${unitPrice}`,
empty: "Öneri için listede fiyatı bilinen ürün yok.",
textItems: "Serbest metin kalemleri öneriye katılmaz.",
missing: "Bu planda alınamayan",
},
mode: {
title: "Alışveriş modu",
progress: (done: number, total: number) => `${done}/${total} sepette`,
other: "Diğer",
otherHint: "Fiyatı bilinmeyen ve serbest metin kalemleri",
inCart: "Sepette",
allDone: "Hepsi sepette.",
planChoice: "Plan",
check: (label: string) => `${label}: sepete alındı olarak işaretle`,
refreshed: "Liste güncellendi",
},
toast: {
saved: "Liste kaydedildi.",
deleted: "Liste silindi.",
itemAdded: "Kalem eklendi.",
itemSaved: "Kalem güncellendi.",
itemRemoved: "Kalem listeden çıkarıldı.",
moved: "Kalem taşındı.",
suggestionsAdded: (n: number) => `${n} öneri listeye eklendi.`,
dismissed: "Öneri reddedildi.",
},
validation: {
quantity: "Geçerli bir miktar girin (ör. 2 veya 1,5)",
productType: "Aktif bir ürün tipi seçin",
product: "Aktif bir ürün seçin",
text: "Kalem adını yazın",
duplicate: "Bu ürün listede zaten var; miktarını düzenleyin.",
duplicateIn: (name: string) => `Bu ürün "${name}" listesinde zaten var.`,
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ş).",
},
dashboard: {
title: "Alışveriş",
empty: "Aktif liste yok.",
open: "Listeleri aç",
suggestions: (n: number) => `${n} stok önerisi bekliyor`,
},
},
productPrices: {
add: "Fiyat gir",
addTitle: "Fiyat gir",