from pathlib import Path
import uuid
import json
import logging
from datetime import datetime, date, timedelta
from typing import List, Dict, Any, Optional
from collections import defaultdict
from PyQt6.QtWidgets import (
    QDialog, QVBoxLayout, QFormLayout, QHBoxLayout, QLineEdit,
    QCheckBox, QComboBox, QRadioButton, QWidget, QGroupBox,
    QLabel, QPushButton, QTableWidget, QTableWidgetItem,
    QHeaderView, QGridLayout, QDialogButtonBox, QMessageBox,
    QDateEdit, QInputDialog, QFileDialog, QCompleter, QApplication
)
from PyQt6.QtGui import QDoubleValidator, QColor, QIcon
from PyQt6.QtCore import Qt, QDate, QTimer, QLocale
from dataclasses import asdict
from core.config import BASE_CURRENCY, SUPPORTED_CURRENCIES, format_number, parse_date_from_display, logger
from core.db import (
    BaseManager,
    DataManager,
    Person,
    DebtRecord,
    CustomerHistoryEvent,
    FinancialTransaction,
    RepairRecord,
    Sale,
    StockItem,
    CashAccount,
    CardAccount,
    SYNC_ACTION_DELETE,
    SYNC_ACTION_UPDATE,
    fetch_row_snapshot_by_id,
    fetch_rows_by_ids,
    queue_row_sync_event,
)
from modules.sales import PaymentDialog
from modules.calculations import hesapla, grid_to_json, log_islem
logger = logging.getLogger(__name__)
class PersonManager(BaseManager):
    """
    Kişi Yönetimi.
    Büyük listelerde arama yaparken tüm veriyi çekmek yerine SQL LIKE kullanır.
    """
    def __init__(self, data_manager: DataManager):
        super().__init__(data_manager, "persons", Person)
    def get_person_names(self) -> List[str]:
        """Sadece isimleri çeker (Daha hafif sorgu)."""
        sql = "SELECT full_name FROM persons WHERE profile_id = %s AND is_deleted = 0 ORDER BY full_name ASC"
        rows = self.db.fetch_all(sql, (self.profile_id,))
        return [r['full_name'] for r in rows]
    def search_persons(self, query_str: str) -> List[Person]:
        """Arama kutusu için özel SQL sorgusu."""
        if not query_str:
            return []
        search_pattern = f"%{query_str}%"
        sql = """
            SELECT * FROM persons
            WHERE profile_id = %s AND is_deleted = 0 AND (full_name LIKE %s OR phone_number LIKE %s)
            ORDER BY full_name ASC LIMIT 50
        """
        rows = self.db.fetch_all(sql, (self.profile_id, search_pattern, search_pattern))
        results = []
        for row in rows:
            obj = self._map_sql_to_dataclass(row)
            if obj: results.append(obj)
        return results
class DebtManager(BaseManager):
    """
    Borç/Alacak Yönetimi.
    Kişiye özel borçları SQL ile filtreler.
    """
    def __init__(self, data_manager: DataManager):
        super().__init__(data_manager, "debt_records", DebtRecord)
    def _custom_map_from_sql(self, row: Dict[str, Any]) -> Optional[Any]:
        """SQL'den DebtRecord nesnesine dönüşüm."""
        args = {}
        args['debt_type'] = row.get('debt_category')
        args['product_type'] = row.get('product_name')
        args['person_full_name'] = row.get('person_full_name')
        def fmt_date(val):
            if not val:
                return ""
            if isinstance(val, (datetime, date)):
                return val.strftime("%d.%m.%Y")
            if isinstance(val, str):
                s = val.strip()
                if "-" in s and len(s.split("-")) == 3:
                    try:
                        if "T" in s or ":" in s:
                            return datetime.fromisoformat(s.replace("Z", "")).strftime("%d.%m.%Y")
                    except Exception:
                        pass
                    try:
                        return datetime.strptime(s[:10], "%Y-%m-%d").strftime("%d.%m.%Y")
                    except Exception:
                        return s
            return str(val)
        args['transaction_date'] = fmt_date(row.get('transaction_date'))
        args['due_date'] = fmt_date(row.get('due_date'))
        args['payment_date'] = fmt_date(row.get('payment_date'))
        mapping = {
            'record_type': 'record_type',
            'original_amount': 'original_amount',
            'currency': 'currency', 'total_amount_base': 'total_amount_base',
            'product_quantity': 'product_quantity', 'description': 'description',
            'status': 'status', 'paid_amount_base': 'paid_amount_base',
            'paid_quantity': 'paid_quantity', 'id': 'id',
            'is_paid': 'is_paid', 'created_at': 'created_at'
        }
        for cls_f, sql_f in mapping.items():
            val = row.get(sql_f)
            if 'amount' in cls_f or 'quantity' in cls_f: val = float(val or 0)
            if 'is_paid' in cls_f: val = bool(val)
            if 'created_at' in cls_f: val = str(val)
            args[cls_f] = val
        return self.data_class(**args)
    def get_debts_by_type(self, record_type: str) -> List[DebtRecord]:
        """Alacak veya Verecek tipine göre filtreler."""
        sql = """
            SELECT * FROM debt_records
            WHERE profile_id = %s AND record_type = %s AND is_deleted = 0
            ORDER BY transaction_date ASC, created_at ASC
        """
        rows = self.db.fetch_all(sql, (self.profile_id, record_type))
        results: List[DebtRecord] = []
        for row in rows:
            obj = self._map_sql_to_dataclass(row)
            if obj:
                results.append(obj)
        return results
    def _custom_map_to_sql(self, item: Any, sql_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """DebtRecord tablosu için özel eşleştirme."""
        data = asdict(item)
        sql_data['debt_category'] = data.get('debt_type')
        if 'debt_type' in sql_data: del sql_data['debt_type']
        sql_data['product_name'] = data.get('product_type')
        if 'product_type' in sql_data: del sql_data['product_type']
        p_name = data.get('person_full_name')
        if p_name:
            res = self.db.fetch_one("SELECT id FROM persons WHERE full_name=%s AND profile_id=%s",
                                  (p_name, self.profile_id))
            sql_data['person_id'] = res['id'] if res else None
        return sql_data
    def get_pending_debts_by_person(self, person_name: str, record_type: str = None) -> List[DebtRecord]:
        """Belirli bir kişinin ÖDENMEMİŞ borçlarını SQL ile çeker."""
        sql = """
            SELECT * FROM debt_records
            WHERE profile_id = %s AND person_full_name = %s AND is_paid = 0
        """
        params = [self.profile_id, person_name]
        if record_type:
            sql += " AND record_type = %s"
            params.append(record_type)
        sql += " ORDER BY transaction_date ASC"
        rows = self.db.fetch_all(sql, tuple(params))
        results = []
        for row in rows:
            obj = self._map_sql_to_dataclass(row)
            if obj: results.append(obj)
        return results
class CustomerHistoryManager(BaseManager):
    """
    Müşteri Geçmişi Yönetimi.
    EN KRİTİK SINIF. Tüm veriyi çekmek yerine (get_all),
    kriterlere göre SQL sorgusu atar.
    """
    def __init__(self, data_manager: DataManager):
        super().__init__(data_manager, "customer_history", CustomerHistoryEvent)
    def get_history_filtered(self, person_name: str, start_date: datetime, end_date: datetime) -> List[CustomerHistoryEvent]:
        """Tarih aralığına göre optimize edilmiş SQL sorgusu."""
        start_iso = start_date.strftime("%Y-%m-%d")
        end_iso = end_date.strftime("%Y-%m-%d") + " 23:59:59"
        sql = """
            SELECT * FROM customer_history_events
            WHERE profile_id = %s
            AND person_full_name = %s
            AND event_date >= %s
            AND event_date <= %s
            ORDER BY event_date DESC, created_at DESC
        """
        rows = self.db.fetch_all(sql, (self.profile_id, person_name, start_iso, end_iso))
        results = []
        for row in rows:
            obj = self._map_sql_to_dataclass(row)
            if obj: results.append(obj)
        return results
    def get_all_by_person(self, person_name: str) -> List[CustomerHistoryEvent]:
        """Belirli bir kişinin tüm tarihçesi."""
        sql = """
            SELECT * FROM customer_history_events
            WHERE profile_id = %s AND person_full_name = %s
            ORDER BY event_date ASC, created_at ASC
        """
        rows = self.db.fetch_all(sql, (self.profile_id, person_name))
        results = []
        for row in rows:
            obj = self._map_sql_to_dataclass(row)
            if obj: results.append(obj)
        return results
    def get_person_balance_sql(self, person_name: str) -> Dict[str, float]:
        """
        Kişinin bakiyesini SQL tarafında hesaplar.
        JSON içindeki 'bedelli' işlemleri hesaba katamaz, o yüzden sadece
        standart (Simple) bakiyeyi döner. Karmaşık işlemler Python tarafında işlenmeli.
        """
        sql = """
            SELECT currency,
                   SUM(debt_original - credit_original) as net_balance
            FROM customer_history_events
            WHERE profile_id = %s AND person_full_name = %s
            AND currency IS NOT NULL AND currency != 'Karma'
            GROUP BY currency
        """
        rows = self.db.fetch_all(sql, (self.profile_id, person_name))
        balances = defaultdict(float)
        for r in rows:
            if r['currency']:
                balances[r['currency']] = float(r['net_balance'] or 0)
        return balances
class AddDebtDialog(QDialog):
    def __init__(self, record_type: str, person_name: str, lang: dict, product_names: List[str], depot_names: List[str], cash_accounts: List[CashAccount], card_accounts: List[CardAccount], parent=None):
        super().__init__(parent)
        self.record_type = record_type
        self.lang = lang
        self.main_app = parent
        title_key = "add_receivable" if record_type == "Receivable" else "add_payable"
        title = self.lang[title_key]
        self.setWindowTitle(f"{title} - {person_name}")
        self.setMinimumWidth(500)
        layout = QFormLayout(self)
        self.date_entry = QDateEdit(datetime.now())
        self.date_entry.setCalendarPopup(True)
        self.date_entry.setDisplayFormat("dd.MM.yyyy")
        layout.addRow(f"{self.lang['date']}:", self.date_entry)
        self.due_date_checkbox = QCheckBox(self.lang["due_date"])
        self.due_date_entry = QDateEdit(datetime.now() + timedelta(days=30))
        self.due_date_entry.setCalendarPopup(True)
        self.due_date_entry.setDisplayFormat("dd.MM.yyyy")
        self.due_date_entry.setEnabled(False)
        self.due_date_checkbox.toggled.connect(self.due_date_entry.setEnabled)
        due_date_layout = QHBoxLayout()
        due_date_layout.addWidget(self.due_date_checkbox)
        due_date_layout.addWidget(self.due_date_entry)
        layout.addRow(due_date_layout)
        self.description_entry = QLineEdit()
        layout.addRow(f"{self.lang['description']}:", self.description_entry)
        radio_layout = QHBoxLayout()
        self.type_cash_radio = QRadioButton(self.lang["cash"]); self.type_product_radio = QRadioButton(self.lang["product"]); self.type_cash_radio.setChecked(True)
        radio_layout.addWidget(self.type_cash_radio); radio_layout.addWidget(self.type_product_radio); radio_layout.addStretch()
        layout.addRow(f"{self.lang['debt_type']}:", radio_layout)
        self.cash_amount_entry = QLineEdit()
        validator_2_dec = QDoubleValidator(0, 999999999, 2)
        validator_2_dec.setLocale(QLocale(QLocale.Language.English, QLocale.Country.UnitedStates))
        self.cash_amount_entry.setValidator(validator_2_dec)
        self.cash_currency_combo = QComboBox(); self.cash_currency_combo.addItems(SUPPORTED_CURRENCIES)
        cash_layout = QHBoxLayout(); cash_layout.addWidget(self.cash_amount_entry); cash_layout.addWidget(self.cash_currency_combo)
        self.source_combo = QComboBox()
        for acc in cash_accounts: self.source_combo.addItem(f"{self.lang['cash']} - {acc.name}", ("cash", acc.id))
        for acc in card_accounts: self.source_combo.addItem(f"{self.lang['card']} - {acc.name}", ("card", acc.id))
        source_label_text = self.lang["source_account"] if record_type == "Receivable" else self.lang["destination_account"]
        self.source_label = QLabel(f"{source_label_text}:")
        self.amount_label = QLabel(f"{self.lang['amount']}:")
        self.cash_widgets = QWidget()
        cash_widgets_layout = QFormLayout(self.cash_widgets)
        cash_widgets_layout.setContentsMargins(0,0,0,0)
        cash_widgets_layout.addRow(self.amount_label, cash_layout)
        cash_widgets_layout.addRow(self.source_label, self.source_combo)
        layout.addRow(self.cash_widgets)
        self.product_combo = QComboBox(); self.product_combo.addItems(product_names)
        self.product_quantity_entry = QLineEdit()
        validator_3_dec = QDoubleValidator(0, 999999999, 3)
        validator_3_dec.setLocale(QLocale(QLocale.Language.English, QLocale.Country.UnitedStates))
        self.product_quantity_entry.setValidator(validator_3_dec)
        self.product_depot_combo = QComboBox()
        self.product_depot_combo.addItems(depot_names)
        depot_label_text = self.lang["source_depot"] if record_type == "Receivable" else self.lang["destination_depot"]
        self.product_depot_label = QLabel(f"{depot_label_text}:")
        self.affect_stock_checkbox = QCheckBox(self.lang["record_stock_flow_now"])
        self.affect_stock_checkbox.setChecked(True)
        self.product_type_label = QLabel(f"{self.lang['product']}:"); self.product_quantity_label = QLabel(f"{self.lang['quantity']}:")
        self.product_widgets = QWidget()
        product_widgets_layout = QFormLayout(self.product_widgets)
        product_widgets_layout.setContentsMargins(0,0,0,0)
        product_widgets_layout.addRow(self.product_type_label, self.product_combo)
        product_widgets_layout.addRow(self.product_quantity_label, self.product_quantity_entry)
        product_widgets_layout.addRow(self.product_depot_label, self.product_depot_combo)
        product_widgets_layout.addRow(self.affect_stock_checkbox)
        layout.addRow(self.product_widgets)
        self.type_cash_radio.toggled.connect(self._toggle_debt_type_widgets)
        self._toggle_debt_type_widgets(True)
        self.buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
        self.buttons.accepted.connect(self.accept)
        self.buttons.rejected.connect(self.reject)
        ok_btn = self.buttons.button(QDialogButtonBox.StandardButton.Ok)
        if ok_btn:
            ok_btn.setDefault(True)
            ok_btn.setAutoDefault(True)
        self.description_entry.returnPressed.connect(self.accept)
        self.cash_amount_entry.returnPressed.connect(self.accept)
        self.product_quantity_entry.returnPressed.connect(self.accept)
        layout.addRow(self.buttons)
    def _toggle_debt_type_widgets(self, is_cash):
        self.cash_widgets.setVisible(is_cash)
        self.product_widgets.setVisible(not is_cash)
        self.affect_stock_checkbox.setVisible(not is_cash)
    def _get_main_affect_cash_value(self) -> bool:
        main_app = self.main_app or self.parent()
        checkbox = getattr(main_app, "affect_cash_checkbox", None)
        return checkbox.isChecked() if checkbox else True
    def get_data(self):
        data = {
            "transaction_date": self.date_entry.date().toString("dd.MM.yyyy"),
            "due_date": self.due_date_entry.date().toString("dd.MM.yyyy") if self.due_date_checkbox.isChecked() else "",
            "description": self.description_entry.text().strip(),
            "debt_type": "Cash" if self.type_cash_radio.isChecked() else "Product"
        }
        if data["debt_type"] == "Cash":
            amount_str = self.cash_amount_entry.text().strip().replace(',', '.')
            if not amount_str: return None
            try:
                data["amount"] = float(amount_str)
                if data["amount"] <= 0: return None
            except ValueError: return None
            data["currency"] = self.cash_currency_combo.currentText()
            source_data = self.source_combo.currentData()
            if not source_data:
                if self.source_combo.count() > 0:
                    self.source_combo.setCurrentIndex(0)
                    source_data = self.source_combo.currentData()
                else:
                    return None
            data["source_type"], data["source_id"] = source_data
            data["affect_cash"] = self._get_main_affect_cash_value()
        else:
            quantity_str = self.product_quantity_entry.text().strip().replace(',', '.')
            if not quantity_str: return None
            try:
                data["quantity"] = float(quantity_str)
                if data["quantity"] <= 0: return None
            except ValueError: return None
            depot_name = self.product_depot_combo.currentText()
            if not depot_name:
                QMessageBox.warning(self, self.lang["warning"], self.lang["no_depot_selected"])
                return None
            data["product_type"] = self.product_combo.currentText()
            data["depot_name"] = depot_name
            data["affect_stock"] = self.affect_stock_checkbox.isChecked()
        return data
class EditDebtDialog(QDialog):
    """Dialog for editing an existing debt record."""
    def __init__(self, debt_record: 'DebtRecord', lang: dict, parent=None):
        super().__init__(parent)
        self.lang = lang
        self.debt_record = debt_record
        self.setWindowTitle(self.lang["edit_debt_record"])
        layout = QFormLayout(self)
        self.date_entry = QDateEdit()
        self.date_entry.setCalendarPopup(True)
        self.date_entry.setDisplayFormat("dd.MM.yyyy")
        self.date_entry.setDate(QDate.fromString(debt_record.transaction_date, "dd.MM.yyyy"))
        layout.addRow(f"{self.lang['date']}:", self.date_entry)
        self.due_date_checkbox = QCheckBox(self.lang["due_date"])
        self.due_date_entry = QDateEdit()
        self.due_date_entry.setCalendarPopup(True)
        self.due_date_entry.setDisplayFormat("dd.MM.yyyy")
        if debt_record.due_date:
            self.due_date_checkbox.setChecked(True)
            self.due_date_entry.setDate(QDate.fromString(debt_record.due_date, "dd.MM.yyyy"))
        else:
            self.due_date_entry.setEnabled(False)
        self.due_date_checkbox.toggled.connect(self.due_date_entry.setEnabled)
        due_date_layout = QHBoxLayout()
        due_date_layout.addWidget(self.due_date_checkbox)
        due_date_layout.addWidget(self.due_date_entry)
        layout.addRow(due_date_layout)
        self.description_entry = QLineEdit(debt_record.description)
        layout.addRow(f"{self.lang['description']}:", self.description_entry)
        self.amount_entry = QLineEdit()
        self.currency_combo = QComboBox()
        self.amount_layout = QHBoxLayout()
        if self.debt_record.debt_type == "Cash":
            self.amount_entry.setText(f"{self.debt_record.original_amount:.2f}")
            validator = QDoubleValidator(0, 999999999, 2)
            validator.setLocale(QLocale(QLocale.Language.English, QLocale.Country.UnitedStates))
            self.amount_entry.setValidator(validator)
            self.currency_combo.addItems(SUPPORTED_CURRENCIES)
            self.currency_combo.setCurrentText(self.debt_record.currency)
            self.amount_layout.addWidget(self.amount_entry)
            self.amount_layout.addWidget(self.currency_combo)
            layout.addRow(self.lang['amount'], self.amount_layout)
        has_payments = self.debt_record.paid_amount_base > 0 or self.debt_record.paid_quantity > 0
        if has_payments:
            warning_label = QLabel(self.lang["edit_record_warning"])
            warning_label.setStyleSheet("color: red;")
            layout.addRow(warning_label)
            if self.debt_record.debt_type == "Cash":
                self.amount_entry.setEnabled(False)
                self.currency_combo.setEnabled(False)
        self.buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
        self.buttons.accepted.connect(self.accept)
        self.buttons.rejected.connect(self.reject)
        ok_btn = self.buttons.button(QDialogButtonBox.StandardButton.Ok)
        if ok_btn:
            ok_btn.setDefault(True)
            ok_btn.setAutoDefault(True)
        self.description_entry.returnPressed.connect(self.accept)
        self.amount_entry.returnPressed.connect(self.accept)
        layout.addRow(self.buttons)
        self.setTabOrder(self.date_entry, self.due_date_checkbox)
        self.setTabOrder(self.due_date_checkbox, self.due_date_entry)
        self.setTabOrder(self.due_date_entry, self.description_entry)
        if self.debt_record.debt_type == "Cash":
            self.setTabOrder(self.description_entry, self.amount_entry)
            self.setTabOrder(self.amount_entry, self.currency_combo)
            self.setTabOrder(self.currency_combo, self.buttons)
        else:
            self.setTabOrder(self.description_entry, self.buttons)
    def get_data(self):
        data = {
            "transaction_date": self.date_entry.date().toString("dd.MM.yyyy"),
            "due_date": self.due_date_entry.date().toString("dd.MM.yyyy") if self.due_date_checkbox.isChecked() else "",
            "description": self.description_entry.text().strip()
        }
        if self.debt_record.debt_type == "Cash" and self.amount_entry.isEnabled():
            try:
                data['original_amount'] = float(self.amount_entry.text().replace(',', '.'))
                data['currency'] = self.currency_combo.currentText()
            except ValueError:
                QMessageBox.warning(self, self.lang['warning'], self.lang['invalid_amount'])
                return None
        return data
class CashTransactionsDialog(QDialog):
    """Nakit işlemleri için dialog penceresi."""
    def __init__(self, main_app, person, lang: dict, parent=None):
        super().__init__(parent)
        self.main_app = main_app
        self.person = person
        self.lang = lang
        self.selected_action = None
        title = lang.get("cash_transactions", "Nakit İşlemleri")
        cash_in_text = f"1. {lang.get('cash_in', 'Nakit Girişi')}"
        cash_out_text = f"2. {lang.get('cash_out', 'Nakit Çıkışı')}"
        self.setWindowTitle(title)
        self.setMinimumWidth(400)
        self.setMinimumHeight(200) # Daha az buton olduğu için yükseklik azaltıldı
        layout = QVBoxLayout(self)
        person_name = person.full_name if hasattr(person, 'full_name') else person
        info_label = QLabel(f"{lang.get('person', 'Kişi')}: {person_name}")
        info_label.setStyleSheet("font-weight: bold; font-size: 12px;")
        layout.addWidget(info_label)
        layout.addWidget(QLabel(""))  # Boşluk
        btn_cash_in = QPushButton(cash_in_text)
        btn_cash_in.setMinimumHeight(60)
        btn_cash_in.setStyleSheet("font-size: 14px; font-weight: bold;")
        btn_cash_in.setShortcut("1")  # Klavye Kısayolu: 1
        btn_cash_in.setDefault(True)
        btn_cash_in.setAutoDefault(True)
        btn_cash_in.clicked.connect(lambda: self._select_action("cash_in"))
        btn_cash_out = QPushButton(cash_out_text)
        btn_cash_out.setMinimumHeight(60)
        btn_cash_out.setStyleSheet("font-size: 14px; font-weight: bold;")
        btn_cash_out.setShortcut("2")  # Klavye Kısayolu: 2
        btn_cash_out.clicked.connect(lambda: self._select_action("cash_out"))
        layout.addWidget(btn_cash_in)
        layout.addWidget(btn_cash_out)
        layout.addStretch()
        btn_cancel = QPushButton(lang.get("cancel", "İptal"))
        btn_cancel.clicked.connect(self.reject)
        layout.addWidget(btn_cancel)
    def _select_action(self, action: str):
        self.selected_action = action
        self.accept()
    def get_selected_action(self) -> str:
        return self.selected_action

_STOCK_LANG_PREFIXES = {
    "Product": "product",
    "Ziynet": "ziynet",
    "Scrap": "scrap",
}

_STOCK_LABEL_DEFAULTS = {
    "Product": {
        "purchase": "Altın Alış",
        "sale": "Altın Satış",
        "bedelli_entry": "Bedelli Altın Girişi",
        "bedelli_exit": "Bedelli Altın Çıkışı",
    },
    "Ziynet": {
        "purchase": "Ziynet Alış",
        "sale": "Ziynet Satış",
        "bedelli_entry": "Bedelli Ziynet Girişi",
        "bedelli_exit": "Bedelli Ziynet Çıkışı",
    },
    "Scrap": {
        "purchase": "Hurda Alış",
        "sale": "Hurda Satış",
        "bedelli_entry": "Bedelli Hurda Girişi",
        "bedelli_exit": "Bedelli Hurda Çıkışı",
    },
}


def _get_stock_kind_from_mode(mode: str) -> str:
    mode = str(mode or "")
    if "Ziynet" in mode:
        return "Ziynet"
    if "Scrap" in mode:
        return "Scrap"
    return "Product"


def _get_stock_label(lang: dict, stock_type: str, label_kind: str) -> str:
    prefix = _STOCK_LANG_PREFIXES.get(stock_type, "product")
    defaults = _STOCK_LABEL_DEFAULTS.get(stock_type, _STOCK_LABEL_DEFAULTS["Product"])

    if label_kind in {"bedelli_entry", "bedelli_exit"}:
        suffix = "entry" if label_kind.endswith("entry") else "exit"
        return (
            lang.get(f"{prefix}_{label_kind}")
            or lang.get(f"{prefix}_paid_{suffix}")
            or defaults[label_kind]
        )

    return lang.get(f"{prefix}_{label_kind}") or defaults[label_kind]


def _get_stock_event_title_for_mode(lang: dict, mode: str, fallback: str = "") -> str:
    mode = str(mode or "")
    if not any(x in mode for x in ["Product", "Ziynet", "Scrap"]):
        return fallback

    stock_type = _get_stock_kind_from_mode(mode)
    if "PaidOut" in mode:
        return _get_stock_label(lang, stock_type, "bedelli_exit")
    if "PaidIn" in mode:
        return _get_stock_label(lang, stock_type, "bedelli_entry")
    if "Out" in mode or "Sale" in mode:
        return _get_stock_label(lang, stock_type, "sale")
    if "In" in mode or "Purchase" in mode:
        return _get_stock_label(lang, stock_type, "purchase")
    return fallback or _get_stock_label(lang, stock_type, "purchase")

class ProductTransactionsDialog(QDialog):
    """Altın/Ziynet/Hurda işlemleri için dialog penceresi."""
    def __init__(self, main_app, person, stock_type: str, lang: dict, parent=None):
        super().__init__(parent)
        self.main_app = main_app
        self.person = person
        self.stock_type = stock_type
        self.lang = lang
        self.selected_action = None
        title_map = {
            "Product": ("product_transactions", "Altın İşlemleri"),
            "Ziynet": ("ziynet_transactions", "Ziynet İşlemleri"),
            "Scrap": ("scrap_transactions", "Hurda İşlemleri"),
        }
        title_key, title_default = title_map.get(stock_type, title_map["Product"])
        title = lang.get(title_key, title_default)
        purchase_text = f"1. {_get_stock_label(lang, stock_type, 'purchase')}"
        sale_text = f"2. {_get_stock_label(lang, stock_type, 'sale')}"
        paid_entry_text = f"3. {_get_stock_label(lang, stock_type, 'bedelli_entry')}"
        paid_exit_text = f"4. {_get_stock_label(lang, stock_type, 'bedelli_exit')}"
        self.setWindowTitle(title)
        self.setMinimumWidth(400)
        self.setMinimumHeight(300)
        layout = QVBoxLayout(self)
        person_name = person.full_name if hasattr(person, 'full_name') else person
        info_label = QLabel(f"{lang.get('person', 'Kişi')}: {person_name}")
        info_label.setStyleSheet("font-weight: bold; font-size: 12px;")
        layout.addWidget(info_label)
        layout.addWidget(QLabel(""))  # Boşluk
        btn_purchase = QPushButton(purchase_text)
        btn_purchase.setMinimumHeight(50)
        btn_purchase.setStyleSheet("font-size: 14px; font-weight: bold;")
        btn_purchase.setShortcut("1")  # Klavye: 1
        btn_purchase.setDefault(True)
        btn_purchase.setAutoDefault(True)
        btn_purchase.clicked.connect(lambda: self._select_action("purchase"))
        btn_sale = QPushButton(sale_text)
        btn_sale.setMinimumHeight(50)
        btn_sale.setStyleSheet("font-size: 14px; font-weight: bold;")
        btn_sale.setShortcut("2")  # Klavye: 2
        btn_sale.clicked.connect(lambda: self._select_action("sale"))
        btn_paid_entry = QPushButton(paid_entry_text)
        btn_paid_entry.setMinimumHeight(50)
        btn_paid_entry.setStyleSheet("font-size: 14px; font-weight: bold;")
        btn_paid_entry.setShortcut("3")  # Klavye: 3
        btn_paid_entry.clicked.connect(lambda: self._select_action("paid_entry"))
        btn_paid_exit = QPushButton(paid_exit_text)
        btn_paid_exit.setMinimumHeight(50)
        btn_paid_exit.setStyleSheet("font-size: 14px; font-weight: bold;")
        btn_paid_exit.setShortcut("4")  # Klavye: 4
        btn_paid_exit.clicked.connect(lambda: self._select_action("paid_exit"))
        layout.addWidget(btn_purchase)
        layout.addWidget(btn_sale)
        layout.addWidget(btn_paid_entry)
        layout.addWidget(btn_paid_exit)
        layout.addStretch()
        cancel_btn = QPushButton(lang.get("cancel", "İptal"))
        cancel_btn.clicked.connect(self.reject)
        layout.addWidget(cancel_btn)
    def _select_action(self, action: str):
        self.selected_action = action
        self.accept()
    def get_selected_action(self) -> str:
        return self.selected_action
class EditHistoryEventDialog(QDialog):
    """
    Bir CustomerHistoryEvent kaydını düzenler.
    Kullanıcıya seçenek sunmaz, HER ZAMAN ana kayıtları da günceller.
    """
    def __init__(self, event: 'CustomerHistoryEvent', lang: dict, parent=None):
        super().__init__(parent)
        self.lang = lang
        self.setWindowTitle(self.lang["edit_transaction"])
        self.setMinimumWidth(400)
        self.event = event
        layout = QFormLayout(self)
        event_datetime = parse_date_from_display(event.event_date) or datetime.now()
        self.date_entry = QDateEdit()
        self.date_entry.setDate(QDate(event_datetime.year, event_datetime.month, event_datetime.day))
        self.date_entry.setCalendarPopup(True)
        self.date_entry.setDisplayFormat("dd.MM.yyyy")
        layout.addRow(f"{self.lang['date']}:", self.date_entry)
        self.description_entry = QLineEdit(event.description)
        layout.addRow(f"{self.lang['description']}:", self.description_entry)
        amount_layout = QHBoxLayout()
        self.amount_entry = QLineEdit()
        validator = QDoubleValidator(0, 999999999, 2)
        validator.setLocale(QLocale(QLocale.Language.English, QLocale.Country.UnitedStates))
        self.amount_entry.setValidator(validator)
        amount_value = 0
        if event.debt_original > 0: amount_value = event.debt_original
        elif event.credit_original > 0: amount_value = event.credit_original
        self.amount_entry.setText(format_number(amount_value))
        self.currency_combo = QComboBox()
        self.currency_combo.addItems(SUPPORTED_CURRENCIES)
        if event.currency and event.currency in SUPPORTED_CURRENCIES:
            self.currency_combo.setCurrentText(event.currency)
        amount_layout.addWidget(self.amount_entry, 2)
        amount_layout.addWidget(self.currency_combo, 1)
        layout.addRow(f"{self.lang['amount']}:", amount_layout)
        self.buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
        self.buttons.accepted.connect(self.accept)
        self.buttons.rejected.connect(self.reject)
        ok_btn = self.buttons.button(QDialogButtonBox.StandardButton.Ok)
        if ok_btn:
            ok_btn.setDefault(True)
            ok_btn.setAutoDefault(True)
        self.description_entry.returnPressed.connect(self.accept)
        self.amount_entry.returnPressed.connect(self.accept)
        layout.addRow(self.buttons)
    def get_data(self) -> dict:
        """Kullanıcının girdiği verileri döndürür."""
        new_date = self.date_entry.date().toPyDate()
        original_datetime = parse_date_from_display(self.event.event_date) or datetime.now()
        updated_datetime = original_datetime.replace(year=new_date.year, month=new_date.month, day=new_date.day)
        try:
            new_amount = float(self.amount_entry.text().replace(',', '.'))
        except (ValueError, TypeError):
            new_amount = 0.0
        return {
            "event_date": updated_datetime.isoformat(),
            "description": self.description_entry.text().strip(),
            "amount": new_amount,
            "currency": self.currency_combo.currentText(),
            "update_main": True
        }
class BedelDetailsDialog(QDialog):
    """
    Bedelli işlemlerin detaylarını gösteren pencere (Güvenli JSON Ayrıştırma).
    """
    def __init__(self, description_json, parent=None):
        super().__init__(parent)
        self.setWindowTitle("İşlem Bedelleri / Karşılıkları")
        self.setMinimumWidth(560)
        self.setMinimumHeight(300)
        layout = QVBoxLayout(self)
        lbl_info = QLabel("Bu işlem karşılığında alınan/verilen bedeller:")
        lbl_info.setStyleSheet("font-weight: bold; font-size: 14px;")
        layout.addWidget(lbl_info)
        self.table = QTableWidget(0, 4)
        self.table.setHorizontalHeaderLabels(["Miktar", "Tür (TL/Altın vb.)", "Gram", "Ayar"])
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
        layout.addWidget(self.table)
        self._parse_and_fill(description_json)
        btn_close = QPushButton("Kapat")
        btn_close.setDefault(True)
        btn_close.setAutoDefault(True)
        btn_close.clicked.connect(self.accept)
        layout.addWidget(btn_close)
    def _parse_and_fill(self, json_str):
        try:
            if not json_str or not json_str.strip().startswith('{'):
                raise ValueError("JSON değil")
            data = json.loads(json_str)
            items = data.get('exchange_items', [])
            if not items:
                self._show_empty("Bu işlem bedelsizdir (Nakit/Cari işlem).")
                return
            self.table.setRowCount(len(items))
            for i, item in enumerate(items):
                try:
                    amount = float(str(item.get('amount', 0)).replace(",", "."))
                except Exception:
                    amount = 0.0
                otype = str(item.get('type', '-'))
                try:
                    gram_val = float(str(item.get('gram', 0)).replace(",", "."))
                except Exception:
                    gram_val = 0.0
                karat_val = str(item.get('karat', '') or "").strip()
                self.table.setItem(i, 0, QTableWidgetItem(format_number(amount)))
                self.table.setItem(i, 1, QTableWidgetItem(otype))
                self.table.setItem(i, 2, QTableWidgetItem(f"{format_number(gram_val, 2)} gr" if gram_val > 0 else "-"))
                self.table.setItem(i, 3, QTableWidgetItem(karat_val if karat_val else "-"))
        except (json.JSONDecodeError, TypeError, ValueError):
            self._show_empty("Bu işlem detaylandırılamadı.")
    def _show_empty(self, message):
        self.table.setRowCount(1)
        self.table.setItem(0, 0, QTableWidgetItem("-"))
        self.table.setItem(0, 1, QTableWidgetItem(message))
        self.table.setItem(0, 2, QTableWidgetItem("-"))
        self.table.setItem(0, 3, QTableWidgetItem("-"))
class CRMManager:
    """
    Müşteri Yönetimi ve Cari Hesaplar Mantığı.
    JSON Parsing ve Bakiye Hesaplamaları Burada Optimize Edildi.
    """
    def __init__(self, main_app):
            self.main_app = main_app
            self.person_manager = main_app.person_manager
            self.debt_manager = main_app.debt_manager
            self.currency_manager = main_app.currency_manager
            self.customer_history_manager = main_app.customer_history_manager
            self.stock_manager = main_app.stock_manager
            self.product_manager = main_app.product_manager
            self.depot_manager = main_app.depot_manager
            self.cash_account_manager = main_app.cash_account_manager
            self.card_account_manager = main_app.card_account_manager

    @staticmethod
    def _safe_to_float(value: Any, default: float = 0.0) -> float:
        if isinstance(value, (int, float)):
            return float(value)
        text = str(value or "").strip()
        if not text:
            return default
        if "," in text and "." in text:
            if text.rfind(",") > text.rfind("."):
                text = text.replace(".", "").replace(",", ".")
            else:
                text = text.replace(",", "")
        elif "," in text:
            text = text.replace(",", ".")
        try:
            return float(text)
        except Exception:
            return default

    @staticmethod
    def _normalize_text(value: Any) -> str:
        text = str(value or "")
        replacements = {
            "Ç": "Ç",
            "ç": "ç",
            "İ": "İ",
            "ı": "ı",
            "Ö": "Ö",
            "ö": "ö",
            "Ü": "Ü",
            "ü": "ü",
            "Ş": "Ş",
            "ş": "ş",
            "ğ": "ğ",
            "NAKİT": "NAKİT",
            "GİRİŞ": "GİRİŞ",
            "ÇIKIŞ": "ÇIKIŞ",
            "SATIŞ": "SATIŞ",
            "ALIŞ": "ALIŞ",
            "İŞLEM": "İŞLEM",
            "ÇEVİRİ": "ÇEVİRİ",
        }
        for bad, good in replacements.items():
            text = text.replace(bad, good)
        return text

    def _get_default_financial_manager(self):
        """
        Bedelli cari işlemlerde hesap seçimi yoksa kullanılacak varsayılan finans
        hesabını döndürür. Önce erişilebilir kasalar, sonra kartlar denenir.
        """
        try:
            for acc in self.main_app._get_accessible_cash_accounts():
                mgr = self.main_app.cash_managers.get(acc.id)
                if mgr:
                    return mgr
        except Exception:
            pass

        for mgr in self.main_app.cash_managers.values():
            if mgr:
                return mgr

        try:
            for acc in self.main_app._get_accessible_card_accounts():
                mgr = self.main_app.card_managers.get(acc.id)
                if mgr:
                    return mgr
        except Exception:
            pass

        for mgr in self.main_app.card_managers.values():
            if mgr:
                return mgr

        return None

    def _apply_exchange_cash_rows(self, person_name: str, date_str: str, mode: str, exchange_items: List[Dict[str, Any]], cursor) -> None:
        """
        Bedel listesinde para birimi satırı varsa kasaya/karta finansal hareket yazar.
        """
        if not exchange_items:
            return

        target_mgr = self._get_default_financial_manager()
        if not target_mgr:
            return

        is_cash_incoming = "PaidOut" in str(mode or "")
        trans_type = "Income" if is_cash_incoming else "Expense"
        desc = f"Bedelli işlem karşılığı ({mode}) - {person_name}"

        for item in exchange_items:
            currency = str(item.get("type") or "").strip().upper()
            amount = self._safe_to_float(item.get("amount"), 0.0)
            if amount <= 0 or currency not in SUPPORTED_CURRENCIES:
                continue

            base_val = self.currency_manager._convert_to_base_currency(amount, currency)
            target_mgr.add_item(
                FinancialTransaction(
                    date_str,
                    trans_type,
                    amount,
                    currency,
                    base_val,
                    desc,
                    person_full_name=person_name,
                ),
                cursor=cursor,
            )

    def _resolve_event_reverse_depot(
        self,
        stock_mgr,
        main_product: str,
        main_qty: float,
        preferred_depot: str,
        is_out: bool,
    ) -> str:
        """
        Eski JSON kayıtlarda main_depot yoksa silme için uygun depoyu bulur.
        """
        preferred = str(preferred_depot or "").strip()
        if preferred and self.depot_manager.get_id_by_name(preferred):
            return preferred

        depots = self.depot_manager.get_depot_names() or []
        if not depots:
            return "Merkez"

        if is_out:
            for depot in depots:
                s = stock_mgr.get_stock_by_type_and_depot(main_product, depot)
                if s and self._safe_to_float(getattr(s, "quantity", 0), 0.0) > 0:
                    return depot
            return depots[0]

        needed = max(self._safe_to_float(main_qty, 0.0), 0.0)
        for depot in depots:
            s = stock_mgr.get_stock_by_type_and_depot(main_product, depot)
            available = self._safe_to_float(getattr(s, "quantity", 0), 0.0) if s else 0.0
            if needed > 0 and available + 1e-9 >= needed:
                return depot
        for depot in depots:
            s = stock_mgr.get_stock_by_type_and_depot(main_product, depot)
            if s and self._safe_to_float(getattr(s, "quantity", 0), 0.0) > 0:
                return depot
        return depots[0]

    def _format_main_product_label(self, product: Any, mode: str = "") -> str:
        raw = self._normalize_text(product).strip()
        if not raw:
            return raw
        try:
            if raw.replace(",", ".").replace(" ", "").isdigit():
                if "Product" in str(mode):
                    return f"{raw} Ayar"
        except Exception:
            pass
        return raw

    def _get_display_event_type(self, event) -> str:
        event_type_text = self._normalize_text(getattr(event, "event_type", ""))
        desc_raw = str(getattr(event, "description", "") or "").strip()
        if not desc_raw.startswith("{"):
            return event_type_text
        try:
            json_data = json.loads(desc_raw)
        except Exception:
            return event_type_text

        mode = str(json_data.get("mode", "") or "")
        translated = _get_stock_event_title_for_mode(self.main_app.lang, mode, event_type_text)
        return self._normalize_text(translated or event_type_text)

    def _build_bedelli_desc(self, event) -> str:
        raw_desc = str(getattr(event, "description", "") or "")
        if not raw_desc.strip().startswith("{"):
            return self._normalize_text(raw_desc)
        try:
            jd = json.loads(raw_desc)
        except Exception:
            return self._normalize_text(raw_desc)

        mode = str(jd.get("mode") or "")
        main_product = self._format_main_product_label(jd.get("main_product") or getattr(event, "currency", ""), mode)
        main_qty = self._safe_to_float(jd.get("main_qty"), 0.0)
        main_total_gram = self._safe_to_float(jd.get("main_total_gram"), 0.0)
        if main_total_gram <= 0:
            details_json = jd.get("details_json") or []
            if isinstance(details_json, str):
                try:
                    details_json = json.loads(details_json)
                except Exception:
                    details_json = []
            if isinstance(details_json, dict):
                details_json = [details_json]
            if isinstance(details_json, list):
                for row in details_json:
                    if isinstance(row, dict):
                        main_total_gram += self._safe_to_float(row.get("gram"), 0.0)
        if main_total_gram <= 0:
            for item in jd.get("exchange_items") or []:
                if isinstance(item, dict):
                    main_total_gram += self._safe_to_float(item.get("gram"), 0.0)

        karats = jd.get("main_karats") or []
        if isinstance(karats, (list, tuple)):
            karat_text = ", ".join(self._normalize_text(k) for k in karats if str(k).strip())
        else:
            karat_text = self._normalize_text(karats)

        base_desc = self._normalize_text(jd.get("main_desc") or "")
        qty_text = ""
        if main_qty > 0:
            if any(x in mode for x in ["Product", "Ziynet"]):
                if abs(main_qty - int(main_qty)) < 1e-9:
                    qty_text = f"{int(main_qty)} Adet"
                else:
                    qty_text = f"{format_number(main_qty, 3)} Adet"
            elif "Scrap" in mode:
                qty_text = f"{format_number(main_qty, 3)} gr"
            else:
                qty_text = format_number(main_qty, 3)

        gram_text = f"({format_number(main_total_gram, 2)} gr)" if main_total_gram > 0 else ""
        main_line_parts = [p for p in [qty_text, gram_text, main_product] if p]
        main_line = " ".join(main_line_parts).strip()

        if not main_line and karat_text and main_product:
            main_line = f"{karat_text} {main_product}".strip()
        if not main_line:
            main_line = base_desc

        items = jd.get("exchange_items") or []
        net_gram = 0.0
        parts = []
        for item in items:
            typ = self._normalize_text(item.get("type"))
            amt = self._safe_to_float(item.get("amount"), 0.0)
            if not typ or amt <= 0:
                continue
            item_gram = self._safe_to_float(item.get("gram"), 0.0)
            item_karat = self._normalize_text(item.get("karat"))
            bedel_detail = []
            if item_gram > 0:
                bedel_detail.append(f"{format_number(item_gram, 2)} gr")
            if item_karat:
                bedel_detail.append(item_karat)
            if bedel_detail:
                parts.append(f"{format_number(amt, 3)} {typ} ({', '.join(bedel_detail)})")
            else:
                parts.append(f"{format_number(amt, 3)} {typ}")
            if typ not in SUPPORTED_CURRENCIES:
                net_gram += amt

        extra = [main_line] if main_line else []
        if parts:
            extra.append("Bedel: " + " + ".join(parts))
        if net_gram > 0 and "Exchange" in mode:
            extra.append(f"Net Gram: {format_number(net_gram, 3)} gr")

        if extra:
            return " | ".join(x for x in extra if x)
        return self._normalize_text(base_desc or raw_desc)

    def _is_hidden_excel_cash_event(self, event_type: Any) -> bool:
        """
        Excel cari görünümünde nakit giriş/çıkış işlemlerini artık gizleme.
        Kullanıcı isteği doğrultusunda bu işlemler tabloya ve borç/alacak bakiyesine yansıtılır.
        """
        return False

    def _refresh_cari_views_after_transaction(self, refresh_cash: bool = True) -> None:
        """
        Cari ekranlarındaki tablo ve özetleri işlem sonrası tutarlı şekilde yeniler.
        """
        self.main_app.refresh_current_account_table()
        if refresh_cash:
            self.main_app.refresh_cash_table()
        self.main_app.refresh_account_balances()
        self.main_app.refresh_dashboard_cards()
    def _select_previous_person(self):
            """Listedeki bir önceki müşteriyi seçer."""
            combo = self.main_app.acc_search_combo
            count = combo.count()
            if count == 0: return
            current_idx = combo.currentIndex()
            if current_idx > 0:
                combo.setCurrentIndex(current_idx - 1)
            else:
                combo.setCurrentIndex(count - 1)
    def _select_next_person(self):
            """Listedeki bir sonraki müşteriyi seçer."""
            combo = self.main_app.acc_search_combo
            count = combo.count()
            if count == 0: return
            current_idx = combo.currentIndex()
            if current_idx < count - 1:
                combo.setCurrentIndex(current_idx + 1)
            else:
                combo.setCurrentIndex(0)
    def _add_update_person(self, update=False) -> None:
            """Yeni kişi ekler veya mevcut kişiyi günceller."""
            full_name = self.main_app.contact_full_name_entry.text().strip()
            if not full_name:
                QMessageBox.warning(self.main_app, self.main_app.lang["missing_info"], f"{self.main_app.lang['first_name_last_name']} {self.main_app.lang['missing_info'].lower()}.")
                return
            if len(full_name) > 150:
                QMessageBox.warning(self.main_app, "Uyarı", "Ad Soyad çok uzun. Maksimum 150 karakter olmalıdır.")
                return
            person_id = None
            if update:
                selected_items = self.main_app.person_table.selectedItems()
                if not selected_items:
                    QMessageBox.warning(self.main_app, self.main_app.lang["warning"], self.main_app.lang["no_selection"].format(item_type=self.main_app.lang["person"].lower()))
                    return
                person_id = self.main_app.person_table.item(selected_items[0].row(), 0).data(Qt.ItemDataRole.UserRole)
            if person_id:
                person = self.person_manager.find_by_id(person_id)
                if person:
                    original_full_name = person.full_name
                    existing_person = self.person_manager.find_by_attribute('full_name', full_name)
                    if existing_person and existing_person.id != person_id:
                        QMessageBox.warning(self.main_app, self.main_app.lang["existing_item"], f"{self.main_app.lang['person']} '{full_name}' {self.main_app.lang['existing_item']}.")
                        return
                    new_data = {
                        "full_name": full_name, "phone_number": self.main_app.contact_phone_entry.text().strip(),
                        "address": self.main_app.contact_address_text_edit.toPlainText().strip(), "notes": self.main_app.contact_notes_text_edit.toPlainText().strip()
                    }
                    try:
                        self.person_manager.update_item(person.id, new_data)
                    except Exception as e:
                        msg = str(e)
                        if "Data too long" in msg or "data too long" in msg:
                            msg = "Ad Soyad çok uzun. Maksimum 150 karakter olmalıdır."
                        QMessageBox.critical(self.main_app, "Hata", f"Kişi güncellenemedi: {msg}")
                        return
                    if original_full_name != full_name:
                        db = self.main_app.data_manager.db
                        profile_id = self.person_manager.profile_id
                        update_specs = [
                            ("debt_records", "person_full_name", "person_full_name", full_name, original_full_name),
                            ("sales", "customer_full_name", "customer_full_name", full_name, original_full_name),
                            ("repair_records", "customer_full_name", "customer_full_name", full_name, original_full_name),
                            ("customer_history_events", "person_full_name", "person_full_name", full_name, original_full_name),
                            ("scheduled_transactions", "person_full_name", "person_full_name", full_name, original_full_name),
                            ("financial_transactions", "related_person_id", "related_person_id", person.id, person.id),
                        ]
                        with db.transaction_scope() as cursor:
                            for table_name, set_col, where_col, new_val, old_val in update_specs:
                                lock_sql = (
                                    f"SELECT id FROM {table_name} "
                                    f"WHERE {where_col}=%s AND profile_id=%s FOR UPDATE"
                                )
                                cursor.execute(lock_sql, (old_val, profile_id))
                                id_rows = cursor.fetchall() or []
                                ids = [str(r.get("id") or "") for r in id_rows if r.get("id")]
                                before_map = fetch_rows_by_ids(
                                    db=db,
                                    table_name=table_name,
                                    profile_id=profile_id,
                                    record_ids=ids,
                                    cursor=cursor,
                                )
                                update_sql = (
                                    f"UPDATE {table_name} SET {set_col}=%s "
                                    f"WHERE {where_col}=%s AND profile_id=%s"
                                )
                                cursor.execute(update_sql, (new_val, old_val, profile_id))
                                after_map = fetch_rows_by_ids(
                                    db=db,
                                    table_name=table_name,
                                    profile_id=profile_id,
                                    record_ids=ids,
                                    cursor=cursor,
                                )
                                for rid in ids:
                                    before_row = before_map.get(rid)
                                    after_row = after_map.get(rid)
                                    if before_row == after_row:
                                        continue
                                    queue_row_sync_event(
                                        db=db,
                                        table_name=table_name,
                                        profile_id=profile_id,
                                        action_type=SYNC_ACTION_UPDATE,
                                        record_id=rid,
                                        before_row=before_row,
                                        after_row=after_row,
                                        cursor=cursor,
                                        meta={"source": "CRMManager._add_update_person"},
                                    )
                    self.main_app._show_status_message(f"Person '{full_name}' updated.")
            else:
                if self.person_manager.find_by_attribute('full_name', full_name):
                    QMessageBox.warning(self.main_app, self.main_app.lang["existing_item"], f"{self.main_app.lang['person']} '{full_name}' {self.main_app.lang['existing_item']}.")
                    return
                new_person = Person(full_name=full_name, phone_number=self.main_app.contact_phone_entry.text().strip(),
                                    address=self.main_app.contact_address_text_edit.toPlainText().strip(), notes=self.main_app.contact_notes_text_edit.toPlainText().strip())
                try:
                    self.person_manager.add_item(new_person)
                except Exception as e:
                    msg = str(e)
                    if "Data too long" in msg or "data too long" in msg:
                        msg = "Ad Soyad çok uzun. Maksimum 150 karakter olmalıdır."
                    QMessageBox.critical(self.main_app, "Hata", f"Kişi eklenemedi: {msg}")
                    return
                self.main_app._show_status_message(f"Person '{full_name}' added.")
            self.update_person_comboboxes()
            self.main_app.refresh_current_account_table()
            self._clear_person_form()
    def _delete_person(self) -> None:
            """
            Seçili kişiyi siler.
            FAZ 8: Silme işleminden sonra arayüzdeki 'Hayalet Seçimler' temizlenir.
            """
            selected_items = self.main_app.person_table.selectedItems()
            if not selected_items:
                QMessageBox.warning(self.main_app, self.main_app.lang["warning"], self.main_app.lang["no_selection"].format(item_type=self.main_app.lang["person"].lower()))
                return
            person_id = self.main_app.person_table.item(selected_items[0].row(), 0).data(Qt.ItemDataRole.UserRole)
            person = self.person_manager.find_by_id(person_id)
            if not person: return
            pending_debts = self.debt_manager.get_pending_debts_by_person(person.full_name)
            if pending_debts:
                QMessageBox.warning(self.main_app, self.main_app.lang["warning"], "Cannot delete person with active debt/credit records.")
                return
            if self.main_app._show_confirm_dialog(self.main_app.lang["delete_person"], f"'{person.full_name}' - {self.main_app.lang['confirm_delete_prompt']}"):
                self.person_manager.delete_item(person.id)
                if hasattr(self.main_app, 'acc_search_combo'):
                    self.main_app.acc_search_combo.setCurrentIndex(-1)
                    self.main_app.acc_search_combo.setEditText("")
                if hasattr(self.main_app, 'transfer_customer_select_combo'):
                    self.main_app.transfer_customer_select_combo.setCurrentIndex(-1)
                self.main_app.refresh_current_account_table()
                self.main_app._show_status_message(f"Person '{person.full_name}' deleted.")
                self._clear_person_form()
                self._update_new_accounts_tab_view()
    def _clear_person_form(self) -> None:
            """Kişi formunu temizler."""
            self.main_app.contact_full_name_entry.clear(); self.main_app.contact_phone_entry.clear()
            self.main_app.contact_address_text_edit.clear(); self.main_app.contact_notes_text_edit.clear()
            self.main_app.person_table.clearSelection(); self.main_app.contact_full_name_entry.setFocus()
    def _person_selected(self) -> None:
            """Listeden kişi seçildiğinde formu doldurur."""
            self._update_person_button_states()
            selected_items = self.main_app.person_table.selectedItems()
            if not selected_items:
                self._clear_person_form()
                return
            person_id = self.main_app.person_table.item(selected_items[0].row(), 0).data(Qt.ItemDataRole.UserRole)
            person = self.person_manager.find_by_id(person_id)
            if person:
                self.main_app.contact_full_name_entry.setText(person.full_name)
                self.main_app.contact_phone_entry.setText(person.phone_number)
                self.main_app.contact_address_text_edit.setPlainText(person.address)
                self.main_app.contact_notes_text_edit.setPlainText(person.notes)
    def _filter_person_list(self, text: str) -> None:
            """Arama kutusuna göre kişi listesini filtreler."""
            search_text = text.lower()
            for row in range(self.main_app.person_table.rowCount()):
                item_name = self.main_app.person_table.item(row, 0).text().lower()
                self.main_app.person_table.setRowHidden(row, search_text not in item_name)
    def _update_person_button_states(self):
            """Kişi butonlarının durumunu günceller."""
            has_selection = len(self.main_app.person_table.selectedItems()) > 0
            self.main_app.update_person_btn.setEnabled(has_selection)
            self.main_app.delete_person_btn.setEnabled(has_selection)
    def update_person_list_display(self) -> None:
            """Kişi listesi tablosunu günceller."""
            if not hasattr(self.main_app, 'person_table'): return
            self.main_app.person_table.setRowCount(0)
            sorted_persons = sorted(self.person_manager.get_all(), key=lambda p: p.full_name.lower())
            if hasattr(self.main_app, 'utility_manager'):
                def fill_row(row, person):
                    full_name_item = QTableWidgetItem(person.full_name)
                    full_name_item.setData(Qt.ItemDataRole.UserRole, person.id)
                    self.main_app.person_table.setItem(row, 0, full_name_item)
                    self.main_app.person_table.setItem(row, 1, QTableWidgetItem(person.phone_number))
                    short_address = person.address.split('\n')[0] if person.address else ""
                    self.main_app.person_table.setItem(row, 2, QTableWidgetItem(short_address))
                self.main_app.utility_manager._populate_table_in_batches(self.main_app.person_table, sorted_persons, fill_row)
            else:
                self.main_app.person_table.setRowCount(len(sorted_persons))
                for row, person in enumerate(sorted_persons):
                    full_name_item = QTableWidgetItem(person.full_name)
                    full_name_item.setData(Qt.ItemDataRole.UserRole, person.id)
                    self.main_app.person_table.setItem(row, 0, full_name_item)
                    self.main_app.person_table.setItem(row, 1, QTableWidgetItem(person.phone_number))
                    self.main_app.person_table.setItem(row, 2, QTableWidgetItem(person.address.split('\n')[0]))
    def update_person_comboboxes(self, force: bool = False) -> None:
        """Tüm Müşteri kutularını SİNYAL KORUMALI günceller."""
        if not force and hasattr(self.main_app, "tabs"):
            current_tab = self.main_app.tabs.currentIndex()
            # Dashboard/Tools/Settings ekranlarında kişi comboları görünmez.
            if current_tab == 0 or current_tab >= 6:
                return
        person_names = self.person_manager.get_person_names()
        target_widget_names = [
            'acc_search_combo', 'repair_customer_combo', 'report_customer_combo',
            'st_person_combo', 'acc_tab_person_combo', 'repair_filter_combo',
            'quick_acc_person_combo', 'transfer_customer_select_combo'
        ]
        combos_to_update = []
        for name in target_widget_names:
            if hasattr(self.main_app, name):
                widget = getattr(self.main_app, name)
                try:
                    if isinstance(widget, QComboBox):
                        combos_to_update.append(widget)
                except: continue
        try:
            stock_widgets = getattr(self.main_app, "stock_widgets", {}) or {}
            for tabs in stock_widgets.values():
                if not isinstance(tabs, dict):
                    continue
                for widgets in tabs.values():
                    if not isinstance(widgets, dict):
                        continue
                    person_combo = widgets.get("person_combo")
                    if isinstance(person_combo, QComboBox) and person_combo not in combos_to_update:
                        combos_to_update.append(person_combo)
        except Exception as e:
            logger.error(f"POS person combo collect error: {e}")
        for combo in combos_to_update:
            try:
                current_text = combo.currentText()
                combo.blockSignals(True) # KRİTİK: Sinyali Durdur
                combo.clear()
                is_filter = (combo == getattr(self.main_app, 'repair_filter_combo', None))
                search_list = []
                if is_filter:
                    show_all = self.main_app.lang.get("show_all", "Tümünü Göster")
                    combo.addItem(show_all)
                    combo.addItems(person_names)
                    search_list = [show_all] + person_names
                else:
                    combo.addItem("") # Boş seçenek
                    combo.addItems(person_names)
                    search_list = person_names
                idx = combo.findText(current_text)
                if idx >= 0:
                    combo.setCurrentIndex(idx)
                elif combo.isEditable():
                    combo.setEditText(current_text)
                if combo.isEditable():
                    completer = QCompleter(search_list)
                    completer.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
                    completer.setFilterMode(Qt.MatchFlag.MatchContains)
                    combo.setCompleter(completer)
            except Exception as e:
                logger.error(f"Person combo update error: {e}")
            finally:
                try:
                    combo.blockSignals(False) # Sinyali Aç
                except: pass
    def _transfer_customers_to_another_profile(self):
        """Mevcut müşterileri seçilen başka bir profile kopyalar."""
        try:
            rows = self.main_app.settings_manager.db.fetch_all(
                "SELECT name FROM profiles WHERE is_active = 1 ORDER BY name ASC"
            )
            current = (self.main_app.profile_name or "").strip()
            other_profiles = [
                (r.get("name") or "").strip()
                for r in (rows or [])
                if isinstance(r, dict) and (r.get("name") or "").strip() and (r.get("name") or "").strip() != current
            ]
            if not other_profiles:
                QMessageBox.warning(self.main_app, "Uyarı", "Aktarım yapılacak başka bir profil bulunamadı.")
                return
            target_profile_name, ok = QInputDialog.getItem(self.main_app, "Profil Seç",
                                                         "Müşterilerin kopyalanacağı hedef profili seçin:",
                                                         other_profiles, 0, False)
            if not ok or not target_profile_name:
                return
            temp_dm = DataManager(profile_file_path=target_profile_name)
            temp_pm = PersonManager(temp_dm)
            existing_names_in_target = set(p.full_name.lower() for p in temp_pm.get_all())
            added_count = 0
            skipped_count = 0
            current_customers = self.person_manager.get_all()
            if not current_customers:
                QMessageBox.warning(self.main_app, "Uyarı", "Bu profilde kopyalanacak müşteri yok.")
                return
            for person in current_customers:
                if person.full_name.lower() in existing_names_in_target:
                    skipped_count += 1
                    continue
                new_person = Person(
                    full_name=person.full_name,
                    phone_number=person.phone_number,
                    address=person.address,
                    notes=person.notes
                )
                temp_pm.add_item(new_person)
                added_count += 1
            msg = f"İşlem Tamamlandı.\n\nHedef Profil: {target_profile_name}\nEklenen Müşteri: {added_count}\nAtlanan (Zaten Var): {skipped_count}"
            QMessageBox.information(self.main_app, "Başarılı", msg)
        except Exception as e:
            logger.error(f"Müşteri aktarım hatası: {e}")
            QMessageBox.critical(self.main_app, "Hata", f"İşlem sırasında hata oluştu: {e}")
    def _connect_accounts_tab_actions(self):
        """Cari Hesaplar sekmesindeki tüm sinyalleri bağlar."""
        try:
            self.main_app.acc_tab_person_combo.currentIndexChanged.connect(self._update_new_accounts_tab_view)
            self.main_app.acc_tab_filter_btn.clicked.connect(self._update_new_accounts_tab_view)
            self.main_app.acc_tab_start_date.dateChanged.connect(self._update_new_accounts_tab_view)
            self.main_app.acc_tab_end_date.dateChanged.connect(self._update_new_accounts_tab_view)
            self.main_app.acc_tab_nakit_giris_btn.clicked.connect(self._acc_tab_tahsilat_yap)
            self.main_app.acc_tab_nakit_cikis_btn.clicked.connect(self._acc_tab_odeme_yap)
            self.main_app.acc_tab_urun_giris_btn.clicked.connect(lambda: self._acc_tab_show_add_debt_dialog("Payable"))
            self.main_app.acc_tab_urun_cikis_btn.clicked.connect(lambda: self._acc_tab_show_add_debt_dialog("Receivable"))
            self.main_app.acc_tab_print_pdf_btn.clicked.connect(self._acc_tab_print_pdf)
            self.main_app.acc_tab_print_excel_btn.clicked.connect(self._acc_tab_print_excel)
            self.main_app.acc_tab_edit_btn.clicked.connect(self._acc_tab_edit_record)
            self.main_app.acc_tab_delete_btn.clicked.connect(self._acc_tab_delete_record)
            self.main_app.acc_tab_history_table.itemSelectionChanged.connect(self._acc_tab_update_manage_buttons)
            self._acc_tab_set_buttons_enabled(False)
            self._acc_tab_update_manage_buttons()
        except Exception as e:
            logger.error(f"Sinyal bağlama hatası: {e}")
            QMessageBox.critical(self.main_app, "Hata", f"Bağlantı hatası: {e}")
    def _acc_tab_update_manage_buttons(self):
        """Tabloda seçim varsa düzenle/sil butonlarını aktif eder."""
        has_selection = len(self.main_app.acc_tab_history_table.selectedItems()) > 0
        self.main_app.acc_tab_edit_btn.setEnabled(has_selection)
        self.main_app.acc_tab_delete_btn.setEnabled(has_selection)
    def _acc_tab_populate_history_table(self, person_name: str):
        """
        Hareketler tablosunu doldurur. (Customer History)
        """
        pass
    def _acc_tab_set_buttons_enabled(self, enabled: bool):
            for btn in [self.main_app.acc_tab_nakit_giris_btn, self.main_app.acc_tab_nakit_cikis_btn,
                        self.main_app.acc_tab_urun_giris_btn, self.main_app.acc_tab_urun_cikis_btn]:
                btn.setEnabled(enabled)
    def _acc_tab_clear_view(self):
            """Ekranı temizler."""
            self.main_app.acc_tab_name_label.setText("-----")
            self.main_app.acc_tab_phone_label.setText("-----")
            self.main_app.acc_tab_address_label.setText("-----")
            self.main_app.acc_tab_borclu_label.setText("0.00")
            self.main_app.acc_tab_alacakli_label.setText("0.00")
            self.main_app.acc_tab_history_table.setRowCount(0)
            self._acc_tab_set_buttons_enabled(False)
    
    
    

    
    def _update_new_accounts_tab_view(self):
        """
        Cari Hesaplar sekmesini günceller.
        FIX: Bakiye hesaplama mantığı eski 'Direction' (Yön) bazlı sisteme döndürüldü.
        Bedelli işlemlerde ana ürün ve ödeme kalemleri zıt yönlerde işlenir.
        """
        # Legacy accounts-tab widgets may not exist in newer UI variants.
        # In that case, this refresh path should no-op instead of crashing.
        if not hasattr(self.main_app, "acc_tab_person_combo"):
            return

        selected_person_name = self.main_app.acc_tab_person_combo.currentText().strip()
        if not selected_person_name:
            self._acc_tab_clear_view()
            return

        # Tarih Aralığı
        try:
            start_date = self.main_app.acc_tab_start_date.date().toPyDate()
            start_dt = datetime(start_date.year, start_date.month, start_date.day)
            end_date = self.main_app.acc_tab_end_date.date().toPyDate()
            end_dt = datetime(end_date.year, end_date.month, end_date.day, 23, 59, 59)
        except Exception:
            start_dt = datetime.now().replace(day=1, hour=0, minute=0)
            end_dt = datetime.now()

        all_events = self.customer_history_manager.get_all_by_person(selected_person_name)
        
        running_balances = defaultdict(float)
        event_balance_map = {} 

        # --- KÜMÜLATİF BAKİYE HESABI (Tüm zamanlar) ---
        for event in all_events:
            
            # 1. JSON İçeren Bedelli / Karma İşlemler
            if event.description and event.description.strip().startswith('{'):
                try:
                    data = json.loads(event.description)
                    items = data.get('exchange_items', [])
                    mode = data.get('mode', '')

                    # A) Çeviri İşlemleri (Özel Durum)
                    if 'Exchange' in mode:
                        for i, item in enumerate(items):
                            amt = self._safe_to_float(item.get('amount', 0), 0.0)
                            typ = item.get('type')
                            if typ and amt > 0:
                                balance_key = typ
                                if typ not in SUPPORTED_CURRENCIES:
                                    item_gram = self._safe_to_float(item.get("gram", 0), 0.0)
                                    item_karat = self._normalize_text(item.get("karat"))
                                    extras = []
                                    if item_gram > 0:
                                        extras.append(f"{format_number(item_gram, 2)} gr")
                                    if item_karat:
                                        extras.append(item_karat)
                                    if extras:
                                        balance_key = f"{typ} ({' '.join(extras)})"
                                if 'ExchangePayable' in mode:
                                    # Borç Çevirisi: Kaynak azalır (Ödeme gibi), Hedef artar (Borçlanma gibi)
                                    if i == 0: running_balances[balance_key] -= amt
                                    else:      running_balances[balance_key] += amt
                                elif 'ExchangeReceivable' in mode:
                                    # Alacak Çevirisi: Kaynak artar (Borçlanma/İade gibi), Hedef azalır (Ödeme gibi)
                                    if i == 0: running_balances[balance_key] += amt
                                    else:      running_balances[balance_key] -= amt
                    elif '(POS)' in mode:
                        if event.currency and event.currency != "Karma":
                            running_balances[event.currency] += (event.debt_original - event.credit_original)
                    
                    # B) Alış / Satış İşlemleri (Bedelli)
                    else:
                        # Bedelli hareketlerde giriş/çıkış zıt işaretle ilerlemeli;
                        # aksi halde bakiye kapanmak yerine katlanır.
                        if 'PaidOut' in mode:
                            bedel_direction = 1.0
                        elif 'PaidIn' in mode:
                            bedel_direction = -1.0
                        elif any(x in mode for x in ['Out', 'Sale']):
                            bedel_direction = 1.0
                        elif any(x in mode for x in ['In', 'Purchase']):
                            bedel_direction = -1.0
                        else:
                            bedel_direction = -1.0

                        for item in items:
                            amt = self._safe_to_float(item.get('amount', 0), 0.0)
                            typ = item.get('type')
                            if typ and amt > 0:
                                balance_key = typ
                                if typ not in SUPPORTED_CURRENCIES:
                                    item_gram = self._safe_to_float(item.get("gram", 0), 0.0)
                                    item_karat = self._normalize_text(item.get("karat"))
                                    extras = []
                                    if item_gram > 0:
                                        extras.append(f"{format_number(item_gram, 2)} gr")
                                    if item_karat:
                                        extras.append(item_karat)
                                    if extras:
                                        balance_key = f"{typ} ({' '.join(extras)})"
                                running_balances[balance_key] += (amt * bedel_direction)

                except (json.JSONDecodeError, ValueError, TypeError):
                    pass
            
            # 2. Standart İşlemler (JSON içermeyen eski veya basit kayıtlar)
            else:
                if event.currency and event.currency != "Karma":
                    # Borç (+), Alacak (-)
                    running_balances[event.currency] += (event.debt_original - event.credit_original)

            event_balance_map[event.id] = running_balances.copy()

        # --- EKRANA YAZDIRMA ---
        self._display_summary_labels(selected_person_name, running_balances)

        # Tarih filtresine uyanları göster (Ters sıra: En yeni en üstte)
        filtered_events = [
            e for e in reversed(all_events)
            if start_dt <= (parse_date_from_display(e.event_date) or datetime.min) <= end_dt
        ]
        
        self.main_app.acc_tab_history_table.setRowCount(len(filtered_events))
        for i, ev in enumerate(filtered_events):
            self._fill_acc_history_row(i, ev, event_balance_map.get(ev.id, {}))

    def _process_event_balance(self, event, running_balances):
        """
        GÜNCEL BAKİYE HESAPLAMA MANTIÄI:
        Hem JSON detaylı işlemleri (Çeviri) hem de standart Bedelli işlemleri kapsar.
        """
        
        # --- FIX 2: BEDELLİ İŞLEMLERİN BAKİYE ETKİSİ ---
        # Eğer işlem "Karma" değilse ve geçerli bir para birimi varsa (TRY, USD, EUR vb.),
        # doğrudan ana sütunlardaki borç/alacak değerlerini işle.
        # Bu sayede JSON içindeki karmaşık yapılara bakmadan net bakiyeyi buluruz.
        from core.config import SUPPORTED_CURRENCIES
        
        if event.currency and event.currency != "Karma" and event.currency in SUPPORTED_CURRENCIES:
            # Borç (Debt) -> Müşteri Bize Borçlanır (+)
            # Alacak (Credit) -> Müşteri Bizden Alacaklanır (-)
            running_balances[event.currency] += (event.debt_original - event.credit_original)
            return  # Bedelli işlem işlendi, çık.

        # JSON verisini güvenli bir şekilde oku (Sadece Çeviri ve Karma işlemler için)
        if event.description and event.description.strip().startswith('{'):
            try:
                data = json.loads(event.description)
                mode = data.get('mode', '')
                items = data.get('exchange_items', [])

                # --- ÇEVİRİ İŞLEMİ MANTIÄI ---
                if 'Exchange' in mode:
                    for i, item in enumerate(items):
                        amt = self._safe_to_float(item.get('amount', 0), 0.0)
                        typ = item.get('type')
                        
                        if typ and amt > 0:
                            balance_key = typ
                            if typ not in SUPPORTED_CURRENCIES:
                                item_gram = self._safe_to_float(item.get("gram", 0), 0.0)
                                item_karat = self._normalize_text(item.get("karat"))
                                extras = []
                                if item_gram > 0:
                                    extras.append(f"{format_number(item_gram, 2)} gr")
                                if item_karat:
                                    extras.append(item_karat)
                                if extras:
                                    balance_key = f"{typ} ({' '.join(extras)})"
                            if 'ExchangePayable' in mode:
                                if i == 0: running_balances[balance_key] -= amt
                                else:      running_balances[balance_key] += amt
                            elif 'ExchangeReceivable' in mode:
                                if i == 0: running_balances[balance_key] += amt
                                else:      running_balances[balance_key] -= amt
                    return

                # --- DİÄER BEDELLİ İŞLEMLER (Emanet/Ürün Bazlı) ---
                # Eğer yukarıdaki if bloğuna girmediyse (yani currency supported_currencies değilse),
                # bu muhtemelen ürün bazlı bir borçlanmadır (Örn: 5 Çeyrek Borç).
                elif items:
                    if 'PaidOut' in mode:
                        bedel_direction = 1.0
                    elif 'PaidIn' in mode:
                        bedel_direction = -1.0
                    elif any(x in mode for x in ['Out', 'Sale']):
                        bedel_direction = 1.0
                    elif any(x in mode for x in ['In', 'Purchase']):
                        bedel_direction = -1.0
                    else:
                        bedel_direction = -1.0
                    for item in items:
                        amt = self._safe_to_float(item.get('amount', 0), 0.0)
                        typ = item.get('type')
                        if typ and amt > 0:
                            running_balances[typ] += (amt * bedel_direction)
                    return

            except (json.JSONDecodeError, ValueError, TypeError):
                pass
        
        # --- STANDART İŞLEM MANTIÄI (JSON olmayan emanetler) ---
        # Eğer yukarıdakilerin hiçbiri değilse ve bir para birimi/ürün varsa
        if event.currency and event.currency != "Karma":
            running_balances[event.currency] += (event.debt_original - event.credit_original)


    def _display_summary_labels(self, person_name, balances):
        """Hesaplanan son bakiyeyi üstteki etiketlere yazar."""
        borc_list = []
        alacak_list = []
        
        for curr, val in sorted(balances.items()):
            if val > 0.001:   # Pozitif bakiye = Müşteri bize BORÇLU
                borc_list.append(f"{format_number(val, 3 if curr not in SUPPORTED_CURRENCIES else 2)} {curr}")
            elif val < -0.001: # Negatif bakiye = Müşteri bizden ALACAKLI
                alacak_list.append(f"{format_number(abs(val), 3 if curr not in SUPPORTED_CURRENCIES else 2)} {curr}")

        person = self.person_manager.find_by_attribute("full_name", person_name)
        if person:
            self.main_app.acc_tab_name_label.setText(person.full_name)
            self.main_app.acc_tab_phone_label.setText(person.phone_number or "-")
            self.main_app.acc_tab_address_label.setText(person.address or "-")

        self.main_app.acc_tab_borclu_label.setText(" | ".join(borc_list) if borc_list else "0.00")
        self.main_app.acc_tab_alacakli_label.setText(" | ".join(alacak_list) if alacak_list else "0.00")
        self._acc_tab_set_buttons_enabled(True)
    def _fill_acc_history_row(self, row, event, balance_at_event):
        """
        GÜNCEL: Cari Hesaplar tablosunun bir satırını doldurur.
        - İşlem detaylarını ayrıştırır.
        - Bedelli ise Detay butonu ekler.
        - O işlem yapıldıktan SONRA oluşan kümülatif bakiyeyi gösterir.
        """
        try:
            display_desc = event.description
            is_bedelli = False
            bedel_str = ""
            json_data = {}
            pos_purchase_inferred_amount = None

            # 1. Açıklamayı ve Bedelleri Ayrıştır (JSON kontrolü)
            if event.description and event.description.strip().startswith('{'):
                try:
                    json_data = json.loads(event.description)
                    display_desc = json_data.get('main_desc', event.description)
                    items = json_data.get('exchange_items', [])
                    mode = str(json_data.get('mode', '') or '')
                    
                    is_pos_purchase = '(POS)' in mode and 'Purchase' in mode
                    
                    if is_pos_purchase and items:
                        # Peşin Alış işleminde ödenen nakit/kart tutarını bul
                        inferred = 0.0
                        for it in items:
                            try:
                                it_type = str(it.get('type', '') or '')
                                it_method = str(it.get('method', '') or '')
                                if it_type in SUPPORTED_CURRENCIES or it_method in ("cash", "card"):
                                    inferred += float(it.get('amount') or it.get('total_val') or 0)
                            except Exception: continue
                        if inferred > 0:
                            pos_purchase_inferred_amount = inferred

                    items_for_display = items
                    if items_for_display:
                        is_bedelli = True
                        parts = [f"{format_number(float(i.get('amount',0)))} {i.get('type','')}" for i in items_for_display[:2]]
                        bedel_str = " + ".join(parts)
                        if len(items_for_display) > 2: bedel_str += "..."
                except Exception:
                    display_desc = "(Bozuk Kayıt)"

            # 2. Tarih ve Saat Formatla
            dt = parse_date_from_display(event.event_date)
            date_s = dt.strftime("%d.%m.%Y") if dt else (event.event_date or "").split(" ")[0]

            # 3. Tablo Hücrelerini Doldur
            table = self.main_app.acc_tab_history_table
            col_count = table.columnCount()
            detail_col = col_count - 1 if col_count > 5 else None
            balance_col = detail_col - 1 if col_count > 6 else None
            
            # İşlem Türü, Tarih, Saat, Açıklama
            table.setItem(row, 0, QTableWidgetItem(self._get_display_event_type(event)))
            if col_count > 1:
                table.setItem(row, 1, QTableWidgetItem(date_s))
            if col_count > 2:
                table.setItem(row, 2, QTableWidgetItem(display_desc))

            # Para Birimi/Ürün
            disp_currency = event.currency
            if (not disp_currency or disp_currency == "Karma") and 'main_product' in locals().get('json_data', {}):
                disp_currency = json_data.get('main_product')
            if col_count > 3:
                table.setItem(row, 3, QTableWidgetItem(disp_currency))

            # Tutar
            raw_amt = getattr(event, 'transaction_amount', 0) or 0
            try: amt_val = float(raw_amt)
            except: amt_val = 0.0
            if amt_val == 0:
                try: amt_val = float(event.debt_original or 0) + float(event.credit_original or 0)
                except: amt_val = 0.0
            if amt_val == 0 and pos_purchase_inferred_amount is not None:
                amt_val = pos_purchase_inferred_amount
            
            if col_count > 4:
                table.setItem(row, 4, QTableWidgetItem(format_number(amt_val)))

            # Bakiye (Hesaplanmış Kümülatif Bakiye)
            if balance_col is not None and balance_col >= 5:
                balance_texts = [
                    f"{format_number(bal, 3 if curr not in SUPPORTED_CURRENCIES else 2)} {curr}" 
                    for curr, bal in sorted(balance_at_event.items()) if abs(bal) > 0.001
                ]
                balance_str = " | ".join(balance_texts) if balance_texts else "0.00"
                balance_item = QTableWidgetItem(balance_str)
                balance_item.setTextAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
                table.setItem(row, balance_col, balance_item)

            # Detay Butonu
            if detail_col is not None and is_bedelli:
                btn_detail = QPushButton("Detay")
                btn_detail.setIcon(QIcon.fromTheme("edit-find"))
                btn_detail.clicked.connect(lambda _, desc=event.description: self._open_bedel_dialog(desc))
                table.setCellWidget(row, detail_col, btn_detail)
            elif detail_col is not None:
                table.setItem(row, detail_col, QTableWidgetItem("-"))
                
            # ID'yi sakla
            table.item(row, 0).setData(Qt.ItemDataRole.UserRole, event.id)

        except Exception as e:
            logger.error(f"Tablo satırı doldurma hatası (row {row}): {e}", exc_info=True)
            try:
                # Hata durumunda satırı temizle ki çökmesin
                for c in range(table.columnCount()):
                    table.setItem(row, c, QTableWidgetItem("HATA!"))
            except:
                pass
    def _open_bedel_dialog(self, json_desc):
            """Bedel detay penceresini açar."""
            from modules.crm import BedelDetailsDialog
            dialog = BedelDetailsDialog(json_desc, self.main_app)
            dialog.exec()
    def _acc_tab_show_add_debt_dialog(self, record_type: str):
        """Borç/Alacak ekleme diyaloğunu gösterir."""
        person_name = self.main_app.acc_tab_person_combo.currentText().strip()
        if not person_name:
            QMessageBox.warning(self.main_app, "Uyarı", "Lütfen bir kişi seçin.")
            return
        try:
            dialog = AddDebtDialog(
                record_type, person_name, self.main_app.lang,
                self.product_manager.get_product_names(),
                self.main_app._get_accessible_depots(),
                self.main_app._get_accessible_cash_accounts(),
                self.main_app._get_accessible_card_accounts(),
                parent=self.main_app
            )
            if dialog.exec():
                data = dialog.get_data()
                if data:
                    self._add_debt_record_from_data(record_type, person_name, data)
                    self._update_new_accounts_tab_view()
        except Exception as e:
            logger.error(f"Borç ekleme hatası: {e}")
            QMessageBox.critical(self.main_app, "Hata", f"İşlem başarısız: {e}")
    def _add_debt_record_from_data(self, record_type: str, person_full_name: str, data: dict):
        """Borç kaydı oluşturur ve seçime göre kasaya/stoğa işler."""
        transaction_date = data["transaction_date"]
        description = data["description"]
        debt_type = data["debt_type"]
        due_date = data["due_date"]
        try:
            trans_date_obj = datetime.strptime(transaction_date, "%d.%m.%Y")
            now = datetime.now()
            final_dt = trans_date_obj.replace(hour=now.hour, minute=now.minute, second=now.second)
            event_date_iso = final_dt.isoformat()
        except ValueError:
            event_date_iso = datetime.now().isoformat()
        original_amount = 0.0
        currency = BASE_CURRENCY
        total_amount_base = 0.0
        product_type = None
        product_quantity = 0.0
        new_debt_id = str(uuid.uuid4())
        should_affect_cash = False
        if debt_type == "Cash":
            original_amount = data["amount"]
            currency = data["currency"]
            total_amount_base = self.currency_manager._convert_to_base_currency(original_amount, currency)
            affect_cash = getattr(self.main_app, 'affect_cash_checkbox', None)
            affect_cash_value = affect_cash.isChecked() if affect_cash else True
            should_affect_cash = data.get("affect_cash", False) and affect_cash_value
            if should_affect_cash:
                source_type, source_id = data["source_type"], data["source_id"]
                manager = self.main_app.cash_managers.get(source_id) if source_type == 'cash' else self.main_app.card_managers.get(source_id)
                if manager:
                    account = self.cash_account_manager.find_by_id(source_id) if source_type == 'cash' else self.card_account_manager.find_by_id(source_id)
                    account_name = account.name if account else "Unknown"
                    if record_type == "Receivable":
                        balance = manager.calculate_balance()
                        if balance.get(currency, 0) < original_amount:
                            QMessageBox.warning(self.main_app, "Uyarı", f"'{account_name}' - Yetersiz Bakiye! Kasa işlemi yapılamadı.")
                            return
                    if record_type == "Receivable":
                        trans_type_fin = "Expense"
                        desc_fin = f"{person_full_name} kişisine borç verildi."
                    else:
                        trans_type_fin = "Income"
                        desc_fin = f"{person_full_name} kişisinden borç alındı."
                    if description:
                        desc_fin += f" ({description})"
                    fin_trans = FinancialTransaction(
                        transaction_date=transaction_date,
                        transaction_type=trans_type_fin,
                        original_amount=original_amount,
                        currency=currency,
                        base_amount=total_amount_base,
                        description=desc_fin,
                        person_full_name=person_full_name,
                        related_id=new_debt_id
                    )
                    manager.add_item(fin_trans)
                else:
                    QMessageBox.warning(self.main_app, "Hata", "Seçilen kasa/hesap bulunamadı.")
                    return
        else:
            product_type = data["product_type"]
            product_quantity = data["quantity"]
            depot_name = data["depot_name"]
            product_def = self.product_manager.find_by_attribute('product_name', product_type)
            if product_def:
                purchase_price_base = self.currency_manager._convert_to_base_currency(product_def.default_purchase_price, product_def.purchase_currency)
                total_amount_base = product_quantity * purchase_price_base
            if data.get("affect_stock", False):
                if record_type == "Receivable":
                    stock_item = self.stock_manager.get_stock_by_type_and_depot(product_type, depot_name)
                    available_qty = stock_item.quantity if stock_item else 0.0
                    if available_qty < product_quantity:
                        QMessageBox.warning(self.main_app, "Uyarı", f"Yetersiz Stok! '{depot_name}' deposunda {available_qty} var.")
                        return
                    self.stock_manager.remove_stock(product_type, product_quantity, depot_name)
                else:
                    price_for_stock = purchase_price_base if product_def else 0.0
                    self.stock_manager.add_or_update_stock(product_type, product_quantity, price_for_stock, depot_name)
        new_debt_record = DebtRecord(
            id=new_debt_id,
            record_type=record_type,
            person_full_name=person_full_name,
            transaction_date=transaction_date,
            debt_type=debt_type,
            total_amount_base=total_amount_base,
            product_type=product_type,
            product_quantity=product_quantity,
            description=description,
            due_date=due_date,
            original_amount=original_amount,
            currency=currency
        )
        self.debt_manager.add_item(new_debt_record)
        if person_full_name != "N/A":
            event_type = self.main_app.lang.get(record_type.lower(), record_type)
            if debt_type == "Product":
                event_type += " (Ürün)"
            self.customer_history_manager.add_item(CustomerHistoryEvent(
                person_full_name=person_full_name,
                event_date=event_date_iso,
                event_type=event_type,
                debt_original=original_amount if debt_type == "Cash" and record_type == "Receivable" else 0,
                credit_original=original_amount if debt_type == "Cash" and record_type == "Payable" else 0,
                currency=currency if debt_type == "Cash" else product_type,
                description=description if description else f"{record_type} kaydı oluşturuldu.",
                related_id=new_debt_record.id
            ))
        self.main_app.refresh_current_account_table()
        if should_affect_cash:
            self.main_app.refresh_cash_table()
            self.main_app.refresh_dashboard_cards()
        self.main_app._show_status_message(f"Kayıt başarıyla eklendi: {person_full_name}")
    def _acc_tab_tahsilat_yap(self): self._acc_tab_handle_generic_payment("Receivable")
    def _acc_tab_odeme_yap(self): self._acc_tab_handle_generic_payment("Payable")
    def _acc_tab_handle_generic_payment(self, record_type: str):
        """Cari Hesaplar sekmesindeki 'Tahsilat' veya 'Ödeme' butonlarına basılınca çalışır."""
        person_name = self.main_app.acc_tab_person_combo.currentText().strip()
        if not person_name:
            QMessageBox.warning(self.main_app, "Uyarı", "Lütfen bir kişi seçin.")
            return
        total_amount_str, ok = QInputDialog.getText(self.main_app, "İşlem Tutarı", "Planlanan toplam işlem tutarını girin:")
        if not ok or not total_amount_str: return
        try:
            total_amount = float(total_amount_str.replace(",", "."))
            if total_amount <= 0: raise ValueError
        except ValueError:
            QMessageBox.warning(self.main_app, "Uyarı", "Geçersiz tutar.")
            return
        dialog = PaymentDialog(
            total_amount=total_amount,
            display_currency=self.main_app.display_currency,
            person_name=person_name,
            cash_accounts=self.main_app._get_accessible_cash_accounts(),
            card_accounts=self.main_app._get_accessible_card_accounts(),
            depot_manager=self.depot_manager,
            product_manager=self.product_manager,
            stock_manager=self.stock_manager,
            lang=self.main_app.lang,
            currency_manager=self.currency_manager,
            allowed_depots=self.main_app._get_accessible_depots(),
            product_trade_direction="in" if record_type == "Receivable" else "out",
            parent=self.main_app
        )
        if dialog.exec():
            breakdown = dialog.get_payment_breakdown()
            self._process_advanced_payment(record_type, person_name, breakdown, total_amount)
            self._update_new_accounts_tab_view()
    def _process_advanced_payment(self, record_type: str, person_name: str, breakdown: list, total_planned_amount: float):
        """PaymentDialog'dan gelen detaylı ödeme listesini işler."""
        transaction_date = datetime.now().strftime("%d.%m.%Y")
        event_date_iso = datetime.now().isoformat()
        is_collection = (record_type == "Receivable")
        did_cash_transaction = False
        for item in breakdown:
            method_type = item['method_type']
            amount = item['amount']
            currency = item.get('currency', self.main_app.display_currency)
            total_val = item.get('total_val', amount)
            if method_type in ['cash', 'card']:
                affect_cash = getattr(self.main_app, 'affect_cash_checkbox', None)
                affect_cash_value = affect_cash.isChecked() if affect_cash else True
                if not affect_cash_value:
                    continue  # Kasa etkilemesin
                account_id = item['account_id']
                manager = self.main_app.cash_managers.get(account_id) if method_type == 'cash' else self.main_app.card_managers.get(account_id)
                if manager:
                    trans_type = "Income" if is_collection else "Expense"
                    payment_base = self._safe_to_float(
                        item.get('base_amount', self.currency_manager._convert_to_base_currency(total_val, currency)),
                        self.currency_manager._convert_to_base_currency(total_val, currency),
                    )
                    desc = f"{person_name} - {'Tahsilat' if is_collection else 'Ödeme'} ({item['account_name']})"
                    new_fin_trans = FinancialTransaction(transaction_date, trans_type, total_val, currency, payment_base, desc, person_full_name=person_name)
                    manager.add_item(new_fin_trans)
                    did_cash_transaction = True
                    self.customer_history_manager.add_item(CustomerHistoryEvent(
                        person_full_name=person_name, event_date=event_date_iso,
                        event_type=f"{'Tahsilat' if is_collection else 'Ödeme'}",
                        credit_original=total_val if is_collection else 0,
                        debt_original=0 if is_collection else total_val,
                        currency=currency, description=desc, related_id=new_fin_trans.id
                    ))
            elif method_type == 'product':
                product_name_pay = item.get('account_id')
                quantity_pay = item['amount']
                depot_name = item.get('depot_name', "Merkez")
                gold_details = item.get('gold_details') or []
                product_def = self.product_manager.find_by_attribute('product_name', product_name_pay)
                current_price_base = self.currency_manager._convert_to_base_currency(product_def.default_purchase_price, product_def.purchase_currency) if product_def else 0
                if is_collection:
                    if gold_details:
                        for detail in gold_details:
                            barcode = str(detail.get('barcode') or '').strip()
                            gram_val = self._safe_to_float(detail.get('gram', 0), 0.0)
                            karat_val = str(detail.get('karat') or '').strip()
                            if not barcode or gram_val <= 0:
                                continue
                            self.stock_manager.add_item(StockItem(
                                item_type=product_name_pay,
                                quantity=1.0,
                                quantity_gr=gram_val,
                                unique_barcode=barcode,
                                depot_name=depot_name,
                                purchase_price_base=current_price_base,
                            ))
                            self.main_app.barcode_history_manager.register_buy(
                                barcode=barcode,
                                p_type=product_name_pay,
                                gram=gram_val,
                                karat=karat_val,
                                source=person_name,
                                price=current_price_base,
                                method="Cari Tahsilat (Takas)",
                            )
                    else:
                        self.stock_manager.add_or_update_stock(product_name_pay, quantity_pay, current_price_base, depot_name)
                else:
                    if gold_details:
                        barcodes = [
                            str(detail.get('barcode') or '').strip()
                            for detail in gold_details
                            if str(detail.get('barcode') or '').strip()
                        ]
                        if barcodes:
                            self.stock_manager.remove_stock_by_barcodes(barcodes, depot_name)
                            for detail in gold_details:
                                barcode = str(detail.get('barcode') or '').strip()
                                if not barcode:
                                    continue
                                self.main_app.barcode_history_manager.register_sell(
                                    barcode=barcode,
                                    target=person_name,
                                    price=self._safe_to_float(item.get('unit_price', 0), 0.0),
                                    method="Cari Ödeme (Takas)",
                                    depot=depot_name,
                                    sold_at=event_date_iso,
                                )
                        else:
                            self.stock_manager.remove_stock(product_name_pay, quantity_pay, depot_name)
                    else:
                        self.stock_manager.remove_stock(product_name_pay, quantity_pay, depot_name)
                event_type_str = f"{'Tahsilat' if is_collection else 'Ödeme'} (Ürünle)"
                desc_str = f"{quantity_pay} {product_name_pay} ({depot_name}) karşılığı işlem."
                if gold_details:
                    total_gram = sum(self._safe_to_float(detail.get('gram', 0), 0.0) for detail in gold_details)
                    desc_str = f"{quantity_pay} {product_name_pay} ({depot_name}) karşılığı işlem. {format_number(total_gram, 3)} gr."
                self.customer_history_manager.add_item(CustomerHistoryEvent(
                    person_full_name=person_name, event_date=event_date_iso,
                    event_type=event_type_str,
                    credit_original=total_val if is_collection else 0,
                    debt_original=0 if is_collection else total_val,
                    currency=currency, description=desc_str, related_id=None
                ))
        total_value_processed_base = sum(
            self._safe_to_float(
                p.get('amount_base', self.currency_manager._convert_to_base_currency(p['total_val'], p['currency'])),
                self.currency_manager._convert_to_base_currency(p['total_val'], p['currency']),
            )
            for p in breakdown if p['method_type'] != 'On Credit'
        )
        target_record_type = "Receivable" if is_collection else "Payable"
        open_debts = [d for d in self.debt_manager.get_all()
                      if d.person_full_name == person_name
                      and d.record_type == target_record_type
                      and not d.is_paid]
        open_debts.sort(key=lambda x: x.transaction_date)
        remaining_money = total_value_processed_base
        updated_debts = []
        for debt in open_debts:
            if remaining_money <= 0.001: break
            debt_remaining_base = debt.total_amount_base - debt.paid_amount_base
            payment_for_this = min(remaining_money, debt_remaining_base)
            if payment_for_this <= 0:
                continue
            debt.paid_amount_base += payment_for_this
            remaining_money -= payment_for_this
            if debt.paid_amount_base >= debt.total_amount_base - 0.01:
                debt.is_paid = True
                debt.payment_date = transaction_date
            updated_debts.append(debt)
        for debt in updated_debts:
            self.debt_manager.update_item(debt.id, {
                "paid_amount_base": debt.paid_amount_base,
                "is_paid": debt.is_paid,
                "payment_date": debt.payment_date,
            })
        self.main_app.refresh_current_account_table()
        if did_cash_transaction:
            self.main_app.refresh_cash_table()
            self.main_app.refresh_dashboard_cards()
        self.main_app._show_status_message("İşlem (Nakit/Ürün) başarıyla cariye işlendi ve bakiyeler güncellendi.")
    def _process_generic_payment(self, record_type: str, person_name: str, amount: float, currency: str, description: str, account_data: tuple) -> bool:
        """
        Genel ödeme/tahsilat işleme mantığı.
        FAZ 4: Tüm adımlar (Finansal Kayıt + Cari Geçmiş + Borç Düşümü) tek bir transaction içinde.
        """
        payment_amount_base = self.currency_manager._convert_to_base_currency(amount, currency)
        account_type, account_id = account_data
        manager = self.main_app.cash_managers.get(account_id) if account_type == 'cash' else self.main_app.card_managers.get(account_id)
        if not manager:
            QMessageBox.critical(self.main_app, "Hata", "Hesap yöneticisi bulunamadı.")
            return False
        debts_to_process = [d for d in self.debt_manager.get_all()
                            if d.person_full_name == person_name and
                            d.record_type == record_type and
                            d.debt_type == "Cash" and not d.is_paid]
        if not debts_to_process:
            msg_key = "no_receivable_found" if record_type == "Receivable" else "no_payable_found"
            QMessageBox.warning(self.main_app, "Uyarı", self.main_app.lang.get(msg_key, "Kapatılacak kayıt bulunamadı."))
            return False
        total_debt_base = sum(d.total_amount_base - d.paid_amount_base for d in debts_to_process)
        if payment_amount_base > total_debt_base + 0.01:
            msg_key = "collection_exceeds_receivable" if record_type == "Receivable" else "payment_exceeds_payable"
            QMessageBox.warning(self.main_app, "Uyarı", self.main_app.lang.get(msg_key, "Tutar toplam borcu aşıyor."))
            return False
        db = self.main_app.data_manager.db
        try:
            with db.transaction_scope() as cursor:
                trans_type = "Income" if record_type == "Receivable" else "Expense"
                fin_desc = description if description else f"{person_name} - {self.main_app.lang.get(record_type.lower(), record_type)}"
                new_fin_trans = FinancialTransaction(
                    datetime.now().strftime("%d.%m.%Y"), trans_type, amount, currency,
                    payment_amount_base, fin_desc, person_full_name=person_name
                )
                manager.add_item(new_fin_trans, cursor=cursor)
                self.customer_history_manager.add_item(CustomerHistoryEvent(
                    person_full_name=person_name, event_date=datetime.now().isoformat(),
                    event_type=self.main_app.lang["payment_for_debt_desc"].format(person_name="").strip(),
                    credit_original=amount if trans_type == "Income" else 0,
                    debt_original=0 if trans_type == "Income" else amount,
                    currency=currency,
                    description=description,
                    related_id=new_fin_trans.id
                ), cursor=cursor)
                remaining_payment = payment_amount_base
                sorted_debts = sorted(debts_to_process, key=lambda d: d.transaction_date)
                for debt in sorted_debts:
                    if remaining_payment <= 0.001: break
                    debt_remaining_base = debt.total_amount_base - debt.paid_amount_base
                    payment_for_this_debt = min(remaining_payment, debt_remaining_base)
                    new_paid_amount = debt.paid_amount_base + payment_for_this_debt
                    is_paid = new_paid_amount >= debt.total_amount_base - 0.01
                    updates = {"paid_amount_base": new_paid_amount}
                    if is_paid:
                        updates["is_paid"] = True
                        updates["payment_date"] = datetime.now().strftime("%d.%m.%Y")
                    self.debt_manager.update_item(debt.id, updates, cursor=cursor)
                    remaining_payment -= payment_for_this_debt
            self.main_app.refresh_cash_table()
            self.main_app.refresh_current_account_table()
            self.main_app.refresh_dashboard_cards()
            self.main_app._show_status_message("İşlem başarıyla kaydedildi.")
            return True
        except Exception as e:
            logger.error(f"Ödeme işlem hatası: {e}")
            QMessageBox.critical(self.main_app, "Hata", f"İşlem sırasında hata oluştu: {e}")
            return False
    def _acc_tab_edit_record(self):
        """
        Seçili cari hareket kaydını ve ANA KAYDI düzenler (Transaction Safe).
        """
        selected_items = self.main_app.acc_tab_history_table.selectedItems()
        if not selected_items: return
        row = selected_items[0].row()
        first_item = self.main_app.acc_tab_history_table.item(row, 0)
        if not first_item:
            return
        event_id = first_item.data(Qt.ItemDataRole.UserRole)
        if not event_id:
            return
        selected_event = self.customer_history_manager.find_by_id(event_id)
        if not selected_event: return
        dialog = EditHistoryEventDialog(selected_event, self.main_app.lang, self.main_app)
        if dialog.exec() != QDialog.DialogCode.Accepted:
            return
        new_data = dialog.get_data() # date, description, amount, currency, update_main
        db = self.main_app.data_manager.db
        try:
            with db.transaction_scope() as cursor:
                profile_id = self.customer_history_manager.profile_id
                hist_before = fetch_row_snapshot_by_id(
                    db=db,
                    table_name="customer_history_events",
                    record_id=event_id,
                    profile_id=profile_id,
                    cursor=cursor,
                )
                new_date_obj = datetime.fromisoformat(new_data["event_date"])
                new_date_sql = new_date_obj.strftime("%Y-%m-%d %H:%M:%S")
                sql_update_hist = """
                    UPDATE customer_history_events
                    SET event_date = %s, description = %s, currency = %s
                    WHERE id = %s AND profile_id = %s
                """
                params_hist = [new_date_sql, new_data["description"], new_data["currency"], event_id, profile_id]
                cursor.execute(sql_update_hist, tuple(params_hist))
                if selected_event.debt_original > 0:
                    cursor.execute(
                        "UPDATE customer_history_events SET debt_original = %s WHERE id = %s AND profile_id = %s",
                        (new_data["amount"], event_id, profile_id),
                    )
                elif selected_event.credit_original > 0:
                    cursor.execute(
                        "UPDATE customer_history_events SET credit_original = %s WHERE id = %s AND profile_id = %s",
                        (new_data["amount"], event_id, profile_id),
                    )
                hist_after = fetch_row_snapshot_by_id(
                    db=db,
                    table_name="customer_history_events",
                    record_id=event_id,
                    profile_id=profile_id,
                    cursor=cursor,
                )
                queue_row_sync_event(
                    db=db,
                    table_name="customer_history_events",
                    profile_id=profile_id,
                    action_type=SYNC_ACTION_UPDATE,
                    record_id=event_id,
                    before_row=hist_before,
                    after_row=hist_after,
                    cursor=cursor,
                    meta={"source": "CRMManager._acc_tab_edit_record"},
                )
                if new_data.get("update_main") and selected_event.related_id:
                    rel_id = selected_event.related_id
                    debt_before = fetch_row_snapshot_by_id(
                        db=db,
                        table_name="debt_records",
                        record_id=rel_id,
                        profile_id=profile_id,
                        cursor=cursor,
                    )
                    fin_before = fetch_row_snapshot_by_id(
                        db=db,
                        table_name="financial_transactions",
                        record_id=rel_id,
                        profile_id=profile_id,
                        cursor=cursor,
                    )
                    new_base = self.currency_manager._convert_to_base_currency(
                        new_data["amount"], new_data["currency"]
                    )
                    new_date_simple = new_date_obj.strftime("%Y-%m-%d") # Tablolar genelde DATE tutar
                    cursor.execute(
                        "UPDATE debt_records SET original_amount=%s, total_amount_base=%s, currency=%s, description=%s, transaction_date=%s WHERE id=%s AND profile_id=%s",
                        (
                            new_data["amount"],
                            new_base,
                            new_data["currency"],
                            new_data["description"],
                            new_date_simple,
                            rel_id,
                            profile_id,
                        ),
                    )
                    if cursor.rowcount == 0:
                        cursor.execute(
                            "UPDATE financial_transactions SET amount=%s, base_amount=%s, currency=%s, description=%s, transaction_date=%s WHERE id=%s AND profile_id=%s",
                            (
                                new_data["amount"],
                                new_base,
                                new_data["currency"],
                                new_data["description"],
                                new_date_simple,
                                rel_id,
                                profile_id,
                            ),
                        )
                    debt_after = fetch_row_snapshot_by_id(
                        db=db,
                        table_name="debt_records",
                        record_id=rel_id,
                        profile_id=profile_id,
                        cursor=cursor,
                    )
                    fin_after = fetch_row_snapshot_by_id(
                        db=db,
                        table_name="financial_transactions",
                        record_id=rel_id,
                        profile_id=profile_id,
                        cursor=cursor,
                    )
                    if debt_before != debt_after:
                        queue_row_sync_event(
                            db=db,
                            table_name="debt_records",
                            profile_id=profile_id,
                            action_type=SYNC_ACTION_UPDATE,
                            record_id=rel_id,
                            before_row=debt_before,
                            after_row=debt_after,
                            cursor=cursor,
                            meta={"source": "CRMManager._acc_tab_edit_record"},
                        )
                    if fin_before != fin_after:
                        queue_row_sync_event(
                            db=db,
                            table_name="financial_transactions",
                            profile_id=profile_id,
                            action_type=SYNC_ACTION_UPDATE,
                            record_id=rel_id,
                            before_row=fin_before,
                            after_row=fin_after,
                            cursor=cursor,
                            meta={"source": "CRMManager._acc_tab_edit_record"},
                        )
            self.main_app._show_status_message("Kayıt başarıyla güncellendi.")
            self._update_new_accounts_tab_view() # Tazele
        except Exception as e:
            logger.error(f"Düzenleme hatası: {e}")
            QMessageBox.critical(self.main_app, "Hata", f"Güncelleme yapılamadı: {e}")
    def _acc_tab_delete_record(self):
        """
        Seçili cari hareket kaydını ve İLİŞKİLİ ANA KAYDI siler (Transaction Safe).
        Artık SQL transaction ile atomik çalışır.
        """
        selected_items = self.main_app.acc_tab_history_table.selectedItems()
        if not selected_items: return
        event_id = self.main_app.acc_tab_history_table.item(selected_items[0].row(), 0).data(Qt.ItemDataRole.UserRole)
        selected_event = self.customer_history_manager.find_by_id(event_id)
        if not selected_event:
            self.main_app._show_status_message("Kayıt veritabanında bulunamadı.")
            return
        msg = f"'{selected_event.description}' işlemini silmek istiyor musunuz?\n\nBU İŞLEM İLİŞKİLİ ANA KAYITLARI DA SİLECEKTİR!"
        if not self.main_app._show_confirm_dialog("Silme Onayı", msg):
            return
        related_id = selected_event.related_id
        db = self.main_app.data_manager.db
        try:
            with db.transaction_scope() as cursor:
                profile_id = self.customer_history_manager.profile_id
                hist_before = fetch_row_snapshot_by_id(
                    db=db,
                    table_name="customer_history_events",
                    record_id=event_id,
                    profile_id=profile_id,
                    cursor=cursor,
                )
                cursor.execute(
                    "DELETE FROM customer_history_events WHERE id = %s AND profile_id = %s",
                    (event_id, profile_id),
                )
                if cursor.rowcount > 0:
                    queue_row_sync_event(
                        db=db,
                        table_name="customer_history_events",
                        profile_id=profile_id,
                        action_type=SYNC_ACTION_DELETE,
                        record_id=event_id,
                        before_row=hist_before,
                        after_row=None,
                        cursor=cursor,
                        meta={"source": "CRMManager._acc_tab_delete_record"},
                    )
                if related_id:
                    tables_to_check = [
                        "financial_transactions", # Finansal işlem
                        "debt_records",           # Cari borç/alacak
                        "sales",                  # Satış fişi
                        "repair_records"          # Tamir kaydı
                    ]
                    deleted_main = False
                    for table in tables_to_check:
                        main_before = fetch_row_snapshot_by_id(
                            db=db,
                            table_name=table,
                            record_id=related_id,
                            profile_id=profile_id,
                            cursor=cursor,
                        )
                        cursor.execute(
                            f"DELETE FROM {table} WHERE id = %s AND profile_id = %s",
                            (related_id, profile_id),
                        )
                        if cursor.rowcount > 0:
                            queue_row_sync_event(
                                db=db,
                                table_name=table,
                                profile_id=profile_id,
                                action_type=SYNC_ACTION_DELETE,
                                record_id=related_id,
                                before_row=main_before,
                                after_row=None,
                                cursor=cursor,
                                meta={"source": "CRMManager._acc_tab_delete_record"},
                            )
                            deleted_main = True
                            break
            self.main_app._show_status_message("Kayıt ve ilişkili veriler başarıyla silindi.")
            self._update_new_accounts_tab_view() # Listeyi tazele
            self.main_app.refresh_current_account_table()
            self.main_app.refresh_cash_table()
            self.main_app.refresh_account_balances()
            self.main_app.refresh_dashboard_cards()
        except Exception as e:
            logger.error(f"Silme işlemi başarısız: {e}")
            QMessageBox.critical(self.main_app, "Hata", f"Silme işlemi veritabanı hatası nedeniyle geri alındı:\n{e}")
    def _acc_tab_print_pdf(self):
        """PDF ekstresi oluşturur."""
        person_name = self.main_app.acc_tab_person_combo.currentText().strip()
        if not person_name:
            QMessageBox.warning(self.main_app, "Uyarı", "Lütfen bir kişi seçin.")
            return
        try:
            headers = ["İŞLEM TÜRÜ", "TARİH", "AÇIKLAMA", "ÖDEME TÜRÜ", "BORÇ", "ALACAK"]
            data_rows = []
            for row in range(self.main_app.acc_tab_history_table.rowCount()):
                row_data = []
                for col in range(self.main_app.acc_tab_history_table.columnCount()):
                    item = self.main_app.acc_tab_history_table.item(row, col)
                    row_data.append(item.text() if item else "")
                data_rows.append(row_data)
            summary_data = {
                "Müşteri": person_name,
                "Borçlu": self.main_app.acc_tab_borclu_label.text(),
                "Alacaklı": self.main_app.acc_tab_alacakli_label.text(),
                "Tarih Aralığı": f"{self.main_app.acc_tab_start_date.date().toString('dd.MM.yyyy')} - {self.main_app.acc_tab_end_date.date().toString('dd.MM.yyyy')}"
            }
            filename = f"cari_ekstre_{person_name}_{datetime.now().strftime('%Y%m%d')}.pdf"
            save_path, _ = QFileDialog.getSaveFileName(self.main_app, "PDF Kaydet", filename, "PDF Files (*.pdf)")
            if save_path:
                self.main_app.pdf_report_generator.generate_report(
                    f"CARİ EKSTRE - {person_name}",
                    headers, data_rows, summary_data, save_path
                )
        except Exception as e:
            logger.error(f"PDF oluşturma hatası: {e}")
            QMessageBox.critical(self.main_app, "Hata", f"PDF oluşturulamadı: {e}")
    def _acc_tab_print_excel(self):
        """Excel ekstresi oluşturur."""
        person_name = self.main_app.acc_tab_person_combo.currentText().strip()
        if not person_name:
            QMessageBox.warning(self.main_app, "Uyarı", "Lütfen bir kişi seçin.")
            return
        try:
            headers = ["İŞLEM TÜRÜ", "TARİH", "AÇIKLAMA", "ÖDEME TÜRÜ", "BORÇ", "ALACAK"]
            data_rows = []
            for row in range(self.main_app.acc_tab_history_table.rowCount()):
                row_data = []
                for col in range(self.main_app.acc_tab_history_table.columnCount()):
                    item = self.main_app.acc_tab_history_table.item(row, col)
                    row_data.append(item.text() if item else "")
                data_rows.append(row_data)
            summary_data = {
                "Müşteri": person_name,
                "Borçlu": self.main_app.acc_tab_borclu_label.text(),
                "Alacaklı": self.main_app.acc_tab_alacakli_label.text(),
                "Tarih Aralığı": f"{self.main_app.acc_tab_start_date.date().toString('dd.MM.yyyy')} - {self.main_app.acc_tab_end_date.date().toString('dd.MM.yyyy')}"
            }
            filename = f"cari_ekstre_{person_name}_{datetime.now().strftime('%Y%m%d')}.xlsx"
            save_path, _ = QFileDialog.getSaveFileName(self.main_app, "Excel Kaydet", filename, "Excel Files (*.xlsx)")
            if save_path:
                self.main_app.excel_report_generator.generate_report(
                    f"CARİ EKSTRE - {person_name}",
                    headers, data_rows, summary_data, save_path
                )
        except Exception as e:
            logger.error(f"Excel oluşturma hatası: {e}")
            QMessageBox.critical(self.main_app, "Hata", f"Excel oluşturulamadı: {e}")
    def _get_current_person(self):
        name = self.main_app.acc_search_combo.currentText().strip()
        if not name:
            QMessageBox.warning(self.main_app, "Uyarı", "Lütfen önce listeden bir kişi seçin.")
            return None
        return name
    def _excel_action_cash_in(self): self._open_excel_dialog('CashIn')
    def _excel_action_cash_out(self): self._open_excel_dialog('CashOut')
    def _excel_action_cash_paid_in(self): self._open_excel_dialog('CashPaidIn')
    def _excel_action_cash_paid_out(self): self._open_excel_dialog('CashPaidOut')
    def _excel_action_prod_in(self): self._open_excel_dialog('ProductIn')
    def _excel_action_prod_out(self): self._open_excel_dialog('ProductOut')
    def _excel_action_exchange(self):
        person = self._get_current_person()
        if not person: return
        dialog = ExchangeSelectionDialog(self.main_app, person, self.main_app.lang)
        if dialog.exec():
            mode = dialog.selected_sub_type # 'ExchangePayable' veya 'ExchangeReceivable'
            self._open_excel_dialog(mode)
    def _excel_action_virman(self):
        person = self._get_current_person()
        if not person: return
        dialog = VirmanSelectionDialog(self.main_app, person, self.main_app.lang)
        if dialog.exec():
            mode = dialog.selected_sub_type # 'VirmanPayable' veya 'VirmanReceivable'
            self._open_excel_dialog(mode)
    def _open_cash_transactions_dialog(self):
        """Nakit işlemleri için yeni dialog penceresi açar."""
        person = self._get_current_person()
        if not person: return
        dialog = CashTransactionsDialog(self.main_app, person, self.main_app.lang)
        if dialog.exec():
            action = dialog.get_selected_action()
            if action == "cash_in":
                self._open_excel_dialog('CashIn')
            elif action == "cash_out":
                self._open_excel_dialog('CashOut')
    def _open_product_transactions_dialog(self):
        """Altın işlemleri için yeni dialog penceresi açar."""
        person = self._get_current_person()
        if not person: return
        dialog = ProductTransactionsDialog(self.main_app, person, "Product", self.main_app.lang)
        if dialog.exec():
            action = dialog.get_selected_action()
            if action == "purchase":
                self._open_excel_dialog('ProductIn')
            elif action == "sale":
                self._open_excel_dialog('ProductOut')
            elif action == "paid_entry":
                self._open_excel_dialog('ProductPaidIn')
            elif action == "paid_exit":
                self._open_excel_dialog('ProductPaidOut')
    def _open_ziynet_transactions_dialog(self):
        """Ziynet işlemleri için yeni dialog penceresi açar."""
        person = self._get_current_person()
        if not person: return
        dialog = ProductTransactionsDialog(self.main_app, person, "Ziynet", self.main_app.lang)
        if dialog.exec():
            action = dialog.get_selected_action()
            if action == "purchase":
                self._open_excel_dialog('ZiynetIn')
            elif action == "sale":
                self._open_excel_dialog('ZiynetOut')
            elif action == "paid_entry":
                self._open_excel_dialog('ZiynetPaidIn')
            elif action == "paid_exit":
                self._open_excel_dialog('ZiynetPaidOut')
    def _open_scrap_transactions_dialog(self):
        """Hurda işlemleri için yeni dialog penceresi açar."""
        person = self._get_current_person()
        if not person: return
        dialog = ProductTransactionsDialog(self.main_app, person, "Scrap", self.main_app.lang)
        if dialog.exec():
            action = dialog.get_selected_action()
            if action == "purchase":
                self._open_excel_dialog('ScrapIn')
            elif action == "sale":
                self._open_excel_dialog('ScrapOut')
            elif action == "paid_entry":
                self._open_excel_dialog('ScrapPaidIn')
            elif action == "paid_exit":
                self._open_excel_dialog('ScrapPaidOut')
    def _open_excel_dialog(self, mode):
        person = self._get_current_person()
        if not person: return
        managers = {
            'cash_acc': self.cash_account_manager,
            'card_acc': self.card_account_manager,
            'product': self.product_manager,
            'ziynet': self.main_app.ziynet_manager,
            'scrap': self.main_app.scrap_manager,
            'product_stock': self.stock_manager,                # Altın Stoğu
            'ziynet_stock': self.main_app.ziynet_stock_manager, # Ziynet Stoğu
            'scrap_stock': self.main_app.scrap_stock_manager,   # Hurda Stoğu
            'depot': self.depot_manager,
            'person': self.person_manager
        }
        from modules.cash import ExcelStyleTransactionDialog
        dialog = ExcelStyleTransactionDialog(mode, person, managers, self.main_app.lang, self.main_app)
        if dialog.exec():
            data = dialog.get_data()
            if not data:
                return
            affect_cash = getattr(self.main_app, 'affect_cash_checkbox', None)
            data['affect_cash'] = affect_cash.isChecked() if affect_cash else True
            if data.get('action_type') == 'payment':
                self._handle_pos_payment_flow(mode, person, data)
            else:
                self._process_excel_data(mode, person, data)

    # --- modules/crm.py İÇİNDEKİ CRMManager SINIFI ---

    def _build_tax_snapshot_for_cari(self, mode: str, person_name: str, data: Dict[str, Any]) -> Dict[str, Any]:
        """
        Cari işlemler için vergi hesaplama (Snapshot oluşturma).
        UI'dan gelen 'selected_taxes' listesini kullanır.
        """
        apply_taxes = bool(data.get("apply_taxes", False))
        selected_taxes_list = data.get("selected_taxes", [])
        
        # Vergi seçilmediyse veya check atılmadıysa boş dön
        if not apply_taxes or not selected_taxes_list:
            return {}
            
        tax_engine = getattr(self.main_app, "tax_engine", None)
        if not tax_engine:
            return {}

        # 3. Satır kalemlerini hazırla
        items: List[Dict[str, Any]] = []
        
        if data.get("product"):
            try:
                qty = float(data.get("quantity") or 0)
                price = float(data.get("price") or 0)
            except Exception: qty, price = 0.0, 0.0
            if qty > 0:
                items.append({"name": str(data.get("product") or ""), "qty": qty, "unit_price": price})
        
        elif "amount" in data:
            try: amount = float(data.get("amount") or 0)
            except: amount = 0.0
            if amount > 0:
                items.append({"name": mode, "qty": 1, "unit_price": amount})
        
        if not items:
            return {}

        # 4. Hesaplama Motorunu Çağır
        try:
            return tax_engine.compute_snapshot(
                items,
                currency=str(data.get("currency") or self.main_app.display_currency or "TRY"),
                selected_taxes=selected_taxes_list, # Liste olarak veriliyor
                apply_taxes=True,
                customer_name=person_name or "",
                note=str(data.get("description") or "").strip() or mode,
            )
        except Exception as e:
            logger.error(f"[TAX] Cari tax snapshot compute failed: {e}")
            return {}

    def _handle_pos_payment_flow(self, mode, person_name, data):
        """
        1.08 MANTIÄI İLE PEŞİN İŞLEM (SQL UYARLAMASI + ÇOKLU VERGİ DESTEÄİ)
        'Ödeme Yap' butonuna basıldığında çalışır.
        """
        # 1. Verileri Hazırla
        product = data.get('product')
        quantity = data.get('quantity', 0)
        price = data.get('price', 0)
        currency = data.get('currency', 'TRY')
        gold_details = data.get('gold_details', [])
        
        # Ham Tutar
        raw_total = quantity * price
        
        # --- VERGİ HESAPLAMA ---
        tax_snapshot = self._build_tax_snapshot_for_cari(mode, person_name, data)
        
        # Eğer vergi varsa, tahsil edilecek tutar (Grand Total) olmalıdır.
        if tax_snapshot:
            payable_total = float(tax_snapshot.get('grand_total', raw_total))
        else:
            payable_total = raw_total
        is_out = 'Out' in mode or 'Sale' in mode

        # 2. Ödeme Dialogunu Aç (Vergili Tutar İle)
        from modules.sales import PaymentDialog
        dialog = PaymentDialog(
            total_amount=payable_total, # <-- ARTIK VERGİLİ TUTAR GİDİYOR
            display_currency=currency, 
            person_name=person_name,
            cash_accounts=self.main_app._get_accessible_cash_accounts(),
            card_accounts=self.main_app._get_accessible_card_accounts(),
            depot_manager=self.depot_manager,
            product_manager=self.product_manager,
            stock_manager=self.stock_manager,
            lang=self.main_app.lang,
            currency_manager=self.currency_manager,
            allowed_depots=self.main_app._get_accessible_depots(),
            product_trade_direction="in" if is_out else "out",
            parent=self.main_app
        )

        if not dialog.exec():
            return # İptal edildi

        payment_breakdown = dialog.get_payment_breakdown()

        # 3. VERİTABANI İŞLEMİ (TRANSACTION)
        db = self.main_app.data_manager.db
        try:
            with db.transaction_scope() as cursor:
                selected_date_text = str(data.get('date') or "").strip()
                if selected_date_text:
                    try:
                        base_dt = datetime.strptime(selected_date_text, "%d.%m.%Y")
                        now = datetime.now()
                        tx_dt = base_dt.replace(hour=now.hour, minute=now.minute, second=now.second, microsecond=0)
                    except ValueError:
                        tx_dt = datetime.now()
                else:
                    tx_dt = datetime.now()
                iso_date = tx_dt.isoformat()
                date_str = tx_dt.strftime("%d.%m.%Y")
                
                is_out = 'Out' in mode or 'Sale' in mode # Satış mı?
                depot = data.get('depot', 'Merkez')

                # --- A) STOK HAREKETLERİ ---
                if data.get('affect_real', True) and any(x in mode for x in ['Product', 'Ziynet', 'Scrap']):
                    stock_mgr = self.stock_manager
                    if 'Ziynet' in mode: stock_mgr = self.main_app.ziynet_stock_manager
                    elif 'Scrap' in mode: stock_mgr = self.main_app.scrap_stock_manager
                    
                    if gold_details:
                        # Barkodlu İşlem
                        for item in gold_details:
                            if is_out:
                                stock_mgr.remove_stock_by_barcodes([item['barcode']], depot, cursor=cursor)
                                self.main_app.barcode_history_manager.register_sell(
                                        item['barcode'],
                                        person_name,
                                        price,
                                        "Peşin Satış",
                                        cursor=cursor,
                                        depot=depot,
                                        sold_at=iso_date,
                                    )
                            else:
                                from core.db import StockItem
                                new_stock = StockItem(
                                    item_type=product,
                                    quantity=1.0,
                                    quantity_gr=float(item['gram']),
                                    unique_barcode=item['barcode'],
                                    depot_name=depot,
                                    purchase_price_base=price
                                )
                                stock_mgr.add_item(new_stock, cursor=cursor)
                                self.main_app.barcode_history_manager.register_buy(
                                    item['barcode'], product, item['gram'], item['karat'], 
                                    person_name, price, "Peşin Alış", cursor=cursor
                                )
                    else:
                        # Yığın İşlem
                        if is_out:
                             stock_mgr.remove_stock(product, quantity, depot, cursor=cursor)
                        else:
                             stock_mgr.add_or_update_stock(product, quantity, price, depot, cursor=cursor)

                # --- B) KASA HAREKETLERİ (Ödemeler) ---
                for pay in payment_breakdown:
                    m_type = pay['method_type']
                    amt = pay.get('amount', 0)
                    curr = pay.get('currency', 'TRY')
                    
                    if m_type in ['cash', 'card']:
                         mgr = self.main_app.cash_managers.get(pay['account_id']) or self.main_app.card_managers.get(pay['account_id'])
                         if mgr:
                             ft_type = "Income" if is_out else "Expense"
                             ft_desc = f"Peşin İşlem: {product} ({person_name})"
                             
                             from core.db import FinancialTransaction
                             ft = FinancialTransaction(
                                 transaction_date=date_str, 
                                 transaction_type=ft_type,
                                 original_amount=amt, 
                                 currency=curr, 
                                 base_amount=amt,
                                 description=ft_desc, 
                                 person_full_name=person_name
                             )
                             mgr.add_item(ft, cursor=cursor)

                # --- C) CARİ HESAP KAYDI ---
                # Detayları JSON yap
                import json
                desc_json = {
                    "main_desc": f"{quantity} {product} ({'Satış' if is_out else 'Alış'}) - Peşin",
                    "mode": mode,
                    "main_product": product,
                    "main_qty": quantity,
                    "main_depot": depot,
                    "barcodes": [i['barcode'] for i in gold_details] if gold_details else []
                }
                
                # Vergi bilgisini de JSON'a ekle
                if tax_snapshot:
                    desc_json['tax_snapshot'] = tax_snapshot

                from core.db import CustomerHistoryEvent
                event = CustomerHistoryEvent(
                    person_full_name=person_name,
                    event_date=iso_date,
                    event_type="PEŞİN " + ("SATIŞ" if is_out else "ALIŞ"),
                    debt_original=0, credit_original=0,
                    currency=product,
                    description=json.dumps(desc_json, ensure_ascii=False),
                    transaction_amount=quantity,
                    related_id=None
                )
                self.customer_history_manager.add_item(event, cursor=cursor)

                # --- D) VERGİ KAYDI (YENİ) ---
                if tax_snapshot:
                    self._persist_tax_application(
                        source_table="customer_history", 
                        source_id=event.id, 
                        tax_snapshot=tax_snapshot,
                        cursor=cursor
                    )

            # Transaction Başarılı
            self._refresh_cari_views_after_transaction(refresh_cash=True)
            self.main_app._show_status_message(f"Peşin işlem başarıyla kaydedildi (Tutar: {format_number(payable_total)}).")

        except Exception as e:
            logger.error(f"Peşin işlem hatası: {e}")
            QMessageBox.critical(self.main_app, "Hata", f"İşlem kaydedilemedi: {e}")
    def _persist_tax_application(
        self,
        *,
        source_table: str,
        source_id: str,
        tax_snapshot: Dict[str, Any],
        cursor,
    ) -> None:
        if not isinstance(tax_snapshot, dict) or not tax_snapshot:
            return
        if not bool(tax_snapshot.get("apply_taxes")):
            return
        tax_engine = getattr(self.main_app, "tax_engine", None)
        tax_app_mgr = getattr(self.main_app, "tax_application_manager", None)
        if not tax_engine or not tax_app_mgr:
            return
        payload = tax_engine.create_tax_application_payload(
            source_table=source_table,
            source_id=source_id,
            snapshot=tax_snapshot,
        )
        tax_app_mgr.add_item(payload, cursor=cursor)
    
    def _process_excel_data(self, mode, person_name, data, pre_selected_barcodes=None):
        """
        Excel tarzı işlem penceresinden gelen veriyi veritabanına kaydeder.
        FIX: Bedelli işlem kalemleri için mükerrer CustomerHistoryEvent oluşturulması engellendi.
        Tüm işlem tek bir atomik tarihçe kaydında (JSON) toplanır.
        """
        import json
        from core.db import DebtRecord, CustomerHistoryEvent, StockItem, FinancialTransaction
        from datetime import datetime

        db = self.main_app.data_manager.db
        profile_id = self.main_app.data_manager.profile_id

        try:
            with db.transaction_scope() as cursor:
                # --- TARİH AYARLAMA ---
                date_str = data.get('date', datetime.now().strftime("%d.%m.%Y"))
                try:
                    trans_date_obj = datetime.strptime(date_str, "%d.%m.%Y")
                    now = datetime.now()
                    # Saati koru
                    final_dt = trans_date_obj.replace(hour=now.hour, minute=now.minute, second=now.second)
                    iso_date = final_dt.isoformat()
                    sql_date = final_dt.strftime("%Y-%m-%d")
                except ValueError:
                    now = datetime.now()
                    iso_date = now.isoformat()
                    sql_date = now.strftime("%Y-%m-%d")

                desc = data.get('description', '')

                # --- 1. VİRMAN (TRANSFER) ---
                if 'Virman' in mode:
                    self._process_virman_transaction(mode, person_name, data, iso_date, cursor)

                # --- 2. EXCHANGE (ÇEVİRİ) ---
                elif 'Exchange' in mode:
                    self._process_exchange_transaction(mode, person_name, data, iso_date, desc, cursor)

                # --- 3. STOK İŞLEMLERİ (Product, Ziynet, Scrap) ---
                elif any(x in mode for x in ['Product', 'Ziynet', 'Scrap']):
                    product = data.get('product')
                    depot = data.get('depot')
                    quantity = self._safe_to_float(data.get('quantity', 0), 0.0)
                    price = self._safe_to_float(data.get('price', 0), 0.0)
                    currency = data.get('currency', 'TRY')
                    gold_details = data.get('gold_details', [])
                    exchange_items = data.get('exchange_items', [])
                    
                    is_out = 'Out' in mode or 'Sale' in mode
                    
                    # Yönetici Seçimi
                    stock_mgr = self.stock_manager
                    if 'Ziynet' in mode: stock_mgr = self.main_app.ziynet_stock_manager
                    elif 'Scrap' in mode: stock_mgr = self.main_app.scrap_stock_manager

                    # Hesaplamalar
                    total_amount = quantity * price
                    base_price = self.currency_manager._convert_to_base_currency(price, currency)
                    total_amount_base = quantity * base_price

                    # --- A. BORÇ KAYDI (Opsiyonel: Eğer fiyat varsa) ---
                    debt_record_id = None
                    if total_amount > 0:
                        debt_record_id = str(uuid.uuid4())
                        record_type = "Receivable" if is_out else "Payable"
                        
                        debt_rec = DebtRecord(
                            id=debt_record_id,
                            profile_id=profile_id,
                            record_type=record_type,
                            person_full_name=person_name,
                            transaction_date=sql_date,
                            debt_type="Cash",
                            original_amount=total_amount,
                            currency=currency,
                            total_amount_base=total_amount_base,
                            product_type=product,
                            product_quantity=quantity,
                            description=f"{desc} ({product})".strip(),
                            status="Active",
                            is_paid=False,
                            paid_amount_base=0.0,
                            paid_quantity=0.0
                        )
                        self.debt_manager.add_item(debt_rec, cursor=cursor)

                    # --- B. FİZİKSEL STOK HAREKETİ ---
                    if data.get('affect_real', True):
                        if gold_details:
                            for item in gold_details:
                                if is_out:
                                    stock_mgr.remove_stock_by_barcodes([item['barcode']], depot, cursor=cursor)
                                    self.main_app.barcode_history_manager.register_sell(
                                        item['barcode'],
                                        person_name,
                                        price,
                                        "Cari Satış",
                                        cursor=cursor,
                                        depot=depot,
                                        sold_at=iso_date,
                                    )
                                else:
                                    # Giriş
                                    new_item = StockItem(
                                        item_type=product, quantity=1.0, quantity_gr=float(item['gram']),
                                        unique_barcode=item['barcode'], depot_name=depot, purchase_price_base=base_price
                                    )
                                    stock_mgr.add_item(new_item, cursor=cursor)
                                    self.main_app.barcode_history_manager.register_buy(
                                        item['barcode'], product, item['gram'], item['karat'], 
                                        person_name, price, "Cari Alış", cursor=cursor
                                    )
                        else:
                            # Yığın işlem
                            if is_out:
                                stock_mgr.remove_stock(product, quantity, depot, cursor=cursor)
                            else:
                                stock_mgr.add_or_update_stock(product, quantity, base_price, depot, cursor=cursor)

                        # Stok Log
                        log_item_type = "Product"
                        if 'Ziynet' in mode: log_item_type = "Ziynet"
                        elif 'Scrap' in mode: log_item_type = "Scrap"
                        
                        self.main_app.stock_movement_manager.add_log(
                            item_type=log_item_type,
                            product_name=product,
                            op_type="Satış" if is_out else "Alış",
                            qty=quantity,
                            source=depot if is_out else person_name,
                            dest=person_name if is_out else depot,
                            desc=desc,
                            user=self.main_app.current_user['name'],
                            cursor=cursor
                        )

                    # Bedel satırları fiziksel stoku etkilemez; ancak para birimi
                    # satırları istenirse varsayılan finans hesabına da yazılır.
                    if exchange_items and data.get('affect_cash', True):
                        self._apply_exchange_cash_rows(person_name, date_str, mode, exchange_items, cursor)

                    # --- D. ANA TARİHÇE KAYDI (TEK VE ATOMİK) ---
                    type_prefix = "ALTIN"
                    if 'Ziynet' in mode: type_prefix = "ZİYNET"
                    elif 'Scrap' in mode: type_prefix = "HURDA"
                    
                    # Eğer işlem bedelli ise, ana tutarları 0 geçiyoruz. 
                    # Bakiye hesaplaması JSON içindeki 'main_qty' ve 'exchange_items' üzerinden yapılacak.
                    # Bu, eski sistemin çalışma şeklidir.
                    hist_debt = total_amount if is_out else 0
                    hist_credit = 0 if is_out else total_amount
                    
                    # Eğer exchange_items varsa, bu "Bedelli" bir işlemdir.
                    # Bedelli işlemlerde debt_original/credit_original genellikle ana para birimi cinsinden tutarı gösterir
                    # ancak detaylı bakiye takibi JSON üzerinden yapılır.
                    
                    total_gold_gram = sum(self._safe_to_float(i.get('gram'), 0.0) for i in (gold_details or []))
                    karat_values = sorted({
                        self._normalize_text(i.get('karat'))
                        for i in (gold_details or [])
                        if str(i.get('karat') or '').strip()
                    })

                    main_product_label = self._format_main_product_label(product, mode)
                    main_desc = f"{format_number(quantity, 3)} {main_product_label}".strip()
                    if total_gold_gram > 0:
                        main_desc += f" ({format_number(total_gold_gram, 3)} gr)"
                    if desc:
                        main_desc += f" - {desc}"

                    detail_json = {
                        "main_desc": main_desc,
                        "exchange_items": exchange_items,
                        "mode": mode,
                        "main_product": product,
                        "main_qty": quantity,
                        "main_depot": depot,
                        "main_total_gram": total_gold_gram,
                        "main_karats": karat_values,
                        "barcodes": [i['barcode'] for i in gold_details] if gold_details else []
                    }
                    
                    event_type = _get_stock_event_title_for_mode(self.main_app.lang, mode)
                    if not event_type:
                        action_str = "SATIŞ" if is_out else "ALIŞ"
                        event_type = f"{type_prefix} {action_str} (BEDELLİ)"

                    self.customer_history_manager.add_item(CustomerHistoryEvent(
                        person_full_name=person_name,
                        event_date=iso_date,
                        event_type=event_type,
                        debt_original=hist_debt,
                        credit_original=hist_credit,
                        currency=currency,
                        description=json.dumps(detail_json, ensure_ascii=False),
                        transaction_amount=total_amount,
                        related_id=debt_record_id
                    ), cursor=cursor)

                # --- 4. NAKİT İŞLEMLERİ (CashIn / CashOut) ---
                elif mode in ['CashIn', 'CashOut', 'CashPaidIn', 'CashPaidOut']:
                    amount = self._safe_to_float(data.get('amount', 0), 0.0)
                    currency = data.get('currency', 'TRY')
                    is_income = 'In' in mode 
                    exchange_items = data.get('exchange_items', []) or []
                    if amount <= 0:
                        raise ValueError("Nakit işlem tutarı 0'dan büyük olmalıdır.")
                    
                    hist_credit = amount if is_income else 0
                    hist_debt = amount if not is_income else 0
                    
                    fin_id = None
                    
                    if data.get('affect_cash', True):
                        acc_data = data.get('account_data')
                        if not isinstance(acc_data, (list, tuple)) or len(acc_data) < 2:
                            raise ValueError("Lütfen geçerli bir kasa/banka hesabı seçin.")
                        mgr = self.main_app.cash_managers.get(acc_data[1]) or self.main_app.card_managers.get(acc_data[1])
                        if not mgr:
                            raise ValueError("Seçilen kasa/banka hesabı bulunamadı.")
                        ft_type = "Income" if is_income else "Expense"
                        base_amt = self.currency_manager._convert_to_base_currency(amount, currency)
                        ft = FinancialTransaction(
                            transaction_date=sql_date,
                            transaction_type=ft_type,
                            original_amount=amount,
                            currency=currency,
                            base_amount=base_amt,
                            description=f"Cari: {person_name} - {desc}",
                            person_full_name=person_name
                        )
                        mgr.add_item(ft, cursor=cursor)
                        fin_id = ft.id
                    
                    self.customer_history_manager.add_item(CustomerHistoryEvent(
                        person_full_name=person_name,
                        event_date=iso_date,
                        event_type="NAKİT " + ("GİRİŞ (TAHSİLAT)" if is_income else "ÇIKIŞ (ÖDEME)"),
                        debt_original=hist_debt,
                        credit_original=hist_credit,
                        currency=currency,
                        description=desc,
                        transaction_amount=amount,
                        related_id=fin_id
                    ), cursor=cursor)

                    # Bedel satırları sadece borç/alacak takibi içindir; fiziksel stoku etkilemez.

            self._refresh_cari_views_after_transaction(refresh_cash=True)
            self.main_app._show_status_message("İşlem başarıyla kaydedildi.")

        except Exception as e:
            logger.error(f"Kayıt hatası: {e}", exc_info=True)
            QMessageBox.critical(self.main_app, "Hata", f"İşlem kaydedilemedi: {e}")

    def _process_bedel_gold_stock_movements(self, mode, exchange_items, default_depot, cursor):
        """
        Bedelli işlemlerde, bedel satırında ALTIN ürünü seçildiyse fiziksel stok etkisini uygular.
        Sadece Product tanımlarına (altın ürün listesi) uygulanır.
        """
        if 'Paid' not in str(mode):
            return

        # PaidOut: bedel bizde kalır -> stoğa giriş
        # PaidIn: bedeli biz veririz -> stoktan çıkış
        direction = 1 if 'PaidOut' in str(mode) else (-1 if 'PaidIn' in str(mode) else 0)
        if direction == 0:
            return

        try:
            gold_names = {str(n).strip() for n in (self.product_manager.get_product_names() or []) if str(n).strip()}
        except Exception:
            gold_names = set()
        if not gold_names:
            return

        from core.db import StockItem

        for item in exchange_items or []:
            if not isinstance(item, dict):
                continue
            typ = self._normalize_text(item.get('type'))
            if not typ or typ not in gold_names:
                continue

            qty = self._safe_to_float(item.get('amount', 0), 0.0)
            if qty <= 0:
                continue

            total_gram = self._safe_to_float(item.get('gram', 0), 0.0)
            depot_name = self._normalize_text(item.get('depot') or default_depot or "Merkez") or "Merkez"

            product_def = self.product_manager.find_by_attribute('product_name', typ)
            price_base = 0.0
            if product_def:
                price_base = self.currency_manager._convert_to_base_currency(
                    product_def.default_purchase_price,
                    product_def.purchase_currency
                )

            if direction > 0:
                # Gram bilgisi saklansın diye satırı doğrudan ekliyoruz.
                self.stock_manager.add_item(StockItem(
                    item_type=typ,
                    quantity=qty,
                    quantity_gr=total_gram,
                    unique_barcode="",
                    depot_name=depot_name,
                    purchase_price_base=price_base
                ), cursor=cursor)
            else:
                self.stock_manager.remove_stock(typ, qty, depot_name, cursor=cursor)

    def _process_exchange_movements(self, person_name, date_str, mode, exchange_items, cursor):
        """Bedelli işlemlerin fiziksel hareketlerini yapar (Transaction içinden çağrılır)."""
        is_incoming = 'PaidOut' in mode
        for item in exchange_items:
            amount = item['amount']
            w_type = item['type']
            if w_type in SUPPORTED_CURRENCIES:
                target_mgr = None
                if self.main_app.cash_managers:
                    target_mgr = list(self.main_app.cash_managers.values())[0] # Varsayılan kasa
                if target_mgr:
                    trans_type = "Income" if is_incoming else "Expense"
                    base_val = self.currency_manager._convert_to_base_currency(amount, w_type)
                    desc = f"Bedelli işlem karşılığı ({mode}) - {person_name}"
                    target_mgr.add_item(FinancialTransaction(
                        date_str, trans_type, amount, w_type, base_val, desc
                    ), cursor=cursor)
            else:
                stock_mgr = None
                p_def = self.product_manager.find_by_attribute('product_name', w_type)
                if p_def: stock_mgr = self.stock_manager
                else:
                    p_def = self.main_app.ziynet_manager.find_by_attribute('product_name', w_type)
                    if p_def: stock_mgr = self.main_app.ziynet_stock_manager
                    else:
                        p_def = self.main_app.scrap_manager.find_by_attribute('product_name', w_type)
                        if p_def: stock_mgr = self.main_app.scrap_stock_manager
                if stock_mgr and p_def:
                    depot_name = "Merkez"
                    price_base = self.currency_manager._convert_to_base_currency(p_def.default_purchase_price, p_def.purchase_currency)
                    if is_incoming:
                        stock_mgr.add_or_update_stock(w_type, amount, price_base, depot_name, cursor=cursor)
                    else:
                        stock_mgr.remove_stock(w_type, amount, depot_name, cursor=cursor)
    def _process_virman_transaction(self, mode, person_name, data, iso_date, cursor):
        """Virman işlemini işler (Transaction Safe)."""
        import json
        target_person = data.get('target_person')
        asset_type = data.get('source_asset')
        amount = self._safe_to_float(data.get('amount', 0), 0.0)
        if not target_person or amount <= 0: return
        is_payable = 'Payable' in mode
        source_gram = self._safe_to_float(data.get('source_gram', 0), 0.0)
        source_karat = self._normalize_text(data.get('source_karat'))
        is_gold_asset = bool(self.product_manager.find_by_attribute('product_name', asset_type))

        desc_source = f"VİRMAN ÇIKIŞ -> {target_person}"
        desc_target = f"VİRMAN GİRİŞ <- {person_name}"
        if is_gold_asset and source_gram > 0 and source_karat:
            source_payload = {
                "main_desc": desc_source,
                "mode": mode,
                "asset_type": asset_type,
                "amount": amount,
                "gram": source_gram,
                "karat": source_karat,
                "direction": "outgoing_virman",
                "source_person": person_name,
                "target_person": target_person,
            }
            target_payload = {
                "main_desc": desc_target,
                "mode": mode,
                "asset_type": asset_type,
                "amount": amount,
                "gram": source_gram,
                "karat": source_karat,
                "direction": "incoming_virman",
                "source_person": person_name,
                "target_person": target_person,
            }
            desc_source = json.dumps(source_payload, ensure_ascii=False)
            desc_target = json.dumps(target_payload, ensure_ascii=False)

        if is_payable:
            source_debt, source_credit = 0, amount
            target_debt, target_credit = amount, 0
        else:
            source_debt, source_credit = amount, 0
            target_debt, target_credit = 0, amount
        self.customer_history_manager.add_item(CustomerHistoryEvent(
            person_full_name=person_name,
            event_date=iso_date,
            event_type="VİRMAN (GÖNDERİLEN)",
            debt_original=source_debt,
            credit_original=source_credit,
            currency=asset_type,
            description=desc_source,
            related_id=None
        ), cursor=cursor)
        self.customer_history_manager.add_item(CustomerHistoryEvent(
            person_full_name=target_person,
            event_date=iso_date,
            event_type="VİRMAN (ALINAN)",
            debt_original=target_debt,
            credit_original=target_credit,
            currency=asset_type,
            description=desc_target,
            related_id=None
        ), cursor=cursor)
    def _process_exchange_transaction(self, mode, person_name, data, iso_date, desc, cursor):
        """
        1.08 MANTIÄI: Çeviri (Exchange) işlemini işler.
        Cari kaydını (JSON detaylı) oluşturur.
        affect_real=True gelirse fiziksel hareketi de tetikler.
        """
        out_type = str(data.get('out_type') or '').strip()
        in_type = str(data.get('in_type') or '').strip()
        out_gram = self._safe_to_float(data.get('out_gram', 0), 0.0)
        in_gram = self._safe_to_float(data.get('in_gram', 0), 0.0)
        out_karat = self._normalize_text(data.get('out_karat'))
        in_karat = self._normalize_text(data.get('in_karat'))

        # 1.08'deki gibi veriyi hazırla
        out_item = {
            'type': out_type,
            'amount': self._safe_to_float(data.get('out_amount', 0), 0.0),
            'account': None,
            'side': 'source'
        }
        if out_gram > 0 and out_karat:
            out_item['gram'] = out_gram
            out_item['karat'] = out_karat
        in_item = {
            'type': in_type,
            'amount': self._safe_to_float(data.get('in_amount', 0), 0.0),
            'account': None,
            'side': 'dest'
        }
        if in_gram > 0 and in_karat:
            in_item['gram'] = in_gram
            in_item['karat'] = in_karat
        
        exchange_items = [out_item, in_item]
        
        # JSON Açıklama Oluştur
        import json
        json_data = {
            "main_desc": desc,
            "exchange_items": exchange_items,
            "mode": mode # ExchangePayable veya ExchangeReceivable
        }
        final_desc = json.dumps(json_data, ensure_ascii=False)
        
        # Olay Tipi Belirle
        event_type = "ÇEVİRİ İŞLEMİ"
        if mode == 'ExchangePayable': 
            event_type += " (BORÇ)" # Benim borcum değişiyor
        else: 
            event_type += " (ALACAK)" # Benim alacağım değişiyor
            
        # Cari Hareketini Kaydet (Bakiye 0, çünkü takas. Bakiye değişimi JSON içinden hesaplanacak)
        from core.db import CustomerHistoryEvent
        hist_event = CustomerHistoryEvent(
            person_full_name=person_name,
            event_date=iso_date,
            event_type=event_type,
            debt_original=0, credit_original=0,
            currency="Karma", # Birden fazla birim olduğu için
            description=final_desc,
            related_id=None,
            transaction_amount=0
        )
        self.customer_history_manager.add_item(hist_event, cursor=cursor)

        # Opsiyonel fiziksel hareket (stok/kasa). UI'da kapalı gelebilir.
        if data.get('affect_real', False):
            self.main_app.finance_manager._execute_exchange_transaction_from_crm(data, cursor=cursor)
    def _export_account_pdf(self):
        """Cari ekstreyi PDF olarak kaydeder."""
        person_name = self.main_app.acc_search_combo.currentText().strip()
        if not person_name:
            QMessageBox.warning(self.main_app, "Uyarı", "Lütfen önce bir kişi seçin.")
            return
        filename = f"Ekstre_{person_name}_{datetime.now().strftime('%Y%m%d')}.pdf".replace(" ", "_")
        save_path, _ = QFileDialog.getSaveFileName(self.main_app, "PDF Kaydet", filename, "PDF Files (*.pdf)")
        if save_path:
            headers = [self.main_app.excel_table.horizontalHeaderItem(i).text() for i in range(self.main_app.excel_table.columnCount())]
            data_rows = []
            for row in range(self.main_app.excel_table.rowCount()):
                row_data = []
                for col in range(self.main_app.excel_table.columnCount()):
                    item = self.main_app.excel_table.item(row, col)
                    row_data.append(item.text() if item else "")
                data_rows.append(row_data)
            summary = {
                "Müşteri": person_name,
                "Telefon": self.main_app.lbl_telefon.text(),
                "Borçlu Bakiye": self.main_app.lbl_borclu_val.text().replace("\n", " | "),
                "Alacaklı Bakiye": self.main_app.lbl_alacakli_val.text().replace("\n", " | "),
                "Oluşturma Tarihi": datetime.now().strftime("%d.%m.%Y %H:%M")
            }
            self.main_app.pdf_report_generator.generate_report(f"Müşteri Ekstresi - {person_name}", headers, data_rows, summary, save_path)
    def _export_account_excel(self):
        """Cari ekstreyi Excel olarak kaydeder."""
        person_name = self.main_app.acc_search_combo.currentText().strip()
        if not person_name:
            QMessageBox.warning(self.main_app, "Uyarı", "Lütfen önce bir kişi seçin.")
            return
        filename = f"Ekstre_{person_name}_{datetime.now().strftime('%Y%m%d')}.xlsx".replace(" ", "_")
        save_path, _ = QFileDialog.getSaveFileName(self.main_app, "Excel Kaydet", filename, "Excel Files (*.xlsx)")
        if save_path:
            headers = [self.main_app.excel_table.horizontalHeaderItem(i).text() for i in range(self.main_app.excel_table.columnCount())]
            data_rows = []
            for row in range(self.main_app.excel_table.rowCount()):
                row_data = []
                for col in range(self.main_app.excel_table.columnCount()):
                    item = self.main_app.excel_table.item(row, col)
                    row_data.append(item.text() if item else "")
                data_rows.append(row_data)
            summary = {
                "Müşteri": person_name,
                "Telefon": self.main_app.lbl_telefon.text(),
                "Borçlu Bakiye": self.main_app.lbl_borclu_val.text().replace("\n", " | "),
                "Alacaklı Bakiye": self.main_app.lbl_alacakli_val.text().replace("\n", " | ")
            }
            self.main_app.excel_report_generator.generate_report(f"Müşteri Ekstresi - {person_name}", headers, data_rows, summary, save_path)
    def _fill_excel_table_row(self, i, e):
        """
        Ekstre tablosunun (Excel View) bir satırını dolduran yardımcı fonksiyon.
        CRMManager sınıfı içine eklenmelidir.
        """
        table = self.main_app.excel_table
        col_count = table.columnCount()
        detail_col = col_count - 1 if col_count > 5 else None

        # Tarih Formatı
        try:
            dt = datetime.fromisoformat(e.event_date)
            date_str = dt.strftime("%d.%m.%Y")
        except:
            date_str = e.event_date

        # İşlem Türü
        event_type_text = self._get_display_event_type(e)
        item_type = QTableWidgetItem(event_type_text)
        item_type.setData(Qt.ItemDataRole.UserRole, e.id)
        table.setItem(i, 0, item_type)
        
        # Tarih
        table.setItem(i, 1, QTableWidgetItem(date_str))
        
        # Açıklama ve Bedel Ayrıştırma
        desc_raw = str(getattr(e, "description", "") or "")
        is_json = desc_raw.strip().startswith("{")
        json_data = {}
        mode = ""
        if is_json:
            try:
                json_data = json.loads(desc_raw)
                mode = str(json_data.get("mode", "") or "")
            except Exception:
                json_data = {}
                mode = ""
        desc = self._build_bedelli_desc(e) if is_json else self._normalize_text(desc_raw)
        
        table.setItem(i, 2, QTableWidgetItem(desc))
        curr = self._normalize_text(getattr(e, "currency", ""))
        is_paid_stock_mode = (
            'Paid' in mode and any(x in mode for x in ['Product', 'Ziynet', 'Scrap'])
        )
        if is_paid_stock_mode:
            curr = self._normalize_text(json_data.get("main_product") or curr)
        table.setItem(i, 3, QTableWidgetItem(curr))
        
        # Tutar
        amt = self._safe_to_float(getattr(e, 'transaction_amount', 0), 0.0)
        if is_paid_stock_mode:
            amt = self._safe_to_float(json_data.get("main_qty", 0), 0.0)
            if amt <= 0:
                amt = self._safe_to_float(json_data.get("main_total_gram", 0), 0.0)
        if amt == 0:
            amt = self._safe_to_float(e.debt_original if e.debt_original > 0 else e.credit_original, 0.0)
        table.setItem(i, 4, QTableWidgetItem(format_number(amt, 3 if curr not in SUPPORTED_CURRENCIES else 2)))
        
        # Detay Butonu (JSON verisi varsa)
        if detail_col is not None and is_json:
            btn = QPushButton("Detay")
            btn.clicked.connect(lambda _, d=desc_raw: self._open_bedel_dialog(d))
            table.setCellWidget(i, detail_col, btn)
        elif detail_col is not None:
            table.setItem(i, detail_col, QTableWidgetItem("-"))

    def _fill_acc_history_row(self, row, event, balance_at_event):
        """
        Cari Hesaplar (Ana Sekme) tablosunun bir satırını dolduran yardımcı fonksiyon.
        CRMManager sınıfı içine eklenmelidir.
        """
        try:
            table = self.main_app.acc_tab_history_table
            col_count = table.columnCount()
            detail_col = col_count - 1 if col_count > 5 else None
            balance_col = detail_col - 1 if col_count > 6 else None
            desc_raw = str(getattr(event, "description", "") or "")
            display_desc = self._normalize_text(desc_raw)
            is_bedelli = False
            json_data = {}
            
            # JSON Ayrıştırma
            if desc_raw.strip().startswith('{'):
                try:
                    json_data = json.loads(desc_raw)
                    display_desc = self._build_bedelli_desc(event)
                    items = json_data.get('exchange_items', [])
                    if items:
                        is_bedelli = True
                except: pass
            
            # Tarih
            try:
                dt = datetime.fromisoformat(event.event_date)
                date_s = dt.strftime("%d.%m.%Y")
            except: 
                date_s = event.event_date
            
            # Hücreleri Doldur
            table.setItem(row, 0, QTableWidgetItem(self._get_display_event_type(event)))
            if col_count > 1:
                table.setItem(row, 1, QTableWidgetItem(date_s))
            if col_count > 2:
                table.setItem(row, 2, QTableWidgetItem(display_desc))
            
            # Para Birimi
            curr = event.currency
            mode = str(json_data.get("mode", "") or "") if is_bedelli else ""
            is_paid_stock_mode = ('Paid' in mode and any(x in mode for x in ['Product', 'Ziynet', 'Scrap']))
            if is_paid_stock_mode:
                curr = json_data.get('main_product', curr)
            elif is_bedelli and (not curr or curr == "Karma"):
                curr = json_data.get('main_product', "Karma")
            if col_count > 3:
                table.setItem(row, 3, QTableWidgetItem(self._normalize_text(curr)))
            
            # Tutar
            amt = self._safe_to_float(getattr(event, 'transaction_amount', 0), 0.0)
            if is_paid_stock_mode:
                amt = self._safe_to_float(json_data.get("main_qty", 0), 0.0)
                if amt <= 0:
                    amt = self._safe_to_float(json_data.get("main_total_gram", 0), 0.0)
            if amt == 0:
                amt = self._safe_to_float(event.debt_original if event.debt_original > 0 else event.credit_original, 0.0)
            if col_count > 4:
                table.setItem(row, 4, QTableWidgetItem(format_number(amt, 3 if curr not in SUPPORTED_CURRENCIES else 2)))
            
            # Bakiye Sütunu
            if balance_col is not None and balance_col >= 5:
                balance_texts = [
                    f"{format_number(bal, 3 if b_curr not in SUPPORTED_CURRENCIES else 2)} {b_curr}" 
                    for b_curr, bal in sorted(balance_at_event.items()) if abs(bal) > 0.001
                ]
                balance_str = " | ".join(balance_texts) if balance_texts else "0.00"
                balance_item = QTableWidgetItem(balance_str)
                balance_item.setTextAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
                table.setItem(row, balance_col, balance_item)

            # Detay Butonu
            if detail_col is not None and is_bedelli:
                btn = QPushButton("Detay")
                btn.clicked.connect(lambda _, d=desc_raw: self._open_bedel_dialog(d))
                table.setCellWidget(row, detail_col, btn)
            elif detail_col is not None:
                table.setItem(row, detail_col, QTableWidgetItem("-"))
                
            # ID Sakla
            first_item = table.item(row, 0)
            if first_item:
                first_item.setData(Qt.ItemDataRole.UserRole, event.id)
        except Exception as e:
            print(f"Satır doldurma hatası: {e}")
    def _update_excel_account_view(self):
        """
        Seçili kişiye göre tabloyu ve bakiyeyi günceller (Excel View).
        1.08 MANTIÄI BURAYA DA UYGULANDI.
        """
        if not hasattr(self.main_app, 'acc_search_combo'): return
        person_name = self.main_app.acc_search_combo.currentText()
        if not person_name: return
        
        person = self.person_manager.find_by_attribute("full_name", person_name)
        if person:
            self.main_app.lbl_ad_soyad.setText(person.full_name)
            self.main_app.lbl_telefon.setText(person.phone_number)
            self.main_app.lbl_adres.setText(person.address)
            
        self.main_app.excel_table.setRowCount(0)
        events = [
            e for e in self.customer_history_manager.get_all()
            if e.person_full_name == person_name
            and not self._is_hidden_excel_cash_event(getattr(e, "event_type", ""))
        ]
        sorted_events = sorted(events, key=lambda x: (parse_date_from_display(x.event_date) or datetime.min), reverse=True)
        self.main_app.excel_table.setRowCount(len(sorted_events))
        
        balances = defaultdict(float)
        
        for i, e in enumerate(sorted_events):
            # Tablo Satırını Doldur
            self._fill_excel_table_row(i, e)

            # Bakiye hesapla (bedelli satırları dahil)
            try:
                e_desc = str(getattr(e, "description", "") or "")
                if e_desc.strip().startswith('{'):
                    json_data = json.loads(e_desc)
                    if isinstance(json_data, dict):
                        mode = str(json_data.get('mode', '') or '')
                        exchange_items = json_data.get('exchange_items', []) or []

                        if 'Exchange' in mode:
                            for idx, item in enumerate(exchange_items):
                                amt = self._safe_to_float(item.get('amount', 0), 0.0)
                                typ = item.get('type')
                                if typ and amt > 0:
                                    balance_key = typ
                                    if typ not in SUPPORTED_CURRENCIES:
                                        item_gram = self._safe_to_float(item.get("gram", 0), 0.0)
                                        item_karat = self._normalize_text(item.get("karat"))
                                        extras = []
                                        if item_gram > 0:
                                            extras.append(f"{format_number(item_gram, 2)} gr")
                                        if item_karat:
                                            extras.append(item_karat)
                                        if extras:
                                            balance_key = f"{typ} ({' '.join(extras)})"
                                    if 'ExchangePayable' in mode:
                                        # 0: Kaynak (-), 1: Hedef (+)
                                        if idx == 0: balances[balance_key] -= amt
                                        else:        balances[balance_key] += amt
                                    elif 'ExchangeReceivable' in mode:
                                        # 0: Kaynak (+), 1: Hedef (-)
                                        if idx == 0: balances[balance_key] += amt
                                        else:        balances[balance_key] -= amt
                        elif '(POS)' in mode:
                            if e.currency and e.currency != "Karma":
                                balances[e.currency] += (e.debt_original - e.credit_original)
                        else:
                            # Bedelli giriş/çıkış zıt işaretle işlenmeli.
                            if 'PaidOut' in mode:
                                bedel_direction = 1.0
                            elif 'PaidIn' in mode:
                                bedel_direction = -1.0
                            elif any(x in mode for x in ['Out', 'Sale']):
                                bedel_direction = 1.0
                            elif any(x in mode for x in ['In', 'Purchase']):
                                bedel_direction = -1.0
                            else:
                                bedel_direction = -1.0
                            for item in exchange_items:
                                amt = self._safe_to_float(item.get('amount', 0), 0.0)
                                typ = item.get('type')
                                if typ and amt > 0:
                                    balance_key = typ
                                    if typ not in SUPPORTED_CURRENCIES:
                                        item_gram = self._safe_to_float(item.get("gram", 0), 0.0)
                                        item_karat = self._normalize_text(item.get("karat"))
                                        extras = []
                                        if item_gram > 0:
                                            extras.append(f"{format_number(item_gram, 2)} gr")
                                        if item_karat:
                                            extras.append(item_karat)
                                        if extras:
                                            balance_key = f"{typ} ({' '.join(extras)})"
                                    balances[balance_key] += (amt * bedel_direction)
                else:
                    if e.currency and e.currency != "Karma":
                        balances[e.currency] += (e.debt_original - e.credit_original)
            except (json.JSONDecodeError, TypeError, KeyError, ValueError):
                if e.currency and e.currency != "Karma":
                    balances[e.currency] += (e.debt_original - e.credit_original)
        
        # Etiketleri Güncelle
        borclu_list = []
        alacakli_list = []
        for curr, bal in balances.items():
            if curr and bal > 0.001:
                borclu_list.append(f"{format_number(bal)} {curr}")
            elif curr and bal < -0.001:
                alacakli_list.append(f"{format_number(abs(bal))} {curr}")
        
        self.main_app.lbl_borclu_val.setText("\n".join(borclu_list) if borclu_list else "0.00")
        self.main_app.lbl_alacakli_val.setText("\n".join(alacakli_list) if alacakli_list else "0.00")
        self._acc_excel_update_buttons()
    def _acc_excel_update_buttons(self):
        """Tabloda seçim durumuna göre Düzenle/Sil butonlarını aktif/pasif yapar."""
        has_selection = len(self.main_app.excel_table.selectedItems()) > 0
        self.main_app.acc_excel_edit_btn.setEnabled(has_selection)
        self.main_app.acc_excel_delete_btn.setEnabled(has_selection)
    def _acc_excel_delete_transaction(self):
        """
        Seçili işlemi ve tüm etkilerini geri alarak siler (ATOMIC REVERSE TRANSACTION).
        FAZ 3: Barkodlu satış iptalinde 'is_sold' durumunu düzeltme eklendi.
        """
        selected_items = self.main_app.excel_table.selectedItems()
        if not selected_items: return
        row = selected_items[0].row()
        first_item = self.main_app.excel_table.item(row, 0)
        if not first_item:
            return
        event_id = first_item.data(Qt.ItemDataRole.UserRole)
        if not event_id:
            return
        event = self.customer_history_manager.find_by_id(event_id)
        if not event: return
        event_type_upper = self._normalize_text(getattr(event, "event_type", "")).upper()
        should_affect = self.main_app.affect_cash_checkbox.isChecked()
        msg = f"'{event.description}' işlemini silmek istediğinize emin misiniz?"
        is_virman = "VİRMAN" in event_type_upper
        if is_virman:
            msg += "\n\n(DİKKAT: Bu bir VİRMAN işlemidir. Karşı taraftaki kayıt da silinecektir.)"
        elif should_affect:
            msg += "\n\n(DİKKAT: Stok ve Kasa bakiyeleri de geri alınacak!)"
        else:
            msg += "\n\n(NOT: Sadece listeden silinecek, bakiyeler etkilenmeyecek.)"
        if not self.main_app._show_confirm_dialog("İşlemi Sil", msg):
            return
        db = self.main_app.data_manager.db
        try:
            with db.transaction_scope() as cursor:
                profile_id = self.customer_history_manager.profile_id
                if is_virman:
                    self.customer_history_manager.delete_item(event_id, cursor=cursor)
                    if event.related_id:
                        pass
                elif should_affect:
                    json_data = {}
                    event_desc = str(getattr(event, "description", "") or "")
                    event_type_upper = self._normalize_text(getattr(event, "event_type", "")).upper()
                    if event_desc.strip().startswith('{'):
                        try:
                            json_data = json.loads(event_desc)
                        except: pass
                    main_product = json_data.get('main_product') or event.currency
                    main_qty = self._safe_to_float(json_data.get('main_qty', 0), 0.0)
                    main_depot = json_data.get('main_depot', '')
                    mode = json_data.get('mode', '')
                    if not main_product and ("ALTIN" in event_type_upper or "ÜRÜN" in event_type_upper):
                        main_qty = event.transaction_amount or (event.debt_original + event.credit_original)
                    if main_product and main_qty > 0:
                        stock_mgr = self.stock_manager
                        if "ZIYNET" in mode.upper() or "ZİYNET" in event_type_upper:
                            stock_mgr = self.main_app.ziynet_stock_manager
                        elif "SCRAP" in mode.upper() or "HURDA" in event_type_upper:
                            stock_mgr = self.main_app.scrap_stock_manager
                        is_out = (
                            'Out' in mode
                            or 'Sale' in mode
                            or "ÇIKIŞ" in event_type_upper
                            or "SATIŞ" in event_type_upper
                        )
                        reverse_depot = self._resolve_event_reverse_depot(
                            stock_mgr=stock_mgr,
                            main_product=main_product,
                            main_qty=main_qty,
                            preferred_depot=main_depot,
                            is_out=is_out,
                        )
                        if is_out:
                            current_s = stock_mgr.get_stock_by_type_and_depot(main_product, reverse_depot)
                            p_base = current_s.purchase_price_base if current_s else 0.0
                            stock_mgr.add_or_update_stock(main_product, main_qty, p_base, reverse_depot, cursor=cursor)
                            select_barcode_sql = """
                                SELECT id
                                FROM barcode_history
                                WHERE profile_id = %s AND product_type = %s AND is_sold = 1
                                ORDER BY sell_date DESC
                                LIMIT 1
                                FOR UPDATE
                            """
                            update_barcode_sql = """
                                UPDATE barcode_history
                                SET is_sold = 0
                                WHERE id = %s AND profile_id = %s
                            """
                            for _ in range(int(main_qty)):
                                cursor.execute(select_barcode_sql, (profile_id, main_product))
                                row = cursor.fetchone()
                                if not row or not row.get("id"):
                                    break
                                barcode_id = str(row.get("id"))
                                before_barcode = fetch_row_snapshot_by_id(
                                    db=db,
                                    table_name="barcode_history",
                                    record_id=barcode_id,
                                    profile_id=profile_id,
                                    cursor=cursor,
                                )
                                cursor.execute(update_barcode_sql, (barcode_id, profile_id))
                                after_barcode = fetch_row_snapshot_by_id(
                                    db=db,
                                    table_name="barcode_history",
                                    record_id=barcode_id,
                                    profile_id=profile_id,
                                    cursor=cursor,
                                )
                                queue_row_sync_event(
                                    db=db,
                                    table_name="barcode_history",
                                    profile_id=profile_id,
                                    action_type=SYNC_ACTION_UPDATE,
                                    record_id=barcode_id,
                                    before_row=before_barcode,
                                    after_row=after_barcode,
                                    cursor=cursor,
                                    meta={"source": "CRMManager._acc_excel_delete_transaction"},
                                )
                        else:
                            stock_mgr.remove_stock(main_product, main_qty, reverse_depot, cursor=cursor)
                    if event.related_id:
                        table_order = [
                            "financial_transactions",
                            "debt_records",
                            "sales",
                            "repair_records",
                        ]
                        before_related = {
                            table_name: fetch_row_snapshot_by_id(
                                db=db,
                                table_name=table_name,
                                record_id=event.related_id,
                                profile_id=profile_id,
                                cursor=cursor,
                            )
                            for table_name in table_order
                        }
                        cursor.execute(
                            "DELETE FROM financial_transactions WHERE id = %s AND profile_id = %s",
                            (event.related_id, profile_id),
                        )
                        if cursor.rowcount > 0:
                            queue_row_sync_event(
                                db=db,
                                table_name="financial_transactions",
                                profile_id=profile_id,
                                action_type=SYNC_ACTION_DELETE,
                                record_id=event.related_id,
                                before_row=before_related.get("financial_transactions"),
                                after_row=None,
                                cursor=cursor,
                                meta={"source": "CRMManager._acc_excel_delete_transaction"},
                            )
                        cursor.execute(
                            "DELETE FROM debt_records WHERE id = %s AND profile_id = %s",
                            (event.related_id, profile_id),
                        )
                        if cursor.rowcount > 0:
                            queue_row_sync_event(
                                db=db,
                                table_name="debt_records",
                                profile_id=profile_id,
                                action_type=SYNC_ACTION_DELETE,
                                record_id=event.related_id,
                                before_row=before_related.get("debt_records"),
                                after_row=None,
                                cursor=cursor,
                                meta={"source": "CRMManager._acc_excel_delete_transaction"},
                            )
                        cursor.execute(
                            "DELETE FROM sales WHERE id = %s AND profile_id = %s",
                            (event.related_id, profile_id),
                        )
                        if cursor.rowcount > 0:
                            queue_row_sync_event(
                                db=db,
                                table_name="sales",
                                profile_id=profile_id,
                                action_type=SYNC_ACTION_DELETE,
                                record_id=event.related_id,
                                before_row=before_related.get("sales"),
                                after_row=None,
                                cursor=cursor,
                                meta={"source": "CRMManager._acc_excel_delete_transaction"},
                            )
                        cursor.execute(
                            "DELETE FROM repair_records WHERE id = %s AND profile_id = %s",
                            (event.related_id, profile_id),
                        )
                        if cursor.rowcount > 0:
                            queue_row_sync_event(
                                db=db,
                                table_name="repair_records",
                                profile_id=profile_id,
                                action_type=SYNC_ACTION_DELETE,
                                record_id=event.related_id,
                                before_row=before_related.get("repair_records"),
                                after_row=None,
                                cursor=cursor,
                                meta={"source": "CRMManager._acc_excel_delete_transaction"},
                            )
                self.customer_history_manager.delete_item(event_id, cursor=cursor)
            self.main_app.refresh_current_account_table()
            self.main_app.refresh_cash_table()
            self.main_app.refresh_account_balances()
            self.main_app.refresh_dashboard_cards()
            if is_virman:
                self.main_app._show_status_message("Virman işlemi silindi.")
            elif should_affect:
                self.main_app._show_status_message("İşlem geri alındı (Stok/Kasa iade edildi).")
            else:
                self.main_app._show_status_message("İşlem kaydı silindi.")
        except Exception as e:
            logger.error(f"Silme hatası: {e}")
            QMessageBox.critical(self.main_app, "Hata", f"Silme işlemi geri alındı:\n{e}")
    def _post_cash_card_breakdown_for_debt(
        self,
        *,
        debt_record: DebtRecord,
        payment_breakdown: List[Dict[str, Any]],
        default_currency: str,
        description: str,
        cursor,
    ) -> tuple[float, bool]:
        transaction_date = datetime.now().strftime("%d.%m.%Y")
        event_date_iso = datetime.now().isoformat()
        is_collection = debt_record.record_type == "Receivable"
        affect_cash = getattr(self.main_app, 'affect_cash_checkbox', None)
        affect_cash_value = affect_cash.isChecked() if affect_cash else True
        total_paid_base = 0.0
        did_cash_transaction = False

        for item in payment_breakdown:
            method_type = item.get('method_type')
            if method_type == 'On Credit':
                raise ValueError("Bu ekranda veresiye bırakma kullanılamaz.")
            if method_type not in ['cash', 'card']:
                raise ValueError("Bu ekranda sadece kasa/kart tahsilatı kullanılabilir.")

            account_id = item.get('account_id')
            amount = self._safe_to_float(item.get('total_val', item.get('amount', 0)), 0.0)
            currency = item.get('currency', default_currency)
            if amount <= 0:
                continue

            applied_amount = self._safe_to_float(
                item.get('amount_base', self.currency_manager._convert_to_base_currency(amount, currency)),
                self.currency_manager._convert_to_base_currency(amount, currency),
            )
            payment_base = self._safe_to_float(
                item.get('base_amount', self.currency_manager._convert_to_base_currency(amount, currency)),
                self.currency_manager._convert_to_base_currency(amount, currency),
            )
            total_paid_base += applied_amount

            related_id = None
            if affect_cash_value:
                manager = self.main_app.cash_managers.get(account_id) if method_type == 'cash' else self.main_app.card_managers.get(account_id)
                if not manager:
                    raise ValueError("Seçilen kasa/kart hesabı bulunamadı.")
                trans_type = "Income" if is_collection else "Expense"
                fin_desc = f"{debt_record.person_full_name} - {'Tahsilat' if is_collection else 'Ödeme'}"
                if description:
                    fin_desc += f" ({description})"
                new_fin_trans = FinancialTransaction(
                    transaction_date=transaction_date,
                    transaction_type=trans_type,
                    original_amount=amount,
                    currency=currency,
                    base_amount=payment_base,
                    description=fin_desc,
                    person_full_name=debt_record.person_full_name,
                    related_id=debt_record.id,
                )
                manager.add_item(new_fin_trans, cursor=cursor)
                related_id = new_fin_trans.id
                did_cash_transaction = True

            self.customer_history_manager.add_item(CustomerHistoryEvent(
                person_full_name=debt_record.person_full_name,
                event_date=event_date_iso,
                event_type="Tahsilat" if is_collection else "Ödeme",
                credit_original=amount if is_collection else 0,
                debt_original=0 if is_collection else amount,
                currency=currency,
                description=description or f"{debt_record.person_full_name} için ödeme",
                related_id=related_id,
            ), cursor=cursor)

        if total_paid_base <= 0:
            raise ValueError("Geçerli bir ödeme bulunamadı.")
        return total_paid_base, did_cash_transaction

    def _open_payment_dialog_for_cash_equivalent(self, debt_record: DebtRecord, amount: float):
        """Ürün borcunun nakit karşılığı için PaymentDialog'u açar ve seçilen kasa/karta yazar."""
        payment_dialog = PaymentDialog(
            total_amount=amount,
            display_currency=self.main_app.display_currency,
            person_name=debt_record.person_full_name,
            cash_accounts=self.main_app._get_accessible_cash_accounts(),
            card_accounts=self.main_app._get_accessible_card_accounts(),
            depot_manager=self.main_app.depot_manager,
            product_manager=self.main_app.product_manager,
            stock_manager=self.main_app.stock_manager,
            lang=self.main_app.lang,
            currency_manager=self.main_app.currency_manager,
            allowed_depots=self.main_app._get_accessible_depots(),
            allow_product_trade=False,
            allow_credit=False,
            parent=self.main_app
        )
        if not payment_dialog.exec():
            return

        payment_breakdown = payment_dialog.get_payment_breakdown()
        db = self.main_app.data_manager.db
        try:
            with db.transaction_scope() as cursor:
                total_paid_base, _ = self._post_cash_card_breakdown_for_debt(
                    debt_record=debt_record,
                    payment_breakdown=payment_breakdown,
                    default_currency=self.main_app.display_currency,
                    description=f"{debt_record.product_type} borcu nakit karşılığı ile kapatıldı.",
                    cursor=cursor,
                )
                updates = {
                    "is_paid": True,
                    "paid_quantity": debt_record.product_quantity,
                    "payment_date": datetime.now().strftime("%d.%m.%Y"),
                    "paid_amount_base": debt_record.total_amount_base if debt_record.total_amount_base > 0 else max(float(debt_record.paid_amount_base or 0), total_paid_base),
                }
                self.debt_manager.update_item(debt_record.id, updates, cursor=cursor)
            self.main_app.refresh_cash_table()
            self.main_app.refresh_current_account_table()
            self.main_app.refresh_dashboard_cards()
            self.main_app._show_status_message("Ürün borcu nakit olarak kapatıldı. Kasa/kart kayıtları işlendi.")
        except Exception as e:
            logger.error(f"Ürün borcu nakit kapatma hatası: {e}")
            QMessageBox.critical(self.main_app, "Hata", f"Ödeme işlenemedi: {e}")

    def _open_payment_dialog_for_cash_debt(self, debt_record: DebtRecord):
        """Seçili nakit borç/alacağı ödeme ekranı ile kapatır."""
        remaining_base = max(float(debt_record.total_amount_base or 0) - float(debt_record.paid_amount_base or 0), 0.0)
        if remaining_base <= 0.001:
            QMessageBox.information(self.main_app, "Bilgi", "Bu kayıt zaten kapatılmış görünüyor.")
            return

        display_currency = debt_record.currency or self.main_app.display_currency
        remaining_display = self.currency_manager._convert_to_display_currency(remaining_base, display_currency)
        payment_dialog = PaymentDialog(
            total_amount=remaining_display,
            display_currency=display_currency,
            person_name=debt_record.person_full_name,
            cash_accounts=self.main_app._get_accessible_cash_accounts(),
            card_accounts=self.main_app._get_accessible_card_accounts(),
            depot_manager=self.main_app.depot_manager,
            product_manager=self.main_app.product_manager,
            stock_manager=self.main_app.stock_manager,
            lang=self.main_app.lang,
            currency_manager=self.main_app.currency_manager,
            allowed_depots=self.main_app._get_accessible_depots(),
            allow_product_trade=False,
            allow_credit=False,
            parent=self.main_app
        )
        if not payment_dialog.exec():
            return

        payment_breakdown = payment_dialog.get_payment_breakdown()
        try:
            db = self.main_app.data_manager.db
            with db.transaction_scope() as cursor:
                total_paid_base, _ = self._post_cash_card_breakdown_for_debt(
                    debt_record=debt_record,
                    payment_breakdown=payment_breakdown,
                    default_currency=display_currency,
                    description=debt_record.description or "Borç kapatma",
                    cursor=cursor,
                )
                new_paid_amount = min(remaining_base + float(debt_record.paid_amount_base or 0), float(debt_record.paid_amount_base or 0) + total_paid_base)
                updates = {
                    "paid_amount_base": new_paid_amount,
                    "payment_date": datetime.now().strftime("%d.%m.%Y"),
                    "is_paid": new_paid_amount >= float(debt_record.total_amount_base or 0) - 0.01,
                }
                self.debt_manager.update_item(debt_record.id, updates, cursor=cursor)
            self.main_app.refresh_cash_table()
            self.main_app.refresh_current_account_table()
            self.main_app.refresh_dashboard_cards()
            self.main_app._show_status_message("Borç/alacak ödemesi işlendi ve kasa/kart kayıtlarına yazıldı.")
        except Exception as e:
            logger.error(f"Nakit borç kapatma hatası: {e}")
            QMessageBox.critical(self.main_app, "Hata", f"Ödeme işlenemedi: {e}")

    def _handle_product_payment(self, record_type: str):
        """Seçili ürün borcunu fiziksel ürün giriş/çıkışı ile kapatır."""
        table = self.main_app.receivables_table if record_type == "Receivable" else self.main_app.payables_table
        selected_items = table.selectedItems()
        if not selected_items:
            return

        row = selected_items[0].row()
        item_id = table.item(row, 0).data(Qt.ItemDataRole.UserRole)
        debt_record = self.debt_manager.find_by_id(item_id)
        if not debt_record or debt_record.is_paid:
            return
        if debt_record.debt_type != "Product":
            QMessageBox.warning(self.main_app, "Uyarı", "Bu kayıt ürün borcu değil. Nakit ödeme ekranını kullanın.")
            return

        product_name = str(debt_record.product_type or "").strip()
        remaining_qty = max(float(debt_record.product_quantity or 0) - float(debt_record.paid_quantity or 0), 0.0)
        if not product_name or remaining_qty <= 0.001:
            QMessageBox.information(self.main_app, "Bilgi", "Bu ürün borcu için kapatılacak bakiye kalmadı.")
            return

        depot_options = self.main_app._get_accessible_depots() or ["Merkez"]
        depot_name, ok = QInputDialog.getItem(
            self.main_app,
            "Depo Seçin",
            f"'{product_name}' için kullanılacak depoyu seçin:",
            depot_options,
            0,
            False,
        )
        if not ok or not depot_name:
            return

        is_collection = record_type == "Receivable"
        gold_details: List[Dict[str, Any]] = []
        product_def = self.product_manager.find_by_attribute('product_name', product_name)
        purchase_price_base = 0.0
        if product_def:
            purchase_price_base = self.currency_manager._convert_to_base_currency(
                product_def.default_purchase_price,
                product_def.purchase_currency,
            )

        if product_def:
            qty_int = int(round(remaining_qty))
            if abs(remaining_qty - qty_int) > 0.0001:
                QMessageBox.warning(self.main_app, "Uyarı", "Altın ürün borçlarında miktar tam sayı olmalıdır.")
                return
            from modules.stock import AdvancedGoldEntryDialog
            dialog = AdvancedGoldEntryDialog(
                "In" if is_collection else "Out",
                product_name,
                qty_int,
                self.stock_manager,
                self.product_manager,
                self.main_app,
            )
            if dialog.exec() != QDialog.DialogCode.Accepted:
                return
            gold_details = dialog.get_details() or []
            if len(gold_details) != qty_int:
                QMessageBox.warning(self.main_app, "Uyarı", "Altın detayları eksik. İşlem iptal edildi.")
                return
            remaining_qty = float(qty_int)
        elif not is_collection:
            stock_item = self.stock_manager.get_stock_by_type_and_depot(product_name, depot_name)
            available_qty = float(getattr(stock_item, "quantity", 0.0) or 0.0)
            if available_qty + 0.0001 < remaining_qty:
                QMessageBox.warning(
                    self.main_app,
                    "Uyarı",
                    f"Yetersiz stok. '{depot_name}' deposunda {available_qty:.3f} adet var.",
                )
                return

        transaction_date = datetime.now().strftime("%d.%m.%Y")
        event_date_iso = datetime.now().isoformat()
        total_gram = sum(self._safe_to_float(item.get('gram', 0), 0.0) for item in gold_details)
        desc_suffix = f"{format_number(total_gram, 3)} gr" if total_gram > 0 else f"{format_number(remaining_qty, 3)} adet"
        history_desc = f"{product_name} fiziksel {'alındı' if is_collection else 'verildi'} - Depo: {depot_name} - {desc_suffix}"

        db = self.main_app.data_manager.db
        try:
            with db.transaction_scope() as cursor:
                if is_collection:
                    if gold_details:
                        for item in gold_details:
                            barcode = str(item.get('barcode') or '').strip()
                            gram_val = self._safe_to_float(item.get('gram', 0), 0.0)
                            karat_val = str(item.get('karat') or '').strip()
                            if not barcode or gram_val <= 0:
                                continue
                            self.stock_manager.add_item(StockItem(
                                item_type=product_name,
                                quantity=1.0,
                                quantity_gr=gram_val,
                                unique_barcode=barcode,
                                depot_name=depot_name,
                                purchase_price_base=purchase_price_base,
                            ), cursor=cursor)
                            self.main_app.barcode_history_manager.register_buy(
                                barcode=barcode,
                                p_type=product_name,
                                gram=gram_val,
                                karat=karat_val,
                                source=debt_record.person_full_name,
                                price=purchase_price_base,
                                method="Ürün Borcu Tahsilatı",
                                cursor=cursor,
                            )
                    else:
                        self.stock_manager.add_or_update_stock(product_name, remaining_qty, purchase_price_base, depot_name, cursor=cursor)
                else:
                    if gold_details:
                        barcodes = [
                            str(item.get('barcode') or '').strip()
                            for item in gold_details
                            if str(item.get('barcode') or '').strip()
                        ]
                        if not barcodes:
                            raise ValueError("Çıkış için geçerli barkod bulunamadı.")
                        self.stock_manager.remove_stock_by_barcodes(barcodes, depot_name, cursor=cursor)
                        for item in gold_details:
                            barcode = str(item.get('barcode') or '').strip()
                            if not barcode:
                                continue
                            self.main_app.barcode_history_manager.register_sell(
                                barcode=barcode,
                                target=debt_record.person_full_name,
                                price=purchase_price_base,
                                method="Ürün Borcu Ödemesi",
                                cursor=cursor,
                                depot=depot_name,
                                sold_at=event_date_iso,
                            )
                    else:
                        self.stock_manager.remove_stock(product_name, remaining_qty, depot_name, cursor=cursor)

                self.debt_manager.update_item(debt_record.id, {
                    "paid_quantity": float(debt_record.product_quantity or 0),
                    "paid_amount_base": float(debt_record.total_amount_base or 0),
                    "is_paid": True,
                    "payment_date": transaction_date,
                }, cursor=cursor)
                self.customer_history_manager.add_item(CustomerHistoryEvent(
                    person_full_name=debt_record.person_full_name,
                    event_date=event_date_iso,
                    event_type="Alacak Kapatma (Ürün)" if is_collection else "Borç Ödeme (Ürün)",
                    credit_original=remaining_qty if is_collection else 0,
                    debt_original=0 if is_collection else remaining_qty,
                    currency=product_name,
                    description=history_desc,
                    related_id=debt_record.id,
                ), cursor=cursor)
            self.main_app.refresh_current_account_table()
            self.main_app.refresh_cash_table()
            self.main_app.refresh_dashboard_cards()
            self.main_app._show_status_message("Ürün borcu fiziksel ürün hareketiyle kapatıldı.")
        except Exception as e:
            logger.error(f"Ürün borcu kapatma hatası: {e}")
            QMessageBox.critical(self.main_app, "Hata", f"Ürün borcu kapatılamadı: {e}")
    def _handle_debt_payment(self, record_type: str):
        """
        Borç/Alacak için ödeme/tahsilat işlemini yönetir.
        GÜNCELLEME: Ürün borçları için "Nakit Ödeme" ve "Ürünle Ödeme" seçenekleri sunar.
        """
        table = self.main_app.receivables_table if record_type == "Receivable" else self.main_app.payables_table
        selected_items = table.selectedItems()
        if not selected_items: return
        row = selected_items[0].row()
        item_id = table.item(row, 0).data(Qt.ItemDataRole.UserRole)
        debt_record = self.debt_manager.find_by_id(item_id)
        if not debt_record or debt_record.is_paid: return
        if debt_record.debt_type == "Product":
            choice, ok = QInputDialog.getItem(
                self.main_app,
                "Ödeme Yöntemi Seçin",
                f"Bu '{debt_record.product_type}' borcunu nasıl kapatmak istersiniz?",
                ["Nakit Karşılığı İle Öde/Tahsil Et", "Ürünü Fiziksel Olarak Geri Al/Ver"],
                0, False
            )
            if not ok: return
            if choice == "Nakit Karşılığı İle Öde/Tahsil Et":
                equivalent_cash, ok = QInputDialog.getDouble(
                    self.main_app, "Nakit Karşılığı",
                    f"{debt_record.product_quantity} {debt_record.product_type} için nakit karşılığını girin ({self.main_app.display_currency}):",
                    decimals=2
                )
                if not ok or equivalent_cash <= 0: return
                self._open_payment_dialog_for_cash_equivalent(debt_record, equivalent_cash)
            else:
                self._handle_product_payment(record_type)
        else: # Eğer borç türü "Cash" ise, direkt ödeme ekranını aç (eski mantık).
            self._open_payment_dialog_for_cash_debt(debt_record)
    def _acc_excel_edit_transaction(self):
        selected_items = self.main_app.excel_table.selectedItems()
        if not selected_items: return
        row = selected_items[0].row()
        event_id = self.main_app.excel_table.item(row, 0).data(Qt.ItemDataRole.UserRole)
        event = self.customer_history_manager.find_by_id(event_id)
        if not event: return
        event_desc = str(getattr(event, "description", "") or "")
        event_type_upper = self._normalize_text(getattr(event, "event_type", "")).upper()
        managers = {
            'cash_acc': self.cash_account_manager,
            'card_acc': self.card_account_manager,
            'depot': self.depot_manager,
            'product': self.product_manager,
            'ziynet': self.main_app.ziynet_manager,
            'scrap': self.main_app.scrap_manager,
            'person': self.person_manager
        }
        from modules.cash import AdvancedEditTransactionDialog
        dialog = AdvancedEditTransactionDialog(event, managers, self.main_app.lang, self.main_app)
        if dialog.exec() != QDialog.DialogCode.Accepted:
            return
        new_data = dialog.get_data()
        should_affect = self.main_app.affect_cash_checkbox.isChecked()
        if should_affect:
            if event_desc.strip().startswith('{'):
                try:
                    import json
                    j_data = json.loads(event_desc)
                    if 'main_product' in j_data:
                        old_prod = j_data['main_product']
                        old_q = self._safe_to_float(j_data.get('main_qty'), 0.0)
                        old_dep = j_data.get('main_depot', '')
                        mode = j_data.get('mode', '')
                        sm = self.main_app.stock_manager
                        if 'Ziynet' in mode: sm = self.main_app.ziynet_stock_manager
                        elif 'Scrap' in mode: sm = self.main_app.scrap_stock_manager
                        is_out_mode = ('Out' in mode)
                        reverse_dep = self._resolve_event_reverse_depot(
                            stock_mgr=sm,
                            main_product=old_prod,
                            main_qty=old_q,
                            preferred_depot=old_dep,
                            is_out=is_out_mode,
                        )
                        if is_out_mode:
                            curr_s = sm.get_stock_by_type_and_depot(old_prod, reverse_dep)
                            p_base = curr_s.purchase_price_base if curr_s else 0.0
                            sm.add_or_update_stock(old_prod, old_q, p_base, reverse_dep)
                        elif 'In' in mode:
                            sm.remove_stock(old_prod, old_q, reverse_dep)
                except: pass
            elif "URUN" in event_type_upper or "ÜRÜN" in event_type_upper or "ALTIN" in event_type_upper:
                old_product = event.currency
                old_qty = event.debt_original + event.credit_original
                parts = event_desc.split(' - ')
                old_depot = parts[0] if len(parts) > 1 else "Merkez"
                stock_mgr = self.main_app.stock_manager
                if "HURDA" in event_type_upper: stock_mgr = self.main_app.scrap_stock_manager
                elif "ZİYNET" in event_type_upper: stock_mgr = self.main_app.ziynet_stock_manager
                if "GIRIS" in event_type_upper or "GİRİŞ" in event_type_upper or "ALIŞ" in event_type_upper:
                    stock_mgr.remove_stock(old_product, old_qty, old_depot)
                elif "CIKIS" in event_type_upper or "ÇIKIŞ" in event_type_upper or "SATIŞ" in event_type_upper:
                    curr_s = stock_mgr.get_stock_by_type_and_depot(old_product, old_depot)
                    p_base = curr_s.purchase_price_base if curr_s else 0.0
                    stock_mgr.add_or_update_stock(old_product, old_qty, p_base, old_depot)
            elif event.related_id:
                for mgr in list(self.main_app.cash_managers.values()) + list(self.main_app.card_managers.values()):
                    if mgr.delete_item(event.related_id):
                        break
                self.debt_manager.delete_item(event.related_id)
        self.customer_history_manager.delete_item(event.id)
        person_name = event.person_full_name
        user_date_str = new_data['date']
        try:
            trans_date_obj = datetime.strptime(user_date_str, "%d.%m.%Y")
            iso_date = trans_date_obj.isoformat()
        except ValueError:
            iso_date = datetime.now().isoformat()
        is_incoming = (event.credit_original > 0)
        new_related_id = None
        if new_data['type'] == "Cash":
            amount = new_data['amount']
            currency = new_data['currency']
            acc_type, acc_id = new_data['account_data'] if new_data['account_data'] else (None, None)
            if should_affect and acc_id:
                mgr = self.main_app.cash_managers.get(acc_id) if acc_type == 'cash' else self.main_app.card_managers.get(acc_id)
                if mgr:
                    trans_type = "Income" if is_incoming else "Expense"
                    base_amt = self.currency_manager._convert_to_base_currency(amount, currency)
                    ft = FinancialTransaction(user_date_str, trans_type, amount, currency, base_amt, f"{person_name} - {new_data['description']}")
                    mgr.add_item(ft)
                    new_related_id = ft.id
            self.customer_history_manager.add_item(CustomerHistoryEvent(
                person_full_name=person_name, event_date=iso_date,
                event_type="DÜZENLENDİ (NAKİT)",
                credit_original=amount if is_incoming else 0,
                debt_original=0 if is_incoming else amount,
                currency=currency, description=new_data['description'], related_id=new_related_id
            ))
        else:
            qty = new_data['quantity']
            product = new_data['product']
            depot = new_data['depot']
            if should_affect:
                stock_mgr = self.main_app.stock_manager
                product_mgr = self.product_manager
                if "HURDA" in event_type_upper or "SCRAP" in event_type_upper:
                    stock_mgr = self.main_app.scrap_stock_manager
                    product_mgr = self.main_app.scrap_manager
                elif "ZİYNET" in event_type_upper or "ZIYNET" in event_type_upper:
                    stock_mgr = self.main_app.ziynet_stock_manager
                    product_mgr = self.main_app.ziynet_manager
                p_def = product_mgr.find_by_attribute('product_name', product)
                price_base = 0
                if p_def:
                    price_base = self.currency_manager._convert_to_base_currency(p_def.default_purchase_price, p_def.purchase_currency)
                if is_incoming:
                    stock_mgr.add_or_update_stock(product, qty, price_base, depot)
                else:
                    stock_mgr.remove_stock(product, qty, depot)
            self.customer_history_manager.add_item(CustomerHistoryEvent(
                person_full_name=person_name, event_date=iso_date,
                event_type=f"DÜZENLENDİ ({'GİRİŞ' if is_incoming else 'ÇIKIŞ'})",
                credit_original=qty if is_incoming else 0,
                debt_original=0 if is_incoming else qty,
                currency=product,
                description=f"{depot} - {new_data['description']}", related_id=None
            ))
        self.main_app.refresh_current_account_table()
        if should_affect:
            self.main_app.refresh_cash_table()
            self.main_app.refresh_account_balances()
            self.main_app.refresh_dashboard_cards()
            self.main_app._show_status_message("İşlem yeniden düzenlendi ve stok/kasa güncellendi.")
        else:
            self.main_app._show_status_message("Sadece cari kayıt düzenlendi (Stok/Kasa değişmedi).")
    def _handle_quick_payment(self, record_type: str):
        person_name = self.main_app.quick_acc_person_combo.currentText().strip()
        if not person_name:
            QMessageBox.warning(self.main_app, self.main_app.lang["warning"], self.main_app.lang["no_selection"].format(item_type=self.main_app.lang["person"]))
            return
        amount = self.main_app.get_float_from_entry(self.main_app.quick_acc_payment_amount, self.main_app.lang["amount"], allow_zero=False)
        if amount is None: return
        currency = self.main_app.quick_acc_payment_currency.currentText()
        description = self.main_app.quick_acc_payment_desc.text().strip() or self.main_app.lang["payment_for_debt_desc"].format(person_name=person_name)
        account_data = self.main_app.quick_acc_account_combo.currentData()
        if not account_data:
            QMessageBox.warning(self.main_app, self.main_app.lang["warning"], self.main_app.lang["no_selection"].format(item_type=self.main_app.lang["source_account"]))
            return
        payment_amount_base = self.currency_manager._convert_to_base_currency(amount, currency)
        account_type, account_id = account_data
        manager = self.main_app.cash_managers.get(account_id) if account_type == 'cash' else self.main_app.card_managers.get(account_id)
        if not manager: return
        debts_to_process = [d for d in self.debt_manager.get_all() if d.person_full_name == person_name and d.record_type == record_type and d.debt_type == "Cash" and not d.is_paid]
        if not debts_to_process:
            msg_key = "no_receivable_found" if record_type == "Receivable" else "no_payable_found"
            QMessageBox.warning(self.main_app, self.main_app.lang["warning"], self.main_app.lang[msg_key])
            return
        total_debt_base = sum(d.total_amount_base - d.paid_amount_base for d in debts_to_process)
        if payment_amount_base > total_debt_base + 0.01:
            msg_key = "collection_exceeds_receivable" if record_type == "Receivable" else "payment_exceeds_payable"
            QMessageBox.warning(self.main_app, self.main_app.lang["warning"], self.main_app.lang[msg_key])
            return
        trans_type = "Income" if record_type == "Receivable" else "Expense"
        new_fin_trans = FinancialTransaction(datetime.now().strftime("%d.%m.%Y"), trans_type, amount, currency, payment_amount_base, description, person_full_name=person_name)
        manager.add_item(new_fin_trans)
        self.customer_history_manager.add_item(CustomerHistoryEvent(
            person_full_name=person_name, event_date=datetime.now().isoformat(),
            event_type=self.main_app.lang["payment_for_debt_desc"].format(person_name=""),
            credit_original=amount if trans_type == "Income" else 0,
            debt_original=0 if trans_type == "Income" else amount,
            currency=currency,
            description=f"{account_data[0]} - {self.main_app.quick_acc_account_combo.currentText()}",
            related_id=new_fin_trans.id
        ))
        remaining_payment = payment_amount_base
        updated_debts = []
        for debt in sorted(debts_to_process, key=lambda d: d.transaction_date):
            if remaining_payment <= 0: break
            debt_remaining_base = debt.total_amount_base - debt.paid_amount_base
            payment_for_this_debt = min(remaining_payment, debt_remaining_base)
            if payment_for_this_debt <= 0:
                continue
            debt.paid_amount_base += payment_for_this_debt
            if debt.paid_amount_base >= debt.total_amount_base - 0.01:
                debt.is_paid = True
                debt.payment_date = datetime.now().strftime("%d.%m.%Y")
            remaining_payment -= payment_for_this_debt
            updated_debts.append(debt)
        for debt in updated_debts:
            self.debt_manager.update_item(debt.id, {
                "paid_amount_base": debt.paid_amount_base,
                "is_paid": debt.is_paid,
                "payment_date": debt.payment_date,
            })
        self.main_app.refresh_cash_table()
        self.main_app.refresh_current_account_table()
        self.main_app.refresh_dashboard_cards()
        self.main_app.quick_acc_payment_amount.clear()
        self.main_app.quick_acc_payment_desc.clear()
        self.main_app._show_status_message(self.main_app.lang["payment_applied" if record_type == "Payable" else "collection_applied"])
    def update_accounts_tab_display(self):
        """Updates both tables and summary labels in the Accounts tab based on the selected person."""
        filter_person = self.main_app.accounts_person_combo.currentText()
        show_all = filter_person == self.main_app.lang["show_all"]
        all_receivables = self.debt_manager.get_debts_by_type("Receivable")
        all_payables = self.debt_manager.get_debts_by_type("Payable")
        receivables_to_display = [r for r in all_receivables if show_all or r.person_full_name == filter_person]
        payables_to_display = [p for p in all_payables if show_all or p.person_full_name == filter_person]
        self._populate_debt_table(self.main_app.receivables_table, receivables_to_display)
        self._populate_debt_table(self.main_app.payables_table, payables_to_display)
        self._update_account_summary_label(self.main_app.receivables_summary_label, self.main_app.lang["overall_receivable_balance"], receivables_to_display)
        self._update_account_summary_label(self.main_app.payables_summary_label, self.main_app.lang["overall_payable_balance"], payables_to_display)
    def _populate_debt_table(self, table: QTableWidget, records: List[DebtRecord]):
        """Borç/Alacak tablolarını verimli bir şekilde doldurur."""
        sorted_records = sorted(records, key=lambda d: (parse_date_from_display(d.transaction_date) or datetime.min, d.created_at), reverse=True)
        today = datetime.now().date()
        def fill_row(row, debt_record):
            if debt_record.debt_type == "Cash":
                total_amount_str = self.currency_manager.format_currency(debt_record.original_amount, debt_record.currency)
            else:
                total_amount_str = f"{format_number(debt_record.product_quantity, 3)} {debt_record.product_type}"
            if debt_record.is_paid:
                remaining_str = "0"
            elif debt_record.debt_type == "Cash":
                remaining_base = debt_record.total_amount_base - debt_record.paid_amount_base
                remaining_orig = self.currency_manager._convert_to_display_currency(remaining_base, debt_record.currency)
                remaining_str = self.currency_manager.format_currency(remaining_orig, debt_record.currency)
            else:
                remaining_qty = debt_record.product_quantity - debt_record.paid_quantity
                remaining_str = f"{format_number(remaining_qty, 3)} {debt_record.product_type}"
            person_item = QTableWidgetItem(debt_record.person_full_name)
            person_item.setData(Qt.ItemDataRole.UserRole, debt_record.id)
            items = [
                person_item,
                QTableWidgetItem(debt_record.transaction_date),
                QTableWidgetItem(debt_record.description),
                QTableWidgetItem(self.main_app.lang.get(debt_record.debt_type.lower(), debt_record.debt_type)),
                QTableWidgetItem(total_amount_str),
                QTableWidgetItem(remaining_str)
            ]
            row_color = None
            if debt_record.is_paid:
                row_color = QColor('gray')
            elif debt_record.due_date:
                try:
                    due_date = datetime.strptime(debt_record.due_date, "%d.%m.%Y").date()
                    if due_date < today:
                        row_color = QColor('red')
                except ValueError:
                    pass
            for col, item in enumerate(items):
                if row_color:
                    item.setForeground(row_color)
                table.setItem(row, col, item)
        self.main_app._populate_table_in_batches(table, sorted_records, fill_row)
    def _update_account_summary_label(self, label: QLabel, title: str, records: List[DebtRecord]):
        """Helper to calculate and format the summary for debt/credit records."""
        cash_balances = defaultdict(float)
        product_balances = defaultdict(float)
        for record in records:
            if record.is_paid: continue
            if record.debt_type == "Cash":
                remaining_base = record.total_amount_base - record.paid_amount_base
                remaining_orig = self.currency_manager._convert_to_display_currency(remaining_base, record.currency)
                cash_balances[record.currency] += remaining_orig
            else:
                remaining_qty = record.product_quantity - record.paid_quantity
                if record.product_type:
                    product_balances[record.product_type] += remaining_qty
        summary_parts = []
        if cash_balances:
            cash_str = " | ".join(f"{format_number(amt)} {curr}" for curr, amt in sorted(cash_balances.items()) if amt > 0.01)
            if cash_str:
                summary_parts.append(cash_str)
        if product_balances:
            product_str = " | ".join(f"{format_number(qty, 3)} {prod}" for prod, qty in sorted(product_balances.items()) if qty > 0.001)
            if product_str:
                summary_parts.append(product_str)
        summary_text = f"<b>{title}</b><br>"
        if summary_parts:
            summary_text += "<br>".join(summary_parts)
        else:
            summary_text += "0"
        label.setText(summary_text)
class ExchangeSelectionDialog(QDialog):
    def __init__(self, main_app, person_name, lang: dict, parent=None):
        super().__init__(parent)
        self.setWindowTitle(lang.get("exchange_transaction", "ÇEVİRİ"))
        self.setMinimumWidth(400)
        self.selected_sub_type = None
        layout = QVBoxLayout(self)
        layout.addWidget(QLabel(lang.get("exchange_type_select_prompt", "<b>Person: {person_name}</b> - Select Exchange Type:").format(person_name=person_name)))
        layout.addWidget(QLabel(""))
        btn_debt = QPushButton(lang.get("exchange_debt_btn", "1. EXCHANGE - DEBT"))
        btn_debt.setMinimumHeight(60)
        btn_debt.setStyleSheet("font-size: 14px; font-weight: bold;")
        btn_debt.setShortcut("1")
        btn_debt.setDefault(True)
        btn_debt.setAutoDefault(True)
        btn_debt.clicked.connect(lambda: self._select("ExchangePayable"))
        btn_credit = QPushButton(lang.get("exchange_credit_btn", "2. EXCHANGE - CREDIT"))
        btn_credit.setMinimumHeight(60)
        btn_credit.setStyleSheet("font-size: 14px; font-weight: bold;")
        btn_credit.setShortcut("2")
        btn_credit.clicked.connect(lambda: self._select("ExchangeReceivable"))
        layout.addWidget(btn_debt)
        layout.addWidget(btn_credit)
        layout.addStretch()
        cancel_btn = QPushButton(lang.get("cancel", "İptal"))
        cancel_btn.clicked.connect(self.reject)
        layout.addWidget(cancel_btn)
    def _select(self, mode):
        self.selected_sub_type = mode
        self.accept()
class VirmanSelectionDialog(QDialog):
    def __init__(self, main_app, person_name, lang: dict, parent=None):
        super().__init__(parent)
        self.setWindowTitle(lang.get("transfer_transaction", "VİRMAN"))
        self.setMinimumWidth(400)
        self.selected_sub_type = None
        layout = QVBoxLayout(self)
        layout.addWidget(QLabel(lang.get("virman_type_select_prompt", "<b>Person: {person_name}</b> - Select Transfer Type:").format(person_name=person_name)))
        layout.addWidget(QLabel(""))
        btn_debt = QPushButton(lang.get("virman_debt_btn", "1. TRANSFER - DEBT"))
        btn_debt.setMinimumHeight(60)
        btn_debt.setStyleSheet("font-size: 14px; font-weight: bold;")
        btn_debt.setShortcut("1")
        btn_debt.setDefault(True)
        btn_debt.setAutoDefault(True)
        btn_debt.clicked.connect(lambda: self._select("VirmanPayable"))
        btn_credit = QPushButton(lang.get("virman_credit_btn", "2. TRANSFER - CREDIT"))
        btn_credit.setMinimumHeight(60)
        btn_credit.setStyleSheet("font-size: 14px; font-weight: bold;")
        btn_credit.setShortcut("2")
        btn_credit.clicked.connect(lambda: self._select("VirmanReceivable"))
        layout.addWidget(btn_debt)
        layout.addWidget(btn_credit)
        layout.addStretch()
        cancel_btn = QPushButton(lang.get("cancel", "İptal"))
        cancel_btn.clicked.connect(self.reject)
        layout.addWidget(cancel_btn)
    def _select(self, mode):
        self.selected_sub_type = mode
        self.accept()





