🛒 Shop KDV Sistemi - Implementation Draft

📅 2025-12-03 | 🎯 Tüm Adımlar + Kod Örnekleri + Mockup'lar | 🏢 İxtif

📋 Özet

✅ Uygulama Stratejisi

  • Database: Sadece price_without_tax + tax_rate (Runtime calculation)
  • Admin Panel: İki input (KDV hariç + KDV dahil), Livewire ile senkron
  • Frontend: Setting'e göre gösterim (shop_product_tax)
  • Cart/Checkout: Her zaman KDV dahil

⚡ Neden Runtime Calculation?

Performans farkı ihmal edilebilir (0.005ms), ama:

  • KDV oranı değişince tek satır UPDATE
  • Tutarsızlık riski yok
  • Single source of truth garantisi

🗄️ Adım 1: Migration

1 Migration Dosyası Oluştur

Modules/Shop/database/migrations/tenant/033_add_price_without_tax_to_shop_products.php

decimal('price_without_tax', 12, 2) ->nullable() ->after('base_price') ->comment('KDV hariç fiyat (runtime calculation kaynak)'); }); // Data Migration: base_price → price_without_tax // Varsayım: base_price KDV HARİÇ girilmiş DB::statement(' UPDATE shop_products SET price_without_tax = base_price WHERE price_without_tax IS NULL '); } public function down() { Schema::table('shop_products', function (Blueprint $table) { $table->dropColumn('price_without_tax'); }); } };

⚠️ ÖNEMLİ: Data Migration Varsayımı

Mevcut base_price değerleri KDV HARİÇ girilmiş varsayıyorum.

Eğer KDV DAHİL girildiyse, önce hesaplama yapmalıyız!

2 Migration Çalıştır

php artisan tenants:migrate --tenants=2

🏗️ Adım 2: ShopProduct Model

3 Fillable ve Casts Ekle

Modules/Shop/app/Models/ShopProduct.php

// Fillable array'e ekle: protected $fillable = [ // ... existing fields 'base_price', 'price_without_tax', // ← YENİ! 'tax_rate', // ... existing fields ]; // Casts array'e ekle: protected $casts = [ // ... existing casts 'base_price' => 'decimal:2', 'price_without_tax' => 'decimal:2', // ← YENİ! 'tax_rate' => 'decimal:2', // ... existing casts ];

4 Accessor Metodları Ekle

/** * KDV dahil fiyat hesaplama (Runtime) */ public function getPriceWithTaxAttribute(): float { if (!$this->price_without_tax) { return 0.0; } $taxRate = $this->tax_rate ?? 20.0; return (float) ($this->price_without_tax * (1 + $taxRate / 100)); } /** * KDV tutarı hesaplama */ public function getTaxAmountAttribute(): float { return $this->price_with_tax - ($this->price_without_tax ?? 0.0); } // Kullanım: $product = ShopProduct::find(1); echo $product->price_without_tax; // 1000.00 (DB'den) echo $product->price_with_tax; // 1200.00 (Hesaplanan!) echo $product->tax_amount; // 200.00 (Hesaplanan!)

🎨 Adım 3: Admin Livewire Component

5 Component Özellik ve Metodlar

Modules/Shop/app/Http/Livewire/Admin/ShopProductManageComponent.php

// Public properties ekle: public $price_without_tax; public $price_with_tax; public $tax_rate = 20.0; // mount() içinde: public function mount($productId = null) { // ... existing code if ($productId) { $product = ShopProduct::find($productId); $this->price_without_tax = $product->price_without_tax; $this->tax_rate = $product->tax_rate ?? 20.0; $this->calculatePriceWithTax(); } } // Livewire update metodları: public function updatedPriceWithoutTax() { $this->calculatePriceWithTax(); } public function updatedPriceWithTax() { $this->calculatePriceWithoutTax(); } public function updatedTaxRate() { // Hangisi doluysa ondan hesapla if ($this->price_without_tax) { $this->calculatePriceWithTax(); } elseif ($this->price_with_tax) { $this->calculatePriceWithoutTax(); } } private function calculatePriceWithTax() { if (!$this->price_without_tax || !$this->tax_rate) { $this->price_with_tax = null; return; } $this->price_with_tax = $this->price_without_tax * (1 + $this->tax_rate / 100); } private function calculatePriceWithoutTax() { if (!$this->price_with_tax || !$this->tax_rate) { $this->price_without_tax = null; return; } $this->price_without_tax = $this->price_with_tax / (1 + $this->tax_rate / 100); } // save() metodunda: public function save() { // ... validation $product->update([ 'price_without_tax' => $this->price_without_tax, 'tax_rate' => $this->tax_rate, // price_with_tax KAYDEDİLMEZ! (accessor ile runtime) ]); }

6 Admin Blade Template

Modules/Shop/resources/views/admin/livewire/shop-product-manage-component.blade.php

<!-- Fiyatlandırma Bölümü --> <div class="row"> <div class="col-md-4"> <div class="mb-3"> <label class="form-label">KDV Hariç Fiyat (₺)</label> <input type="number" wire:model.live="price_without_tax" class="form-control" step="0.01" placeholder="Örnek: 1000.00" > <small class="form-text text-muted"> Buraya girilince KDV dahil otomatik hesaplanır </small> </div> </div> <div class="col-md-4"> <div class="mb-3"> <label class="form-label">KDV Dahil Fiyat (₺)</label> <input type="number" wire:model.live="price_with_tax" class="form-control" step="0.01" placeholder="Örnek: 1200.00" > <small class="form-text text-muted"> Buraya girilince KDV hariç otomatik hesaplanır </small> </div> </div> <div class="col-md-4"> <div class="mb-3"> <label class="form-label">KDV Oranı (%)</label> <input type="number" wire:model.live="tax_rate" class="form-control" step="0.01" > </div> </div> </div> <!-- Fiyat Özeti --> <div class="alert alert-info"> <strong>Özet:</strong> KDV Hariç: {{ number_format($price_without_tax ?? 0, 2) }} ₺ | KDV: {{ number_format(($price_with_tax ?? 0) - ($price_without_tax ?? 0), 2) }} ₺ | KDV Dahil: {{ number_format($price_with_tax ?? 0, 2) }} ₺ </div>

🎨 Admin Panel Mockup

Ürün Fiyatlandırma

→ KDV dahil otomatik hesaplanır
→ KDV hariç otomatik hesaplanır
KDV Hariç
1.000,00 ₺
KDV Tutarı
200,00 ₺
KDV Dahil
1.200,00 ₺

🌐 Adım 4: Frontend - Ürün Kartları

7 Ürün Kartlarında Gösterim

resources/views/components/ixtif/product-card.blade.php

@php // Setting'den gösterim modunu al $displayMode = setting('shop_product_tax', true); // true = KDV dahil göster // false = KDV hariç göster @endphp <div class="product-card"> <h3>{{ $product->title }}</h3> <div class="product-price"> @if($displayMode) <!-- KDV Dahil Göster --> <span class="price"> {{ number_format($product->price_with_tax, 2) }} ₺ </span> <small>KDV Dahil</small> @else <!-- KDV Hariç Göster --> <span class="price"> {{ number_format($product->price_without_tax, 2) }} ₺ </span> <small>+ KDV</small> @endif </div> <button>Sepete Ekle</button> </div>

🎨 Frontend Mockup - İki Senaryo

Senaryo 1: shop_product_tax = AÇIK (KDV Dahil Göster)

🏗️ Forklift 3 Ton Akülü

Elektrikli forklift, 3000 kg kaldırma kapasitesi

1.200,00 ₺ KDV Dahil

Senaryo 2: shop_product_tax = KAPALI (KDV Hariç Göster)

🏗️ Forklift 3 Ton Akülü

Elektrikli forklift, 3000 kg kaldırma kapasitesi

1.000,00 ₺ + KDV

⚠️ ÖNEMLİ: Cart & Checkout HER ZAMAN KDV Dahil!

Ürün kartlarında hangi gösterim seçilirse seçilsin, sepet ve ödeme sayfalarında HER ZAMAN KDV dahil fiyat gösterilir.

🛒 Adım 5: CartService Güncelleme

8 CartService setPricing Metodunu Güncelle

Modules/Cart/app/Services/CartService.php

protected function setPricing($cartItem, $item, $quantity): void { // ESKİ: base_price kullanıyorduk // YENİ: accessor'ları kullan // KDV hariç fiyat $priceWithoutTax = $item->price_without_tax ?? 0; // KDV dahil fiyat (accessor ile runtime) $priceWithTax = $item->price_with_tax ?? 0; // KDV oranı $taxRate = $item->tax_rate ?? 20.0; // KDV tutarı $taxAmount = $priceWithTax - $priceWithoutTax; // Cart item'e set et $cartItem->unit_price = $priceWithoutTax; $cartItem->tax_amount = $taxAmount; $cartItem->tax_rate = $taxRate; $cartItem->final_price = $priceWithTax; $cartItem->subtotal = $priceWithoutTax * $quantity; $cartItem->total = $priceWithTax * $quantity; }

💡 Artık Hesaplama Yok!

Eskiden base_price'tan KDV dahil fiyatı hesaplıyorduk.

Şimdi accessor'dan direkt alıyoruz → Kod daha temiz!

💳 Adım 6: Subscription (Aynı Mantık)

9 SubscriptionPlan Güncelleme

Modules/Subscription/app/Models/SubscriptionPlan.php

// billing_cycles JSON yapısı güncellenecek: // ESKİ: { "monthly": { "price": 199.90, "duration_months": 1 } } // YENİ: { "monthly": { "price_without_tax": 166.58, "price_with_tax": 199.90, "tax_rate": 20.00, "duration_months": 1 } } // Accessor ekle: public function getPriceWithTaxForCycle($cycle) { $data = $this->billing_cycles[$cycle] ?? null; if (!$data) return 0; $priceWithoutTax = $data['price_without_tax'] ?? 0; $taxRate = $data['tax_rate'] ?? 20.0; return $priceWithoutTax * (1 + $taxRate / 100); }

✅ Test Planı

10 Test Adımları

  • Admin Panel:
    • KDV hariç fiyat gir → KDV dahil otomatik hesaplanmalı
    • KDV dahil fiyat gir → KDV hariç otomatik hesaplanmalı
    • KDV oranı değiştir → İki fiyat da güncellenmeli
    • Kaydet → Sadece price_without_tax database'e yazılmalı
  • Frontend:
    • shop_product_tax = true → KDV dahil gösterilmeli
    • shop_product_tax = false → KDV hariç + KDV gösterilmeli
  • Cart:
    • Ürün ekle → Her zaman KDV dahil fiyat görünmeli
    • Toplam hesaplama doğru mu kontrol et
  • Performans:
    • 100 ürün listesi → Load time artışı 0.01ms'den az olmalı

🎯 Onay Bekleniyor

✅ Tüm Adımlar Hazır!

  • ✅ Migration: price_without_tax ekle, base_price'dan data migrate et
  • ✅ Model: Accessor'lar ekle (getPriceWithTaxAttribute, getTaxAmountAttribute)
  • ✅ Admin Livewire: İki input senkron çalışma
  • ✅ Frontend: shop_product_tax setting'e göre gösterim
  • ✅ CartService: Runtime calculation kullan
  • ✅ Subscription: Aynı mantığı uygula

⚠️ Data Migration Onayı Gerekli!

Mevcut base_price değerleri KDV HARİÇ mi yoksa KDV DAHİL mi girilmiş?

Varsayımım: KDV hariç (çünkü tax_rate alanı var)

Onaylar mısınız? Yoksa bir örnek ürüne bakayım mı?

🚀 Uygulama Onayı

"UYGUNDUR" derseniz tüm adımları sırayla uygulayacağım:

  1. Migration çalıştır
  2. Model güncelle
  3. Admin Livewire güncelle
  4. Frontend güncelle
  5. CartService güncelle
  6. Cache temizle + test