Proje: Okul.com.tr CRM · Hub: Okul.com.tr CRM — Domain

Tanım

Müşteriye (firma) bir veya birden fazla okul için ürün teklifi oluşturma akışı. CRM tarafında temelde “yeni teklif” sayfası (/sales/checkouts/basket) ve detay (/sales/checkouts/{id}).

API

  • POST /admin/checkouts — teklif oluştur
  • POST /admin/checkouts/calculate — anlık hesaplama (debounce’lu, idempotent)
  • GET /admin/checkouts/{id} — detay (include[]: customer, creator, campaign, discountCode, customerUserTitle)

Veri Modeli

Checkout tablosunda items alanı JSON (cast: array). İçeriği CheckoutItemsDTO::toArray() ile snake_case yazılıyor:

{
  total_discount, total_discount_percentage, discounted_months,
  final_amount, remaining_final_amount,
  initial_amount_yearly, initial_amount_monthly,
  discount_details: { [key]: { label, discount_rate, amount, amount_yearly } },
  school_products: [
    { school_id, school_name, school_type_id, school_type_name,
      product_id, product_name, city_id, city_name,
      product_price_id, yearly_product_price, monthly_product_price, quantity }
  ]
}

Calculate endpoint camelCase döner (DTO’yu doğrudan JSON-encode ediyor: finalAmount, initialAmountMonthly); kayıttaki items ise snake_case (toArray çağrılıyor). Frontend her iki formu da pick(...) helper’ıyla destekliyor.

Basket Sayfası Akışı

src/pages/checkouts/basket.tsx:

  1. Müşteri seçCustomerService.search ile combobox
  2. Müşterinin okulları yüklenirSchoolService.getWithParams({"filter[customer-id]": id}). CustomerSearchRepository response’unda auth_person1_* yok; alıcı bilgileri kullanıcı tarafından doldurulur.
  3. Ürün satırı: okul seç → school_type_id, province_id için SchoolService.getById ile detay çekilir → şehir+kademe doldurulur → product price listesi yüklenir.
  4. Yeni okul (“Listede yok”): school_id=null, school_name=<input> ile gönderilir, sunucu yeni okul oluşturur.

Ürün Listeleme Kuralları

src/pages/checkouts/basket.tsx::loadProductPrices filtreleri:

  • product.product_type === 'listing' (diğerleri: subscription, premium vs. dışlanır)
  • product.is_free !== '1' (promosyon/free ürünler dışlanır)
  • Ürün adı: productsMapRef’ten resolve edilir; backend ?includes[]=product parametresini şu an yok sayıyor → bkz. 2026-05-07-product-price-includes-yoksayiliyor

Bilinen Buglar

Hesaplama Tetiklemesi

Calculate endpoint’i state değişikliklerine debounce’lu reaksiyon vermeli; handler içinden değil, state’i izleyen useEffect’ten tetikle → bkz. debounced-calculate-useeffect