from collections import defaultdict
import sys
import os
import json
import logging
from contextlib import contextmanager
from copy import deepcopy
from functools import partial
from datetime import datetime, timedelta
from typing import List, Dict, Any, Optional, Union, Callable
from pathlib import Path
from modules.gold_ops import GoldBatchDialog
from PyQt6.QtCore import QDateTime, QLocale, Qt
try:
    from reportlab.pdfgen import canvas
    from reportlab.graphics.barcode import code128
    from reportlab.lib.units import mm
    HAS_REPORTLAB = True
except ImportError:
    HAS_REPORTLAB = False
from PyQt6.QtWidgets import (
    QMainWindow, QWidget, QVBoxLayout, QTabWidget, QMessageBox,
    QInputDialog, QFileDialog, QStyleFactory, QDialog, QListWidget, QListWidgetItem,
    QColorDialog, QStatusBar, QDateEdit, QStyle, QApplication, QTableWidgetItem,
    QHeaderView, QComboBox, QLineEdit, QPushButton, QTableWidget, QLabel, QHBoxLayout, QFrame, QStackedWidget, QGridLayout, QDoubleSpinBox
)
from PyQt6.QtCore import Qt, QDate, QLocale, QTimer, QSize, QRegularExpression, pyqtSignal, QThread
from PyQt6.QtGui import QFont, QTextCharFormat, QFontDatabase, QIcon, QPixmap, QAction, QKeySequence, QTextDocument, QKeyEvent, QColor, QRegularExpressionValidator
from core.constants import StockType
from core.config import (
    get_global_setting,
    set_global_setting,
    APP_THEMES, 
    BASE_CURRENCY, 
    SUPPORTED_CURRENCIES, 
    RESOURCES_DIR, 
    PROFILES_CONFIG_FILE,  # <-- Bu eksikti, eklendi.
    LOGO_PATH,             
    get_app_data_directory,# <-- Bu da önceki adımda eklendi, burada lazım olacak.
    format_number, 
    parse_date_from_display, 
    logger,
    load_language,
    get_default_depot_name,
)
from core.db import (
    DataManager,
    SettingsManager,
    Person,
    RepairRecord,
    FinancialTransaction,
    DebtRecord,
    Sale,
    ScheduledTransaction,
    CustomerHistoryEvent,
    CashAccount,
    CardAccount,
    Depot,
    ProductDefinition,
    StockItem,
    StockMovementManager,
    SYNC_ACTION_DELETE,
    SYNC_ACTION_UPDATE,
    fetch_row_snapshot_by_id,
    queue_row_sync_event,
)
from core.sync_worker import SyncWorker
from core.auth import AdminManager
from modules.stock import ProductDefinitionManager, StockManager, DepotManager, ManualStockDialog, BulkBarcodeDialog, BarcodeCreatedDialog, BarcodeQueryDialog, BarcodeSelectionDialog, BarcodeSaleSelectionDialog
from modules.sales import SaleManager, PaymentDialog
from modules.cash import CurrencyManager, FinancialTransactionManager, CashAccountManager, CardAccountManager, ScheduledTransactionManager, FinanceManager, EditScheduledTransactionDialog
from modules.repair import RepairManager
from modules.crm import PersonManager, DebtManager, CustomerHistoryManager, CRMManager
from modules.reports import PdfReportGenerator, ExcelReportGenerator, ReportManager
from modules.utils import CalendarManager, UtilityManager
from modules.documents.document_service import DocumentService
from ui.theme import CustomThemeManager, AppThemeManager, CreateThemeDialog
from ui.dialogs import LoadingDialog
from ui.ai_response_formatter import format_ai_response_html
from modules.zakat import ZakatCalculator
from modules.calculations import hesapla, log_islem, grid_to_json, parse_decimal_input, _to_decimal as calc_to_decimal
from ui.interface_builder import InterfaceBuilder
from modules.stock import AdvancedGoldEntryDialog, ManualStockDialog # ikisini de import edin
from ui.shortcuts import ShortcutManager
from core.ai_agent import AIAgentWorker
from core.tenant_manager import TenantManager


class DashboardWorker(QThread):
    """Fetches dashboard data in a background thread to keep UI responsive."""

    data_ready = pyqtSignal(object)
    error = pyqtSignal(str)

    def __init__(self, app_instance):
        super().__init__()
        self.app = app_instance

    def run(self):
        try:
            cards = []
            cash_label = self.app.lang.get("cash", "Nakit")
            card_label = self.app.lang.get("card", "Kart")

            for acc in self.app._get_accessible_cash_accounts():
                if self.isInterruptionRequested():
                    return
                manager = self.app.cash_managers.get(acc.id)
                balances = manager.calculate_balance() if manager else {}
                cards.append(
                    {
                        "id": f"cash_{acc.id}",
                        "title": acc.name,
                        "subtitle": cash_label,
                        "balances": dict(balances or {}),
                    }
                )

            for acc in self.app._get_accessible_card_accounts():
                if self.isInterruptionRequested():
                    return
                manager = self.app.card_managers.get(acc.id)
                balances = manager.calculate_balance() if manager else {}
                cards.append(
                    {
                        "id": f"card_{acc.id}",
                        "title": acc.name,
                        "subtitle": card_label,
                        "balances": dict(balances or {}),
                    }
                )

            all_trans = []
            for manager in list(self.app.cash_managers.values()) + list(self.app.card_managers.values()):
                if self.isInterruptionRequested():
                    return
                for trans in manager.get_all():
                    all_trans.append(
                        {
                            "created_at": str(getattr(trans, "created_at", "") or ""),
                            "transaction_type": str(getattr(trans, "transaction_type", "") or ""),
                            "original_amount": float(getattr(trans, "original_amount", 0.0) or 0.0),
                            "currency": str(getattr(trans, "currency", "") or ""),
                            "description": str(getattr(trans, "description", "") or ""),
                        }
                    )

            all_trans.sort(key=lambda x: x.get("created_at", ""), reverse=True)
            rates_cache = deepcopy(getattr(self.app.currency_manager, "rates_cache", {}) or {})

            self.data_ready.emit(
                {
                    "cards": cards,
                    "recent_transactions": all_trans[:15],
                    "rates": rates_cache,
                    "display_currency": self.app.display_currency,
                }
            )
        except Exception as e:
            self.error.emit(str(e))


class TransactionWorker(QThread):
    """Runs POS transaction processing on a background thread."""

    finished_success = pyqtSignal(dict)
    error_occurred = pyqtSignal(str)

    def __init__(self, transaction_service, context):
        super().__init__()
        self.service = transaction_service
        self.context = context

    def run(self):
        try:
            result = self.service.process_pos_transaction(self.context)
            if isinstance(result, dict):
                self.finished_success.emit(result)
            else:
                self.finished_success.emit({"generated_barcodes": result or []})
        except Exception as e:
            self.error_occurred.emit(str(e))


class JewelryStoreApp(QMainWindow):
    """Main application window for the Advanced Jeweler Management System."""
    data_changed = pyqtSignal(str, object)
    transaction_complete = pyqtSignal(str, object)
    def __init__(
        self,
        profile_file_path: str,
        profile_name: str,
        user_data: dict = None,
        data_manager: Optional[DataManager] = None,
        settings_manager: Optional[SettingsManager] = None,
        tenant_manager: Optional[TenantManager] = None,
        is_boss_mode: bool = False,
    ):
        super().__init__()
        self.setStyleSheet("QMainWindow { background-color: #2d2d2d; color: white; }")
        self.profile_file_path = profile_file_path
        self.profile_name = profile_name
        self.is_boss_mode = bool(is_boss_mode)
        self.current_user = user_data if user_data else {"role": "admin", "name": "Admin", "permissions": "ALL"}
        self.tenant_manager = tenant_manager or TenantManager()
        self.data_manager = data_manager or DataManager(profile_file_path=self.profile_file_path)
        self.currency_manager = CurrencyManager()
        self.settings_manager = settings_manager or SettingsManager(data_manager=self.data_manager)
        self.document_service = DocumentService(self.data_manager, self.settings_manager, parent=self)
        self.custom_theme_manager = CustomThemeManager()
        self._load_profile_custom_themes()
        profile_lang_code = str(self.settings_manager.get_setting("language", "") or "").strip()
        if not profile_lang_code:
            profile_lang_code = str(get_global_setting("language", "TR") or "TR").strip() or "TR"
        self.current_lang_code: str = profile_lang_code
        set_global_setting("language", self.current_lang_code)
        self.lang: Dict[str, str] = load_language(self.current_lang_code)
        self.display_currency: str = self.settings_manager.get_setting("display_currency", "TRY")
        self.base_currency: str = BASE_CURRENCY
        self.all_themes = {**APP_THEMES, **self.custom_theme_manager.get_themes()}
        self._startup_phase = True
        self._initialize_managers()
        self.report_manager = ReportManager(self)
        self.crm_manager = CRMManager(self)
        self.finance_manager = FinanceManager(self)
        self.admin_manager = AdminManager(self)
        self.utility_manager = UtilityManager(self)
        self.theme_manager = AppThemeManager(self)
        self.setWindowTitle(f'{self.lang["app_title"]} - [{self.profile_name}] - {self.current_user["name"]}')
        self.setGeometry(100, 100, 1600, 950)
        font_size = 11 if sys.platform == 'darwin' else 10
        self.setFont(QFont("Segoe UI", font_size))
        self.theme_manager.apply_theme()
        self.pos_carts = {'Product_Sale': [], 'Product_Purchase': [], 'Scrap_Sale': [], 'Scrap_Purchase': [], 'Ziynet_Sale': [], 'Ziynet_Purchase': []}
        self.stock_widgets = {'Product': {}, 'Scrap': {}, 'Ziynet': {}}
        self.shortcut_manager = ShortcutManager(logger)
        self._create_ui()
        self.data_changed.connect(self._on_data_changed)
        self.transaction_complete.connect(self._on_transaction_complete)
        self._create_shortcuts()
        self._loading_dialog = None
        self._loading_depth = 0
        self._loading_owns_dialog = False
        self._dashboard_worker = None
        self._tx_worker = None
        self._dashboard_refresh_pending = False
        self._is_logging_out = False
        self._sync_worker = None
        self._sync_deferred_notice_shown = False
        self._sync_expired_notice_shown = False
        logger.info(f"Uygulama başlatıldı. Kullanıcı: {self.current_user['name']}, Profil: {self.profile_name}")
        self.datetime_timer = QTimer(self)
        self.datetime_timer.timeout.connect(self._update_dashboard_datetime)
        self.datetime_timer.start(1000)
        self.rates_timer = QTimer(self)
        self.rates_timer.timeout.connect(self.currency_manager._fetch_all_rates)
        self.rates_timer.start(1800000) # 30 dakikada bir
        self.dashboard_data_timer = QTimer(self)
        self.dashboard_data_timer.timeout.connect(self._refresh_dashboard_widgets)
        self.dashboard_data_timer.start(15000) # 15 saniyede bir
        self.conflict_check_timer = QTimer(self)
        self.conflict_check_timer.timeout.connect(self._check_conflict_logs)
        self.conflict_check_timer.start(15000)
        self._start_sync_worker()
        self._schedule_auto_backup()
        self._update_dashboard_datetime()
        # Delay remote rates fetch so main UI appears instantly first.
        QTimer.singleShot(1500, self.currency_manager._fetch_all_rates)
        QTimer.singleShot(300, self._refresh_dashboard_widgets)
    def _open_gold_batch_dialog(self, mode="Purchase"):
        """
        Altın (Product) için Hızlı Barkodlu Alış/Satış.
        DüZELTME: Depo seçimi entegre edildi.
        """
        accessible_depots = self._get_accessible_depots()
        if not accessible_depots:
            QMessageBox.warning(self, "Hata", "Erişim izniniz olan hiçbir depo bulunamadı.")
            return
        dialog = GoldBatchDialog(mode, self.stock_manager, self.product_manager, accessible_depots, self)
        if dialog.exec():
            data = dialog.get_data()
            items = data["items"]
            target_depot = data["depot"] # <-- Seçilen depo buradan geliyor
            action_tr = "Alış" if mode == "Purchase" else "Satış"
            if mode == "Purchase": # ALIŞ (GiRiŞ)
                for item in items:
                    p_def = self.product_manager.find_by_attribute("product_name", item["type"])
                    base_price = 0.0
                    if p_def:
                        base_price = self.currency_manager._convert_to_base_currency(
                            p_def.default_purchase_price, p_def.purchase_currency
                        )
                    new_stock = StockItem(
                        item_type=item["type"],
                        quantity=1.0,
                        quantity_gr=item["gram"],
                        unique_barcode=item["barcode"],
                        depot_name=target_depot, # <--- DüZELTME BURADA (Merkez yerine seçilen depo)
                        purchase_price_base=base_price
                    )
                    self.stock_manager.add_item(new_stock)
                    self.barcode_history_manager.register_buy(
                        barcode=item["barcode"],
                        p_type=item["type"],
                        gram=item["gram"],
                        karat=item.get("karat", ""),
                        source="Manuel Giriş",
                        price=base_price,
                        method="Hızlı Barkodlu Alış"
                    )
                
                # Barkod popup'ını çağır
                generated_barcodes = [item["barcode"] for item in items]
                if generated_barcodes:
                    from modules.stock import BarcodeCreatedDialog
                    dlg = BarcodeCreatedDialog(generated_barcodes, self)
                    dlg.exec()
            elif mode == "Sale": # SATIŞ (çIKIŞ)
                barcodes_to_remove = [item["barcode"] for item in items]
                self.stock_manager.remove_stock_by_barcodes(barcodes_to_remove, target_depot)
                for item in items:
                    self.barcode_history_manager.register_sell(
                        barcode=item["barcode"],
                        target="Manuel çıkış",
                        price=0, # Fiyat manuel işlemde belirsiz olabilir
                        method="Hızlı Barkodlu çıkış",
                        depot=target_depot,
                    )
            person_name = "Peşin Müşteri"
            if hasattr(self, 'acc_search_combo') and self.acc_search_combo.currentText():
                person_name = self.acc_search_combo.currentText()
            desc_lines = [f"{i['barcode']} ({i['gram']}gr)" for i in items]
            full_desc = f"Hızlı Altın {action_tr} ({data['total_qty']} Adet, {data['total_gram']:.2f}gr) - Depo: {target_depot}\nDetay: " + ", ".join(desc_lines)
            from datetime import datetime
            from core.db import CustomerHistoryEvent
            self.customer_history_manager.add_item(CustomerHistoryEvent(
                person_full_name=person_name,
                event_date=datetime.now().isoformat(),
                event_type=f"Altın {action_tr} (Barkodlu)",
                debt_original=0,
                credit_original=0,
                currency="Altın",
                description=full_desc,
                transaction_amount=data['total_qty']
            ))
            self.update_all_displays()
            QMessageBox.information(self, "Başarılı", f"{data['total_qty']} adet ürün '{target_depot}' deposuna işlendi.")

    def _open_device_pairing_dialog(self):
        """Web cihazı bağlama (QR) penceresini açar."""
        from ui.dialogs import DevicePairingDialog
        from core.tenant_manager import TenantManager
        
        tenant = TenantManager()
        if not tenant.is_web_mobile_enabled():
            QMessageBox.warning(self, "Cihaz Eslestirme", self._sync_expired_warning_text() if tenant.get_sync_status() == "expired" else "Bu lisans planinda telefon ve web erisimi kapali.")
            return

        dialog = DevicePairingDialog(tenant, self)
        if dialog.exec() == QDialog.DialogCode.Accepted:
            QMessageBox.information(self, "Başarılı", "Telefon başarıyla sisteme bağlandı!\nWeb tarayıcınızda otomatik giriş yapıldı.")
            # Cihazlar sekmesini yeniden yüklemek iyi olur (kullanıcı Ayarlar sekmesini değiştirdiğinde zaten güncellenir)
            self._refresh_device_management_view()

    def _open_thermal_printer_discovery_dialog(self):
        """Yerel agda termal yaziciyi bulup IP alanina yazar."""
        from ui.dialogs import ThermalPrinterDiscoveryDialog

        dialog = ThermalPrinterDiscoveryDialog(self)
        if dialog.exec() != QDialog.DialogCode.Accepted:
            return

        selected_ip = str(getattr(dialog, "selected_printer_ip", "") or "").strip()
        if not selected_ip:
            return

        if hasattr(self, "printer_name_entry") and self.printer_name_entry:
            self.printer_name_entry.setText(selected_ip)
            QMessageBox.information(
                self,
                "Yazici Secildi",
                f"Bulunan termal yazici IP'si alana yazildi:\n{selected_ip}",
            )

    def _device_api_context(self):
        from core.tenant_manager import TenantManager

        tenant = TenantManager()
        if tenant.is_offline_mode_enabled():
            raise RuntimeError("Offline mode aktifken web cihaz ve VPS islemleri kullanilamaz.")
        if not tenant.is_web_mobile_enabled():
            if tenant.get_sync_status() == "expired":
                raise RuntimeError(self._sync_expired_warning_text())
            raise RuntimeError("Bu lisans planinda telefon ve web erisimi kapali.")
        token = tenant.get_api_token()
        if not token:
            raise RuntimeError("API token bulunamadı. Lütfen tekrar giriş yapın.")
        base_url = "https://app.golder.biz/api/devices"
        headers = {"Authorization": f"Bearer {token}"}
        return base_url, headers

    def _fetch_linked_web_devices(self) -> list:
        import requests

        base_url, headers = self._device_api_context()
        resp = requests.get(f"{base_url}/list.php", headers=headers, timeout=10)
        resp.raise_for_status()
        data = resp.json()
        if data.get("status") != "ok":
            raise RuntimeError(str(data.get("message") or "Cihaz listesi alınamadı."))
        devices = list(data.get("devices") or [])
        return [d for d in devices if str(d.get("device_type") or "web").lower() == "web"]

    def _on_device_list_selection_changed(self, row: int):
        btn = getattr(self, "device_delete_btn", None)
        if btn is not None:
            btn.setEnabled(row >= 0)

    def _refresh_device_management_view(self):
        list_widget = getattr(self, "device_list_widget", None)
        if list_widget is None:
            return
        delete_btn = getattr(self, "device_delete_btn", None)
        list_widget.clear()
        try:
            devices = self._fetch_linked_web_devices()
            if not devices:
                list_widget.addItem("Aktif bağlı cihaz bulunamadı.")
                list_widget.setEnabled(False)
                if delete_btn is not None:
                    delete_btn.setEnabled(False)
                return

            list_widget.setEnabled(True)
            for dev in devices:
                device_id = int(dev.get("id") or 0)
                name = str(dev.get("device_name") or "Adsız Cihaz")
                device_group = str(dev.get("device_group") or dev.get("device_type") or "Web")
                last_active = str(dev.get("last_seen_at") or dev.get("last_active_at") or dev.get("last_login") or "-")
                active_sessions = int(dev.get("active_sessions") or 0)
                ip = str(dev.get("ip_address") or "-")
                if "is_active_device" in dev:
                    status = "Aktif" if int(dev.get("is_active_device") or 0) == 1 else "Pasif"
                else:
                    status = "Engelli" if int(dev.get("is_blocked") or 0) == 1 else "Aktif"
                text = f"[{device_group}] {name} | Son Görülme: {last_active} | Oturum: {active_sessions} | IP: {ip} | Durum: {status}"
                item = QListWidgetItem(text)
                item.setData(Qt.ItemDataRole.UserRole, device_id)
                list_widget.addItem(item)
            if list_widget.count() > 0:
                list_widget.setCurrentRow(0)
        except Exception as e:
            list_widget.setEnabled(False)
            if delete_btn is not None:
                delete_btn.setEnabled(False)
            QMessageBox.warning(self, "Cihaz Yönetimi", f"Cihazlar alınamadı:\n{e}")

    def _delete_selected_linked_device(self):
        import requests

        list_widget = getattr(self, "device_list_widget", None)
        if list_widget is None:
            return
        item = list_widget.currentItem()
        if item is None:
            return

        device_id = int(item.data(Qt.ItemDataRole.UserRole) or 0)
        if device_id <= 0:
            return

        confirm = QMessageBox.question(
            self,
            "Cihazı Sil",
            "Seçili bağlı telefonu silmek istiyor musunuz?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
        )
        if confirm != QMessageBox.StandardButton.Yes:
            return

        try:
            base_url, headers = self._device_api_context()
            resp = requests.post(
                f"{base_url}/remove.php",
                json={"device_id": device_id},
                headers=headers,
                timeout=10,
            )
            resp.raise_for_status()
            data = resp.json()
            if data.get("status") != "ok":
                raise RuntimeError(str(data.get("message") or "Cihaz silinemedi."))
            QMessageBox.information(self, "Başarılı", "Cihaz silindi.")
            self._refresh_device_management_view()
        except Exception as e:
            QMessageBox.warning(self, "Cihaz Yönetimi", f"Cihaz silinemedi:\n{e}")

    def _open_device_management_dialog(self):
        """Bu kullanıcıya ait web cihazlarını listeleme/silme penceresini açar."""
        from ui.dialogs import DeviceManagementDialog
        from core.tenant_manager import TenantManager

        dialog = DeviceManagementDialog(TenantManager(), self)
        dialog.exec()

    def _emit_data_changed(self, key: str, payload: Optional[Dict[str, Any]] = None) -> None:
        """Small event bus for UI refresh after DB writes."""
        try:
            self.data_changed.emit(key, payload or {})
        except Exception as e:
            logger.error(f"Failed to emit data_changed ({key}): {e}")
    def _on_data_changed(self, key: str, payload: object) -> None:
        """Routes data change events (product module only)."""
        try:
            if key == "product_definitions":
                stock_type = payload.get("stock_type") if isinstance(payload, dict) else None
                self._reload_product_definition_models(stock_type)
        except Exception as e:
            logger.error(f"data_changed handler failed ({key}): {e}", exc_info=True)
    def dispatch_transaction_complete(self, source: str = "unknown", payload: Optional[Dict[str, Any]] = None) -> None:
        """Cross-widget refresh dispatcher emitted after successful financial writes."""
        try:
            self.transaction_complete.emit(source, payload or {})
        except Exception as e:
            logger.error(f"Failed to emit transaction_complete ({source}): {e}")
    def _on_transaction_complete(self, source: str, payload: object) -> None:
        """Refreshes dashboard metrics/cards when a transaction completes."""
        try:
            if not hasattr(self, "tabs") or self.tabs.currentIndex() != 0:
                return
            QTimer.singleShot(0, self._refresh_dashboard_if_visible)
        except Exception as e:
            logger.error(f"transaction_complete handler failed ({source}): {e}", exc_info=True)
    def _safe_ui_call(self, fn, *args, **kwargs) -> None:
        try:
            fn(*args, **kwargs)
        except RuntimeError:
            return
        except Exception as e:
            logger.error(f"UI refresh call failed ({getattr(fn, '__name__', fn)}): {e}", exc_info=True)
    def _safe_repaint(self, widget) -> None:
        if not widget:
            return
        try:
            if hasattr(widget, "viewport"):
                widget.viewport().update()
            else:
                widget.update()
        except RuntimeError:
            return
        except Exception:
            return

    def _pulse_ui(self) -> None:
        """Keeps UI event loop alive during long synchronous operations."""
        try:
            QApplication.processEvents()
        except Exception:
            return

    def _find_visible_loading_dialog(self):
        try:
            for dlg in self.findChildren(LoadingDialog):
                if dlg and dlg.isVisible():
                    return dlg
        except Exception:
            return None
        return None

    def _show_loading(self, message: str = "Veriler okunuyor...") -> None:
        self._loading_depth += 1
        if self._loading_depth > 1:
            dlg = getattr(self, "_loading_dialog", None)
            if dlg and hasattr(dlg, "set_message"):
                try:
                    dlg.set_message(message)
                except Exception:
                    pass
            self._pulse_ui()
            return

        existing = self._find_visible_loading_dialog()
        if existing is not None:
            self._loading_dialog = existing
            self._loading_owns_dialog = False
            if hasattr(existing, "set_message"):
                try:
                    existing.set_message(message)
                except Exception:
                    pass
        else:
            dlg = LoadingDialog(message, self)
            self._loading_dialog = dlg
            self._loading_owns_dialog = True
            try:
                dlg.show()
                dlg.raise_()
                dlg.activateWindow()
            except Exception:
                pass
        self._pulse_ui()

    def _hide_loading(self) -> None:
        if self._loading_depth <= 0:
            self._loading_depth = 0
            return
        self._loading_depth -= 1
        if self._loading_depth > 0:
            self._pulse_ui()
            return

        dlg = getattr(self, "_loading_dialog", None)
        owns_dialog = bool(getattr(self, "_loading_owns_dialog", False))
        self._loading_dialog = None
        self._loading_owns_dialog = False

        if owns_dialog and dlg is not None:
            try:
                dlg.close()
                dlg.deleteLater()
            except Exception:
                pass
        self._pulse_ui()

    @contextmanager
    def _loading_scope(self, message: str = "Veriler okunuyor..."):
        self._show_loading(message)
        try:
            yield
        finally:
            self._hide_loading()

    def refresh_staff_table(self) -> None:
        """Refreshes staff/personnel list widgets (Settings -> Staff)."""
        self._refresh_staff_ui()
    def refresh_current_account_table(self) -> None:
        """Refreshes current-account (Cari) tables and related person combos."""
        self._refresh_crm_ui()
    def refresh_cash_table(self) -> None:
        """Refreshes finance (cash/card) tables, summaries, and account combos."""
        self._refresh_finance_ui()
    def refresh_definition_tables(self) -> None:
        """Refreshes non-product definition screens (depots, services, staff, finance accounts)."""
        self.reload_related_combos()
        self._safe_ui_call(self.update_depot_displays)
        self._refresh_services_ui(key="service_definitions", payload={})
        self._refresh_staff_ui()
    def reload_definition_tables(self) -> None:
        """Alias for refresh_definition_tables()."""
        self.refresh_definition_tables()
    def reload_related_combos(self) -> None:
        """Reloads shared non-product combos (persons, accounts, depots, transfer)."""
        if hasattr(self, "crm_manager"):
            self._safe_ui_call(self.crm_manager.update_person_comboboxes)
        if hasattr(self, "finance_manager"):
            self._safe_ui_call(self.finance_manager.update_all_account_combos)
        if hasattr(self, "_populate_transfer_tab_combos"):
            self._safe_ui_call(self._populate_transfer_tab_combos)
    def refresh_dashboard_cards(self) -> None:
        """Legacy helper: dispatches transaction_complete so subscribers can refresh."""
        self.dispatch_transaction_complete(source="refresh_dashboard_cards", payload={})
    def refresh_account_balances(self) -> None:
        """Refreshes balance summaries in finance + current accounts."""
        if hasattr(self, "finance_manager"):
            self._safe_ui_call(self.finance_manager.update_cash_transaction_display)
            self._safe_ui_call(self.finance_manager.update_card_transaction_display)
        if hasattr(self, "crm_manager") and hasattr(self, "acc_search_combo") and getattr(self, "acc_search_combo").currentText():
            if hasattr(self.crm_manager, "_update_excel_account_view"):
                self._safe_ui_call(self.crm_manager._update_excel_account_view)
    def _refresh_dashboard_if_visible(self) -> None:
        if not hasattr(self, "tabs") or self.tabs.currentIndex() != 0:
            return
        self._safe_ui_call(self.update_home_tab_display)
        self._safe_ui_call(self._update_calendar_markers)
        if hasattr(self, "_refresh_dashboard_widgets"):
            self._safe_ui_call(self._refresh_dashboard_widgets)
    def _refresh_tools_if_visible(self) -> None:
        if not hasattr(self, "tabs") or self.tabs.currentIndex() != 6:
            return
        if hasattr(self, "report_manager"):
            self._safe_ui_call(self.report_manager._filter_report)
    def _refresh_crm_ui(self) -> None:
        """CRM: persons/debts/history tables + related combos."""
        if not hasattr(self, "crm_manager"):
            return
        self._safe_ui_call(self.crm_manager.update_person_list_display)
        if hasattr(self.crm_manager, "_update_new_accounts_tab_view"):
            self._safe_ui_call(self.crm_manager._update_new_accounts_tab_view)
        if hasattr(self, "acc_search_combo") and getattr(self, "acc_search_combo").currentText():
            if hasattr(self.crm_manager, "_update_excel_account_view"):
                self._safe_ui_call(self.crm_manager._update_excel_account_view)
        self._safe_ui_call(self.crm_manager.update_person_comboboxes)
        if hasattr(self, "_populate_transfer_tab_combos"):
            self._safe_ui_call(self._populate_transfer_tab_combos)
        self._safe_repaint(getattr(self, "person_table", None))
        self._safe_repaint(getattr(self, "accounts_table", None))
    def _refresh_finance_ui(self) -> None:
        """Finance: accounts/transactions tables + related combos."""
        if not hasattr(self, "finance_manager"):
            return
        self._safe_ui_call(self.finance_manager.update_cash_transaction_display)
        self._safe_ui_call(self.finance_manager.update_card_transaction_display)
        self._safe_ui_call(self.update_scheduled_transactions_display)
        self._safe_ui_call(self.finance_manager.update_all_account_combos)
        if hasattr(self, "_populate_transfer_tab_combos"):
            self._safe_ui_call(self._populate_transfer_tab_combos)
        self._safe_repaint(getattr(self, "cash_transaction_table", None))
        self._safe_repaint(getattr(self, "card_transaction_table", None))
        self._safe_repaint(getattr(self, "scheduled_transactions_table", None))
    def _refresh_scheduled_transactions_ui(self) -> None:
        self._safe_ui_call(self.update_scheduled_transactions_display)
        self._safe_ui_call(self._update_calendar_markers)
        self._safe_repaint(getattr(self, "scheduled_transactions_table", None))
    def _refresh_repairs_ui(self) -> None:
        self._safe_ui_call(self.update_repair_list_display)
        self._safe_repaint(getattr(self, "repair_table", None))
    def _refresh_services_ui(self, key: str, payload: object) -> None:
        """Services: refresh service tabs + customer combos inside service forms."""
        tabs = getattr(self, "services_tab_widget", None)
        if not isinstance(tabs, QTabWidget):
            return
        services_widget = None
        for i in range(tabs.count()):
            w = tabs.widget(i)
            if hasattr(w, "_refresh_tabs") and hasattr(w, "tabs"):
                services_widget = w
                break
        if not services_widget:
            return
        if key == "service_definitions":
            self._safe_ui_call(services_widget._refresh_tabs)
        else:
            try:
                inner_tabs = getattr(services_widget, "tabs", None)
                if isinstance(inner_tabs, QTabWidget):
                    for j in range(inner_tabs.count()):
                        tab = inner_tabs.widget(j)
                        if hasattr(tab, "_refresh_customers"):
                            self._safe_ui_call(tab._refresh_customers)
                        if hasattr(tab, "_load_data"):
                            self._safe_ui_call(tab._load_data)
            except Exception:
                self._safe_ui_call(services_widget._refresh_tabs)
        try:
            inner_tabs = getattr(services_widget, "tabs", None)
            if isinstance(inner_tabs, QTabWidget):
                current_tab = inner_tabs.currentWidget()
                if current_tab and hasattr(current_tab, "table"):
                    self._safe_repaint(getattr(current_tab, "table", None))
        except Exception:
            pass
    def _refresh_staff_ui(self) -> None:
        if hasattr(self, "_refresh_employee_list"):
            self._safe_ui_call(self._refresh_employee_list)
        self._safe_repaint(getattr(self, "emp_list", None))
    def _refresh_stock_ops_ui(self) -> None:
        for st in ["Product", "Scrap", "Ziynet"]:
            self._safe_ui_call(self._update_generic_stock_list, st)
            if hasattr(self, "_update_stock_movement_table"):
                self._safe_ui_call(self._update_stock_movement_table, st)
        if hasattr(self, "_update_sold_barcodes_table"):
            self._safe_ui_call(self._update_sold_barcodes_table)
        self._safe_ui_call(self.update_depot_displays)
        if hasattr(self, "_populate_transfer_tab_combos"):
            self._safe_ui_call(self._populate_transfer_tab_combos)
        for st in ["Product", "Scrap", "Ziynet"]:
            try:
                widgets = self.stock_widgets.get(st, {}) if hasattr(self, "stock_widgets") else {}
                self._safe_repaint(widgets.get("stock_table"))
                self._safe_repaint(widgets.get("movement_table"))
            except Exception:
                continue
        self._safe_repaint(getattr(self, "depot_content_table", None))
    def _refresh_all_non_product_ui(self) -> None:
        self._refresh_crm_ui()
        self._refresh_finance_ui()
        self._refresh_scheduled_transactions_ui()
        self._refresh_repairs_ui()
        self._refresh_services_ui(key="services", payload={})
        self._refresh_staff_ui()
        self._refresh_stock_ops_ui()
    def _reload_product_definition_models(self, stock_type: Optional[str] = None) -> None:
        """
        reload_model â†’ refresh_combos â†’ table reset
        """
        if stock_type in ["Product", "Scrap", "Ziynet"]:
            stock_types = [stock_type]
        else:
            stock_types = ["Product", "Scrap", "Ziynet"]
        for st in stock_types:
            self._update_generic_product_definition_list(st)
            if st in self.stock_widgets and "def_form" in self.stock_widgets[st]:
                self._update_product_def_button_states(st)
        self.update_product_comboboxes()
    def update_all_displays(self, show_loading: bool = True) -> None:
        """
        Tüm UI bileşenlerini güvenli bir şekilde yeniler.
        Sinyalleri bloklayarak gereksiz tetiklemeleri önler.
        """
        logger.info("Updating all application displays (Safe Mode).")
        if show_loading:
            self._show_loading("Veriler okunuyor...")
        try:
            if hasattr(self, 'crm_manager'):
                self.crm_manager.update_person_comboboxes()
            self._pulse_ui()

            if hasattr(self, 'finance_manager'):
                self.finance_manager.update_all_account_combos()
            self._pulse_ui()

            self.update_product_comboboxes()
            self._pulse_ui()

            current_tab = self.tabs.currentIndex()
            if current_tab == 0:  # Dashboard
                self._refresh_dashboard_if_visible()
                self._pulse_ui()
            elif current_tab == 1:  # Trading
                for st in ['Product', 'Scrap', 'Ziynet']:
                    for tt in ['Sale', 'Purchase']:
                        self._update_cart_display(st, tt)
                        self._pulse_ui()
            elif current_tab == 2:  # Finance
                if hasattr(self, 'finance_manager'):
                    self.finance_manager.update_cash_transaction_display()
                    self._pulse_ui()
                    self.finance_manager.update_card_transaction_display()
                    self._pulse_ui()
                self.update_scheduled_transactions_display()
                self._pulse_ui()
            elif current_tab == 3:  # Stocks
                self._update_generic_stock_list("Product")
                self._pulse_ui()
                self._update_generic_stock_list("Scrap")
                self._pulse_ui()
                self._update_generic_stock_list("Ziynet")
                self._pulse_ui()
                if hasattr(self, "_update_sold_barcodes_table"):
                    self._update_sold_barcodes_table()
                    self._pulse_ui()
                self.update_depot_displays()
                self._pulse_ui()
            elif current_tab == 4:  # Contacts (Cari)
                if hasattr(self, 'crm_manager'):
                    self.crm_manager.update_person_list_display()
                    self._pulse_ui()
                    if hasattr(self, "acc_search_combo") and self.acc_search_combo.currentText():
                        self.crm_manager._update_excel_account_view()
                        self._pulse_ui()
                    self.crm_manager._update_new_accounts_tab_view()
                    self._pulse_ui()
            elif current_tab == 5:  # Services
                self.update_repair_list_display()
                self._pulse_ui()
                if hasattr(self, 'services_tab_widget'):
                    pass
            elif current_tab == 6:  # Tools
                if hasattr(self, 'report_manager'):
                    self.report_manager._filter_report()
                    self._pulse_ui()
        finally:
            if show_loading:
                self._hide_loading()
    def has_permission(self, perm_id: str) -> bool:
        """
        Kullanıcının belirli bir alt sekmeye yetkisi olup olmadığını kontrol eder.
        Admin her zaman True döner.
        """
        if self.current_user.get("role") == "admin":
            return True
        user_perms = self.current_user.get("permissions", [])
        if user_perms == "ALL":
            return True
        if isinstance(user_perms, list):
            return perm_id in user_perms
        return False
    def _create_ui(self) -> None:
        """
        Ana Arayüz:
        Sekme 0: Dashboard
        Sekme 1-7: Kategoriler
        GüNCELLEME: Dashboard açılışta sekmeleri gizlemiyor.
        """
        self.ui_builder = InterfaceBuilder(self)
        main_widget = QWidget()
        main_layout = QHBoxLayout(main_widget)
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(0)
        self.main_layout = main_layout
        self.setCentralWidget(main_widget)
        self.sidebar_container = QWidget()
        self.sidebar_layout = QVBoxLayout(self.sidebar_container)
        self.sidebar_layout.setContentsMargins(0, 0, 0, 0)
        self.sidebar_container.hide()
        self.sidebar_container.setFixedWidth(260)
        main_layout.addWidget(self.sidebar_container)
        self.tabs = QTabWidget()
        self.tabs.setIconSize(QSize(24, 24))
        self.tabs.currentChanged.connect(self._on_tab_changed)
        self.dashboard_tab_widget = self.ui_builder.create_dashboard_tab()
        self.tabs.addTab(self.dashboard_tab_widget, self.lang.get("dashboard_tab", "Dashboard"))
        self.tabs.addTab(self.ui_builder.build_shopping_category(), self.lang.get("cat_shopping", "Alışveriş"))
        self.tabs.addTab(self.ui_builder.build_finance_category(), self.lang.get("cat_money", "Para işlemleri"))
        self.tabs.addTab(self.ui_builder.build_stock_category(), self.lang.get("cat_stocks", "Stok işlemleri"))
        self.tabs.addTab(self.ui_builder.build_customer_category(), self.lang.get("cat_customers", "Müşteri işlemleri"))
        self.tabs.addTab(self.ui_builder.build_services_category(), self.lang.get("cat_services", "Hizmetler"))
        self.tabs.addTab(self.ui_builder.build_tools_category(), self.lang.get("cat_tools", "Araçlar"))
        license_data = self.tenant_manager.load_license()
        if int(license_data.get("is_ai_enabled", 0)) == 1 and self.tenant_manager.get_api_token():
            self.tabs.addTab(self.ui_builder.create_ai_tab(), "AI Asistan 🤖")
        if self.current_user.get("role") == "admin":
             self.tabs.addTab(self.ui_builder.build_settings_category(), self.lang.get("settings_tab", "Ayarlar"))
        main_layout.addWidget(self.tabs)
        self.setStatusBar(QStatusBar())
        self._sync_status_label = QLabel("")
        self._sync_status_label.setStyleSheet("color: #B71C1C; font-weight: bold; padding-right: 8px;")
        self._sync_status_label.setVisible(False)
        self.statusBar().addPermanentWidget(self._sync_status_label)
        self.tabs.setCurrentIndex(0) # Dashboard
        self._apply_tab_position()
        self._refresh_sync_status_ui(show_popup=False)
        if hasattr(self, "transfer_target_profile_combo"):
            self._populate_transfer_tab_combos()
        QTimer.singleShot(50, self._load_initial_data)
    def _normalized_tab_position(self) -> str:
        raw_value = str(self.settings_manager.get_setting("tab_position", "Üst") or "").strip()
        normalized = {
            "ust": "Üst",
            "üst": "Üst",
            "top": "Üst",
            "north": "Üst",
            "alt": "Alt",
            "bottom": "Alt",
            "south": "Alt",
            "sol": "Sol",
            "left": "Sol",
            "sag": "Sağ",
            "sağ": "Sağ",
            "right": "Sağ",
        }.get(raw_value.lower())
        return normalized or (raw_value if raw_value in {"Üst", "Alt", "Sol", "Sağ"} else "Üst")
    def _apply_tab_position(self):
        """
        Ayarlardaki sekme konumuna göre arayüzü günceller.
        GüNCELLEME: Dashboard'da gizleme mantığı kaldırıldı.
        """
        pos_str = self._normalized_tab_position()
        main_layout = getattr(self, "main_layout", None)
        if isinstance(main_layout, QHBoxLayout):
            main_layout.removeWidget(self.sidebar_container)
            main_layout.removeWidget(self.tabs)
            if pos_str == "Sağ":
                main_layout.addWidget(self.tabs)
                main_layout.addWidget(self.sidebar_container)
            else:
                main_layout.addWidget(self.sidebar_container)
                main_layout.addWidget(self.tabs)
        while self.sidebar_layout.count():
            child = self.sidebar_layout.takeAt(0)
            if child.widget():
                child.widget().deleteLater()
        buttons_config = self.ui_builder.get_main_tabs_config()
        sidebar = self.ui_builder.create_sidebar_widget(buttons_config)
        content_widget = sidebar.widget()
        if content_widget:
            for child in content_widget.findChildren(QPushButton):
                idx = child.property("tab_index")
                if idx is not None:
                    child.clicked.connect(lambda checked, i=idx: self.switch_to_tab(i))
        self.sidebar_layout.addWidget(sidebar)
        dashboard_widget = getattr(self, 'dashboard_tab_widget', None)
        dashboard_buttons_panel = getattr(dashboard_widget, 'left_panel', None) if dashboard_widget else None
        if pos_str in ["Sol", "Sağ"]:
            self.sidebar_container.show()
            self.tabs.tabBar().hide()
            self.tabs.setTabPosition(QTabWidget.TabPosition.North)
            self._update_sidebar_buttons()
            if dashboard_buttons_panel:
                dashboard_buttons_panel.hide()
        else:
            self.sidebar_container.hide()
            self.tabs.tabBar().show() # HER ZAMAN GöSTER
            if pos_str == "Alt":
                self.tabs.setTabPosition(QTabWidget.TabPosition.South)
            else:
                self.tabs.setTabPosition(QTabWidget.TabPosition.North)
            if dashboard_buttons_panel:
                dashboard_buttons_panel.show()
    def _update_sidebar_buttons(self):
        """Sidebar butonlarının aktif durumunu günceller."""
        if self.sidebar_container.isHidden(): return
        current_idx = self.tabs.currentIndex()
        if self.sidebar_layout.count() > 0:
            scroll_area = self.sidebar_layout.itemAt(0).widget()
            if scroll_area and scroll_area.widget():
                for btn in scroll_area.widget().findChildren(QPushButton):
                    idx = btn.property("tab_index")
                    if idx == current_idx:
                        btn.setChecked(True)
                    else:
                        btn.setChecked(False)
    def _on_tab_changed(self, index):
        """
        Sekme değişince çalışır.
        GüNCELLEME: Dashboard'a geçilince tab bar GiZLENMEYECEK.
        """
        if getattr(self, "_startup_phase", False):
            return
        pos_str = self._normalized_tab_position()
        if pos_str in ["Sol", "Sağ"]:
            self.tabs.tabBar().hide() # Tab bar her zaman gizli
            self._update_sidebar_buttons()
        else:
            self.sidebar_container.hide() # Sidebar'ın gizli olduğundan emin ol
            self.tabs.tabBar().show()     # Her zaman göster (Dashboard dahil)
        QTimer.singleShot(50, lambda: self.update_all_displays())

    def _ask_ai_question(self):
        question = self.ai_question_input.text().strip()
        if not question:
            return

        self.ai_question_input.clear()
        self.ai_send_btn.setEnabled(False)
        self.ai_question_input.setEnabled(False)

        self.ai_chat_display.append(f"<br><b>👤 Siz:</b> {question}")
        self.ai_history_list.addItem(question)

        # Worker'ı başlat. profile_id gönderiyoruz ki AI sadece bu dükkanı görsün
        self._ai_worker = AIAgentWorker(self.tenant_manager, self.data_manager.db, self.data_manager.profile_id, question)
        self._ai_worker.status_update.connect(self._on_ai_status)
        self._ai_worker.response_ready.connect(self._on_ai_response)
        self._ai_worker.error_occurred.connect(self._on_ai_error)
        self._ai_worker.finished.connect(self._on_ai_finished)
        self._ai_worker.start()

    def _on_ai_status(self, status_text):
        self.ai_chat_display.append(f"<i style='color:gray; font-size:11px;'>⚙️ {status_text}</i>")

    def _on_ai_response(self, text, quota):
        rendered_html = format_ai_response_html(text)
        self.ai_chat_display.append(f"<br><b style='color:#D4AF37;'>🤖 Golder AI:</b><br>{rendered_html}<br><hr>")
        self.ai_quota_label.setText(f"Kalan Soru Hakkı: {quota}/100")

    def _on_ai_error(self, err_text):
        self.ai_chat_display.append(f"<br><b style='color:#D32F2F;'>❌ Hata:</b> {err_text}<br><hr>")

    def _on_ai_finished(self):
        self.ai_send_btn.setEnabled(True)
        self.ai_question_input.setEnabled(True)
        self.ai_question_input.setFocus()
    def go_home(self):
        """Ana Sayfaya Dön"""
        self.tabs.setCurrentIndex(0)
    def switch_to_tab(self, index):
        """Dashboard butonları buna bağlanacak"""
        self.tabs.setCurrentIndex(index)
    def _open_profile_security_settings(self) -> None:
        """Jump to Settings -> Security & Profile."""
        settings_widget = getattr(self, "settings_category_widget", None)
        if settings_widget and hasattr(self, "tabs"):
            idx = self.tabs.indexOf(settings_widget)
            if idx != -1:
                self.tabs.setCurrentIndex(idx)
        settings_tabs = getattr(self, "settings_tabs_widget", None)
        security_tab = getattr(self, "security_settings_tab", None)
        if settings_tabs and security_tab:
            settings_tabs.setCurrentWidget(security_tab)
        if hasattr(self, "password_edit"):
            self.password_edit.setFocus()
    def _apply_staff_creation_guard(self, show_dialog: bool = False) -> bool:
        """
        Enforce: profile password must be set before staff creation.
        Returns True when allowed.
        """
        if self.current_user.get("role") != "admin":
            return True
        add_btn = getattr(self, "add_staff_btn", None)
        staff_list = getattr(self, "staff_list", None)
        edit_btn = getattr(self, "edit_staff_btn", None)
        del_btn = getattr(self, "del_staff_btn", None)
        if not add_btn:
            return True
        profile_id = getattr(self.settings_manager, "profile_id", None) or getattr(self.data_manager, "profile_id", None)
        profile_has_password = self.admin_manager.has_admin_password(profile_id)
        tooltip = self.lang.get(
            "staff_creation_blocked_no_admin_password",
            "This profile has no admin password set.\nYou must set a profile password before adding staff.",
        )
        if not profile_has_password:
            for w in (add_btn, staff_list, edit_btn, del_btn):
                if not w:
                    continue
                w.setEnabled(False)
                w.setToolTip(tooltip)
                try:
                    w.setAttribute(Qt.WidgetAttribute.WA_AlwaysShowToolTips, True)
                except Exception:
                    pass
            if show_dialog:
                self.admin_manager._show_staff_creation_blocked_dialog()
            return False
        for w in (add_btn, staff_list, edit_btn, del_btn):
            if not w:
                continue
            w.setEnabled(True)
            w.setToolTip("")
        return True
    def _on_settings_tabs_changed(self, index: int) -> None:
        settings_tabs = getattr(self, "settings_tabs_widget", None)
        staff_tab = getattr(self, "staff_settings_tab", None)
        if not settings_tabs or not staff_tab:
            return
        try:
            if settings_tabs.widget(index) is staff_tab:
                self._apply_staff_creation_guard(show_dialog=True)
        except Exception:
            return
    def resizeEvent(self, event):
        super().resizeEvent(event)
    def _get_default_tabs_config(self) -> List[Dict[str, Any]]:
        """Varsayılan sekme yapılandırmasını döndürür. Ajanda burada 'enabled: True' olmalı."""
        return [
            {"id": "dashboard", "icon": "dashboard", "lang_key": "dashboard_tab", "enabled": True},
            {"id": "shopping", "icon": "transaction", "lang_key": "cat_shopping", "enabled": True},
            {"id": "finance", "icon": "cash", "lang_key": "cat_money", "enabled": True},
            {"id": "stocks", "icon": "stock", "lang_key": "cat_stocks", "enabled": True},
            {"id": "customers", "icon": "contacts", "lang_key": "cat_customers", "enabled": True},
            {"id": "services", "icon": "repair", "lang_key": "cat_services", "enabled": True},
            {"id": "tools", "icon": "calculator", "lang_key": "cat_tools", "enabled": True},
            {"id": "settings", "icon": "settings", "lang_key": "settings_tab", "enabled": True}
        ]
    def _save_calendar_note(self):
        if not hasattr(self, 'calendar') or not hasattr(self, 'notes_text'): return
        selected_date_str = self.calendar.selectedDate().toString("dd.MM.yyyy")
        html_content = self.notes_text.toHtml()
        is_reminder = self.agenda_reminder_checkbox.isChecked()
        self.calendar_manager.save_note(selected_date_str, html_content, is_reminder)
        self._update_calendar_markers()
        self._show_status_message(self.lang["note_saved"])
    def _delete_calendar_note(self):
        if self.current_user["role"] != "admin" and "delete_all" not in self.current_user.get("permissions", []):
            QMessageBox.critical(self, "Yetki Hatası", "Bu kaydı silmeye yetkiniz yok.\n(Yönetici onayı gerekir)")
            return
        if not hasattr(self, 'calendar'): return
        selected_date_str = self.calendar.selectedDate().toString("dd.MM.yyyy")
        self.calendar_manager.delete_note(selected_date_str)
        self.notes_text.clear()
        self.agenda_reminder_checkbox.setChecked(False)
        self._update_calendar_markers()
        self._show_status_message(self.lang["note_deleted"])
    def _apply_font_family(self):
        if hasattr(self, 'font_combo') and hasattr(self, 'notes_text'):
            font = self.font_combo.currentFont()
            self.notes_text.setCurrentFont(font)
    def _apply_font_size(self):
        if hasattr(self, 'font_size_combo') and hasattr(self, 'notes_text'):
            try:
                size = int(self.font_size_combo.currentText())
                self.notes_text.setFontPointSize(size)
            except: pass
    def _apply_font_color(self):
        if hasattr(self, 'notes_text'):
            color = QColorDialog.getColor(self.notes_text.textColor(), self)
            if color.isValid():
                self.notes_text.setTextColor(color)
    def _apply_bold_style(self):
        if hasattr(self, 'bold_btn') and hasattr(self, 'notes_text'):
            if self.bold_btn.isChecked():
                self.notes_text.setFontWeight(QFont.Weight.Bold)
            else:
                self.notes_text.setFontWeight(QFont.Weight.Normal)
    def _safe_navigate_to_tab(self, widget_attr_name: str):
        """
        istenilen sekmeye güvenli geçiş yapar.
        FAZ 5: Hata durumunda (AttributeError) uygulamanın çökmemesi için try-except eklendi.
        """
        try:
            if hasattr(self, widget_attr_name):
                target_widget = getattr(self, widget_attr_name)
                if target_widget:
                    index = self.tabs.indexOf(target_widget)
                    if index != -1:
                        self.tabs.setCurrentIndex(index)
                        return
                    else:
                        logger.warning(f"Tab found but index is -1 for {widget_attr_name}")
                else:
                    logger.warning(f"Widget attribute {widget_attr_name} is None")
            else:
                logger.warning(f"Widget attribute {widget_attr_name} does not exist")
            QMessageBox.warning(self, self.lang["warning"], "Bu sekmeye erişiminiz yoktur veya modül yüklenemedi.")
        except Exception as e:
            logger.error(f"Tab navigation error: {e}")
            QMessageBox.critical(self, "Hata", f"Sekme geçişinde hata oluştu: {e}")

    def _open_stocks_subtab_by_object_name(self, object_name: str) -> None:
        if not object_name:
            return
        self._safe_navigate_to_tab("stocks_tab_widget")
        tabs = getattr(self, "stocks_tab_widget", None)
        if not isinstance(tabs, QTabWidget):
            return
        for i in range(tabs.count()):
            w = tabs.widget(i)
            if w is not None and w.objectName() == object_name:
                tabs.setCurrentIndex(i)
                return

    def _open_stock_definitions(self, stock_type: str) -> None:
        st = getattr(stock_type, "value", stock_type)
        self._open_stocks_subtab_by_object_name(f"product_definitions_{st}")

    def _open_depots_tab(self) -> None:
        self._open_stocks_subtab_by_object_name("depots_tab")

    def _get_other_profile_names(self) -> List[str]:
        """Aktif profilleri DB'den çeker ve mevcut profili hariç tutar."""
        current = (self.profile_name or "").strip()
        try:
            rows = self.settings_manager.db.fetch_all(
                """
                SELECT name
                FROM profiles
                WHERE is_active = 1 AND COALESCE(is_deleted, 0) = 0
                ORDER BY name ASC
                """
            )
        except Exception as e:
            logger.error(f"Profil listesi DB'den alınamadı: {e}")
            return []

        names: List[str] = []
        for r in rows or []:
            if not isinstance(r, dict):
                continue
            name = (r.get("name") or "").strip()
            if not name or name == current:
                continue
            names.append(name)
        return names

    def _show_empty_combo_prompt(
        self, page_label: str, open_callback: Optional[Callable[[], None]] = None
    ) -> None:
        template = self.lang.get(
            "empty_combo_prompt",
            "This list is empty.\nIf you want to predefine items, please open \"{page}\".",
        )
        try:
            msg = template.format(page=page_label)
        except Exception:
            msg = str(template)

        msg_box = QMessageBox(self)
        msg_box.setIcon(QMessageBox.Icon.Warning)
        msg_box.setWindowTitle(self.lang.get("info", "Info"))
        msg_box.setText(msg)
        open_btn = None
        if callable(open_callback):
            open_btn = msg_box.addButton(
                self.lang.get("open_page", "Open Page"), QMessageBox.ButtonRole.ActionRole
            )
        msg_box.addButton(self.lang.get("ok", "OK"), QMessageBox.ButtonRole.RejectRole)
        msg_box.exec()
        if open_btn and msg_box.clickedButton() == open_btn and callable(open_callback):
            try:
                open_callback()
            except Exception as e:
                logger.error(f"Empty combo navigation failed: {e}")
    def _load_tabs_config(self) -> List[Dict[str, Any]]:
        """DB içinden sekme yapılandırmasını yükler veya oluşturur."""
        tabs_config = self.data_manager.load_data("tabs_config", default_value=None)
        if not tabs_config:
            default_config = self._get_default_tabs_config()
            try:
                self.data_manager.save_data("tabs_config", default_config)
            except Exception as e:
                logger.error(f"Failed to create default tabs config: {e}")
            return default_config
        return tabs_config
    def _navigate_to_tab_by_name(self, widget_attr_name: str):
        """
        Dashboard butonuna tıklandığında, ilgili widget'ın varlığını kontrol eder
        ve güvenli bir şekilde o sekmeye geçiş yapar.
        Kartlar butonu için özel işlem: Kasalar sekmesini açıp içindeki 2. alt sekmeyi (Kartlar) aktif eder.
        """
        if widget_attr_name == "cards_tab_widget":
            finance_widget = getattr(self, "finance_tab_widget", None)
            if finance_widget:
                index = self.tabs.indexOf(finance_widget)
                if index != -1:
                    self.tabs.setCurrentIndex(index)
                    if isinstance(finance_widget, QTabWidget):
                        finance_widget.setCurrentIndex(5)
                    return
            QMessageBox.warning(self, self.lang["warning"], "Para işlemleri sekmesi bulunamadı.")
            return
        target_widget = getattr(self, widget_attr_name, None)
        if target_widget:
            index = self.tabs.indexOf(target_widget)
            if index != -1:
                self.tabs.setCurrentIndex(index)
            else:
                QMessageBox.warning(self, self.lang["warning"], "Bu modül şu anda yüklenemedi veya devre dışı.")
        else:
            QMessageBox.warning(self, self.lang["warning"], "Bu modül aktif değil (Yetkiniz yok veya kapalı).")
    
    def _normalize_stock_type(self, stock_type: Union[str, StockType]) -> str:
        """StockType enum veya string değerini normalize eder."""
        try:
            return str(stock_type.value)
        except Exception:
            return str(stock_type)
    def _get_stock_manager(self, stock_type: Union[str, StockType]) -> StockManager:
        """
        Verilen stock_type için doğru StockManager'ı döndürür.
        (Yanlış manager seçimi; karışık stok listesi, hatalı transfer/rapor gibi buglara yol açıyordu.)
        """
        st = self._normalize_stock_type(stock_type)
        if st in (StockType.PRODUCT.value, "Altın"):
            return self.stock_manager
        if st == StockType.ZIYNET.value:
            return self.ziynet_stock_manager
        return self.scrap_stock_manager
    def _get_definition_manager(self, stock_type: Union[str, StockType]) -> ProductDefinitionManager:
        """Verilen stock_type için doğru ProductDefinitionManager'ı döndürür."""
        st = self._normalize_stock_type(stock_type)
        if st in (StockType.PRODUCT.value, "Altın"):
            return self.product_manager
        if st == StockType.ZIYNET.value:
            return self.ziynet_manager
        return self.scrap_manager
    def _generate_stylesheet_from_theme(self) -> str:
        return self.theme_manager.generate_stylesheet_from_theme()
    def apply_theme(self) -> None:
        """Applies the selected UI theme."""
        theme_name = self.settings_manager.get_setting("theme_name", "Green Harmony")
        app = QApplication.instance()
        QApplication.setStyle(QStyleFactory.create("Fusion"))
        if theme_name in self.all_themes:
            self.theme_manager.apply_theme()
        elif theme_name in QStyleFactory.keys():
            app.setStyleSheet("")
            QApplication.setStyle(QStyleFactory.create(theme_name))
        else:
            app.setStyleSheet("")
        if hasattr(self, 'dashboard_tab_widget') and self.dashboard_tab_widget:
            self.ui_builder.refresh_dashboard_theme(self.dashboard_tab_widget)
    def _decode_custom_themes_setting(self) -> Dict[str, Dict[str, str]]:
        raw_value = self.settings_manager.get_setting("custom_themes_json", "")
        parsed: Any = {}
        if isinstance(raw_value, dict):
            parsed = raw_value
        elif isinstance(raw_value, str):
            text = raw_value.strip()
            if text:
                try:
                    parsed = json.loads(text)
                except Exception:
                    parsed = {}
        if not isinstance(parsed, dict):
            return {}
        cleaned: Dict[str, Dict[str, str]] = {}
        for name, theme_data in parsed.items():
            if not isinstance(name, str) or not isinstance(theme_data, dict):
                continue
            key = name.strip()
            if not key:
                continue
            cleaned[key] = {
                str(k): str(v)
                for k, v in theme_data.items()
                if isinstance(k, str) and isinstance(v, str)
            }
        return cleaned
    def _save_custom_themes_setting(self) -> None:
        try:
            payload = dict(self.custom_theme_manager.get_themes())
            self.settings_manager.set_setting("custom_themes_json", payload)
        except Exception as e:
            logger.error(f"Custom theme setting save failed: {e}", exc_info=True)
    def _load_profile_custom_themes(self) -> None:
        has_profile_value = "custom_themes_json" in self.settings_manager.settings
        profile_themes = self._decode_custom_themes_setting()
        if has_profile_value:
            self.custom_theme_manager.custom_themes = dict(profile_themes)
            try:
                self.custom_theme_manager._save_all_themes()
            except Exception:
                pass
            return
        if self.custom_theme_manager.get_themes():
            self._save_custom_themes_setting()
    def _reload_theme_state_from_settings(self) -> None:
        try:
            self.settings_manager.settings = self.settings_manager._load_settings()
        except Exception as e:
            logger.error(f"Theme settings reload failed: {e}", exc_info=True)
            return
        profile_themes = self._decode_custom_themes_setting()
        self.custom_theme_manager.custom_themes = dict(profile_themes)
        try:
            self.custom_theme_manager._save_all_themes()
        except Exception:
            pass
        self.all_themes = {**APP_THEMES, **self.custom_theme_manager.get_themes()}
        combo = getattr(self, "theme_combo", None)
        if combo is not None:
            was_blocked = combo.blockSignals(True)
            try:
                self._populate_theme_combo()
            finally:
                combo.blockSignals(was_blocked)
        self.apply_theme()
    def _on_sync_events_applied(self, events: object) -> None:
        try:
            if not isinstance(events, list):
                return

            theme_changed = False
            tables_affected = set()

            for event in events:
                if not isinstance(event, dict):
                    continue

                table_name = str(event.get("table_name") or "")
                tables_affected.add(table_name)

                # Tema kontrolü (Eski kodun muhafazası)
                if table_name == "app_settings":
                    payload = event.get("payload_json")
                    if isinstance(payload, dict):
                        rows = []
                        if isinstance(payload.get("after"), dict):
                            rows.append(payload.get("after"))
                        if isinstance(payload.get("before"), dict):
                            rows.append(payload.get("before"))
                        for row in rows:
                            key = str(row.get("setting_key") or "")
                            if key in {"theme_name", "custom_themes_json"}:
                                theme_changed = True
                                break

            # 1. Tema Değiştiyse
            if theme_changed:
                self._reload_theme_state_from_settings()
                self._show_status_message("Tema ayarları senkronize edildi.")

            # 2. Akıllı UI Güncellemeleri (Etkilenen tablolara göre ekranı tazele)
            
            # Eğer Müşteriler veya Cari işlemler etkilendiyse (WEB'den Tahsilat / Satış Gelirse)
            if any(t in tables_affected for t in ["persons", "debt_records", "customer_history_events"]):
                self._safe_ui_call(self._refresh_crm_ui)
                # GÜNCEL: Web'den cari girildiğinde açık olan sekmeyi zorla yenile
                if hasattr(self, "crm_manager"):
                    self._safe_ui_call(self.crm_manager._update_excel_account_view)
                    self._safe_ui_call(self.crm_manager._update_new_accounts_tab_view)

            # Eğer Stok, Tanımlar veya Depolar etkilendiyse
            if any(t in tables_affected for t in ["stock_items", "product_definitions", "categories", "depots", "barcode_history", "stock_movements"]):
                self._safe_ui_call(self._refresh_stock_ops_ui)
                self._safe_ui_call(lambda: self.update_product_comboboxes(force=True))
                if hasattr(self, "_refresh_category_list"):
                    self._safe_ui_call(self._refresh_category_list)
                self._safe_ui_call(self.reload_related_combos)

            # Eğer Finansal Kasa/Kart işlemleri etkilendiyse
            if any(t in tables_affected for t in ["financial_transactions", "financial_accounts"]):
                self._safe_ui_call(self._refresh_finance_ui)
                self._safe_ui_call(self.reload_related_combos)
                # Kasa bakiyelerini panellerde güncelle
                if hasattr(self, "finance_manager"):
                    self._safe_ui_call(self.finance_manager.update_cash_transaction_display)
                    self._safe_ui_call(self.finance_manager.update_card_transaction_display)

            # Eğer Tamir veya Hizmet işlemleri etkilendiyse
            if any(t in tables_affected for t in ["repair_records", "service_jobs", "service_definitions"]):
                self._safe_ui_call(self._refresh_repairs_ui)
                self._safe_ui_call(self._refresh_services_ui, "services", {})

            # HER HALÜKARDA DASHBOARD VE GENEL EKRANLARI GÜNCELLE
            self._safe_ui_call(self.update_home_tab_display)
            self._safe_ui_call(self._refresh_dashboard_if_visible)
        except Exception as e:
            logger.error(f"Sync event UI handler failed: {e}", exc_info=True)

    def _on_profile_deleted(self, payload: object) -> None:
        try:
            if not isinstance(payload, dict):
                return
            profile_id = str(payload.get("profile_id") or "").strip()
            profile_name = str(payload.get("profile_name") or self.profile_name or "").strip()
            if not profile_id:
                return
            if profile_id != str(self.data_manager.profile_id or "").strip():
                self._show_status_message(f"Profil senkronizasyondan silindi: {profile_name or profile_id}")
                return
            QMessageBox.warning(
                self,
                "Profil Silindi",
                f"'{profile_name or profile_id}' profili uzak sistemden silindi. Profil seçim ekranına dönülecek.",
            )
            self._shutdown_runtime_services()
            try:
                self.hide()
            except Exception:
                pass
            QApplication.instance().exit(2)
        except Exception as e:
            logger.error(f"Profile delete UI handler failed: {e}", exc_info=True)
    def _get_icon(self, icon_name: str) -> QIcon:
        """
        ikonları önce merkezi 'Belgeler' klasöründen, sonra programın yanından arar.
        Resources klasöründe bulunamazsa standart Qt simgelerini fallback olarak kullanır.
        """
        icon_candidates = [
            RESOURCES_DIR / f"{icon_name}.png",
            Path("Resources") / f"{icon_name}.png"
        ]
        for path in icon_candidates:
            if path.exists():
                return QIcon(str(path))
        icon_mapping = {
            "dashboard": "SP_TitleBarMenuButton",
            "home": "SP_ComputerIcon",
            "transaction": "SP_FileDialogDetailedView",
            "stock": "SP_FileDialogListView",
            "repair": "SP_ToolBarHorizontalExtensionButton",
            "transactions": "SP_FileLinkIcon",
            "cash": "SP_DriveHDIcon",
            "card": "SP_DriveNetIcon",
            "depot": "SP_DirHomeIcon",
            "contacts": "SP_DesktopIcon",
            "accounts": "SP_DirLinkIcon",
            "exchange": "SP_BrowserReload",
            "report": "SP_DialogApplyButton",
            "agenda": "SP_FileDialogDetailedView",
            "search": "SP_FileDialogStart",
            "calculator": "SP_CommandLink",
            "settings": "SP_DirOpenIcon",
        }
        if icon_name in icon_mapping:
            try:
                return self.style().standardIcon(getattr(QStyle.StandardPixmap, icon_mapping[icon_name]))
            except AttributeError:
                pass
        if icon_name.startswith("SP_"):
            try:
                return self.style().standardIcon(getattr(QStyle.StandardPixmap, icon_name))
            except AttributeError:
                pass
        return QIcon()
    def _create_shortcuts(self) -> None:
            """Sets up keyboard shortcuts for quick actions."""
            shortcuts = {
                Qt.Key.Key_F1: 0,
                Qt.Key.Key_F2: 1,
                Qt.Key.Key_F3: 2,
                Qt.Key.Key_F4: 3,
                Qt.Key.Key_F5: 4,
                Qt.Key.Key_F6: 5,
                Qt.Key.Key_F7: 6,
                Qt.Key.Key_F8: 7,
                Qt.Key.Key_F9: 8,
                Qt.Key.Key_F10: 9,
                Qt.Key.Key_F11: 10,
                Qt.Key.Key_F12: 11,
            }
            for key, index in shortcuts.items():
                action = QAction(f"Go to Tab {index + 1}", self)
                action.setShortcut(QKeySequence(key))
                action.triggered.connect(lambda _, i=index: self.tabs.setCurrentIndex(i))
                self.addAction(action)
            for i in range(9):
                action = QAction(f"Go to Sub-Tab {i + 1}", self)
                action.setShortcut(QKeySequence(f"Ctrl+{i + 1}"))
                action.triggered.connect(lambda checked=False, index=i: self._switch_sub_tab(index))
                self.addAction(action)
    def _switch_sub_tab(self, index: int):
            """Switches the current sub-tab to the given index."""
            current_main_tab = self.tabs.currentWidget()
            if isinstance(current_main_tab, QTabWidget):
                if index < current_main_tab.count():
                    current_main_tab.setCurrentIndex(index)
    def _load_initial_data(self):
        """ilk veri yüklemelerini ve OTO ONARIMLARI yapar."""
        self._show_loading("Başlangıç verileri hazırlanıyor...")
        def safe_ui_call(fn, label: str):
            try:
                fn()
            except (AttributeError, RuntimeError) as e:
                logger.error(f"Post-login UI call skipped ({label}): {e}", exc_info=True)
        try:
            needs_history_fix = str(
                self.settings_manager.get_setting("startup_history_fix_done_v1", "0")
            ) != "1"
            if needs_history_fix:
                history_changed = False
                for idx, event in enumerate(self.customer_history_manager.get_all()):
                    if event.event_type == "NAKiT iŞLEMi":
                        new_type = None
                        if event.credit_original > 0:
                            new_type = "NAKiT GiRiŞi"
                        elif event.debt_original > 0:
                            new_type = "NAKiT çIKIŞI"
                        if new_type:
                            try:
                                self.customer_history_manager.update_item(event.id, {"event_type": new_type})
                                history_changed = True
                            except Exception as e:
                                logger.error(f"Customer history oto-onarım başarısız (id={event.id}): {e}")
                    if idx % 25 == 0:
                        self._pulse_ui()
                self.settings_manager.set_setting("startup_history_fix_done_v1", "1")
                if history_changed:
                    logger.info("Eski 'NAKiT iŞLEMi' kayıtları otomatik olarak düzeltildi.")

            if hasattr(self, "crm_manager"):
                safe_ui_call(self.crm_manager.update_person_comboboxes, "crm_manager.update_person_comboboxes")
                self._pulse_ui()
            if hasattr(self, "finance_manager"):
                safe_ui_call(self.finance_manager.update_all_account_combos, "finance_manager.update_all_account_combos")
                self._pulse_ui()
            safe_ui_call(self.update_product_comboboxes, "update_product_comboboxes")
            self._pulse_ui()

            # Startup on cloud can be latency-heavy; perform lighter refresh first.
            QTimer.singleShot(100, lambda: safe_ui_call(self.update_all_displays, "update_all_displays"))
            QTimer.singleShot(500, lambda: safe_ui_call(self._run_startup_checks, "_run_startup_checks"))
        finally:
            self._startup_phase = False
            self._hide_loading()
    def _run_startup_checks(self):
        """Runs all necessary checks on application startup."""
        self._check_for_due_transactions()
        self._show_startup_reminders()
        if False and self.current_user.get('username') == 'admin': # <-- .get() KULLANARAK GüNCELLEYiN
            user_data = self.auth_repo.get_user_by_username('admin')
            if self.auth_repo.verify_credentials(user_data['id'], 'admin'):
                msg = "GüVENLiK UYARISI:\n\nHala varsayılan 'admin' şifresini kullanıyorsunuz.\nLütfen Ayarlar -> Personel sekmesinden şifrenizi hemen değiştirin!"
                QMessageBox.warning(self, "Güvenlik Riski", msg)
    def _check_for_due_transactions(self):
        """Checks for scheduled transactions that are due."""
        today = datetime.now().date()
        due_transactions = []
        for trans in self.scheduled_transaction_manager.get_pending_transactions():
            try:
                due_date = datetime.strptime(trans.due_date, "%d.%m.%Y").date()
                if due_date <= today:
                    due_transactions.append(trans)
            except ValueError:
                continue
        if not due_transactions:
            return
        QMessageBox.information(self, self.lang["due_transactions_notification"], self.lang["there_are_due_transactions"].format(count=len(due_transactions)))
        for trans in due_transactions:
            self._handle_due_transaction(trans)
    def _handle_due_transaction(self, trans: ScheduledTransaction):
        """Displays a dialog for a single due transaction and handles the user's choice."""
        msg_box = QMessageBox(self)
        msg_box.setWindowTitle(self.lang["due_transactions_notification"])
        target_name = "Unknown"
        if trans.target in self.cash_managers:
            cash_acc = self.cash_account_manager.find_by_id(trans.target)
            if cash_acc: target_name = f"{self.lang['cash']} ({cash_acc.name})"
        elif trans.target in self.card_managers:
            card_acc = self.card_account_manager.find_by_id(trans.target)
            if card_acc: target_name = f"{self.lang['card']} ({card_acc.name})"
        details = f"""
        <b>{self.lang['due_date']}:</b> {trans.due_date}<br>
        <b>{self.lang['transaction_type']}:</b> {self.lang.get(trans.transaction_type.lower(), trans.transaction_type)}<br>
        <b>{self.lang['amount']}:</b> {self.currency_manager.format_currency(trans.amount, trans.currency)}<br>
        <b>{self.lang['description']}:</b> {trans.description}<br>
        <b>{self.lang['source']}:</b> {target_name}
        """
        msg_box.setText(details)
        complete_btn = msg_box.addButton(self.lang["complete_now"], QMessageBox.ButtonRole.AcceptRole)
        postpone_btn = msg_box.addButton(self.lang["postpone"], QMessageBox.ButtonRole.ActionRole)
        ignore_btn = msg_box.addButton(self.lang["ignore"], QMessageBox.ButtonRole.RejectRole)
        msg_box.exec()
        if msg_box.clickedButton() == complete_btn:
            self._complete_scheduled_transaction(trans)
        elif msg_box.clickedButton() == postpone_btn:
            days_str, ok = QInputDialog.getText(self, self.lang["postpone"], self.lang["postpone_for_days"])
            if ok and days_str.isdigit():
                days = int(days_str)
                current_due_date = datetime.strptime(trans.due_date, "%d.%m.%Y")
                new_due_date = current_due_date + timedelta(days=days)
                self.scheduled_transaction_manager.update_item(trans.id, {"due_date": new_due_date.strftime("%d.%m.%Y")})
                self._show_status_message(self.lang["transaction_postponed"])
                self.update_scheduled_transactions_display()
    def _complete_scheduled_transaction(self, trans: ScheduledTransaction):
        """Completes a scheduled transaction by creating a real financial transaction."""
        base_amount = self.currency_manager._convert_to_base_currency(trans.amount, trans.currency)
        fin_trans = FinancialTransaction(
            transaction_date=datetime.now().strftime("%d.%m.%Y"),
            transaction_type=trans.transaction_type,
            original_amount=trans.amount,
            currency=trans.currency,
            base_amount=base_amount,
            description=trans.description,
            related_id=trans.id,
             person_full_name=trans.person_full_name
        )
        manager = self.cash_managers.get(trans.target) or self.card_managers.get(trans.target)
        if not manager:
            QMessageBox.critical(self, self.lang['error'], f"Account for scheduled transaction not found (ID: {trans.target})")
            return
        db = self.data_manager.db
        profile_id = self.data_manager.profile_id
        try:
            with db.transaction_scope() as cursor:
                cursor.execute(
                    "SELECT id FROM scheduled_transactions WHERE id = %s AND profile_id = %s FOR UPDATE",
                    (trans.id, profile_id),
                )
                if not cursor.fetchone():
                    raise ValueError("Scheduled transaction not found or not in this profile.")
                cursor.execute(
                    "SELECT id FROM financial_accounts WHERE id = %s AND profile_id = %s FOR UPDATE",
                    (trans.target, profile_id),
                )
                if not cursor.fetchone():
                    raise ValueError(f"Account not found for scheduled transaction (ID: {trans.target}).")
                manager.add_item(fin_trans, cursor=cursor)
                before_row = fetch_row_snapshot_by_id(
                    db=db,
                    table_name="scheduled_transactions",
                    record_id=trans.id,
                    profile_id=profile_id,
                    cursor=cursor,
                )
                cursor.execute(
                    "UPDATE scheduled_transactions SET status = %s WHERE id = %s AND profile_id = %s",
                    ("Completed", trans.id, profile_id),
                )
                after_row = fetch_row_snapshot_by_id(
                    db=db,
                    table_name="scheduled_transactions",
                    record_id=trans.id,
                    profile_id=profile_id,
                    cursor=cursor,
                )
                queue_row_sync_event(
                    db=db,
                    table_name="scheduled_transactions",
                    profile_id=profile_id,
                    action_type=SYNC_ACTION_UPDATE,
                    record_id=trans.id,
                    before_row=before_row,
                    after_row=after_row,
                    cursor=cursor,
                    meta={"source": "JewelryStoreApp._complete_scheduled_transaction"},
                )
        except Exception as e:
            logger.error(f"Scheduled transaction complete error: {e}")
            QMessageBox.critical(self, self.lang['error'], f"Scheduled transaction could not be completed:\n{e}")
            return
        self._show_status_message(self.lang["transaction_completed"])
        self.update_all_displays()
    def _update_depot_manage_buttons(self):
        """Depo ekle/sil butonlarının durumunu günceller."""
        has_selection = self.depot_selector_combo.currentIndex() != -1
        self.edit_depot_btn.setEnabled(has_selection)
        self.delete_depot_btn.setEnabled(has_selection)
    def _add_depot(self):
        name, ok = QInputDialog.getText(self, self.lang["add_depot"], self.lang["depot_name_placeholder"])
        if ok and name.strip():
            name = name.strip()
            if self.depot_manager.find_by_attribute("name", name):
                QMessageBox.warning(self, self.lang["warning"], self.lang["depot_exists"])
                return
            self.depot_manager.add_item(Depot(name=name))
            self.update_depot_displays()
            self._show_status_message(self.lang["depot_saved_success"].format(name=name))
        if hasattr(self, "transfer_target_profile_combo"):
            self._populate_transfer_tab_combos()
    def _edit_depot(self):
        depot_id = self.depot_selector_combo.currentData()
        if not depot_id: return
        depot = self.depot_manager.find_by_id(depot_id)
        if not depot: return
        new_name, ok = QInputDialog.getText(self, self.lang["edit_depot"], self.lang["depot_name_placeholder"], text=depot.name)
        if ok and new_name.strip():
            new_name = new_name.strip()
            if depot.name != new_name:
                self.depot_manager.update_item(depot.id, {"name": new_name})
            self.update_depot_displays()
            self._show_status_message(self.lang["depot_saved_success"].format(name=new_name))
        if hasattr(self, "transfer_target_profile_combo"):
            self._populate_transfer_tab_combos()
    def _delete_depot(self):
        if self.current_user["role"] != "admin" and "delete_all" not in self.current_user.get("permissions", []):
            QMessageBox.critical(self, "Yetki Hatası", "Bu kaydı silmeye yetkiniz yok.\n(Yönetici onayı gerekir)")
            return
        depot_id = self.depot_selector_combo.currentData()
        if not depot_id: return
        depot = self.depot_manager.find_by_id(depot_id)
        if not depot: return
        if any(s for s in self.stock_manager.get_all() if s.depot_name == depot.name) or \
           any(s for s in self.scrap_stock_manager.get_all() if s.depot_name == depot.name) or \
           any(s for s in self.ziynet_stock_manager.get_all() if s.depot_name == depot.name):
            QMessageBox.warning(self, self.lang["warning"], "içinde stok bulunan depo silinemez.")
            return
        if self._show_confirm_dialog(self.lang["delete_depot"], self.lang["confirm_delete_depot"].format(name=depot.name)):
            self.depot_manager.delete_item(depot.id)
            self.update_depot_displays()
            self._show_status_message(self.lang["depot_deleted_success"].format(name=depot.name))
        if hasattr(self, "transfer_target_profile_combo"):
            self._populate_transfer_tab_combos()
    def _update_depot_transfer_products(self):
        """Depolar sekmesindeki transfer bölümü için ürün listesini günceller."""
        if not hasattr(self, 'depot_trans_product_combo'): return
        stock_type_str = self.depot_trans_type_combo.currentText()
        if stock_type_str == "Altın":
            stock_manager = self._get_stock_manager(StockType.PRODUCT)
        elif stock_type_str == "Ziynet":
            stock_manager = self._get_stock_manager(StockType.ZIYNET)
        else:
            stock_manager = self._get_stock_manager(StockType.SCRAP)
        source_depot = self.depot_trans_source_combo.currentText()
        self.depot_trans_product_combo.clear()
        if source_depot:
            products = [s.item_type for s in stock_manager.get_all() if s.depot_name == source_depot and s.quantity > 0]
            self.depot_trans_product_combo.addItems(sorted(products))
    def update_depot_content_display(self):
        """
        Seçili deponun içeriğini (Altın, Hurda, Ziynet) gösterir.
        GüNCELLEME: Kur dönüşümü olmadan ham değerleri gösterir.
        """
        if not hasattr(self, 'depot_content_table'): return
        depot_name = self.depot_selector_combo.currentText()
        table = self.depot_content_table
        table.setRowCount(0)
        if not depot_name: return
        all_items = []
        products = [s for s in self.stock_manager.get_all() if s.depot_name == depot_name and s.quantity > 0]
        for p in products:
            all_items.append({
                "type": "Altın",
                "name": p.item_type,
                "detail": p.unique_barcode, # Barkod
                "qty": p.quantity, # Adet
                "gram": p.quantity_gr,
                "value": p.quantity * p.purchase_price_base,
                "id": p.id,
                "manager": "product"
            })
        scraps = [s for s in self.scrap_stock_manager.get_all() if s.depot_name == depot_name and s.quantity > 0]
        for s in scraps:
            all_items.append({
                "type": "Hurda",
                "name": s.item_type,
                "detail": "(Yığın)",
                "qty": s.quantity, # Gram/Miktar
                "gram": s.quantity, # Hurda için miktar gramdır
                "value": s.quantity * s.purchase_price_base,
                "id": s.id,
                "manager": "scrap"
            })
        ziynets = [s for s in self.ziynet_stock_manager.get_all() if s.depot_name == depot_name and s.quantity > 0]
        for z in ziynets:
            all_items.append({
                "type": "Ziynet",
                "name": z.item_type,
                "detail": "(Adet)",
                "qty": z.quantity,
                "gram": 0.0,
                "value": z.quantity * z.purchase_price_base,
                "id": z.id,
                "manager": "ziynet"
            })
        table.setRowCount(len(all_items))
        total_value_base = 0.0
        for row, item in enumerate(all_items):
            total_value_base += item['value']
            val_disp = self.currency_manager._convert_to_display_currency(item['value'], self.display_currency)
            table.setItem(row, 0, QTableWidgetItem(item['type']))
            table.setItem(row, 1, QTableWidgetItem(item['name']))
            table.setItem(row, 2, QTableWidgetItem(item['detail']))
            qty_str = format_number(item['qty'], 3)
            if item['type'] == "Hurda": qty_str += " gr"
            else: qty_str = str(int(item['qty'])) + " Adet"
            table.setItem(row, 3, QTableWidgetItem(qty_str))
            table.setItem(row, 4, QTableWidgetItem(self.currency_manager.format_currency(val_disp, self.display_currency)))
            table.item(row, 0).setData(Qt.ItemDataRole.UserRole, item)
        total_disp = self.currency_manager._convert_to_display_currency(total_value_base, self.display_currency)
        self.depot_total_value_label.setText(f"{self.lang.get('total_value_lbl', 'Total Value')}: {self.currency_manager.format_currency(total_disp, self.display_currency)}")
    def _update_depot_transfer_products_list(self):
        """
        Transfer panelindeki ürün listesini (Combobox) günceller.
        Kaynak Depo ve Seçilen Tür'e göre filtreleme yapar.
        """
        if not hasattr(self, 'depot_trans_product_combo') or not hasattr(self, 'depot_trans_type_combo'):
            return
        source_depot = self.depot_selector_combo.currentText()
        if not source_depot: return
        selected_type = self.depot_trans_type_combo.currentText() # Altın, Hurda, Ziynet
        if selected_type == "Altın":
            manager = self.stock_manager
        elif selected_type == "Hurda":
            manager = self.scrap_stock_manager
        else: # Ziynet
            manager = self.ziynet_stock_manager
        products_in_depot = sorted(list(set([
            item.item_type for item in manager.get_all()
            if item.depot_name == source_depot and item.quantity > 0
        ])))
        self.depot_trans_product_combo.blockSignals(True)
        self.depot_trans_product_combo.clear()
        self.depot_trans_product_combo.addItems(products_in_depot)
        self.depot_trans_product_combo.blockSignals(False)
    def _execute_general_depot_transfer(self):
        """
        Depolar sekmesindeki form üzerinden transfer yapar.
        Tablo seçiminden bağımsızdır.
        """
        source_depot = self.depot_selector_combo.currentText()
        dest_depot = self.depot_trans_dest_combo.currentText()
        stock_type_str = self.depot_trans_type_combo.currentText() # Altın, Hurda...
        product_name = self.depot_trans_product_combo.currentText().strip()
        qty_text = self.depot_trans_qty_entry.text().replace(',', '.')
        if not all([source_depot, dest_depot, product_name, qty_text]):
            QMessageBox.warning(self, "Uyarı", "Lütfen tüm alanları doldurun (ürün ve Miktar).")
            return
        if source_depot == dest_depot:
            QMessageBox.warning(self, "Uyarı", "Kaynak ve hedef depolar farklı olmalıdır.")
            return
        try:
            transfer_qty = float(qty_text)
            if transfer_qty <= 0: raise ValueError
        except:
            QMessageBox.warning(self, "Hata", "Geçersiz miktar.")
            return
        if stock_type_str == "Altın":
            manager = self.stock_manager
            stock_type_code = "Product"
        elif stock_type_str == "Hurda":
            manager = self.scrap_stock_manager
            stock_type_code = "Scrap"
        else: # Ziynet
            manager = self.ziynet_stock_manager
            stock_type_code = "Ziynet"
        if stock_type_str == "Altın":
            qty_int = int(transfer_qty)
            if qty_int != transfer_qty:
                QMessageBox.warning(self, "Hata", "Altın transferi tam sayı (Adet) olmalıdır.")
                return
            available_items = [i for i in manager.get_all() if i.item_type == product_name and i.depot_name == source_depot and i.quantity > 0]
            if len(available_items) < qty_int:
                QMessageBox.warning(self, "Yetersiz Stok", f"'{source_depot}' deposunda yeterli '{product_name}' yok.\nMevcut: {len(available_items)} Adet")
                return
            from modules.stock import BarcodeSaleSelectionDialog
            dialog = BarcodeSaleSelectionDialog(
                product_name, qty_int, manager, source_depot, self.lang, self.product_manager, self
            )
            if dialog.exec() == QDialog.DialogCode.Accepted:
                selected_barcodes = dialog.get_selected_barcodes()
                for code in selected_barcodes:
                    stock_item = next((i for i in manager.get_all() if i.unique_barcode == code and i.depot_name == source_depot), None)
                    if stock_item:
                        manager.update_item(stock_item.id, {"depot_name": dest_depot})
                        self.barcode_history_manager.log_movement(
                            barcode=code,
                            from_loc=source_depot,
                            to_loc=dest_depot,
                            desc=f"Depo Transferi ({product_name})",
                            user=self.current_user['name']
                        )
                self.stock_movement_manager.add_log(
                    item_type="Product", product_name=product_name, op_type="Transfer",
                    qty=qty_int, source=source_depot, dest=dest_depot,
                    desc=f"Barkodlu Transfer: {', '.join(selected_barcodes)}", user=self.current_user['name']
                )
                self.depot_trans_qty_entry.clear()
                self.update_depot_content_display()
                self._update_depot_transfer_products_list() # Listeyi tazele
                self._show_status_message(f"Transfer Başarılı: {len(selected_barcodes)} adet altın taşındı.")
            return
        else:
            try:
                manager.transfer_stock_bulk(
                    item_type=product_name,
                    quantity=transfer_qty,
                    source_depot_name=source_depot,
                    dest_depot_name=dest_depot,
                )
            except ValueError as e:
                QMessageBox.warning(self, "Yetersiz Stok", str(e))
                return
            except Exception as e:
                QMessageBox.critical(self, "Hata", f"Transfer başarısız:\n{e}")
                return
            self.stock_movement_manager.add_log(
                item_type=stock_type_code,
                product_name=product_name, op_type="Transfer",
                qty=transfer_qty, source=source_depot, dest=dest_depot,
                desc="Yığın Transferi", user=self.current_user['name']
            )
            self.depot_trans_qty_entry.clear()
            self.update_depot_content_display()
            self._update_depot_transfer_products_list() # Listeyi tazele
            self._show_status_message("Transfer Başarılı.")
    def _manual_stock_action(self, stock_type: str, mode: str):
        """
        Manuel Stok işlemi.
        DüZELTME: Girilen fiyat ve para birimi artık doğru şekilde kaydediliyor.
        """
        if stock_type == "Product":
            action_code = "Purchase" if mode == "In" else "Sale"
            self._open_gold_batch_dialog(action_code)
            return
        if stock_type == "Ziynet":
            manager = self.ziynet_manager
            stock_mgr = self.ziynet_stock_manager
        else:
            manager = self.scrap_manager
            stock_mgr = self.scrap_stock_manager
        product_list = manager.get_product_names()
        depot_list = self._get_accessible_depots()
        if not product_list:
            QMessageBox.warning(self, "Uyarı", "önce ürün tanımlamalısınız.")
            return
        if not depot_list:
            QMessageBox.warning(self, "Uyarı", "Erişim izniniz olan depo bulunamadı.")
            return
        dialog = ManualStockDialog(mode, stock_type, product_list, depot_list, self.lang, self)
        if dialog.exec() == QDialog.DialogCode.Accepted:
            data = dialog.get_data()
            product_name = data['product']
            depot_name = data['depot']
            qty = data['quantity']
            desc = data['description']
            price_val = data['price'] # Kullanıcının girdiği fiyat
            currency = data['currency'] # Kullanıcının seçtiği para birimi
            if qty <= 0: return
            if mode == "In":
                stock_mgr.add_or_update_stock(product_name, qty, price_val, depot_name)
                op_type = "Manuel Giriş"
                source = "Dışarı"; dest = depot_name
                log_desc = f"Manuel Giriş: {desc} ({format_number(price_val)} {currency})"
            else:
                stock_mgr.remove_stock(product_name, qty, depot_name)
                op_type = "Manuel çıkış"
                source = depot_name; dest = "Dışarı"
                log_desc = f"Manuel çıkış: {desc} ({format_number(price_val)} {currency})"
            self.stock_movement_manager.add_log(
                item_type=stock_type, product_name=product_name, op_type=op_type,
                qty=qty, source=source, dest=dest,
                desc=log_desc, user=self.current_user['name']
            )
            self.update_all_displays()
            self._show_status_message(f"işlem Başarılı: {product_name}")
    def _update_generic_stock_list(self, stock_type: str, page: int = 1):
        """
        Stok listesini günceller - SAYFALAMA (PAGINATION) VE SQL FiLTRELEME DESTEKLi.
        Değişiklikler:
        1. BaseManager.get_all() yerine BaseManager.get_page() kullanılır.
        2. Filtreleme Python döngüsü yerine SQL WHERE koşullarıyla yapılır.
        3. Büyük veri setlerinde UI donmasını ve RAM şişmesini engeller.
        """
        if stock_type not in self.stock_widgets: return
        widgets = self.stock_widgets[stock_type]
        if 'stock_table' not in widgets: return
        table = widgets['stock_table']
        manager = self._get_stock_manager(stock_type)
        filters = {}
        depot_combo = widgets.get('depot_filter_combo')
        if depot_combo:
            selected_depot = depot_combo.currentData() # 'Merkez' veya None
            if selected_depot:
                depot_obj = self.depot_manager.find_by_attribute("name", selected_depot)
                if depot_obj:
                    filters['depot_id'] = depot_obj.id
        type_combo = widgets.get('type_filter_combo')
        if stock_type == "Product" and type_combo:
            selected_type = type_combo.currentData()
            if selected_type:
                filters['item_type_name'] = selected_type
        PAGE_SIZE = 50
        try:
            items, total_count = manager.get_page(page, PAGE_SIZE, filters)
        except AttributeError:
            items = manager.get_all()
            total_count = len(items)
            start = (page - 1) * PAGE_SIZE
            end = start + PAGE_SIZE
            items = items[start:end]
        self.current_stock_page = page
        self.total_stock_pages = (total_count + PAGE_SIZE - 1) // PAGE_SIZE if total_count > 0 else 1
        table.setRowCount(0)
        if stock_type == "Product":
            table.setRowCount(len(items))
            total_val_base = 0.0
            for row, item in enumerate(items):
                try:
                    col_offset = 0
                    if getattr(self, "is_boss_mode", False):
                        branch_name = getattr(item, "_branch_name", "Bilinmeyen")
                        branch_item = QTableWidgetItem(branch_name)
                        branch_item.setBackground(QColor("#4CAF50"))
                        branch_item.setForeground(QColor("white"))
                        table.setItem(row, 0, branch_item)
                        col_offset = 1
                    qty = float(item.quantity or 0)
                    gram_val = float(item.quantity_gr or 0)
                    pricing_qty = gram_val if gram_val > 0 else qty
                    price_base = float(item.purchase_price_base or 0)
                    item_val = pricing_qty * price_base
                    total_val_base += item_val
                    table.setItem(row, col_offset + 0, QTableWidgetItem(str(item.item_type or "")))
                    table.setItem(row, col_offset + 1, QTableWidgetItem(str(item.unique_barcode or "-")))
                    table.setItem(row, col_offset + 2, QTableWidgetItem(str(item.depot_name or "")))
                    table.setItem(row, col_offset + 3, QTableWidgetItem(str(int(qty))))
                    table.setItem(row, col_offset + 4, QTableWidgetItem(format_number(gram_val, 3)))
                    avg_price_disp = self.currency_manager._convert_to_display_currency(price_base, self.display_currency)
                    tot_val_disp = self.currency_manager._convert_to_display_currency(item_val, self.display_currency)
                    unit_suffix = " /gr" if gram_val > 0 else ""
                    table.setItem(row, col_offset + 5, QTableWidgetItem(f"{format_number(avg_price_disp)} {self.display_currency}{unit_suffix}"))
                    table.setItem(row, col_offset + 6, QTableWidgetItem(format_number(tot_val_disp)))
                    table.setItem(row, col_offset + 7, QTableWidgetItem("")) # işlem sütunu boş
                    table.item(row, col_offset + 0).setData(Qt.ItemDataRole.UserRole, item.id)
                except Exception as e:
                    logger.error(f"Tablo satır hatası (Product): {e}")
                    continue
            if 'total_stock_value_label' in widgets:
                total_disp = self.currency_manager._convert_to_display_currency(total_val_base, self.display_currency)
                widgets['total_stock_value_label'].setText(f"Sayfa Toplamı: {self.currency_manager.format_currency(total_disp, self.display_currency)}")
        else:
            grouped = {}
            for s in items:
                if getattr(self, "is_boss_mode", False):
                    key = (s.item_type, s.depot_name, getattr(s, "_branch_name", "Bilinmeyen"))
                else:
                    key = (s.item_type, s.depot_name)
                if key not in grouped:
                    grouped[key] = {
                        'qty': 0.0,
                        'basis_qty': 0.0,
                        'val': 0.0,
                        'item': s.item_type,
                        'depot': s.depot_name,
                        'ids': [],
                        'branch': getattr(s, "_branch_name", None),
                    }
                q = float(s.quantity or 0)
                g = float(getattr(s, 'quantity_gr', 0) or 0)
                basis_q = g if g > 0 else q
                p = float(s.purchase_price_base or 0)
                grouped[key]['qty'] += q
                grouped[key]['basis_qty'] += basis_q
                grouped[key]['val'] += (basis_q * p)
                grouped[key]['ids'].append(s.id)
                if grouped[key].get('branch') is None:
                    grouped[key]['branch'] = getattr(s, "_branch_name", None)
            if getattr(self, "is_boss_mode", False):
                sorted_groups = sorted(grouped.values(), key=lambda x: ((x.get('branch') or ''), x['item'], x['depot']))
            else:
                sorted_groups = sorted(grouped.values(), key=lambda x: (x['item'], x['depot']))
            table.setRowCount(len(sorted_groups))
            total_grand = 0.0
            for row, d in enumerate(sorted_groups):
                total_grand += d['val']
                avg = d['val'] / d['basis_qty'] if d['basis_qty'] > 0 else 0
                avg_disp = self.currency_manager._convert_to_display_currency(avg, self.display_currency)
                tot_disp = self.currency_manager._convert_to_display_currency(d['val'], self.display_currency)
                col_offset = 0
                if getattr(self, "is_boss_mode", False):
                    branch_name = d.get("branch") or "Bilinmeyen"
                    branch_item = QTableWidgetItem(branch_name)
                    branch_item.setBackground(QColor("#4CAF50"))
                    branch_item.setForeground(QColor("white"))
                    table.setItem(row, 0, branch_item)
                    col_offset = 1
                table.setItem(row, col_offset + 0, QTableWidgetItem(str(d['item'])))
                table.setItem(row, col_offset + 1, QTableWidgetItem(str(d['depot'])))
                qty_str = format_number(d['qty'], 3)
                if stock_type == "Ziynet": qty_str = str(int(d['qty']))
                table.setItem(row, col_offset + 2, QTableWidgetItem(qty_str))
                table.setItem(row, col_offset + 3, QTableWidgetItem(f"{format_number(avg_disp)} {self.display_currency}"))
                table.setItem(row, col_offset + 4, QTableWidgetItem(format_number(tot_disp)))
                first_id = d['ids'][0] if d['ids'] else ""
                table.item(row, col_offset + 0).setData(Qt.ItemDataRole.UserRole, first_id)
            if 'total_stock_value_label' in widgets:
                total_disp = self.currency_manager._convert_to_display_currency(total_grand, self.display_currency)
                widgets['total_stock_value_label'].setText(self.currency_manager.format_currency(total_disp, self.display_currency))
        self.statusBar().showMessage(f"Veriler yüklendi. Sayfa: {page}/{self.total_stock_pages} (Toplam: {total_count})")
    def update_depot_content_display(self):
        """
        Seçili deponun içeriğini (Altın, Hurda, Ziynet) gösterir.
        GüNCELLEME: Kur dönüşümü olmadan ham değerleri gösterir ve NoneType hatasına karşı korumalıdır.
        """
        if not hasattr(self, 'depot_content_table'): return
        depot_name = self.depot_selector_combo.currentText()
        table = self.depot_content_table
        table.setRowCount(0)
        if not depot_name: return
        all_items = []
        def safe_collect(manager, type_label, id_prefix):
            items = []
            for s in manager.get_all():
                if s is None: continue
                s_depot = str(s.depot_name or "")
                try:
                    qty = float(s.quantity)
                except: qty = 0.0
                if s_depot == depot_name and qty > 0:
                    try:
                        price = float(s.purchase_price_base)
                    except: price = 0.0
                    items.append({
                        "type": type_label,
                        "name": str(s.item_type or "Bilinmeyen"),
                        "detail": str(s.unique_barcode or "(Yığın)"),
                        "qty": qty,
                        "gram": float(s.quantity_gr) if hasattr(s, 'quantity_gr') and s.quantity_gr else qty,
                        "value": qty * price,
                        "id": s.id,
                        "manager": id_prefix
                    })
            return items
        all_items.extend(safe_collect(self.stock_manager, "Altın", "product"))
        all_items.extend(safe_collect(self.scrap_stock_manager, "Hurda", "scrap"))
        all_items.extend(safe_collect(self.ziynet_stock_manager, "Ziynet", "ziynet"))
        table.setRowCount(len(all_items))
        total_value_base = 0.0
        for row, item in enumerate(all_items):
            try:
                total_value_base += item['value']
                val_disp = self.currency_manager._convert_to_display_currency(item['value'], self.display_currency)
                table.setItem(row, 0, QTableWidgetItem(item['type']))
                table.setItem(row, 1, QTableWidgetItem(item['name']))
                detail_txt = item['detail']
                if item['type'] == "Ziynet" and detail_txt == "(Yığın)": detail_txt = "(Adet)"
                table.setItem(row, 2, QTableWidgetItem(detail_txt))
                qty_str = format_number(item['qty'], 3)
                if item['type'] == self.lang.get("scrap", "Scrap") or item['type'] == "Hurda": qty_str += " gr"
                else: qty_str = str(int(item['qty'])) + f" {self.lang.get('pieces', 'Pieces')}"
                table.setItem(row, 3, QTableWidgetItem(qty_str))
                table.setItem(row, 4, QTableWidgetItem(self.currency_manager.format_currency(val_disp, self.display_currency)))
                item_widget = table.item(row, 0)
                if item_widget:
                    item_widget.setData(Qt.ItemDataRole.UserRole, item)
            except Exception as e:
                logger.error(f"Depo içerik satırı hatası: {e}")
                continue
        total_disp = self.currency_manager._convert_to_display_currency(total_value_base, self.display_currency)
        self.depot_total_value_label.setText(f"{self.lang.get('total_value_lbl', 'Total Value')}: {self.currency_manager.format_currency(total_disp, self.display_currency)}")
    def _update_sold_barcodes_table(self):
        """Seçilen tarihte satılan barkodları listeler. DüZELTiLDi."""
        if not hasattr(self, 'sold_barcode_date_edit'): return
        if 'Product' not in self.stock_widgets or 'sold_barcodes_table' not in self.stock_widgets['Product']: return
        widgets = self.stock_widgets['Product']
        table = widgets['sold_barcodes_table']
        table.setRowCount(0)
        selected_date_str = self.sold_barcode_date_edit.date().toString("dd.MM.yyyy")
        selected_depot = None
        depot_filter = widgets.get('sold_depot_filter')
        if depot_filter:
            selected_depot = depot_filter.currentData()

        sold_entries = self.barcode_history_manager.get_sold_entries(
            date_str=selected_date_str,
            depot_name=selected_depot,
        )
        table.setRowCount(len(sold_entries))
        for row, item in enumerate(sold_entries):
            try:
                sell_date = str(item.get("sell_date") or "")
                time_str = sell_date.split(' ')[1]
            except:
                time_str = str(item.get("sell_date") or "")
            table.setItem(row, 0, QTableWidgetItem(time_str))
            table.setItem(row, 1, QTableWidgetItem(str(item.get("barcode") or "")))
            table.setItem(row, 2, QTableWidgetItem(str(item.get("product_type") or "")))
            table.setItem(row, 3, QTableWidgetItem(str(item.get("sell_to") or "")))
            table.setItem(row, 4, QTableWidgetItem(str(item.get("sell_method") or "")))
    def _export_depot_content(self, export_type: str):
        """Seçili deponun içeriğini PDF veya Excel olarak dışa aktarır."""
        depot_name = self.depot_selector_combo.currentText()
        if not depot_name:
            QMessageBox.warning(self, self.lang["warning"], self.lang["no_depot_selected"])
            return
        products = [s for s in self.stock_manager.get_all() if s.depot_name == depot_name]
        scraps = [s for s in self.scrap_stock_manager.get_all() if s.depot_name == depot_name]
        all_items = products + scraps
        if not all_items:
            QMessageBox.information(self, self.lang["info"], self.lang["pdf_no_data"])
            return
        all_items.sort(key=lambda x: x.item_type)
        headers = [self.lang["product_type"], self.lang["product_name_type"], self.lang["quantity"], self.lang["total_value"]]
        data_rows = []
        total_value_base = 0.0
        for item in all_items:
            is_product = item in products
            type_str = "Altın" if is_product else "Hurda"
            value_base = item.quantity * item.purchase_price_base
            total_value_base += value_base
            value_disp = self.currency_manager._convert_to_display_currency(value_base, self.display_currency)
            row = [
                type_str,
                item.item_type,
                f"{format_number(item.quantity, 3)}",
                self.currency_manager.format_currency(value_disp, self.display_currency)
            ]
            data_rows.append(row)
        total_disp = self.currency_manager._convert_to_display_currency(total_value_base, self.display_currency)
        summary_data = {
            "Depo Adı": depot_name,
            "Rapor Tarihi": datetime.now().strftime("%d.%m.%Y %H:%M"),
            "Toplam Kalem Sayısı": str(len(all_items)),
            "TOPLAM DEÄER": self.currency_manager.format_currency(total_disp, self.display_currency)
        }
        default_filename = f"Depo_{depot_name}_{datetime.now().strftime('%Y%m%d')}".replace(" ", "_")
        report_title = f"Depo Stok Raporu - {depot_name}"
        if export_type == "pdf":
            filename, _ = QFileDialog.getSaveFileName(self, "PDF Kaydet", f"{default_filename}.pdf", "PDF Files (*.pdf)")
            if filename:
                self.pdf_report_generator.generate_report(report_title, headers, data_rows, summary_data, filename)
        elif export_type == "excel":
            filename, _ = QFileDialog.getSaveFileName(self, "Excel Kaydet", f"{default_filename}.xlsx", "Excel Files (*.xlsx)")
            if filename:
                self.excel_report_generator.generate_report(report_title, headers, data_rows, summary_data, filename)
    def _show_startup_reminders(self) -> None:
        """Checks for agenda reminders and upcoming due dates on startup."""
        today_str = datetime.now().strftime("%d.%m.%Y")
        today_date = datetime.now().date()
        reminders_text = []
        note_data = self.calendar_manager.get_note_data(today_str)
        if note_data.get("is_reminder") and note_data.get("html", "").strip():
            doc = QTextDocument()
            doc.setHtml(note_data["html"])
            note_content_plain = doc.toPlainText().strip()
            if note_content_plain:
                reminders_text.append(f"<b>{self.lang['agenda_reminders_for_today']}</b>")
                reminders_text.append(f"- {note_content_plain}")
        upcoming_dues = []
        for debt in self.debt_manager.get_all():
            if debt.due_date and not debt.is_paid:
                try:
                    due_date = datetime.strptime(debt.due_date, "%d.%m.%Y").date()
                    if today_date <= due_date <= (today_date + timedelta(days=7)):
                        if debt.debt_type == "Cash":
                            remaining_base = debt.total_amount_base - debt.paid_amount_base
                            remaining_display = self.currency_manager._convert_to_display_currency(remaining_base, self.display_currency)
                            amount_str = self.currency_manager.format_currency(remaining_display, self.display_currency)
                        else:
                            remaining_qty = debt.product_quantity - debt.paid_quantity
                            amount_str = f"{format_number(remaining_qty, precision=3)} {debt.product_type}"
                        upcoming_dues.append(
                            f"- {debt.person_full_name}: {amount_str} ({self.lang['due_date']}: {debt.due_date})"
                        )
                except (ValueError, TypeError): continue
        if upcoming_dues:
            reminders_text.append(f"<br><b>{self.lang['upcoming_due_dates']}</b>")
            reminders_text.extend(upcoming_dues)
        if reminders_text:
            QMessageBox.information(self, self.lang["reminders"], "<br>".join(reminders_text))
        else:
            self._show_status_message(self.lang["no_reminders"])
    def _pos_update_prices(self, stock_type: str, transaction_type: str, source: str):
        """
        POS ekranındaki Miktar, Birim Fiyat ve Toplam Fiyat arasındaki 3'lü ilişkiyi yönetir.
        source: 'qty' (miktar değişti), 'unit' (birim fiyat değişti), 'total' (toplam değişti)
        """
        if stock_type not in self.stock_widgets or transaction_type not in self.stock_widgets[stock_type]:
            return
        widgets = self.stock_widgets[stock_type][transaction_type]
        qty_widget = widgets['quantity_entry']
        unit_widget = widgets['price_entry']
        total_widget = widgets['total_price_entry']
        try:
            qty_text = qty_widget.text().replace(',', '.')
            qty_val = float(qty_text) if qty_text else 0.0
            unit_text = unit_widget.text().replace(',', '.')
            unit_val = float(unit_text) if unit_text else 0.0
            total_text = total_widget.text().replace(',', '.')
            total_val = float(total_text) if total_text else 0.0
        except ValueError:
            return
        qty_widget.blockSignals(True)
        unit_widget.blockSignals(True)
        total_widget.blockSignals(True)
        try:
            if source == 'qty' or source == 'unit':
                new_total = qty_val * unit_val
                total_widget.setText(f"{new_total:.2f}".rstrip('0').rstrip('.'))
            elif source == 'total':
                if qty_val > 0:
                    new_unit = total_val / qty_val
                    unit_widget.setText(f"{new_unit:.2f}".rstrip('0').rstrip('.'))
                else:
                    pass
        finally:
            qty_widget.blockSignals(False)
            unit_widget.blockSignals(False)
            total_widget.blockSignals(False)
    def _change_profile_name(self):
        """Aktif profilin görünen adını veritabanında günceller."""
        new_name, ok = QInputDialog.getText(self, "Profil Adı Değiştir",
                                            "Yeni profil adını girin:",
                                            text=self.profile_name)
        if not ok or not new_name.strip():
            return
        new_name = new_name.strip()
        if new_name == self.profile_name:
            return
        try:
            db = self.data_manager.db
            profile_id = str(self.data_manager.profile_id or "").strip()
            if not profile_id:
                raise RuntimeError("Aktif profil kimligi bulunamadi.")
            existing = db.fetch_one(
                """
                SELECT id
                FROM profiles
                WHERE name = %s AND COALESCE(is_deleted, 0) = 0 AND id <> %s
                LIMIT 1
                """,
                (new_name, profile_id),
            )
            if existing:
                QMessageBox.warning(self, "Uyarı", "Bu isimde bir profil zaten mevcut.")
                return
            with db.transaction_scope() as cursor:
                before_row = fetch_row_snapshot_by_id(db, "profiles", profile_id, cursor=cursor)
                if not before_row:
                    raise RuntimeError("Aktif profil kaydi bulunamadi.")
                cursor.execute(
                    "UPDATE profiles SET name = %s WHERE id = %s",
                    (new_name, profile_id),
                )
                after_row = fetch_row_snapshot_by_id(db, "profiles", profile_id, cursor=cursor)
                queue_row_sync_event(
                    db=db,
                    table_name="profiles",
                    profile_id=profile_id,
                    action_type=SYNC_ACTION_UPDATE,
                    record_id=profile_id,
                    before_row=before_row,
                    after_row=after_row,
                    cursor=cursor,
                    meta={"source": "JewelryStoreApp._change_profile_name"},
                )
            old_name = self.profile_name
            self.profile_name = new_name
            self.data_manager.profile_name = new_name
            self.setWindowTitle(f'{self.lang["app_title"]} - [{self.profile_name}] - {self.current_user["name"]}')
            if hasattr(self, 'profile_name_display'):
                self.profile_name_display.setText(new_name)
            QMessageBox.information(self, "Başarılı", f"Profil adı '{old_name}' -> '{new_name}' olarak değiştirildi.")
        except Exception as e:
            logger.error(f"Profil adı değiştirme hatası: {e}")
            QMessageBox.critical(self, "Hata", f"Profil adı değiştirilemedi: {e}")
    def _update_app_logo(self):
        """
        Dashboard'daki ve Ayarlar sekmesindeki logo önizlemesini günceller.
        """
        # DEÄiŞiKLiK: config.py'deki merkezi yolu kullan
        logo_path = LOGO_PATH 
        
        if not logo_path.exists():
            # Yedek: Resources klasörüne de bakabiliriz ama öncelik ana klasör
            from core.config import RESOURCES_DIR
            logo_path = RESOURCES_DIR / "golder.png"
            
        if not logo_path.exists():
            return

        pixmap = QPixmap(str(logo_path))
        if hasattr(self, 'dashboard_logo_label'):
             current_size = self.dashboard_logo_label.size()
             scaled_pixmap = pixmap.scaled(current_size, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
             self.dashboard_logo_label.setPixmap(scaled_pixmap)
        else:
            pass
        if hasattr(self, 'logo_preview_label'):
            self.logo_preview_label.setPixmap(pixmap)
    def _copy_text_to_clipboard(self, text):
        """Metni panoya kopyalar ve kullanıcıya bildirir."""
        if not text: return
        QApplication.clipboard().setText(text)
        self._show_status_message("Metin kopyalandı!", 2000)
    # YENi (DüZELTiLMiŞ) KOD
    def _refresh_employee_list(self):
        """çalışan listesini veritabanından (users tablosu) çeker ve günceller."""
        if not hasattr(self, 'emp_list'): return
        
        self.emp_list.clear()
        
        # Artık settings_manager değil, auth_repo kullanıyoruz
        try:
            users = self.auth_repo.get_all_users()
            
            for user in users:
                # Admin kullanıcısını listede göstermek istemiyorsanız alttaki satırı açabilirsiniz:
                # if user['role'] == 'admin': continue 
                
                user_id = user['id']
                full_name = user['full_name']
                role = user['role']
                
                # Dil dosyasından çevirilerini al
                role_translated = self.lang.get(f"role_{role}", role)
                
                # Eğer tam ad, sistemin oluşturduğu varsayılan "Yonetici" / "Yönetici" ise onu da çevir
                disp_name = full_name
                if full_name in ["Yonetici", "Yönetici"]:
                    disp_name = self.lang.get("default_admin_name", "Manager")
                
                display_text = f"{disp_name} ({role_translated})"
                
                item = QListWidgetItem(display_text)
                item.setData(Qt.ItemDataRole.UserRole, user_id)
                
                # Admin ve çalışanları görsel olarak ayırmak isterseniz ikon ekleyebilirsiniz (opsiyonel)
                if role == 'admin':
                    item.setIcon(self._get_icon("SP_ComputerIcon"))
                else:
                    item.setIcon(self._get_icon("SP_UserIcon"))
                    
                self.emp_list.addItem(item)
                
        except Exception as e:
            print(f"çalışan listesi yenilenirken hata: {e}")
    def _get_accessible_depots(self) -> List[str]:
        """O anki kullanıcının yetkili olduğu depo isimlerini döndürür."""
        all_depot_names = self.depot_manager.get_depot_names()
        if not all_depot_names and self.current_user["role"] == "admin":
            return [get_default_depot_name()]
        if self.current_user["role"] == "admin":
            return all_depot_names
        allowed_depots = self.current_user.get("allowed_depots", [])
        if not allowed_depots:
            return []
        return [d for d in all_depot_names if d in allowed_depots]
    def _save_zakat_date_and_calc(self):
        """Zekat başlangıç tarihini kaydeder ve hesaplamayı tetikler."""
        date_str = self.zakat_start_date.date().toString("dd.MM.yyyy")
        self.settings_manager.set_setting("zakat_nisap_date", date_str)
        self._calculate_zakat()
        self._show_status_message("Nisap tarihi kaydedildi ve hesaplandı.")
    def _guest_gold_detail_options(self) -> List[Dict[str, str]]:
        return [
            {"label": "24 Ayar (995)", "value": "24"},
            {"label": "22 Ayar (916)", "value": "22"},
            {"label": "21 Ayar (875)", "value": "21"},
            {"label": "18 Ayar (750)", "value": "18"},
            {"label": "14 Ayar (585)", "value": "14"},
            {"label": "8 Ayar (333)", "value": "8"},
            {"label": "Has 999", "value": "999"},
            {"label": "Has 995", "value": "995"},
        ]
    def _guest_silver_detail_options(self) -> List[Dict[str, str]]:
        return [
            {"label": "Has 1000", "value": "1000"},
            {"label": "925", "value": "925"},
            {"label": "900", "value": "900"},
            {"label": "800", "value": "800"},
        ]
    def _update_guest_asset_row_ui(self, row: Dict[str, Any]) -> None:
        type_combo = row.get("type_combo")
        detail_combo = row.get("detail_combo")
        amount_spin = row.get("amount_spin")
        if not type_combo or not detail_combo or not amount_spin:
            return
        asset_type = str(type_combo.currentData() or "cash")
        detail_combo.blockSignals(True)
        detail_combo.clear()
        target_idx = 0
        fallback_currencies = ["TRY", "USD", "EUR", "GBP"]
        currencies = list(SUPPORTED_CURRENCIES) if isinstance(SUPPORTED_CURRENCIES, list) else []
        if not currencies:
            currencies = fallback_currencies
        if asset_type == "gold":
            for item in self._guest_gold_detail_options():
                detail_combo.addItem(item["label"], item["value"])
            amount_spin.setSuffix(" Gr")
            amount_spin.setDecimals(3)
        elif asset_type == "silver":
            for item in self._guest_silver_detail_options():
                detail_combo.addItem(item["label"], item["value"])
            amount_spin.setSuffix(" Gr")
            amount_spin.setDecimals(3)
        else:
            for curr in currencies:
                detail_combo.addItem(curr, curr)
            target_idx = detail_combo.findText(self.display_currency)
            amount_spin.setSuffix("")
            amount_spin.setDecimals(2)
        if detail_combo.count() == 0:
            for curr in fallback_currencies:
                detail_combo.addItem(curr, curr)
            target_idx = detail_combo.findText(self.display_currency)
        if detail_combo.count() > 0:
            if target_idx < 0:
                target_idx = 0
            detail_combo.setCurrentIndex(target_idx)
            detail_combo.setCurrentText(detail_combo.itemText(target_idx))
            detail_combo.update()
        detail_combo.blockSignals(False)
    def _add_guest_asset_row(self) -> None:
        if not hasattr(self, "guest_asset_rows"):
            self.guest_asset_rows = []
        rows_layout = getattr(self, "guest_asset_rows_layout", None)
        if rows_layout is None:
            return
        row_widget = QWidget()
        row_layout = QHBoxLayout(row_widget)
        row_layout.setContentsMargins(0, 0, 0, 0)
        row_layout.setSpacing(8)

        type_combo = QComboBox()
        type_combo.addItem(self.lang.get("money", "Money"), "cash")
        type_combo.addItem(self.lang.get("gold", "Gold"), "gold")
        type_combo.addItem(self.lang.get("silver", "Silver"), "silver")
        type_combo.setCurrentIndex(0)
        type_combo.setMinimumWidth(140)
        theme_name = self.settings_manager.get_setting("theme_name", "Green Harmony")
        theme_data = self.all_themes.get(theme_name, {})
        field_text_color = str(theme_data.get("text_dark", "#111111"))
        combo_style = (
            f"QComboBox {{ color: {field_text_color}; }}"
            f"QComboBox QAbstractItemView {{ color: {field_text_color}; }}"
        )
        type_combo.setStyleSheet(combo_style)

        detail_combo = QComboBox()
        detail_combo.setMinimumWidth(170)
        detail_combo.setStyleSheet(combo_style)
        # İlk yüklemede boş görünmemesi için başlangıç değerlerini hemen ekle.
        for curr in (SUPPORTED_CURRENCIES if isinstance(SUPPORTED_CURRENCIES, list) and SUPPORTED_CURRENCIES else ["TRY", "USD", "EUR", "GBP"]):
            detail_combo.addItem(curr, curr)
        preferred_idx = detail_combo.findText(self.display_currency)
        if preferred_idx < 0:
            preferred_idx = 0
        if detail_combo.count() > 0:
            detail_combo.setCurrentIndex(preferred_idx)

        amount_spin = QDoubleSpinBox()
        amount_spin.setRange(0, 999999999)
        amount_spin.setButtonSymbols(QDoubleSpinBox.ButtonSymbols.NoButtons)
        amount_spin.setMinimumWidth(150)
        amount_spin.setStyleSheet(f"QDoubleSpinBox {{ color: {field_text_color}; }}")

        remove_btn = QPushButton(self.lang.get("delete", "Delete"))
        remove_btn.setObjectName("dangerButton")
        remove_btn.setMinimumWidth(70)

        row_layout.addWidget(type_combo, 2)
        row_layout.addWidget(detail_combo, 2)
        row_layout.addWidget(amount_spin, 2)
        row_layout.addWidget(remove_btn, 1)

        row = {
            "widget": row_widget,
            "type_combo": type_combo,
            "detail_combo": detail_combo,
            "amount_spin": amount_spin,
            "remove_btn": remove_btn,
        }
        self.guest_asset_rows.append(row)
        self._update_guest_asset_row_ui(row)

        type_combo.currentIndexChanged.connect(lambda _=None, r=row: self._update_guest_asset_row_ui(r))
        remove_btn.clicked.connect(lambda _=None, r=row: self._remove_guest_asset_row(r))
        rows_layout.addWidget(row_widget)
    def _remove_guest_asset_row(self, row: Dict[str, Any]) -> None:
        rows = getattr(self, "guest_asset_rows", [])
        if row not in rows:
            return
        if len(rows) <= 1:
            spin = row.get("amount_spin")
            if spin:
                spin.setValue(0)
            return
        rows.remove(row)
        widget = row.get("widget")
        if widget:
            widget.setParent(None)
            widget.deleteLater()
    def _calculate_guest_zakat(self):
        """Müşteri sekmesindeki verileri alır ve hesaplar."""
        g_debt = self.guest_debt_spin.value()
        c_debt = self.guest_debt_currency.currentText()

        asset_items: List[Dict[str, Any]] = []
        for row in getattr(self, "guest_asset_rows", []):
            type_combo = row.get("type_combo")
            detail_combo = row.get("detail_combo")
            amount_spin = row.get("amount_spin")
            if not type_combo or not detail_combo or not amount_spin:
                continue
            amount = float(amount_spin.value() or 0)
            if amount <= 0:
                continue
            item_type = str(type_combo.currentData() or "cash")
            detail_data = detail_combo.currentData()
            detail_text = str(detail_data if detail_data is not None else detail_combo.currentText()).strip()
            if not detail_text:
                continue
            asset_items.append({"type": item_type, "detail": detail_text, "amount": amount})

        self.currency_manager._fetch_all_rates()
        res = self.zakat_calculator.calculate_guest_zakat(
            debts_amount=g_debt,
            debts_currency=c_debt,
            items=asset_items,
        )
        curr = res["currency"]
        color = "#2E7D32" if res["is_reached"] else "gray"
        status = "ZEKAT FARZDIR" if res["is_reached"] else "NiSAP ALTINDA (Gerekmez)"
        html = f"""
        <h2 style="color:{color}; text-align:center;">{status}</h2>
        <table border="0" cellpadding="5" width="100%" style="font-size:14px;">
            <tr><td><b>Altın Değeri:</b></td><td align="right">{format_number(res['val_gold'])} {curr}</td></tr>
            <tr><td><b>Gümüş Değeri:</b></td><td align="right">{format_number(res['val_silver'])} {curr}</td></tr>
            <tr><td><b>Nakit Varlık:</b></td><td align="right">{format_number(res['val_cash'])} {curr}</td></tr>
            <tr><td style="color:red;"><b>Düşülen Borç:</b></td><td align="right" style="color:red;">-{format_number(res['val_debt'])} {curr}</td></tr>
            <tr><td colspan="2"><hr></td></tr>
            <tr><td><b>NET VARLIK:</b></td><td align="right"><b>{format_number(res['net_assets'])} {curr}</b></td></tr>
            <tr><td><i>Nisap Sınırı (85g):</i></td><td align="right"><i>{format_number(res['nisap_val'])} {curr}</i></td></tr>
        </table>
        """
        if res["is_reached"]:
            html += f"""
            <div style="background-color:#E8F5E9; padding:10px; margin-top:10px; border-radius:5px; text-align:center;">
                <span style="font-size:16px;">ödenmesi Gereken Miktar (%2.5):</span><br>
                <b style="font-size:24px; color:#D32F2F;">{format_number(res['zakat_due'])} {curr}</b>
            </div>
            """
        self.guest_result_label.setText(html)
    def _calculate_zakat(self):
        """
        Zekat hesaplamasını yapar ve arayüzü günceller (YENİ FORMAT).
        """
        # Tarihi Kaydet
        date_str = self.zakat_start_date.date().toString("dd.MM.yyyy")
        self.settings_manager.set_setting("zakat_nisap_date", date_str)
        
        # Hesapla
        report = self.zakat_calculator.calculate_report(date_str)
        curr = report["currency"]
        
        # Kartları Güncelle
        def set_card(card, title, value, color):
            card.findChild(QLabel, "cardTitle").setText(title)
            lbl = card.findChild(QLabel, "cardValue")
            lbl.setText(value)
            lbl.setStyleSheet(f"font-size: 16px; color: {color}; font-weight: bold;")
            
        # Toplam Has Altın + Gümüş + Nakit değerini Varlık olarak gösteriyoruz
        total_assets_val = report["net_asset_value"] + report["total_debts"] # Brüt Varlık
        
        set_card(self.lbl_zakat_assets, "Brüt Varlık", f"{format_number(total_assets_val)} {curr}", "#388E3C")
        set_card(self.lbl_zakat_debts, "Düşülecek Borçlar", f"-{format_number(report['total_debts'])} {curr}", "#D32F2F")
        set_card(self.lbl_zakat_net, "Net Zekât Matrahı", f"{format_number(report['net_asset_value'])} {curr}", "#1976D2")
        
        if report['is_reached']:
            res_text = f"{format_number(report['zakat_due'])} {curr}"
            res_color = "#D32F2F"
            msg = "ZEKÂT FARZDIR."
        else:
            res_text = "NİSAP ALTI"
            res_color = "gray"
            msg = "Nisap sınırının altındasınız."
            
        hawl = report.get('hawl_status', '-')
        set_card(self.lbl_zakat_result, f"Ödenecek ({hawl})", res_text, res_color)
        
        # Tabloyu Doldur
        table = self.zakat_table
        table.setRowCount(0)
        table.setRowCount(len(report['details']))
        
        for row, item in enumerate(report['details']):
            # Sütunlar: Grup | İsim | Ayar | Brüt | Has | Değer
            table.setItem(row, 0, QTableWidgetItem(item['group']))
            table.setItem(row, 1, QTableWidgetItem(item['name']))
            table.setItem(row, 2, QTableWidgetItem(item['karat']))
            
            # Brüt Miktar
            gross_val = item['gross']
            gross_str = f"{format_number(gross_val, 2)}"
            # Eğer nakitse birim, değilse gr/adet
            if item['group'] == "Nakit": gross_str += f" {item['karat']}" # Karat alanında para birimi var
            elif item['group'] == "Ziynet": pass # İsimde yazıyor
            else: gross_str += " gr"
            
            table.setItem(row, 3, QTableWidgetItem(gross_str))
            
            # Has Gram
            pure_val = item['pure']
            pure_str = f"{format_number(pure_val, 3)} gr" if pure_val > 0 else "-"
            table.setItem(row, 4, QTableWidgetItem(pure_str))
            
            # Değer
            val_item = QTableWidgetItem(f"{format_number(item['value'])} {curr}")
            if item['value'] < 0: val_item.setForeground(QColor("red"))
            table.setItem(row, 5, val_item)
            
        QMessageBox.information(self, "Hesaplama Tamamlandı", f"{msg}\n\nDetaylı döküm tabloya yansıtılmıştır.")
    def _update_dashboard_datetime(self):
        """
        Dashboard tarih ve saatini günceller.
        FAZ 5: Olası locale hatalarına karşı koruma eklendi.
        """
        if not hasattr(self, 'dashboard_date_label') or not self.dashboard_date_label:
            return
        try:
            locale = QLocale(QLocale.Language.Turkish, QLocale.Country.Turkey)
            now = QDateTime.currentDateTime()
            date_part = locale.toString(now, "dd MMMM yyyy dddd")
            time_part = now.toString("HH:mm:ss")
            full_str = f"{date_part} - {time_part}"
            self.dashboard_date_label.setTextFormat(Qt.TextFormat.PlainText)
            self.dashboard_date_label.setText(full_str)
        except Exception as e:
            self.dashboard_date_label.setText(datetime.now().strftime("%d.%m.%Y %H:%M:%S"))
    def _transfer_stock_to_profile(self, stock_type: str):
        """Seçili stoğu mevcut depodan düşüp, başka bir profilin seçilen deposuna ekler."""
        if stock_type not in self.stock_widgets or 'stock_table' not in self.stock_widgets[stock_type]: return
        table = self.stock_widgets[stock_type]['stock_table']
        selected_items = table.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "Uyarı", "Lütfen transfer edilecek stoğu tablodan seçin.")
            return
        row = selected_items[0].row()
        item_id = table.item(row, 0).data(Qt.ItemDataRole.UserRole)
        current_manager = self._get_stock_manager(stock_type)
        stock_item = current_manager.find_by_id(item_id)
        if not stock_item: return
        try:
            other_profiles = self._get_other_profile_names()
            if not other_profiles:
                QMessageBox.warning(self, "Uyarı", "Transfer yapılacak başka profil yok.")
                return
            target_profile_name, ok = QInputDialog.getItem(self, "Hedef Profil",
                                                         f"'{stock_item.item_type}' altınını hangi profile göndereceksiniz?",
                                                         other_profiles, 0, False)
            if not ok or not target_profile_name: return
            max_qty = stock_item.quantity
            qty_str, ok = QInputDialog.getText(self, "Miktar Girin",
                                             f"Transfer edilecek miktar (Mevcut: {max_qty:.3f}):",
                                             text=str(max_qty))
            if not ok: return
            try:
                transfer_qty = float(qty_str.replace(',', '.'))
                if transfer_qty <= 0 or transfer_qty > max_qty:
                    raise ValueError("Geçersiz miktar")
            except ValueError:
                QMessageBox.warning(self, "Hata", "Geçersiz veya yetersiz miktar girdiniz.")
                return
            temp_dm = DataManager(profile_file_path=target_profile_name)
            temp_depot_mgr = DepotManager(temp_dm)
            target_depots = temp_depot_mgr.get_depot_names()
            if not target_depots:
                target_depots = [get_default_depot_name()]
            target_depot_name, ok = QInputDialog.getItem(self, "Hedef Depo",
                                                       f"'{target_profile_name}' profilindeki hangi depoya eklensin?",
                                                       target_depots, 0, False)
            if not ok or not target_depot_name: return
            current_manager.remove_stock(stock_item.item_type, transfer_qty, stock_item.depot_name)
            temp_stock_mgr = StockManager(temp_dm, stock_type)
            cost_price = stock_item.purchase_price_base
            temp_stock_mgr.add_or_update_stock(stock_item.item_type, transfer_qty, cost_price, target_depot_name)
            self.update_all_displays()
            msg = f"Transfer Başarılı!\n\n" \
                  f"Altın: {stock_item.item_type}\n" \
                  f"Miktar: {transfer_qty:.3f}\n" \
                  f"Kaynak: {self.profile_name} ({stock_item.depot_name})\n" \
                  f"Hedef: {target_profile_name} ({target_depot_name})"
            QMessageBox.information(self, "Başarılı", msg)
        except Exception as e:
            logger.error(f"Profil arası stok transfer hatası: {e}")
            QMessageBox.critical(self, "Hata", f"Transfer sırasında hata oluştu: {e}")
    def _open_bulk_barcode_dialog(self):
        """Toplu barkod penceresini açar."""
        if not HAS_REPORTLAB:
            QMessageBox.critical(self, "Hata", "Bu özellik için 'reportlab' kütüphanesi gereklidir.\nKurulum: pip install reportlab")
            return
        dialog = BulkBarcodeDialog(self.product_manager, self.stock_manager, self.lang, self)
        dialog.exec()
    def _open_barcode_query_dialog(self):
        """Detaylı barkod sorgulama penceresini açar."""
        from modules.stock import BarcodeQueryDialog
        dialog = BarcodeQueryDialog(
            stock_manager=self.stock_manager,
            sale_manager=self.sale_manager,
            product_manager=self.product_manager,
            currency_manager=self.currency_manager,
            stock_movement_manager=self.stock_movement_manager,
            barcode_history_manager=self.barcode_history_manager, # <-- BURASI DA EKLENMELi
            lang=self.lang,
            parent=self
        )
        dialog.exec()
    def _copy_definitions_to_profile(self, stock_type: str):
        """Mevcut profildeki altın/hurda tanımlarını seçilen başka bir profile kopyalar."""
        try:
            other_profiles = self._get_other_profile_names()
            if not other_profiles:
                QMessageBox.warning(self, "Uyarı", "Aktarım yapılacak başka bir profil bulunamadı.")
                return
            target_profile_name, ok = QInputDialog.getItem(self, "Profil Seç",
                                                         "Tanımların kopyalanacağı hedef profili seçin:",
                                                         other_profiles, 0, False)
            if not ok or not target_profile_name: return
            source_manager = self.product_manager if stock_type == "Product" else self.scrap_manager
            temp_dm = DataManager(profile_file_path=target_profile_name)
            temp_pm = ProductDefinitionManager(temp_dm, stock_type)
            source_items = source_manager.get_all()
            if not source_items:
                QMessageBox.warning(self, "Uyarı", "Kopyalanacak tanım bulunamadı.")
                return
            existing_names_target = set(p.product_name.lower() for p in temp_pm.get_all())
            added_count = 0
            skipped_count = 0
            for item in source_items:
                if item.product_name.lower() in existing_names_target:
                    skipped_count += 1
                    continue
                item_dict = item.__dict__.copy()
                if 'id' in item_dict: del item_dict['id']
                if 'created_at' in item_dict: del item_dict['created_at']
                if 'updated_at' in item_dict: del item_dict['updated_at']
                new_def = ProductDefinition(**item_dict)
                temp_pm.add_item(new_def)
                added_count += 1
            QMessageBox.information(self, "Başarılı",
                                    f"işlem Tamamlandı.\n\n"
                                    f"Hedef: {target_profile_name}\n"
                                    f"Kopyalanan: {added_count}\n"
                                    f"Atlanan (Zaten Var): {skipped_count}")
        except Exception as e:
            logger.error(f"Tanım kopyalama hatası: {e}")
            QMessageBox.critical(self, "Hata", f"işlem sırasında hata oluştu: {e}")
    def _get_accessible_cash_accounts(self) -> List[CashAccount]:
        """O anki kullanıcının yetkili olduğu nakit kasaları döndürür."""
        all_accounts = self.cash_account_manager.get_all()
        if self.current_user["role"] == "admin":
            return all_accounts
        allowed_ids = self.current_user.get("allowed_cash_accounts", [])
        if not allowed_ids:
            return []
        return [acc for acc in all_accounts if acc.id in allowed_ids]
    def _get_accessible_card_accounts(self) -> List[CardAccount]:
        """O anki kullanıcının yetkili olduğu kart hesaplarını döndürür."""
        all_accounts = self.card_account_manager.get_all()
        if self.current_user["role"] == "admin":
            return all_accounts
        allowed_ids = self.current_user.get("allowed_card_accounts", [])
        if not allowed_ids:
            return []
        return [acc for acc in all_accounts if acc.id in allowed_ids]
    def _process_cari_payment_with_product(self, debt_record: DebtRecord, payment_dialog: PaymentDialog):
        """
        CARi BORCU üRüNLE öDEME
        Kullanım: Müşteri Cari sekmesinden "ödeme Yap" dediğinde
        Mantık:
        1. ürün miktar ve depo bilgisi al
        2. Stok hareketi yap (Al/Ver)
        3. Borcu düş
        4. Müşteri geçmişine yaz
        FAZ 3 DüZELTME:
        - Alacak tahsilatında (Receivable) müşteriden ürün alırız -> ALIŞ FiYATI kullanılır.
        - Borç ödemesinde (Payable) müşteriye ürün veririz -> SATIŞ FiYATI kullanılır.
        """
        payment_breakdown = payment_dialog.get_payment_breakdown()
        for payment in payment_breakdown:
            if payment['method_type'] == 'product':
                product_name = payment['product_name']
                quantity = payment['quantity']
                depot_name = payment['depot_name']
                if debt_record.debt_type == "Cash":
                    product_def = self.product_manager.find_by_attribute('product_name', product_name)
                    if not product_def:
                        QMessageBox.warning(self, "Hata", f"Altın tanımı bulunamadı: {product_name}")
                        continue
                    if debt_record.record_type == "Receivable":
                        target_price = product_def.default_purchase_price
                        target_currency = product_def.purchase_currency
                    else:
                        target_price = product_def.default_sale_price
                        target_currency = product_def.sale_currency
                    unit_value_base = self.currency_manager._convert_to_base_currency(
                        target_price, target_currency
                    )
                    payment_value_base = quantity * unit_value_base
                else:
                    payment_value_base = (debt_record.total_amount_base / debt_record.product_quantity) * quantity
                if debt_record.record_type == "Receivable":
                    self.stock_manager.add_or_update_stock(
                        product_name, quantity, unit_value_base, depot_name
                    )
                else:
                    if not self.stock_manager.remove_stock(product_name, quantity, depot_name):
                        QMessageBox.warning(self, "Hata", "Yetersiz stok!")
                        continue
                debt_record.paid_amount_base += payment_value_base
                if debt_record.paid_amount_base >= debt_record.total_amount_base - 0.01:
                    debt_record.is_paid = True
                    debt_record.payment_date = datetime.now().strftime("%d.%m.%Y")
                self.debt_manager.update_item(debt_record.id, {
                    "paid_amount_base": debt_record.paid_amount_base,
                    "is_paid": debt_record.is_paid,
                    "payment_date": debt_record.payment_date,
                })
                self.customer_history_manager.add_item(CustomerHistoryEvent(
                    person_full_name=debt_record.person_full_name,
                    event_date=datetime.now().isoformat(),
                    event_type="ödeme (Altınla)",
                    credit_original=0 if debt_record.record_type == "Receivable" else payment_value_base,
                    debt_original=payment_value_base if debt_record.record_type == "Receivable" else 0,
                    currency=debt_record.currency,
                    description=f"{quantity:.3f} {product_name} ({depot_name})",
                    related_id=debt_record.id
                ))
        self.update_all_displays()
    def _report_this_year(self):
        self.report_start_date_entry.setDate(QDate(datetime.now().year, 1, 1))
        self.report_end_date_entry.setDate(datetime.now().date())
        self.report_manager._filter_report()
    def _create_float_validator(self, precision=2) -> QRegularExpressionValidator:
        """Accepts both comma and dot as decimal separators."""
        if precision <= 0:
            pattern = r"^\d*$"
        else:
            pattern = rf"^$|^\d+([.,]\d{{0,{precision}}})?$"
        return QRegularExpressionValidator(QRegularExpression(pattern))
    def _create_date_validator(self) -> QRegularExpressionValidator:
        """Creates a QRegularExpressionValidator for DD.MM.YYYY date format."""
        regex = QRegularExpression(r"^(0[1-9]|[12][0-9]|3[01])\.(0[1-9]|1[012])\.\d{4}$")
        return QRegularExpressionValidator(regex)
    def _show_confirm_dialog(self, title: str, message: str) -> bool:
        """Shows a confirmation dialog with localized Yes/No buttons."""
        msg_box = QMessageBox(self)
        msg_box.setWindowTitle(title)
        msg_box.setText(message)
        msg_box.setIcon(QMessageBox.Icon.Question)
        yes_button = msg_box.addButton(self.lang["yes"], QMessageBox.ButtonRole.YesRole)
        msg_box.addButton(self.lang["no"], QMessageBox.ButtonRole.NoRole)
        msg_box.exec()
        return msg_box.clickedButton() == yes_button
    def get_float_from_entry(
        self,
        line_edit_widget: QLineEdit,
        field_name: str,
        allow_zero: bool = True,
        precision: Optional[int] = None,
    ) -> Optional[float]:
        """
        Retrieves a float value from a QLineEdit.
        FAZ 8: Akıllı format temizleme (1.234,56 veya 1,234.56 desteği).
        """
        raw_text = line_edit_widget.text().strip()
        if not raw_text:
            if field_name:
                QMessageBox.warning(self, self.lang["missing_info"], f"'{field_name}' {self.lang['missing_info'].lower()}.")
                line_edit_widget.setFocus()
            return None
        try:
            normalized_text = raw_text.replace(",", ".")
            parsed_decimal = calc_to_decimal(normalized_text, default=None)
            if parsed_decimal is None:
                parsed_decimal = calc_to_decimal(raw_text, default=None)
            if parsed_decimal is None:
                raise ValueError
            value = parse_decimal_input(str(parsed_decimal), allow_zero=allow_zero)
            if precision is not None:
                value = round(value, precision)
            return value
        except ValueError:
            QMessageBox.critical(self, self.lang["error"], f"'{field_name}' {self.lang['invalid_value'].lower()}. Geçerli bir sayı giriniz.\n(Girdiğiniz: {raw_text})")
            line_edit_widget.setFocus()
            return None
    def get_date_from_entry(self, line_edit_widget: Union[QLineEdit, QDateEdit], field_name: str, is_optional: bool = False) -> Optional[str]:
        """ Retrieves a validated date string in DD.MM.YYYY format. """
        date_str = ""
        if isinstance(line_edit_widget, QLineEdit):
            date_str = line_edit_widget.text().strip()
        elif isinstance(line_edit_widget, QDateEdit):
            date_str = line_edit_widget.date().toString("dd.MM.yyyy")
        if not date_str:
            if is_optional: return ""
            QMessageBox.warning(self, self.lang["missing_info"], f"'{field_name}' {self.lang['missing_info'].lower()}.")
            line_edit_widget.setFocus()
            return None
        try:
            datetime.strptime(date_str, "%d.%m.%Y")
            return date_str
        except ValueError:
            QMessageBox.critical(self, self.lang["invalid_format"], f"'{field_name}' {self.lang['invalid_format'].lower()}. Expected DD.MM.YYYY.")
            line_edit_widget.setFocus()
            return None

    def _sync_expired_warning_text(self) -> str:
        return "Senkronizasyon Süreniz Doldu - Yerel Modda Çalışılıyor. Telefon uygulaması çalışmayacaktır."

    def _refresh_sync_status_ui(self, show_popup: bool = False) -> None:
        tm = self.tenant_manager or TenantManager()
        message = self._sync_expired_warning_text()
        is_expired = tm.get_sync_status() == "expired"

        label = getattr(self, "_sync_status_label", None)
        if label is not None:
            label.setText(message if is_expired else "")
            label.setVisible(is_expired)

        sync_status_display = getattr(self, "current_sync_status_display", None)
        if sync_status_display is not None:
            sync_status_display.setText(tm.get_sync_status())
        sync_expiry_display = getattr(self, "current_sync_expiry_display", None)
        if sync_expiry_display is not None:
            sync_expiry_display.setText(tm.get_sync_expiry_display())

        pair_btn = getattr(self, "pair_new_device_btn", None)
        if pair_btn is not None:
            web_enabled = tm.is_web_mobile_enabled()
            pair_btn.setEnabled(web_enabled)
            if web_enabled:
                pair_btn.setToolTip("")
            elif is_expired:
                pair_btn.setToolTip(message)
            else:
                pair_btn.setToolTip("Bu lisans planinda telefon/web erisimi kapali.")

        if is_expired:
            try:
                self._show_status_message(message, 15000)
            except Exception:
                pass
            if show_popup and not self._sync_expired_notice_shown:
                self._sync_expired_notice_shown = True
                try:
                    QMessageBox.warning(self, "Yerel Mod", message)
                except Exception:
                    pass

    def _on_sync_expired(self, _reason: str) -> None:
        self._refresh_sync_status_ui(show_popup=True)

    def _start_sync_worker(self) -> None:
        try:
            tm = self.tenant_manager or TenantManager()
            if not tm.is_profile_sync_enabled():
                self._refresh_sync_status_ui(show_popup=False)
                logger.info("Bulut senkronizasyonu kapali. Sync worker baslatilmadi.")
                return
            
            # İnternet bağlantısını hızlıca kontrol et
            is_offline = False
            try:
                import requests
                requests.get("https://app.golder.biz", timeout=3)
            except Exception:
                is_offline = True

            if not tm.get_api_token() or is_offline:
                logger.warning("Bulut oturumu kullanilamiyor veya internet yok.")
                QTimer.singleShot(600, self._show_sync_deferred_notice)
                # İnternet olmasa da Worker'ı başlatıyoruz ki arka planda beklesin, internet gelince verileri göndersin.

            worker = getattr(self, "_sync_worker", None)
            if worker is not None and worker.isRunning():
                return
            self._sync_worker = SyncWorker(self.data_manager.db, self.data_manager.profile_id, parent=self)
            self._sync_worker.events_applied.connect(self._on_sync_events_applied)
            self._sync_worker.profile_deleted.connect(self._on_profile_deleted)
            self._sync_worker.sync_expired.connect(self._on_sync_expired)
            self._sync_worker.start()
        except Exception as e:
            logger.error(f"Sync worker could not be started: {e}", exc_info=True)

    def _show_sync_deferred_notice(self) -> None:
        if getattr(self, "_sync_deferred_notice_shown", False):
            return
        self._sync_deferred_notice_shown = True
        
        # Tam istediğiniz mesaj formatı
        message = (
            "İnternet bağlantısı kesildi veya sunucuya ulaşılamıyor.\n\n"
            "Uygulama ÇEVRİMDIŞI (Offline) olarak tam kapasiteyle çalışmaya devam edecektir.\n"
            "Şu anlık senkronizasyon yapılmayacaktır. İşlemleriniz güvenle kaydedilecek ve "
            "internet bağlantısı sağlandığında otomatik olarak eşitlenecektir."
        )
        try:
            self._show_status_message("İnternet yok. Çevrimdışı çalışılıyor, senkronizasyon beklemede.", 15000)
        except Exception:
            pass
        try:
            QMessageBox.information(self, "Çevrimdışı Mod", message)
        except Exception:
            pass
    def _show_sync_deferred_notice(self) -> None:
        if self._sync_deferred_notice_shown:
            return
        self._sync_deferred_notice_shown = True
        message = (
            "Internet baglantisi veya bulut oturumu su anda kullanilamiyor. "
            "Uygulama cevrimdisi calismaya devam edecek; senkronizasyon simdilik yapilmayacak."
        )
        try:
            self._show_status_message(message, 15000)
        except Exception:
            pass
        try:
            QMessageBox.information(self, "Cevrimdisi Calisma", message)
        except Exception:
            pass
    def _schedule_auto_backup(self) -> None:
        try:
            from core.tenant_manager import TenantManager

            if not TenantManager().can_use_backup_features():
                return
        except Exception:
            return

        def _run_backup() -> None:
            try:
                from core.backup_service import maybe_run_auto_backup

                maybe_run_auto_backup(self.data_manager.db, self.data_manager.profile_id)
            except Exception as exc:
                logger.warning("Auto backup failed: %s", exc)

        def _start_thread() -> None:
            import threading

            threading.Thread(target=_run_backup, daemon=True).start()

        QTimer.singleShot(2000, _start_thread)
    def _check_conflict_logs(self) -> None:
        try:
            rows = self.data_manager.db.fetch_all(
                "SELECT id, message FROM conflict_logs WHERE is_read = 0 ORDER BY created_at ASC"
            ) or []
            if not rows:
                return
            QMessageBox.critical(
                self,
                "Senkronizasyon Çakışması",
                "DİKKAT: Cihazlar arası çakışma tespit edildi. İşleminiz merkez tarafından iptal edildi!",
            )
            ids = [int(r.get("id")) for r in rows if r.get("id") is not None]
            if not ids:
                return
            placeholders = ", ".join(["%s"] * len(ids))
            query = f"UPDATE conflict_logs SET is_read = 1 WHERE id IN ({placeholders})"
            self.data_manager.db.execute_non_query(query, tuple(ids))
        except Exception as e:
            logger.error(f"Conflict log check failed: {e}", exc_info=True)
    def _shutdown_runtime_services(self) -> None:
        """Stops timers/background workers before app exit or logout."""
        try:
            self.tenant_manager.record_last_run_time()
        except Exception as exc:
            logger.warning("Last run time kaydedilemedi: %s", exc)
        if hasattr(self, 'datetime_timer'):
            self.datetime_timer.stop()
        if hasattr(self, 'rates_timer'):
            self.rates_timer.stop()
        if hasattr(self, 'dashboard_data_timer'):
            self.dashboard_data_timer.stop()
        if hasattr(self, "conflict_check_timer"):
            self.conflict_check_timer.stop()
        try:
            while getattr(self, "_loading_depth", 0) > 0:
                self._hide_loading()
        except Exception:
            pass
        worker = getattr(self, "_dashboard_worker", None)
        if worker is not None and worker.isRunning():
            try:
                worker.requestInterruption()
                if not worker.wait(5000):
                    logger.warning("Dashboard worker did not stop in time. Terminating thread.")
                    worker.terminate()
                    worker.wait(1000)
            except Exception:
                pass
        tx_worker = getattr(self, "_tx_worker", None)
        if tx_worker is not None and tx_worker.isRunning():
            try:
                if not tx_worker.wait(5000):
                    logger.warning("Transaction worker did not stop in time. Terminating thread.")
                    tx_worker.terminate()
                    tx_worker.wait(1000)
            except Exception:
                pass
        sync_worker = getattr(self, "_sync_worker", None)
        if sync_worker is not None and sync_worker.isRunning():
            try:
                sync_worker.requestInterruption()
                if not sync_worker.wait(5000):
                    logger.warning("Sync worker did not stop in time. Terminating thread.")
                    sync_worker.terminate()
                    sync_worker.wait(1000)
            except Exception:
                pass

    def closeEvent(self, event):
        """Uygulama kapatılırken güvenli çıkış."""
        if getattr(self, "_is_logging_out", False):
            self._shutdown_runtime_services()
            logger.info("User logged out. Returning to login screen.")
            event.accept()
            return
        if self._show_confirm_dialog(self.lang["confirm_exit"], self.lang["are_you_sure_exit"]):
            self._shutdown_runtime_services()
            logger.info("Application closing gracefully.")
            event.accept()
        else:
            event.ignore()
    def keyPressEvent(self, event: QKeyEvent):
        """Klavye kısayolları (1-6) ile sayfa değişimi ve Hesap Makinesi entegrasyonu."""
        if self.tabs.currentIndex() == 0:
            key_tab_map = {
                Qt.Key.Key_1: 1,  # ALIŞVERiŞ
                Qt.Key.Key_2: 2,  # PARA iŞLEMLERi
                Qt.Key.Key_3: 3,  # STOK iŞLEMLERi
                Qt.Key.Key_4: 4,  # MüŞTERi iŞLEMLERi
                Qt.Key.Key_5: 5,  # HiZMETLER
                Qt.Key.Key_6: 6   # ARAçLAR
            }
            if event.key() in key_tab_map:
                self.tabs.setCurrentIndex(key_tab_map[event.key()])
                event.accept()
                return
            elif event.key() == Qt.Key.Key_Escape:
                event.accept()
                return
        if hasattr(self, 'tools_tab_widget') and self.tabs.currentWidget() == self.tools_tab_widget:
             tools_widget = self.tabs.currentWidget()
             if isinstance(tools_widget, QTabWidget):
                 current_sub = tools_widget.currentWidget()
                 if current_sub and current_sub.findChild(QLineEdit, "calc_display"):
                     key_map = {
                        Qt.Key.Key_Enter: '=', Qt.Key.Key_Return: '=',
                        Qt.Key.Key_Backspace: '<-', Qt.Key.Key_Delete: 'C',
                        Qt.Key.Key_0: '0', Qt.Key.Key_1: '1', Qt.Key.Key_2: '2', Qt.Key.Key_3: '3',
                        Qt.Key.Key_4: '4', Qt.Key.Key_5: '5', Qt.Key.Key_6: '6', Qt.Key.Key_7: '7',
                        Qt.Key.Key_8: '8', Qt.Key.Key_9: '9',
                        Qt.Key.Key_Plus: '+', Qt.Key.Key_Minus: '-', Qt.Key.Key_Asterisk: '*', Qt.Key.Key_Slash: '/',
                        Qt.Key.Key_Period: '.', Qt.Key.Key_Comma: '.',
                        Qt.Key.Key_ParenLeft: '(', Qt.Key.Key_ParenRight: ')'
                    }
                     if event.key() in key_map:
                        self.utility_manager._on_calc_button_click(key_map[event.key()])
                        event.accept()
                        return
        if event.key() == Qt.Key.Key_Escape:
            self.tabs.setCurrentIndex(0)
            event.accept()
        else:
            super().keyPressEvent(event)
    def _populate_theme_combo(self):
        """Populates the theme combobox with only built-in and custom themes."""
        self.theme_combo.clear()
        
        # 1. Dahili ve özel temaları yükle
        self.all_themes = {**APP_THEMES, **self.custom_theme_manager.get_themes()}
        self.theme_combo.addItems(sorted(self.all_themes.keys()))
        
        # 2. Ayırıcı ve 'Yeni Tema Oluştur' seçeneğini ekle
        self.theme_combo.insertSeparator(len(self.all_themes))
        self.theme_combo.addItem(f"--- {self.lang['create_new_theme']} ---", "CREATE_NEW")
        
        # 3. SİSTEM TEMALARI KALDIRILDI
        # QStyleFactory.keys() içeren satırlar tamamen silindi.
        
        # 4. Mevcut temayı güvenli bir şekilde seç
        current_theme = self.settings_manager.get_setting("theme_name", "Green Harmony")
        
        # Eğer kaydedilmiş tema artık listede yoksa (örn: 'windows11' ise), varsayılana dön
        if current_theme not in self.all_themes:
            current_theme = "Green Harmony"
            self.settings_manager.set_setting("theme_name", current_theme) # Ayarı da düzelt

        self.theme_combo.setCurrentText(current_theme)
    def _on_theme_selected(self, index):
        """Handles selection in the theme combobox, opening the creator if needed."""
        if self.theme_combo.itemData(index) == "CREATE_NEW":
            self._show_create_theme_dialog()
            current_theme = self.settings_manager.get_setting("theme_name", "Green Harmony")
            self.theme_combo.setCurrentText(current_theme)
        else:
            selected_theme = self.theme_combo.currentText().strip()
            if selected_theme == "" or selected_theme not in self.all_themes:
                return
            current_theme = str(self.settings_manager.get_setting("theme_name", "Green Harmony") or "Green Harmony")
            if selected_theme == current_theme:
                self._update_theme_buttons_state()
                return
            self.settings_manager.set_setting("theme_name", selected_theme)
            self.apply_theme()
            self._update_theme_buttons_state()
    def _show_create_theme_dialog(self):
        """Opens the theme creation dialog and handles the result."""
        dialog = CreateThemeDialog(self.lang, self)
        if dialog.exec():
            theme_data = dialog.get_theme_data()
            if theme_data:
                name = theme_data["name"]
                theme_dict = theme_data["theme"]
                self.custom_theme_manager.save_theme(name, theme_dict)
                self.all_themes[name] = theme_dict
                self._save_custom_themes_setting()
                self._populate_theme_combo()
                self.theme_combo.setCurrentText(name)
                self.settings_manager.set_setting("theme_name", name)
                self.apply_theme()
                QMessageBox.information(self, self.lang["success"], self.lang["theme_saved"].format(name=name))
    def _edit_selected_theme(self):
        theme_name = self.theme_combo.currentText()
        if theme_name in self.custom_theme_manager.get_themes():
            theme_data = self.custom_theme_manager.get_themes()[theme_name]
            dialog = CreateThemeDialog(self.lang, self, name=theme_name, theme_data=theme_data)
            if dialog.exec():
                new_theme_data = dialog.get_theme_data()
                if new_theme_data:
                    if theme_name != new_theme_data["name"]:
                        self.custom_theme_manager.delete_theme(theme_name)
                    new_name = new_theme_data["name"]
                    new_theme_dict = new_theme_data["theme"]
                    self.custom_theme_manager.save_theme(new_name, new_theme_dict)
                    self.all_themes = {**APP_THEMES, **self.custom_theme_manager.get_themes()}
                    self._save_custom_themes_setting()
                    self._populate_theme_combo()
                    self.theme_combo.setCurrentText(new_name)
                    self.settings_manager.set_setting("theme_name", new_name)
                    self.apply_theme()
    def _delete_selected_theme(self):
        if self.current_user["role"] != "admin" and "delete_all" not in self.current_user.get("permissions", []):
            QMessageBox.critical(self, "Yetki Hatası", "Bu kaydı silmeye yetkiniz yok.\n(Yönetici onayı gerekir)")
            return
        theme_name = self.theme_combo.currentText()
        if theme_name in self.custom_theme_manager.get_themes():
            if self._show_confirm_dialog(self.lang["delete_theme"], self.lang["confirm_delete_theme"].format(name=theme_name)):
                self.custom_theme_manager.delete_theme(theme_name)
                self.all_themes = {**APP_THEMES, **self.custom_theme_manager.get_themes()}
                self._save_custom_themes_setting()
                self._populate_theme_combo()
                self.settings_manager.set_setting("theme_name", "Green Harmony")
                self.apply_theme()
    def _update_theme_buttons_state(self):
        is_custom = self.theme_combo.currentText() in self.custom_theme_manager.get_themes()
        self.edit_theme_btn.setEnabled(is_custom)
        self.delete_theme_btn.setEnabled(is_custom)
    def _logout_current_user(self) -> None:
        """Logs out tenant/cloud session and forces fresh main login."""
        if not self._show_confirm_dialog(
            "Ana Kullanıcı Çıkışı",
            "MySQL ana kullanıcı oturumundan çıkılsın mı?\nUygulama yeniden açılacak ve ana giriş ekranı gelecektir.",
        ):
            return
        try:
            self.tenant_manager.logout_current_desktop_session()
        except Exception as e:
            QMessageBox.warning(
                self,
                "Cikis Tamamlanamadi",
                "Sunucuya masaustu cikisi bildirilemedi. Bu nedenle PC hakkiniz hemen bosalmamis olabilir.\n\n"
                f"Detay: {e}",
            )
            return
        self._is_logging_out = True
        self._shutdown_runtime_services()
        self.hide()
        QApplication.instance().exit(4)
    def _switch_profile(self):
        """Restarts the application to the profile selection screen."""
        if self._show_confirm_dialog(self.lang["switch_profile"], self.lang["profile_switch_restart_confirm"]):
            QApplication.instance().exit(2)
    def _print_pos_invoice(self, stock_type: str, transaction_type: str) -> None:
        """
        Merkezi belge servisi ile POS ekranından fatura/fiş üretir.
        Not: Eski 80mm termal fiş çıktısı `_print_pos_invoice_legacy` içinde korunur.
        """
        pos_key = f"{stock_type}_{transaction_type}"
        cart = self.pos_carts.get(pos_key, [])
        widgets = self.stock_widgets[stock_type][transaction_type]
        if not cart:
            msg = QMessageBox(self)
            msg.setIcon(QMessageBox.Icon.Warning)
            msg.setWindowTitle(self.lang.get("warning", "Warning"))
            msg.setText(self.lang.get("empty_cart_no_document", "Cart is empty, document cannot be created."))
            ok_btn = msg.addButton(self.lang.get("ok", "OK"), QMessageBox.ButtonRole.AcceptRole)
            ok_btn.setDefault(True)
            msg.exec()
            return
        options = {
            self.lang.get("document_type_invoice_a4", "Invoice (A4)"): "invoice",
            self.lang.get("document_type_receipt_a4", "Retail Receipt (A4)"): "receipt",
            self.lang.get("document_type_thermal_legacy", "Thermal Receipt (Legacy)"): "legacy",
        }
        choice_dialog = QInputDialog(self)
        choice_dialog.setWindowTitle(self.lang.get("document_type_title", "Document Type"))
        choice_dialog.setLabelText(self.lang.get("document_type_prompt", "Select the document type to print:"))
        choice_dialog.setComboBoxItems(list(options.keys()))
        choice_dialog.setOkButtonText(self.lang.get("ok", "OK"))
        choice_dialog.setCancelButtonText(self.lang.get("cancel", "Cancel"))
        if not choice_dialog.exec():
            return
        choice = choice_dialog.textValue()
        selected_type = options.get(choice)
        if selected_type == "legacy":
            self._print_pos_invoice_legacy(stock_type, transaction_type)
            return
        doc_type = selected_type or "invoice"
        customer_name = ""
        try:
            customer_name = widgets["person_combo"].currentText().strip()
        except Exception:
            customer_name = ""
        currency = self._get_cart_currency(cart)
        note = "Satış" if transaction_type == "Sale" else "Alış"
        note_full = f"{stock_type} {note}"

        tax_snapshot = self._build_pos_tax_snapshot(stock_type, transaction_type, cart, widgets)
        currency = str(tax_snapshot.get("currency") or currency or self.display_currency)
        source = "sales" if transaction_type == "Sale" else "stock"
        if stock_type == "Scrap":
            source = "scrap"
        elif stock_type in ("Product", "Ziynet"):
            source = "gold" if transaction_type == "Sale" else "stock"
        transaction_data = {
            "source": source,
            "customer": {"name": customer_name} if customer_name else {},
            "currency": currency,
            "payment_method": "",
            "note": note_full,
            "tax_snapshot": tax_snapshot,
        }
        if not hasattr(self, "document_service") or not self.document_service:
            QMessageBox.critical(self, "Hata", "Belge servisi başlatılamadı (document_service yok).")
            return
        self.document_service.create_document_from_transaction(transaction_data, doc_type)
    def _print_pos_invoice_legacy(self, stock_type: str, transaction_type: str):
        """
        Satış/Alış ekranındaki sepet içeriğini 80mm Termal Fiş Yazıcısı formatında basar.
        """
        if not HAS_REPORTLAB:
            QMessageBox.critical(self, "Hata", "Fiş yazdırmak için 'reportlab' kütüphanesi gerekli.\nLütfen kurun: pip install reportlab")
            return
        pos_key = f"{stock_type}_{transaction_type}"
        cart = self.pos_carts.get(pos_key, [])
        widgets = self.stock_widgets[stock_type][transaction_type]
        if not cart:
            msg = QMessageBox(self)
            msg.setIcon(QMessageBox.Icon.Warning)
            msg.setWindowTitle(self.lang.get("warning", "Warning"))
            msg.setText(self.lang.get("empty_cart_no_receipt", "Cart is empty, receipt cannot be created."))
            ok_btn = msg.addButton(self.lang.get("ok", "OK"), QMessageBox.ButtonRole.AcceptRole)
            ok_btn.setDefault(True)
            msg.exec()
            return
        shop_name = self.settings_manager.get_setting("shop_name", "GOLDER KUYUMCU MUHASEBE PROGRAMI")
        shop_phone = self.settings_manager.get_setting("shop_phone", "")
        safe_shop_name = shop_name.replace("i","I").replace("ı","i").replace("Ş","S").replace("ş","s").replace("Ä","G").replace("ğ","g")
        person_name = widgets['person_combo'].currentText().strip() or "Musteri"
        safe_person_name = person_name.replace("i","I").replace("ı","i").replace("Ş","S").replace("ş","s").replace("Ä","G").replace("ğ","g")
        total_str = widgets['total_amount_label'].text()
        PAGE_WIDTH = 80 * mm
        MARGIN = 2 * mm
        estimated_height = (70 + (len(cart) * 12) + 40) * mm
        filename = f"Fis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
        save_path, _ = QFileDialog.getSaveFileName(self, "Fiş Kaydet", filename, "PDF Files (*.pdf)")
        if not save_path: return
        try:
            c = canvas.Canvas(save_path, pagesize=(PAGE_WIDTH, estimated_height))
            font_name = "Helvetica"
            y = estimated_height - 15 * mm
            c.setFont(font_name + "-Bold", 12)
            c.drawCentredString(PAGE_WIDTH / 2, y, safe_shop_name)
            y -= 5 * mm
            c.setFont(font_name, 8)
            if shop_phone:
                c.drawCentredString(PAGE_WIDTH / 2, y, f"Tel: {shop_phone}")
            y -= 8 * mm
            c.setLineWidth(0.5)
            c.line(MARGIN, y, PAGE_WIDTH - MARGIN, y)
            y -= 5 * mm
            c.setFont(font_name, 8)
            c.drawString(MARGIN, y, f"Tarih: {datetime.now().strftime('%d.%m.%Y %H:%M')}")
            y -= 4 * mm
            disp_name = (safe_person_name[:20] + '..') if len(safe_person_name) > 20 else safe_person_name
            c.drawString(MARGIN, y, f"Sayin: {disp_name}")
            y -= 4 * mm
            is_sale = (transaction_type == "Sale")
            type_lbl = "Satis Fisi" if is_sale else "Alis Fisi"
            c.drawString(MARGIN, y, f"Islem: {type_lbl}")
            y -= 6 * mm
            c.setFont(font_name + "-Bold", 7)
            c.drawString(MARGIN, y, "URUN")
            c.drawRightString(PAGE_WIDTH - MARGIN - 20*mm, y, "MIK x FIYAT")
            c.drawRightString(PAGE_WIDTH - MARGIN, y, "TUTAR")
            y -= 2 * mm
            c.line(MARGIN, y, PAGE_WIDTH - MARGIN, y)
            y -= 4 * mm
            c.setFont(font_name, 7)
            for item in cart:
                p_name = item['type'].replace("i","I").replace("ı","i").replace("Ş","S").replace("ş","s").replace("Ä","G").replace("ğ","g")
                qty = item['quantity']
                price = item['unit_price']
                subtotal = item['subtotal']
                p_name_short = (p_name[:18] + '..') if len(p_name) > 18 else p_name
                c.drawString(MARGIN, y, p_name_short)
                detail_str = f"{format_number(qty, 3)} x {format_number(price)}"
                c.drawRightString(PAGE_WIDTH - MARGIN - 20*mm, y, detail_str)
                total_str_item = f"{format_number(subtotal)}"
                c.drawRightString(PAGE_WIDTH - MARGIN, y, total_str_item)
                y -= 4 * mm
            y -= 2 * mm
            c.line(MARGIN, y, PAGE_WIDTH - MARGIN, y)
            y -= 6 * mm
            c.setFont(font_name + "-Bold", 12)
            c.drawString(MARGIN, y, "TOPLAM:")
            c.drawRightString(PAGE_WIDTH - MARGIN, y, total_str)
            y -= 10 * mm
            c.setFont(font_name, 7)
            c.drawCentredString(PAGE_WIDTH / 2, y, "Bizi tercih ettiginiz icin")
            y -= 3 * mm
            c.drawCentredString(PAGE_WIDTH / 2, y, "tesekkur ederiz.")
            c.showPage()
            c.save()
            if sys.platform == 'win32':
                os.startfile(save_path)
            else:
                import subprocess
                opener = 'open' if sys.platform == 'darwin' else 'xdg-open'
                subprocess.call([opener, save_path])
        except Exception as e:
            logger.error(f"Fis basma hatasi: {e}")
            QMessageBox.critical(self, "Hata", f"Fis olusturulamadi: {e}")
    def _update_calendar_markers(self, year=None, month=None):
        """Visually marks dates on the calendar that have notes for the visible month."""
        if not hasattr(self, 'calendar') or self.calendar is None:
            return
        try:
            if self.calendar.isHidden() and not self.calendar.isVisible():
                 _ = self.calendar.yearShown()
        except RuntimeError:
            return
        note_format = QTextCharFormat()
        note_format.setFontWeight(QFont.Weight.Bold)
        reminder_format = QTextCharFormat()
        reminder_format.setFontWeight(QFont.Weight.Bold)
        reminder_format.setForeground(QColor("red"))
        default_format = QTextCharFormat()
        try:
            start_date = QDate(self.calendar.yearShown(), self.calendar.monthShown(), 1)
            end_date = start_date.addMonths(1).addDays(-1)
            current_date = start_date
            while current_date <= end_date:
                date_str = current_date.toString("dd.MM.yyyy")
                note_data = self.calendar_manager.get_note_data(date_str)
                if note_data.get("html", "").strip():
                    if note_data.get("is_reminder"):
                        self.calendar.setDateTextFormat(current_date, note_format)
                else:
                    self.calendar.setDateTextFormat(current_date, default_format)
                current_date = current_date.addDays(1)
        except RuntimeError:
            pass
    def _print_selected_barcode_label(self, stock_type: str):
        """Seçili ürün için Kuyumcu Tipi (Küçük) Barkod etiketi oluşturur."""
        if not HAS_REPORTLAB:
            QMessageBox.warning(self, self.lang["error"], "ReportLab kütüphanesi yüklü değil.\nLütfen kurun: pip install reportlab")
            return
        if stock_type != "Product":
            return
        table = self.stock_widgets[stock_type]['def_form']['table']
        selected_items = table.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, self.lang["warning"], self.lang["no_selection"].format(item_type=self.lang["product"]))
            return
        item_id = table.item(selected_items[0].row(), 0).data(Qt.ItemDataRole.UserRole)
        product = self.product_manager.find_by_id(item_id)
        if not product: return
        barcode_val = product.barcode if product.barcode else f"{product.product_name[:3].upper()}{datetime.now().strftime('%M%S')}"
        filename = f"etiket_{product.product_name}_{barcode_val}.pdf".replace(" ", "_")
        save_path, _ = QFileDialog.getSaveFileName(self, "Etiketi Kaydet", filename, "PDF Files (*.pdf)")
        if not save_path: return
        try:
            shop_name = self.settings_manager.get_setting("shop_name", "GOLDER KUYUMCU MUHASEBE PROGRAMI")
            safe_shop_name = shop_name.replace("i","I").replace("ı","i").replace("Ş","S").replace("ş","s").replace("Ä","G").replace("ğ","g")
            lbl_w, lbl_h = 40 * mm, 25 * mm
            c = canvas.Canvas(save_path, pagesize=(lbl_w, lbl_h))
            c.setFont("Helvetica-Bold", 6)
            c.drawCentredString(lbl_w / 2, lbl_h - 3.5 * mm, safe_shop_name)
            c.setFont("Helvetica", 5)
            short_name = (product.product_name[:12] + '..') if len(product.product_name) > 12 else product.product_name
            short_name = short_name.replace("i","I").replace("ı","i").replace("Ş","S").replace("ş","s").replace("Ä","G").replace("ğ","g")
            karat_clean = product.karat_purity.replace("i","I").replace("ı","i").replace("Ş","S").replace("ş","s").replace("Ä","G").replace("ğ","g")
            c.drawString(2 * mm, lbl_h - 7 * mm, short_name)
            c.drawString(2 * mm, lbl_h - 9.5 * mm, f"{karat_clean}")
            c.setFont("Helvetica-Bold", 7)
            price_str = self.currency_manager.format_currency(product.default_sale_price, product.sale_currency, include_symbol=False)
            c.drawRightString(lbl_w - 2 * mm, lbl_h - 7 * mm, f"{price_str}")
            c.setFont("Helvetica", 5)
            c.drawRightString(lbl_w - 2 * mm, lbl_h - 9.5 * mm, f"{product.default_weight_gr:.2f} gr")
            bc = code128.Code128(barcode_val, barHeight=6 * mm, barWidth=0.18 * mm)
            bc_width = bc.width
            x_pos = (lbl_w - bc_width) / 2
            bc.drawOn(c, x_pos, 5 * mm)
            c.setFont("Helvetica", 4)
            c.drawCentredString(lbl_w / 2, 2 * mm, barcode_val)
            c.showPage()
            c.save()
            if sys.platform == 'win32':
                os.startfile(save_path)
            else:
                import subprocess
                opener = 'open' if sys.platform == 'darwin' else 'xdg-open'
                subprocess.call([opener, save_path])
        except Exception as e:
            logger.error(f"Barkod basma hatası: {e}")
            QMessageBox.critical(self, self.lang["error"], f"Etiket oluşturulamadı: {e}")
    def _update_stock_item_price(self, stock_type: str):
        """Updates the purchase price of selected stock rows (group-aware)."""
        if stock_type == "Product":
            manager = self.stock_manager
        elif stock_type == "Ziynet":
            manager = self.ziynet_stock_manager
        else:
            manager = self.scrap_stock_manager
        table = self.stock_widgets[stock_type]['stock_table']
        selected_items = table.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, self.lang["warning"], self.lang["no_selection"].format(item_type=self.lang["stock_status"].lower()))
            return
        row = selected_items[0].row()
        item_id = table.item(row, 0).data(Qt.ItemDataRole.UserRole)
        stock_item = manager.find_by_id(item_id)
        if not stock_item: return
        current_price_display = self.currency_manager._convert_to_display_currency(stock_item.purchase_price_base, self.display_currency)
        new_price_str, ok = QInputDialog.getText(self, self.lang["update_stock_price"],
                                                 self.lang["new_unit_price_prompt"].format(item=stock_item.item_type, currency=self.display_currency),
                                                 text=f"{current_price_display:.2f}")
        if ok and new_price_str:
            try:
                new_price_display = float(new_price_str.replace(',', '.'))
                new_price_base = self.currency_manager._convert_to_base_currency(new_price_display, self.display_currency)

                item_type = (table.item(row, 0).text() if table.item(row, 0) else stock_item.item_type) or stock_item.item_type
                depot_col = 2 if stock_type == "Product" else 1
                depot_name = (table.item(row, depot_col).text() if table.item(row, depot_col) else stock_item.depot_name) or stock_item.depot_name
                depot_id = self.depot_manager.get_id_by_name(depot_name)
                stock_type_db = manager._stock_type_value() if hasattr(manager, "_stock_type_value") else stock_type

                updated_rows = 0
                if depot_id and item_type:
                    with manager.db.transaction_scope() as cursor:
                        cursor.execute(
                            """
                            SELECT id,
                                   COALESCE(quantity, 0) AS quantity,
                                   COALESCE(quantity_gr, 0) AS quantity_gr,
                                   COALESCE(unique_barcode, '') AS unique_barcode
                            FROM stock_items
                            WHERE profile_id = %s
                              AND stock_type = %s
                              AND item_type_name = %s
                              AND depot_id = %s
                              AND is_deleted = 0
                            FOR UPDATE
                            """,
                            (manager.profile_id, stock_type_db, item_type, depot_id)
                        )
                        rows = cursor.fetchall() or []

                        barcode_rows = [r for r in rows if str(r.get('unique_barcode') or '').strip()]
                        bulk_rows = [r for r in rows if not str(r.get('unique_barcode') or '').strip()]

                        # Barkodlu satırlar: tek tek aynı fiyata çek.
                        for r in barcode_rows:
                            row_id = str(r.get("id") or "")
                            before_row = fetch_row_snapshot_by_id(
                                db=manager.db,
                                table_name="stock_items",
                                record_id=row_id,
                                profile_id=manager.profile_id,
                                cursor=cursor,
                            )
                            cursor.execute(
                                "UPDATE stock_items SET purchase_price_base = %s, updated_at = NOW() WHERE id = %s",
                                (new_price_base, row_id)
                            )
                            row_changed = int(cursor.rowcount or 0)
                            updated_rows += row_changed
                            if row_changed > 0:
                                after_row = fetch_row_snapshot_by_id(
                                    db=manager.db,
                                    table_name="stock_items",
                                    record_id=row_id,
                                    profile_id=manager.profile_id,
                                    cursor=cursor,
                                )
                                queue_row_sync_event(
                                    db=manager.db,
                                    table_name="stock_items",
                                    profile_id=manager.profile_id,
                                    action_type=SYNC_ACTION_UPDATE,
                                    record_id=row_id,
                                    before_row=before_row,
                                    after_row=after_row,
                                    cursor=cursor,
                                    meta={"source": "JewelryStoreApp._update_stock_item_price"},
                                )

                        # Barkodsuz yığın satırlar: tek satırda birleştirip çakışmayı önle.
                        if bulk_rows:
                            total_qty = sum(float(r.get('quantity') or 0.0) for r in bulk_rows)
                            total_gr = sum(float(r.get('quantity_gr') or 0.0) for r in bulk_rows)
                            keeper_id = str(bulk_rows[0].get('id') or "")
                            if not keeper_id:
                                raise ValueError("Stok kaydı kimliği okunamadı.")
                            keeper_before = fetch_row_snapshot_by_id(
                                db=manager.db,
                                table_name="stock_items",
                                record_id=keeper_id,
                                profile_id=manager.profile_id,
                                cursor=cursor,
                            )

                            cursor.execute(
                                """
                                UPDATE stock_items
                                SET quantity = %s,
                                    quantity_gr = %s,
                                    purchase_price_base = %s,
                                    is_deleted = 0,
                                    updated_at = NOW()
                                WHERE id = %s
                                """,
                                (total_qty, total_gr, new_price_base, keeper_id)
                            )
                            keeper_changed = int(cursor.rowcount or 0)
                            updated_rows += keeper_changed
                            if keeper_changed > 0:
                                keeper_after = fetch_row_snapshot_by_id(
                                    db=manager.db,
                                    table_name="stock_items",
                                    record_id=keeper_id,
                                    profile_id=manager.profile_id,
                                    cursor=cursor,
                                )
                                queue_row_sync_event(
                                    db=manager.db,
                                    table_name="stock_items",
                                    profile_id=manager.profile_id,
                                    action_type=SYNC_ACTION_UPDATE,
                                    record_id=keeper_id,
                                    before_row=keeper_before,
                                    after_row=keeper_after,
                                    cursor=cursor,
                                    meta={"source": "JewelryStoreApp._update_stock_item_price"},
                                )

                            for r in bulk_rows[1:]:
                                merged_id = str(r.get("id") or "")
                                merged_before = fetch_row_snapshot_by_id(
                                    db=manager.db,
                                    table_name="stock_items",
                                    record_id=merged_id,
                                    profile_id=manager.profile_id,
                                    cursor=cursor,
                                )
                                cursor.execute(
                                    """
                                    UPDATE stock_items
                                    SET quantity = 0,
                                        quantity_gr = 0,
                                        is_deleted = 1,
                                        unique_barcode = %s,
                                        updated_at = NOW()
                                    WHERE id = %s
                                    """,
                                    (f"__MERGED__{merged_id}", merged_id)
                                )
                                merged_changed = int(cursor.rowcount or 0)
                                updated_rows += merged_changed
                                if merged_changed > 0:
                                    merged_after = fetch_row_snapshot_by_id(
                                        db=manager.db,
                                        table_name="stock_items",
                                        record_id=merged_id,
                                        profile_id=manager.profile_id,
                                        cursor=cursor,
                                    )
                                    queue_row_sync_event(
                                        db=manager.db,
                                        table_name="stock_items",
                                        profile_id=manager.profile_id,
                                        action_type=SYNC_ACTION_UPDATE,
                                        record_id=merged_id,
                                        before_row=merged_before,
                                        after_row=merged_after,
                                        cursor=cursor,
                                        meta={"source": "JewelryStoreApp._update_stock_item_price"},
                                    )

                if updated_rows <= 0:
                    manager.update_item(stock_item.id, {"purchase_price_base": new_price_base})

                self.update_all_displays()
                self._show_status_message(self.lang["stock_price_updated"].format(item=stock_item.item_type))
            except ValueError:
                QMessageBox.critical(self, self.lang["error"], self.lang["invalid_value"])
            except Exception as e:
                logger.error(f"Stok fiyat güncelleme hatası: {e}", exc_info=True)
                QMessageBox.critical(self, self.lang["error"], f"Fiyat güncellenemedi: {e}")
    def _delete_stock_item(self, stock_type: str) -> None:
        """
        Deletes a selected stock item (Product, Scrap, or Ziynet).
        GüNCELLEME: Backend Yetki Kontrolü eklendi.
        """
        if self.current_user["role"] != "admin" and "delete_all" not in self.current_user.get("permissions", []):
            QMessageBox.critical(self, "Yetki Hatası", "Bu kaydı silmeye yetkiniz yok.\n(Yönetici onayı gerekir)")
            return
        perm_map = {
            "Product": "delete_stock", # Genel stok silme yetkisi veya spesifik
            "Ziynet": "delete_stock",
            "Scrap": "delete_stock"
        }
        required_perm = perm_map.get(stock_type, "delete_stock")
        has_perm = False
        if self.current_user["role"] == "admin":
            has_perm = True
        else:
            if "delete_stock" in self.current_user.get("permissions", []):
                has_perm = True
        if not has_perm:
            QMessageBox.critical(self, "Yetki Hatası", "Bu işlemi (Stok Silme) yapmaya yetkiniz yok.\nSadece Yöneticiler silebilir.")
            return
        if stock_type == "Product":
            manager = self.stock_manager
        elif stock_type == "Ziynet":
            manager = self.ziynet_stock_manager
        else:
            manager = self.scrap_stock_manager
        table = self.stock_widgets[stock_type]['stock_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)
        item_type = table.item(row, 0).text()
        stock_item = manager.find_by_id(item_id)
        if stock_item and stock_item.unique_barcode:
             QMessageBox.warning(self, "Uyarı", "Barkodlu stok grupları buradan silinemez.\nLütfen 'Manuel çıkış' veya 'Satış' işlemi yapın.")
             return
        if self._show_confirm_dialog(self.lang["delete_selected_stock"], f"'{item_type}' - {self.lang['confirm_delete_prompt']}"):
            if manager.delete_item(item_id):
                self.update_all_displays()
                self._show_status_message(f"Stock item '{item_type}' deleted.")
            else:
                QMessageBox.critical(self, self.lang["error"], f"Failed to delete {item_type} from stock.")
    def _add_product_definition(self, stock_type: str) -> None:
        """Adds a new product, scrap, or ziynet definition."""
        is_product = stock_type == "Product"
        is_ziynet = stock_type == "Ziynet"
        
        if stock_type == "Product": manager = self.product_manager
        elif stock_type == "Ziynet": manager = self.ziynet_manager
        else: manager = self.scrap_manager
        
        form = self.stock_widgets[stock_type]['def_form']
        product_name = form['product_name'].text().strip()
        
        if not product_name:
            QMessageBox.warning(self, self.lang["missing_info"], f"{self.lang['product_name_type']} {self.lang['missing_info'].lower()}.")
            return
            
        existing = manager.find_by_attribute('product_name', product_name)
        if existing and getattr(existing, 'is_active', True):
            QMessageBox.warning(self, self.lang["existing_item"], f"'{product_name}' {self.lang['existing_item']}.")
            return
            
        purchase_price = self.get_float_from_entry(form['purchase'], self.lang["default_purchase_price"], allow_zero=True)
        sale_price = self.get_float_from_entry(form['sale'], self.lang["default_sale_price"], allow_zero=True)
        if purchase_price is None or sale_price is None: return
        
        category_val = "Altın"
        if 'category' in form:
            category_val = form['category'].currentText()
            
        # --- ZİYNET GRAMAJI GÜNCELLEMESİ ---
        default_weight_gr = 0.0
        if is_ziynet:
            # Artık 1.0 zorlaması yok, kutudan okuyoruz
            ziynet_widget = form.get('ziynet_gram')
            if ziynet_widget:
                default_weight_gr = self.get_float_from_entry(ziynet_widget, "Has Gramaj", allow_zero=True) or 0.0
        
        workmanship = 0.0
        if is_product:
            workmanship = self.get_float_from_entry(form['workmanship'], self.lang["product_workmanship"], allow_zero=True) or 0.0
            
        new_def = ProductDefinition(
            product_name=product_name,
            default_purchase_price=purchase_price,
            default_sale_price=sale_price,
            purchase_currency=form['purchase_curr'].currentText(),
            sale_currency=form['sale_curr'].currentText(),
            category=category_val,
            default_weight_gr=default_weight_gr, # DB'ye gerçek gramaj kaydedilir
            karat_purity=form['karat'].text().strip(),
            workmanship_fee=workmanship,
            workmanship_currency=form['workmanship_curr'].currentText() if is_product else self.display_currency,
            is_active=True
        )
        manager.add_item(new_def)
        self._emit_data_changed("product_definitions", {"action": "after_insert", "stock_type": stock_type})
        self._show_status_message(f"Tanım '{product_name}' eklendi.")
        self._clear_product_form(stock_type)

    def _add_category(self):
        name = self.cat_name_entry.text().strip()
        if name:
            existing = self.category_manager.find_by_attribute("name", name)
            if existing:
                QMessageBox.warning(self, "Uyarı", "Bu kategori zaten var.")
                return
            from core.db import Category
            new_cat = Category(name=name, profile_id=self.data_manager.profile_id)
            self.category_manager.add_item(new_cat)
            self.cat_name_entry.clear()
            self._refresh_category_list()
            self.update_product_comboboxes() # Diğer sekmeleri de yenile
            self._show_status_message(f"Kategori eklendi: {name}") # Tüm combo'ları güncelle
    def _refresh_category_list(self) -> None:
        """Reloads categories into the category manage tab list."""
        if not hasattr(self, "category_manager") or not hasattr(self, "category_list_widget"):
            return
        self.category_list_widget.clear()
        all_cats = self.category_manager.get_all()
        self.category_list_widget.addItems([c.name for c in all_cats])
    def _delete_category(self):
        """Seçili kategoriyi siler."""
        if self.current_user["role"] != "admin" and "delete_all" not in self.current_user.get("permissions", []):
            QMessageBox.critical(self, "Yetki Hatası", "Bu kaydı silmeye yetkiniz yok.\n(Yönetici onayı gerekir)")
            return
        item = self.category_list_widget.currentItem()
        if item:
            name = item.text()
            cat = self.category_manager.find_by_attribute("name", name)
            if cat:
                self.category_manager.delete_item(cat.id)
                self.category_list_widget.takeItem(self.category_list_widget.row(item))
                self.update_product_comboboxes()

    

    def _get_selected_row_id(self, table) -> str:
        try:
            items = table.selectedItems() if table else []
            if not items:
                return ""
            item = table.item(items[0].row(), 0)
            return str(item.data(Qt.ItemDataRole.UserRole) or "").strip() if item else ""
        except Exception:
            return ""
    
    

    

    # ui/main_window.py Sınıfı içine eklenecek metodlar:

    # ... (Diğer metodlar) ...

    def _initialize_managers(self) -> None:
        """
        Yöneticileri ServiceContainer üzerinden başlatır.
        DI (Dependency Injection) yapısına geçiş.
        """
        from core.container import ServiceContainer
        
        self.container = ServiceContainer(
            self.profile_file_path,
            data_manager=self.data_manager,
            settings_manager=self.settings_manager,
            currency_manager=self.currency_manager,
        )
        
        self.data_manager = self.container.data_manager
        self.settings_manager = self.container.settings_manager
        self.currency_manager = self.container.currency_manager
        self.auth_repo = self.container.auth_repo
        
        self.person_manager = self.container.person_manager
        self.product_manager = self.container.product_manager
        self.stock_manager = self.container.stock_manager
        self.scrap_manager = self.container.scrap_manager
        self.scrap_stock_manager = self.container.scrap_stock_manager
        self.ziynet_manager = self.container.ziynet_manager
        self.ziynet_stock_manager = self.container.ziynet_stock_manager
        
        self.debt_manager = self.container.debt_manager
        self.repair_manager = self.container.repair_manager
        self.sale_manager = self.container.sale_manager
        self.calendar_manager = self.container.calendar_manager
        self.customer_history_manager = self.container.customer_history_manager
        
        self.depot_manager = self.container.depot_manager
        self.scheduled_transaction_manager = self.container.scheduled_transaction_manager
        self.stock_movement_manager = self.container.stock_movement_manager
        self.barcode_history_manager = self.container.barcode_history_manager
        self.category_manager = self.container.category_manager
        
        self.tax_definition_manager = self.container.tax_definition_manager
        # self.tax_profile_manager = self.container.tax_profile_manager          <-- SİLİNDİ
        # self.tax_profile_item_manager = self.container.tax_profile_item_manager <-- SİLİNDİ
        self.tax_application_manager = self.container.tax_application_manager
        self.tax_engine = self.container.tax_engine
        
        self.cash_account_manager = self.container.cash_account_manager
        self.card_account_manager = self.container.card_account_manager
        self.cash_managers = self.container.cash_managers
        self.card_managers = self.container.card_managers
        
        self.transaction_service = self.container.transaction_service
        self.transaction_service.current_user = self.current_user 
        
        self.pdf_report_generator = PdfReportGenerator(self.currency_manager, self.settings_manager, self.lang)
        self.excel_report_generator = ExcelReportGenerator(self.currency_manager, self.settings_manager, self.lang)
        self.zakat_calculator = ZakatCalculator(self)
        
        logger.info("ServiceContainer entegrasyonu tamamlandı.")

    def _refresh_taxes_ui(self) -> None:
        """Vergi Tanımları ve Defter tablosunu yeniler."""
        # 1. Tanımları Doldur
        if not getattr(self, "tax_definition_manager", None): return
        taxes = self.tax_definition_manager.get_all() or []
        table = getattr(self, "tax_definitions_table", None)
        if table:
            table.setRowCount(len(taxes))
            for i, t in enumerate(taxes):
                table.setItem(i, 0, QTableWidgetItem(t.name))
                table.setItem(i, 1, QTableWidgetItem("Yüzde" if t.type=="percent" else "Sabit"))
                table.setItem(i, 2, QTableWidgetItem(str(t.rate)))
                table.item(i, 0).setData(Qt.ItemDataRole.UserRole, t.id)
        
        # 2. Defteri Doldur
        l_table = getattr(self, "tax_ledger_table", None)
        if l_table:
            # SQL ile son 50 kaydı çekmek daha performanslıdır
            try:
                tax_rows = self.data_manager.db.fetch_all(
                    """
                    SELECT id, source_table, source_id, currency, subtotal, tax_total, grand_total, snapshot_json, created_at
                    FROM tax_applications
                    WHERE profile_id = %s
                    ORDER BY created_at DESC
                    LIMIT 50
                    """,
                    (self.data_manager.profile_id,),
                )
            except Exception:
                tax_rows = []

            l_table.setRowCount(len(tax_rows))
            for i, r in enumerate(tax_rows):
                date_str = str(r.get("created_at") or "")
                try: 
                    if isinstance(r.get("created_at"), datetime):
                        date_str = r.get("created_at").strftime("%d.%m.%Y %H:%M")
                    else:
                        date_str = datetime.fromisoformat(str(r.get("created_at"))).strftime("%d.%m.%Y %H:%M")
                except: pass
                
                # Snapshot içinden müşteri adını ve uygulanan vergileri bul
                cust = "-"
                applied_taxes = "-"
                try:
                    snap_raw = r.get("snapshot_json")
                    snap = json.loads(snap_raw) if isinstance(snap_raw, str) else snap_raw
                    if isinstance(snap, dict):
                        cust = snap.get("customer_name") or "-"
                        applied_taxes = snap.get("applied_taxes_summary") or "-"
                except: pass
                
                # Tablo sütunları: Tarih, Kaynak, Müşteri, Uygulanan Vergiler, Matrah, Vergi, Toplam
                l_table.setItem(i, 0, QTableWidgetItem(date_str))
                l_table.setItem(i, 1, QTableWidgetItem(r.get("source_table")))
                l_table.setItem(i, 2, QTableWidgetItem(cust))
                l_table.setItem(i, 3, QTableWidgetItem(applied_taxes))
                l_table.setItem(i, 4, QTableWidgetItem(format_number(float(r.get("subtotal") or 0))))
                l_table.setItem(i, 5, QTableWidgetItem(format_number(float(r.get("tax_total") or 0))))
                l_table.setItem(i, 6, QTableWidgetItem(format_number(float(r.get("grand_total") or 0))))

    # ... (Diğer metodlar) ...

    def _on_tax_checkbox_clicked(self, stock_type: str, transaction_type: str):
        """POS ekranında Vergi Uygula tıklandığında açılan mantık."""
        widgets = self.stock_widgets[stock_type][transaction_type]
        chk = widgets['apply_taxes_checkbox']
        lbl = widgets['tax_info_label']
        
        if chk.isChecked():
            # Vergileri veritabanından çek
            all_taxes = self.tax_definition_manager.get_all()
            if not all_taxes:
                msg = QMessageBox(self)
                msg.setIcon(QMessageBox.Icon.Warning)
                msg.setWindowTitle(self.lang.get("warning", "Warning"))
                msg.setText(self.lang.get("defined_tax_missing", "No tax definition found. Add one from Settings > Taxes."))
                ok_btn = msg.addButton(self.lang.get("ok", "OK"), QMessageBox.ButtonRole.AcceptRole)
                ok_btn.setDefault(True)
                msg.exec()
                chk.setChecked(False)
                return
            
            # Daha önce seçili olanların ID'lerini bul
            prev_selected = widgets.get('selected_taxes', [])
            prev_ids = [x['id'] for x in prev_selected]
            
            # Diyalog aç
            from ui.tax_dialogs import TaxSelectionDialog
            dlg = TaxSelectionDialog(all_taxes, prev_ids, self)
            if dlg.exec():
                selected = dlg.get_selected_taxes()
                widgets['selected_taxes'] = selected
                if selected:
                    names = ", ".join([t['name'] for t in selected])
                    lbl.setText(f"{self.lang.get('selected_taxes_lbl', 'Selected:')} {names}")
                else:
                    chk.setChecked(False)
                    lbl.setText(self.lang.get("selected_none", "Selected: None"))
                    widgets['selected_taxes'] = []
            else:
                # İptal edilirse, eğer hiç seçili yoksa checki kaldır
                if not widgets.get('selected_taxes'):
                    chk.setChecked(False)
        else:
            # Check kaldırıldı, temizle
            widgets['selected_taxes'] = []
            lbl.setText(self.lang.get("selected_none", "Selected: None"))
            
        # Fiyatı güncelle
        self.fiyat_hesapla(stock_type, transaction_type)

    def _add_tax_definition(self):
        from ui.tax_dialogs import TaxDefinitionEditorDialog
        from core.db import TaxDefinition
        d = TaxDefinitionEditorDialog(parent=self)
        if d.exec():
            self.tax_definition_manager.add_item(TaxDefinition(**d.get_data()))
            self._refresh_taxes_ui()

    def _edit_tax_definition(self):
        table = getattr(self, "tax_definitions_table", None)
        items = table.selectedItems()
        if not items: return
        tid = items[0].data(Qt.ItemDataRole.UserRole)
        tax = self.tax_definition_manager.find_by_id(tid)
        if not tax: return
        from ui.tax_dialogs import TaxDefinitionEditorDialog
        d = TaxDefinitionEditorDialog(tax, self)
        if d.exec():
            self.tax_definition_manager.update_item(tid, d.get_data())
            self._refresh_taxes_ui()

    def _delete_tax_definition(self):
        table = getattr(self, "tax_definitions_table", None)
        items = table.selectedItems()
        if not items: return
        if QMessageBox.question(self, "Sil", "Vergi tanımı silinsin mi?") == QMessageBox.StandardButton.Yes:
            tid = items[0].data(Qt.ItemDataRole.UserRole)
            self.tax_definition_manager.delete_item(tid)
            self._refresh_taxes_ui()

    def _refresh_tax_profile_pos_combos(self) -> None:
        """Refresh POS tax profile dropdowns after profile changes."""
        try:
            profiles = self.tax_profile_manager.get_all() or []
            profiles = sorted(profiles, key=lambda p: (p.name or "").lower())
        except Exception:
            profiles = []
        for stock_type, tabs in (self.stock_widgets or {}).items():
            if not isinstance(tabs, dict):
                continue
            for trans_type, widgets in tabs.items():
                if not isinstance(widgets, dict):
                    continue
                combo = widgets.get("tax_profile_combo")
                if not isinstance(combo, QComboBox):
                    continue
                current_id = combo.currentData()
                combo.blockSignals(True)
                combo.clear()
                combo.addItem("Vergi Profili Seçin", "")
                for p in profiles:
                    combo.addItem(p.name, p.id)
                if current_id:
                    idx = combo.findData(current_id)
                    if idx != -1:
                        combo.setCurrentIndex(idx)
                combo.blockSignals(False)

    def _load_tax_profile_items_map(self, profile_id: str) -> Dict[str, Dict[str, Any]]:
        db = self.data_manager.db
        try:
            rows = db.fetch_all(
                """
                SELECT tax_id, priority, enabled_by_default
                FROM tax_profile_items
                WHERE profile_id=%s AND tax_profile_id=%s
                """,
                (self.data_manager.profile_id, profile_id),
            )
        except Exception:
            rows = []
        existing_by_tax_id: Dict[str, Dict[str, Any]] = {}
        for row in rows or []:
            tax_id = str(row.get("tax_id") or "").strip()
            if not tax_id:
                continue
            existing_by_tax_id[tax_id] = {
                "priority": int(row.get("priority") or 0),
                "enabled_by_default": bool(row.get("enabled_by_default", 1)),
            }
        return existing_by_tax_id

    def _save_tax_profile_items(self, profile_id: str, selected_items: List[Dict[str, Any]]) -> None:
        from core.db import TaxProfileItem

        db = self.data_manager.db
        with db.transaction_scope() as cursor:
            cursor.execute(
                """
                SELECT *
                FROM tax_profile_items
                WHERE profile_id=%s AND tax_profile_id=%s
                FOR UPDATE
                """,
                (self.data_manager.profile_id, profile_id),
            )
            existing_rows = cursor.fetchall() or []
            cursor.execute(
                "DELETE FROM tax_profile_items WHERE profile_id=%s AND tax_profile_id=%s",
                (self.data_manager.profile_id, profile_id),
            )
            if int(cursor.rowcount or 0) > 0:
                for row in existing_rows:
                    row_id = str(row.get("id") or "")
                    if not row_id:
                        continue
                    queue_row_sync_event(
                        db=db,
                        table_name="tax_profile_items",
                        profile_id=self.data_manager.profile_id,
                        action_type=SYNC_ACTION_DELETE,
                        record_id=row_id,
                        before_row=dict(row),
                        after_row=None,
                        cursor=cursor,
                        meta={"source": "JewelryStoreApp._save_tax_profile_items"},
                    )
            for item in selected_items or []:
                obj = TaxProfileItem(
                    tax_profile_id=profile_id,
                    tax_id=str(item.get("tax_id") or ""),
                    priority=int(item.get("priority") or 0),
                    enabled_by_default=bool(item.get("enabled_by_default", True)),
                )
                self.tax_profile_item_manager.add_item(obj, cursor=cursor)

    def _open_tax_profile_items_dialog(self, profile) -> Optional[List[Dict[str, Any]]]:
        from ui.tax_dialogs import TaxProfileItemsDialog

        taxes = self.tax_definition_manager.get_all() or []
        existing_by_tax_id = self._load_tax_profile_items_map(profile.id)
        dialog = TaxProfileItemsDialog(
            profile=profile,
            taxes=taxes,
            existing_items_by_tax_id=existing_by_tax_id,
            parent=self,
        )
        if dialog.exec() != QDialog.DialogCode.Accepted:
            return None
        return dialog.get_items()

    def _add_tax_profile(self) -> None:
        if self.current_user.get("role") != "admin":
            QMessageBox.critical(self, "Yetki Hatası", "Bu işlem için yönetici yetkisi gerekir.")
            return
        from core.db import TaxProfile
        from ui.tax_dialogs import TaxProfileEditorDialog

        dialog = TaxProfileEditorDialog(parent=self)
        if dialog.exec() != QDialog.DialogCode.Accepted:
            return
        data = dialog.get_data()
        new_profile = TaxProfile(**data)
        self.tax_profile_manager.add_item(new_profile)
        selected_items = self._open_tax_profile_items_dialog(new_profile)
        if selected_items is not None:
            try:
                self._save_tax_profile_items(new_profile.id, selected_items)
            except Exception as e:
                QMessageBox.critical(self, "Hata", f"Profil vergileri kaydedilemedi:\n{e}")
                return
        self._refresh_taxes_ui()
        self._refresh_tax_profile_pos_combos()

    def _edit_tax_profile(self) -> None:
        if self.current_user.get("role") != "admin":
            QMessageBox.critical(self, "Yetki Hatası", "Bu işlem için yönetici yetkisi gerekir.")
            return
        table = getattr(self, "tax_profiles_table", None)
        profile_id = self._get_selected_row_id(table)
        if not profile_id:
            QMessageBox.warning(self, "Uyarı", "Lütfen düzenlemek için bir profil seçin.")
            return
        profile = self.tax_profile_manager.find_by_id(profile_id)
        if not profile:
            QMessageBox.warning(self, "Uyarı", "Profil bulunamadı.")
            return
        from ui.tax_dialogs import TaxProfileEditorDialog

        dialog = TaxProfileEditorDialog(profile, parent=self)
        if dialog.exec() != QDialog.DialogCode.Accepted:
            return
        data = dialog.get_data()
        self.tax_profile_manager.update_item(profile_id, data)
        self._refresh_taxes_ui()
        self._refresh_tax_profile_pos_combos()

    def _delete_tax_profile(self) -> None:
        if self.current_user.get("role") != "admin":
            QMessageBox.critical(self, "Yetki Hatası", "Bu işlem için yönetici yetkisi gerekir.")
            return
        table = getattr(self, "tax_profiles_table", None)
        profile_id = self._get_selected_row_id(table)
        if not profile_id:
            QMessageBox.warning(self, "Uyarı", "Lütfen silmek için bir profil seçin.")
            return
        confirm = QMessageBox.question(
            self,
            "Onay",
            "Seçili vergi profili silinsin mi?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
        )
        if confirm != QMessageBox.StandardButton.Yes:
            return
        if not self.tax_profile_manager.delete_item(profile_id):
            QMessageBox.warning(self, "Uyarı", "Profil silinemedi.")
        self._refresh_taxes_ui()
        self._refresh_tax_profile_pos_combos()

    def _edit_tax_profile_items(self) -> None:
        if self.current_user.get("role") != "admin":
            QMessageBox.critical(self, "Yetki Hatası", "Bu işlem için yönetici yetkisi gerekir.")
            return
        table = getattr(self, "tax_profiles_table", None)
        profile_id = self._get_selected_row_id(table)
        if not profile_id:
            QMessageBox.warning(self, "Uyarı", "Lütfen bir profil seçin.")
            return
        profile = self.tax_profile_manager.find_by_id(profile_id)
        if not profile:
            QMessageBox.warning(self, "Uyarı", "Profil bulunamadı.")
            return
        selected_items = self._open_tax_profile_items_dialog(profile)
        if selected_items is None:
            return
        try:
            self._save_tax_profile_items(profile_id, selected_items)
        except Exception as e:
            QMessageBox.critical(self, "Hata", f"Profil vergileri kaydedilemedi:\n{e}")
            return
        self._refresh_taxes_ui()
    def _update_product_definition(self, stock_type: str) -> None:
        """Mevcut tanımı günceller."""
        is_product = stock_type == "Product"
        if stock_type == "Product": manager = self.product_manager
        elif stock_type == "Ziynet": manager = self.ziynet_manager
        else: manager = self.scrap_manager
        form = self.stock_widgets[stock_type]['def_form']
        table = form['table']
        selected_items = table.selectedItems()
        if not selected_items: return
        item_id = table.item(selected_items[0].row(), 0).data(Qt.ItemDataRole.UserRole)
        product = manager.find_by_id(item_id)
        if not product: return
        new_name = form['product_name'].text().strip() # <-- DüZELTiLDi
        if not new_name:
            QMessageBox.warning(self, self.lang["missing_info"], f"{self.lang['product_name_type']} girilmelidir.")
            return
        existing = manager.find_by_attribute('product_name', new_name)
        if existing and existing.id != item_id and getattr(existing, 'is_active', True):
            QMessageBox.warning(self, self.lang["existing_item"], f"'{new_name}' {self.lang['existing_item']}.")
            return
        purchase_price = self.get_float_from_entry(form['purchase'], self.lang["default_purchase_price"], allow_zero=True)
        sale_price = self.get_float_from_entry(form['sale'], self.lang["default_sale_price"], allow_zero=True)
        if purchase_price is None or sale_price is None: return
        workmanship = 0.0
        if is_product:
            workmanship = self.get_float_from_entry(form['workmanship'], self.lang["product_workmanship"], allow_zero=True) or 0.0
        ziynet_gram = None
        if stock_type == "Ziynet":
            ziynet_widget = form.get('ziynet_gram')
            if ziynet_widget:
                ziynet_gram = self.get_float_from_entry(ziynet_widget, "Has Gramaj", allow_zero=True) or 0.0
        
        new_data = {
            "product_name": new_name,
            "category": form['category'].currentText().strip() if form.get("category") else "",
            "karat_purity": form['karat'].text().strip(),
            "workmanship_fee": workmanship,
            "workmanship_currency": form['workmanship_curr'].currentText() if is_product else self.display_currency,
            "default_purchase_price": purchase_price,
            "default_sale_price": sale_price,
            "purchase_currency": form['purchase_curr'].currentText(),
            "sale_currency": form['sale_curr'].currentText(),
        }
        
        if ziynet_gram is not None:
            new_data["default_weight_gr"] = ziynet_gram # Güncellenen gramajı kaydet
            
        manager.update_item(product.id, new_data)
        self._emit_data_changed("product_definitions", {"action": "after_update", "stock_type": stock_type})
        self._show_status_message(f"Tanım güncellendi.")
    def _delete_product_definition(self, stock_type: str) -> None:
        if self.current_user["role"] != "admin" and "delete_all" not in self.current_user.get("permissions", []):
            QMessageBox.critical(self, "Yetki Hatası", "Bu kaydı silmeye yetkiniz yok.\n(Yönetici onayı gerekir)")
            return
        """Deletes or disables a selected product definition based on strict usage rules."""
        if stock_type == "Product":
            manager = self.product_manager
            stock_manager = self.stock_manager
        elif stock_type == "Ziynet":
            manager = self.ziynet_manager
            stock_manager = self.ziynet_stock_manager
        else:
            manager = self.scrap_manager
            stock_manager = self.scrap_stock_manager
        table = self.stock_widgets[stock_type]['def_form']['table']
        selected_items = table.selectedItems()
        if not selected_items: return
        item_id = table.item(selected_items[0].row(), 0).data(Qt.ItemDataRole.UserRole)
        product_def = manager.find_by_id(item_id)
        if not product_def: return
        is_used_in_stock = any(s.item_type == product_def.product_name for s in stock_manager.get_all())
        is_used_in_sales = False
        all_sales = self.sale_manager.get_all()
        for sale in all_sales:
            for item in sale.products_sold:
                if item.get('type') == product_def.product_name:
                    is_used_in_sales = True
                    break
            if is_used_in_sales: break
        if is_used_in_stock or is_used_in_sales:
            msg = f"'{product_def.product_name}' tanımı stokta veya geçmiş kayıtlarda kullanılıyor.\n\n" \
                  f"KURUMSAL KURAL: Veri bütünlüğü için bu kayıt silinemez.\n" \
                  f"Sadece 'Devre Dışı' bırakılabilir."
            if self._show_confirm_dialog("Tanımı Devre Dışı Bırak", msg):
                manager.update_item(product_def.id, {"is_active": False})
                self._emit_data_changed("product_definitions", {"action": "after_delete", "stock_type": stock_type})
                self._show_status_message(f"Definition '{product_def.product_name}' disabled (Soft Delete).")
                self._clear_product_form(stock_type)
        else:
            if self._show_confirm_dialog(self.lang["delete"], f"'{product_def.product_name}' - {self.lang['confirm_delete_prompt']}"):
                manager.delete_item(product_def.id)
                self._emit_data_changed("product_definitions", {"action": "after_delete", "stock_type": stock_type})
                self._show_status_message(f"Definition '{product_def.product_name}' deleted permanently.")
                self._clear_product_form(stock_type)
    def _populate_transfer_tab_combos(self):
        """Veri Aktarım sekmesindeki combobox'ları doldurur."""
        if not hasattr(self, "transfer_target_profile_combo"):
            return
        other_profiles = self._get_other_profile_names()
        target_prev = self.transfer_target_profile_combo.currentText()
        self.transfer_target_profile_combo.blockSignals(True)
        self.transfer_target_profile_combo.clear()
        self.transfer_target_profile_combo.addItems(other_profiles)
        if target_prev:
            idx = self.transfer_target_profile_combo.findText(target_prev)
            if idx != -1:
                self.transfer_target_profile_combo.setCurrentIndex(idx)
        self.transfer_target_profile_combo.blockSignals(False)
        if hasattr(self, "transfer_customer_select_combo"):
            prev = self.transfer_customer_select_combo.currentText()
            self.transfer_customer_select_combo.blockSignals(True)
            self.transfer_customer_select_combo.clear()
            self.transfer_customer_select_combo.addItems(self.person_manager.get_person_names())
            if prev:
                idx = self.transfer_customer_select_combo.findText(prev)
                if idx != -1:
                    self.transfer_customer_select_combo.setCurrentIndex(idx)
            self.transfer_customer_select_combo.blockSignals(False)
        if hasattr(self, "transfer_stock_depot_combo"):
            prev = self.transfer_stock_depot_combo.currentText()
            self.transfer_stock_depot_combo.blockSignals(True)
            self.transfer_stock_depot_combo.clear()
            self.transfer_stock_depot_combo.addItems(self._get_accessible_depots())
            if prev:
                idx = self.transfer_stock_depot_combo.findText(prev)
                if idx != -1:
                    self.transfer_stock_depot_combo.setCurrentIndex(idx)
            self.transfer_stock_depot_combo.blockSignals(False)
        if hasattr(self, "transfer_finance_source_combo"):
            prev_data = self.transfer_finance_source_combo.currentData()
            self.transfer_finance_source_combo.blockSignals(True)
            self.transfer_finance_source_combo.clear()
            for acc in self._get_accessible_cash_accounts():
                self.transfer_finance_source_combo.addItem(f"{self.lang.get('prefix_cash', 'Cash')}: {acc.name}", ("cash", acc.id))
            for acc in self._get_accessible_card_accounts():
                self.transfer_finance_source_combo.addItem(f"{self.lang.get('prefix_bank', 'Bank')}: {acc.name}", ("card", acc.id))
            if prev_data:
                idx = self.transfer_finance_source_combo.findData(prev_data)
                if idx != -1:
                    self.transfer_finance_source_combo.setCurrentIndex(idx)
            self.transfer_finance_source_combo.blockSignals(False)
        if hasattr(self, "transfer_finance_target_combo"):
            self._on_transfer_target_profile_changed()
    def _on_transfer_target_profile_changed(self):
        """Hedef profil seçildiğinde, o profilin kasa/hesap listesini yükler."""
        if not hasattr(self, "transfer_target_profile_combo") or not hasattr(self, "transfer_finance_target_combo"):
            return
        target_profile_name = self.transfer_target_profile_combo.currentText()
        if not target_profile_name:
            self.transfer_finance_target_combo.clear()
            return
        try:
            temp_dm = DataManager(profile_file_path=target_profile_name)
            temp_cash_mgr = CashAccountManager(temp_dm)
            temp_card_mgr = CardAccountManager(temp_dm)
            self.transfer_finance_target_combo.clear()
            for acc in temp_cash_mgr.get_all():
                self.transfer_finance_target_combo.addItem(f"{self.lang.get('prefix_cash', 'Cash')}: {acc.name}", ("cash", acc.id))
            for acc in temp_card_mgr.get_all():
                self.transfer_finance_target_combo.addItem(f"{self.lang.get('prefix_bank', 'Bank')}: {acc.name}", ("card", acc.id))
        except Exception as e:
            logger.error(f"Hedef profil kasaları yüklenemedi: {e}")
            self.transfer_finance_target_combo.clear()
    def _execute_data_transfer(self):
        """Veri Aktarım Sihirbazı'ndaki ana mantığı çalıştırır (Şifre Korumalı)."""
        if not hasattr(self, "transfer_target_profile_combo"):
            logger.warning("Data transfer requested but 'transfer_target_profile_combo' is missing. Skipping to avoid crash.")
            QMessageBox.warning(self, "Uyarı", "Veri aktarım arayüzü bu kullanıcı için aktif değil veya yüklenemedi.")
            return
        target_profile_name = self.transfer_target_profile_combo.currentText()
        if not target_profile_name:
            QMessageBox.warning(self, "Uyarı", "Lütfen bir hedef profil seçin.")
            return
        is_move = self.transfer_action_combo.currentIndex() == 1
        # Profil şifresi doğrulama (PBKDF2 + legacy MD5 auto-upgrade tek noktada: AuthRepository.verify_profile_password)
        if self.auth_repo.has_admin_password(self.profile_id):
            password, ok = QInputDialog.getText(self, "Kaynak Profil Onayı", f"'{self.profile_name}' profili için şifrenizi girin:", QLineEdit.EchoMode.Password)
            if not ok: return
            if not self.auth_repo.verify_profile_password(password, profile_id=self.profile_id):
                QMessageBox.critical(self, "Hata", "Kaynak profil şifresi yanlış!")
                return
        try:
            target_dm = DataManager(profile_file_path=target_profile_name)
            target_sm = SettingsManager(data_manager=target_dm)
            from core.auth import AuthRepository
            target_auth_repo = AuthRepository(target_dm.db, target_dm.profile_id)
            if target_auth_repo.has_admin_password(target_dm.profile_id):
                password, ok = QInputDialog.getText(self, "Hedef Profil Onayı", f"'{target_profile_name}' profili için şifreyi girin:", QLineEdit.EchoMode.Password)
                if not ok: return
                if not target_auth_repo.verify_profile_password(password, profile_id=target_dm.profile_id):
                    QMessageBox.critical(self, "Hata", "Hedef profil şifresi yanlış!")
                    return
        except Exception as e:
            QMessageBox.critical(self, "Hata", f"Hedef profil okunamadı: {e}")
            return
        actions_to_run = []
        summary_text = f"<b>Kaynak:</b> {self.profile_name}<br><b>Hedef:</b> {target_profile_name}<br><b>işlem:</b> {'Taşıma' if is_move else 'Kopyalama'}<br><br><b>Aktarılacak Veriler:</b><ul>"
        if self.transfer_chk_customers.isChecked():
            actions_to_run.append("customers")
            summary_text += "<li>Tüm Müşteri Tanımları</li>"
        if self.transfer_chk_def_product.isChecked():
            actions_to_run.append("def_product")
            summary_text += "<li>Altın Tanımları</li>"
        if self.transfer_chk_def_scrap.isChecked():
            actions_to_run.append("def_scrap")
            summary_text += "<li>Hurda Tanımları</li>"
        if self.transfer_chk_def_ziynet.isChecked():
            actions_to_run.append("def_ziynet")
            summary_text += "<li>Ziynet Tanımları</li>"
        if self.transfer_chk_services.isChecked():
            actions_to_run.append("services")
            summary_text += "<li>Hizmet Tanımları ve Kayıtları</li>"
        finance_amount = 0.0
        try: finance_amount = float(self.transfer_finance_amount.text().replace(',', '.'))
        except: pass
        if finance_amount > 0:
            actions_to_run.append("finance")
            summary_text += f"<li><b>Finansal Virman:</b> {self.transfer_finance_amount.text()} {self.transfer_finance_currency.currentText()}</li>"
        summary_text += "</ul><br>Bu işlemi onaylıyor musunuz?"
        if not actions_to_run:
            QMessageBox.warning(self, "Uyarı", "Lütfen aktarılacak en az bir veri türü seçin veya finansal transfer tutarı girin.")
            return
        if not self._show_confirm_dialog("Aktarımı Onayla", summary_text):
            return
        loading = LoadingDialog("Veriler aktarılıyor...", self)
        loading.show()
        QApplication.processEvents()
        try:
            log_messages = []
            if "customers" in actions_to_run:
                target_pm = PersonManager(target_dm)
                source_items = self.person_manager.get_all()
                existing_names = {p.full_name.lower() for p in target_pm.get_all()}
                count = 0
                for item in source_items:
                    if item.full_name.lower() not in existing_names:
                        target_pm.add_item(Person(full_name=item.full_name, phone_number=item.phone_number, address=item.address, notes=item.notes))
                        if is_move: self.person_manager.delete_item(item.id)
                        count += 1
                log_messages.append(f"- {count} müşteri aktarıldı.")
            def_map = {"def_product": "Product", "def_scrap": "Scrap", "def_ziynet": "Ziynet"}
            for action, stock_type in def_map.items():
                if action in actions_to_run:
                    source_mgr = getattr(self, f"{stock_type.lower()}_manager")
                    target_mgr = ProductDefinitionManager(target_dm, stock_type)
                    existing_names = {p.product_name.lower() for p in target_mgr.get_all()}
                    count = 0
                    for item in source_mgr.get_all():
                        if item.product_name.lower() not in existing_names:
                            item_dict = item.__dict__.copy()
                            for key in ['id', 'created_at', 'updated_at']: item_dict.pop(key, None)
                            target_mgr.add_item(ProductDefinition(**item_dict))
                            if is_move: source_mgr.delete_item(item.id)
                            count += 1
                    log_messages.append(f"- {count} adet {stock_type} tanımı aktarıldı.")
            if "services" in actions_to_run:
                from modules.services import ServiceManager, ServiceDefinition, ServiceJob
                source_serv_mgr = ServiceManager(self.data_manager)
                target_serv_mgr = ServiceManager(target_dm)
                def_count = 0
                existing_defs = {d.name.lower() for d in target_serv_mgr.definitions}
                for definition in source_serv_mgr.definitions:
                    if definition.name.lower() not in existing_defs:
                        target_serv_mgr.add_definition(ServiceDefinition(name=definition.name, custom_columns=definition.custom_columns))
                        if is_move: source_serv_mgr.definitions = [d for d in source_serv_mgr.definitions if d.id != definition.id]
                        def_count += 1
                job_count = 0
                existing_jobs_ids = {j.id for j in target_serv_mgr.jobs}
                for job in source_serv_mgr.jobs:
                    if job.id not in existing_jobs_ids:
                        target_serv_mgr.add_job(job)
                        if is_move: source_serv_mgr.jobs = [j for j in source_serv_mgr.jobs if j.id != job.id]
                        job_count += 1
                if is_move:
                    source_serv_mgr.save_definitions()
                    source_serv_mgr.save_jobs()
                else: # Sadece Kopyalamada hedefi kaydetmek yeterli
                    target_serv_mgr.save_definitions()
                    target_serv_mgr.save_jobs()
                log_messages.append(f"- {def_count} hizmet tanımı ve {job_count} iş kaydı aktarıldı.")
            if "finance" in actions_to_run:
                source_data = self.transfer_finance_source_combo.currentData()
                target_data = self.transfer_finance_target_combo.currentData()
                currency = self.transfer_finance_currency.currentText()
                source_mgr = self.cash_managers.get(source_data[1]) if source_data[0] == 'cash' else self.card_managers.get(source_data[1])
                source_base_amount = self.currency_manager._convert_to_base_currency(finance_amount, currency)
                source_mgr.add_item(FinancialTransaction(datetime.now().strftime("%d.%m.%Y"), "Expense", finance_amount, currency, source_base_amount, f"Profil Transferi -> {target_profile_name}"))
                target_cash_mgr = CashAccountManager(target_dm)
                target_card_mgr = CardAccountManager(target_dm)
                target_cash_trans_managers = {acc.id: FinancialTransactionManager(target_dm, f"cash_transactions_{acc.id}") for acc in target_cash_mgr.get_all()}
                target_card_trans_managers = {acc.id: FinancialTransactionManager(target_dm, f"card_transactions_{acc.id}") for acc in target_card_mgr.get_all()}
                target_mgr = target_cash_trans_managers.get(target_data[1]) if target_data[0] == 'cash' else target_card_trans_managers.get(target_data[1])
                if not target_mgr: raise Exception(f"Hedef hesap yöneticisi bulunamadı: {target_data}")
                target_base_amount = CurrencyManager()._convert_to_base_currency(finance_amount, currency)
                target_mgr.add_item(FinancialTransaction(datetime.now().strftime("%d.%m.%Y"), "Income", finance_amount, currency, target_base_amount, f"Profil Transferi <- {self.profile_name}"))
                log_messages.append(f"- {finance_amount} {currency} transfer edildi.")
            loading.close()
            QMessageBox.information(self, "işlem Başarılı", "Veri aktarımı tamamlandı.\n\n" + "\n".join(log_messages))
            if is_move: self.update_all_displays()
        except Exception as e:
            loading.close()
            logger.error(f"Veri aktarım hatası: {e}", exc_info=True)
            QMessageBox.critical(self, "Kritik Hata", f"Aktarım sırasında bir hata oluştu: {e}")
    def _product_definition_selected(self, stock_type: str) -> None:
        """Loads selected definition into the form."""
        is_product = stock_type == "Product"
        
        # Butonları aktif et (Güncelle/Sil)
        self._update_product_def_button_states(stock_type)
        
        form = self.stock_widgets[stock_type]['def_form']
        table = form['table']
        selected_items = table.selectedItems()
        
        if not selected_items:
            self._clear_product_form(stock_type)
            return
            
        # Doğru yöneticiyi seç
        if stock_type == "Product":
            manager = self.product_manager
        elif stock_type == "Ziynet":
            manager = self.ziynet_manager
        else:
            manager = self.scrap_manager
            
        item_id = table.item(selected_items[0].row(), 0).data(Qt.ItemDataRole.UserRole)
        product = manager.find_by_id(item_id)
        
        if product:
            # 1. Ortak Alanlar
            form['product_name'].setText(product.product_name)
            form['karat'].setText(product.karat_purity)
            form['purchase'].setText(f"{product.default_purchase_price:.2f}")
            form['sale'].setText(f"{product.default_sale_price:.2f}")
            form['purchase_curr'].setCurrentText(product.purchase_currency)
            form['sale_curr'].setCurrentText(product.sale_currency)
            
            # 2. Kategori (ComboBox veya LineEdit olabilir, kontrol ediyoruz)
            category_widget = form.get("category")
            cat_val = getattr(product, "category", "") or ""
            if isinstance(category_widget, QComboBox):
                category_widget.setCurrentText(cat_val)
            elif isinstance(category_widget, QLineEdit):
                category_widget.setText(cat_val)
            
            # 3. ZİYNET GRAMAJI (Zekat için Has Gram)
            # Eğer UI Builder bu widget'ı oluşturduysa (sadece Ziynet sekmesinde)
            ziynet_gram_widget = form.get("ziynet_gram")
            if isinstance(ziynet_gram_widget, QLineEdit):
                # Veritabanından gelen gramajı yaz (Artık 1.0 zorunlu değil)
                z_gram = getattr(product, "default_weight_gr", 0.0) or 0.0
                ziynet_gram_widget.setText(f"{z_gram:.3f}".replace(".", ","))
                
            # 4. ALTIN (Product) Özel Alanları
            if is_product:
                if form.get('barcode_header'):
                    form['barcode_header'].setText(getattr(product, 'barcode_header', ''))
                
                # İşçilik
                form['workmanship'].setText(f"{product.workmanship_fee:.2f}")
                form['workmanship_curr'].setCurrentText(product.workmanship_currency)
    def _clear_product_form(self, stock_type: str) -> None:
        """Clears the product definition form."""
        form = self.stock_widgets[stock_type]['def_form']
        for key, widget in form.items():
            if isinstance(widget, QLineEdit): widget.clear()
            elif isinstance(widget, QComboBox):
                if key in ("purchase_curr", "sale_curr", "workmanship_curr"):
                    widget.setCurrentText("TRY")
                elif widget.count() > 0:
                    widget.setCurrentIndex(0)
        form['table'].clearSelection()
        form['product_name'].setFocus() # <-- DüZELTME BURADA
    def _generate_stock_report_text(self, stock_type: str) -> None:
        """
        Stok raporunu oluşturur.
        KATi KURAL: Veritabanındaki satırları temel alır, dinamik olarak hesaplar.
        Gruplama sadece rapor gösterimi içindir, veri bütünlüğünü bozmaz.
        """
        manager = self._get_stock_manager(stock_type)
        display_widget = self.stock_widgets[stock_type]['report_display']
        report = f"{'Item Type':<25} | {'Quantity':>12} | {f'Avg. Purchase ({self.base_currency})':>20} | {f'Total Value ({self.display_currency})':>22}\n"
        report += "-" * 84 + "\n"
        total_value_base = 0.0
        from collections import defaultdict
        grouped_stock = defaultdict(lambda: {'quantity': 0.0, 'value_base': 0.0})
        all_items = manager.get_all()
        for s in all_items:
            if s.quantity <= 0: continue
            item_val = s.quantity * s.purchase_price_base
            key = s.item_type
            grouped_stock[key]['quantity'] += s.quantity
            grouped_stock[key]['value_base'] += item_val
        for item_type, data in sorted(grouped_stock.items()):
            total_value_base += data['value_base']
            avg_price = data['value_base'] / data['quantity'] if data['quantity'] > 0 else 0
            val_disp = self.currency_manager._convert_to_display_currency(data['value_base'], self.display_currency)
            report += f"{item_type:<25.25s} | {format_number(data['quantity'], 3):>12s} | {f'{format_number(avg_price)}':>20s} | {f'{format_number(val_disp)}':>22s}\n"
        report += "=" * 84 + "\n"
        total_value_display = self.currency_manager._convert_to_display_currency(total_value_base, self.display_currency)
        report += f"{'TOTAL STOCK VALUE:':>60} {format_number(total_value_display):>22s} {self.display_currency}"
        display_widget.setPlainText(report)
        self._show_status_message(f"{stock_type} stock report generated.")
    def _refresh_dashboard_widgets(self):
        """Dashboard verilerini worker thread ile yeniler."""
        if not hasattr(self, "tabs") or self.tabs.currentIndex() != 0:
            return
        if not hasattr(self, "dashboard_ui_helper"):
            return

        helper = self.dashboard_ui_helper
        if hasattr(helper, "refresh_data") and callable(getattr(helper, "refresh_data")):
            helper.refresh_data()

        worker = getattr(self, "_dashboard_worker", None)
        if worker is not None and worker.isRunning():
            self._dashboard_refresh_pending = True
            return

        self._dashboard_refresh_pending = False
        worker = DashboardWorker(self)
        self._dashboard_worker = worker
        worker.data_ready.connect(lambda payload, w=worker: self._on_dashboard_data_ready(w, payload))
        worker.error.connect(lambda err, w=worker: self._on_dashboard_worker_error(w, err))
        worker.finished.connect(lambda w=worker: self._on_dashboard_worker_finished(w))
        worker.start()

    def _on_dashboard_data_ready(self, worker: DashboardWorker, payload: object) -> None:
        """Worker'dan gelen dashboard verisini UI thread'de işler."""
        if worker is not getattr(self, "_dashboard_worker", None):
            return
        if not hasattr(self, "dashboard_ui_helper") or payload is None:
            return
        try:
            helper = self.dashboard_ui_helper
            if hasattr(helper, "render_dashboard_snapshot"):
                helper.render_dashboard_snapshot(payload)
        except Exception as e:
            logger.error(f"Dashboard UI render failed: {e}", exc_info=True)

    def _on_dashboard_worker_error(self, worker: DashboardWorker, err: str) -> None:
        if worker is not getattr(self, "_dashboard_worker", None):
            return
        logger.error(f"Dashboard background refresh error: {err}")

    def _on_dashboard_worker_finished(self, worker: DashboardWorker) -> None:
        is_current_worker = worker is getattr(self, "_dashboard_worker", None)
        if is_current_worker:
            self._dashboard_worker = None
        try:
            worker.deleteLater()
        except Exception:
            pass

        if (
            is_current_worker
            and self._dashboard_refresh_pending
            and hasattr(self, "tabs")
            and self.tabs.currentIndex() == 0
        ):
            self._dashboard_refresh_pending = False
            QTimer.singleShot(0, self._refresh_dashboard_widgets)
    def _update_stock_movement_table(self, stock_type):
        """Stok hareketleri tablosunu filtreye göre günceller."""
        if stock_type not in self.stock_widgets: return
        widgets = self.stock_widgets[stock_type]
        if 'movement_table' not in widgets: return
        table = widgets['movement_table']
        if 'movement_depot_filter' not in widgets: return
        depot_combo = widgets.get('movement_depot_filter')
        depot_filter = depot_combo.currentData() if depot_combo else None
        date_filter_enabled = bool(
            widgets.get('movement_date_filter_enabled').isChecked()
        ) if widgets.get('movement_date_filter_enabled') else False
        selected_date = None
        if date_filter_enabled and widgets.get('movement_date_filter'):
            selected_date = widgets.get('movement_date_filter').date().toPyDate()

        def _parse_log_datetime(raw_value):
            if isinstance(raw_value, datetime):
                return raw_value
            text = str(raw_value or "").strip()
            if not text:
                return None
            try:
                return datetime.fromisoformat(text.replace("Z", ""))
            except Exception:
                pass
            for fmt in ("%d.%m.%Y %H:%M:%S", "%d.%m.%Y %H:%M", "%d.%m.%Y", "%Y-%m-%d %H:%M:%S", "%Y-%m-%d"):
                try:
                    return datetime.strptime(text, fmt)
                except Exception:
                    continue
            return None

        all_logs = self.stock_movement_manager.get_all()
        all_logs.sort(key=lambda x: x.date, reverse=True)
        filtered_logs = []
        for log in all_logs:
            if log.item_type != stock_type:
                continue
            if depot_filter and log.source != depot_filter and log.destination != depot_filter:
                continue
            log_dt = _parse_log_datetime(getattr(log, "date", ""))
            if selected_date and (not log_dt or log_dt.date() != selected_date):
                continue
            filtered_logs.append(log)
        table.setRowCount(len(filtered_logs))
        for row, log in enumerate(filtered_logs):
            try:
                parsed_dt = _parse_log_datetime(log.date)
                dt = parsed_dt.strftime("%d.%m.%Y %H:%M") if parsed_dt else str(log.date)
            except Exception:
                dt = log.date
            table.setItem(row, 0, QTableWidgetItem(dt))
            table.setItem(row, 1, QTableWidgetItem(log.product_name))
            table.setItem(row, 2, QTableWidgetItem(log.operation_type))
            table.setItem(row, 3, QTableWidgetItem(format_number(log.quantity, 3)))
            table.setItem(row, 4, QTableWidgetItem(log.source))
            table.setItem(row, 5, QTableWidgetItem(log.destination))
            table.setItem(row, 6, QTableWidgetItem(log.user))
    def _pos_product_selected(self, stock_type: str, transaction_type: str, text=None) -> None:
        """
        ürün seçildiğinde çalışır.
        - Eğer 'Product' (ürün) ise: Miktar otomatik '1' olur (Adet hesabı için).
        - Eğer 'Ziynet' ise: Miktar otomatik '1' olur (Adet hesabı için).
        - Eğer 'Scrap' (Hurda) ise: Tanımlı gramajı getirir (Gram hesabı için).
        """
        widgets = self.stock_widgets[stock_type][transaction_type]
        if stock_type == "Product":
            manager = self.product_manager
        elif stock_type == "Ziynet":
            manager = self.ziynet_manager
        else:
            manager = self.scrap_manager
        search_term = widgets['product_combo'].currentText().strip()
        if not search_term:
            if 'workmanship_checkbox' in widgets: widgets['workmanship_checkbox'].setChecked(False)
            return
        product = manager.find_by_attribute('product_name', search_term)
        if not product and stock_type == "Product":
            product = manager.find_by_attribute('barcode', search_term)
            if product:
                widgets['product_combo'].setCurrentText(product.product_name)
        if product:
            is_sale = transaction_type == "Sale"
            price = product.default_sale_price if is_sale else product.default_purchase_price
            currency = product.sale_currency if is_sale else product.purchase_currency
            if stock_type in ("Product", "Ziynet"):
                widgets['quantity_entry'].setText("1")
            else:
                def_weight = getattr(product, 'default_weight_gr', 0.0)
                if def_weight is None:
                    def_weight = 0.0
                if def_weight > 0:
                    widgets['quantity_entry'].setText(f"{def_weight:.3f}".replace('.',','))
                else:
                    widgets['quantity_entry'].setText("1")
            if 'workmanship_checkbox' in widgets:
                widgets['current_workmanship_fee'] = product.workmanship_fee
                widgets['current_workmanship_currency'] = product.workmanship_currency
                if widgets['workmanship_checkbox'].isChecked():
                    workmanship_in_base = self.currency_manager._convert_to_base_currency(product.workmanship_fee, product.workmanship_currency)
                    workmanship_in_trans_curr = self.currency_manager._convert_to_display_currency(workmanship_in_base, currency)
                    price += workmanship_in_trans_curr
            widgets['price_entry'].setText(f"{price:.2f}")
            widgets['price_currency_combo'].setCurrentText(currency)
            self._pos_update_prices(stock_type, transaction_type, 'unit')
            widgets['quantity_entry'].setFocus()
            widgets['quantity_entry'].selectAll()
        else:
            pass
    def _toggle_workmanship(self, stock_type: str, transaction_type: str, checked: bool):
        """Adds or removes workmanship fee from the price field."""
        widgets = self.stock_widgets[stock_type][transaction_type]
        price_str = widgets['price_entry'].text().replace(',', '.')
        self._pos_update_prices(stock_type, transaction_type, 'unit')
        if not price_str: return
        try:
            price = float(price_str)
            workmanship_fee = widgets.get('current_workmanship_fee', 0.0)
            workmanship_currency = widgets.get('current_workmanship_currency', self.display_currency)
            transaction_currency = widgets['price_currency_combo'].currentText()
            workmanship_in_base = self.currency_manager._convert_to_base_currency(workmanship_fee, workmanship_currency)
            workmanship_in_trans_curr = self.currency_manager._convert_to_display_currency(workmanship_in_base, transaction_currency)
            if checked:
                new_price = price + workmanship_in_trans_curr
            else:
                new_price = price - workmanship_in_trans_curr
            if new_price < 0: new_price = 0
            widgets['price_entry'].setText(f"{new_price:.2f}")
        except ValueError:
            pass
    def _get_cart_currency(self, cart: List[Dict[str, Any]]) -> str:
        """Returns the currency used in the cart (first non-empty entry)."""
        for item in cart or []:
            currency = str(item.get("currency") or "").strip()
            if currency:
                return currency
        return self.display_currency
    def _convert_amount_between_currencies(self, amount: float, from_currency: str, to_currency: str) -> float:
        src = str(from_currency or "").strip() or self.display_currency or "TRY"
        dst = str(to_currency or "").strip() or self.display_currency or "TRY"
        if src == dst:
            return float(amount or 0.0)
        rate = self.currency_manager.get_exchange_rate(src, dst)
        if rate is None or float(rate) <= 0:
            raise ValueError(f"Kur bulunamadı: {src} -> {dst}")
        return float(amount or 0.0) * float(rate)
    def _normalize_cart_to_display_currency(self, cart: List[Dict[str, Any]]) -> str:
        """Converts all cart lines to the selected display currency."""
        target_currency = str(self.display_currency or "TRY").strip() or "TRY"
        for item in cart or []:
            source_currency = str(item.get("currency") or target_currency).strip() or target_currency
            qty = float(item.get("quantity", 0) or 0)
            unit_price = float(item.get("unit_price", 0) or 0)
            converted_unit = self._convert_amount_between_currencies(unit_price, source_currency, target_currency)
            row_calc = hesapla(qty, converted_unit, 0, 0)
            item["unit_price"] = converted_unit
            item["subtotal"] = float(row_calc.get("toplam", 0) or 0)
            item["currency"] = target_currency
        return target_currency
    def _add_to_cart(self, stock_type: str, transaction_type: str) -> None:
        """
        ürünü sepete ekler.
        GüNCELLEME: Ziynet için NoneType hatası düzeltildi ve Altın (Product) için gelişmiş diyalog eklendi.
        """
        from modules.stock import AdvancedGoldEntryDialog
        widgets = self.stock_widgets[stock_type][transaction_type]
        pos_key = f"{stock_type}_{transaction_type}"
        cart = self.pos_carts[pos_key]
        is_sale = transaction_type == "Sale"
        if stock_type == "Product":
            def_manager = self.product_manager
            stock_manager = self.stock_manager
        elif stock_type == "Ziynet":
            def_manager = self.ziynet_manager
            stock_manager = self.ziynet_stock_manager
        else:
            def_manager = self.scrap_manager
            stock_manager = self.scrap_stock_manager
        product_name = widgets['product_combo'].currentText().strip()
        depot_name = widgets['pos_depot_combo'].currentText()
        if not product_name: return
        if not depot_name:
            QMessageBox.warning(self, self.lang["warning"], self.lang["no_depot_selected"])
            return
        product_def = def_manager.find_by_attribute('product_name', product_name)
        if not product_def:
            QMessageBox.warning(self, self.lang["warning"], f"Tanım bulunamadı: '{product_name}'")
            return
        qty_field_name = "Adet" if stock_type in ("Product", "Ziynet") else self.lang["quantity"]
        qty_precision = 3 if stock_type == "Scrap" else None
        input_qty = self.get_float_from_entry(
            widgets['quantity_entry'],
            qty_field_name,
            allow_zero=False,
            precision=qty_precision,
        )
        if input_qty is None: return
        price_label = self.lang['unit_sale_price'] if is_sale else self.lang['unit_purchase_price']
        unit_price = self.get_float_from_entry(widgets['price_entry'], price_label, allow_zero=True)
        if unit_price is None: return
        source_currency = widgets['price_currency_combo'].currentText()
        try:
            currency = self._normalize_cart_to_display_currency(cart)
            unit_price = self._convert_amount_between_currencies(unit_price, source_currency, currency)
        except Exception as e:
            QMessageBox.warning(self, "Kur Dönüşüm Hatası", f"Fiyat dönüştürülemedi:\n{e}")
            return
        
        if stock_type == "Product":
            qty_int = int(input_qty)
            if qty_int != input_qty:
                QMessageBox.warning(self, "Hata", "Altın ürünler için miktar tam sayı (Adet) olmalıdır.")
                return
            mode = "Out" if is_sale else "In"
            dialog = AdvancedGoldEntryDialog(
                mode,
                product_name,
                qty_int,
                stock_manager,
                self.product_manager,
                self
            )
            if dialog.exec():
                details_list = dialog.get_details()
                for item_detail in details_list:
                    single_qty = 1.0
                    single_gram = item_detail['gram']
                    single_barcode = item_detail['barcode']
                    calc_row = hesapla(single_qty, unit_price, 0, 0)
                    subtotal = float(calc_row["toplam"])
                    cart.append({
                        'product_id': product_def.id,
                        'type': product_name,
                        'quantity': single_qty,
                        'quantity_gr': single_gram,
                        'unique_barcode': [single_barcode],
                        'unit_price': unit_price,
                        'currency': currency,
                        'subtotal': subtotal,
                        'depot_name': depot_name,
                        'is_product_mode': True,
                        'karat': item_detail['karat']
                    })
                self._update_cart_display(stock_type, transaction_type)
                self._show_status_message(f"{qty_int} adet {product_name} detaylarıyla eklendi.")
                widgets['product_combo'].setCurrentIndex(-1)
                widgets['quantity_entry'].setText("1")
                widgets['price_entry'].clear()
                if 'total_price_entry' in widgets: widgets['total_price_entry'].clear()
            return
        if stock_type == "Ziynet":
            qty_int = int(input_qty)
            if qty_int != input_qty:
                QMessageBox.warning(self, "Hata", "Ziynet için miktar tam sayı (Adet) olmalıdır.")
                return
            input_qty = qty_int
        weight_to_deduct = 0.0
        if stock_type == "Ziynet":
            # Ziynet adet bazlı: arka planda 1 adet = 1 birim alınır.
            weight_to_deduct = float(input_qty)
        else:  # Scrap
            weight_to_deduct = input_qty
        calc_row = hesapla(input_qty, unit_price, 0, 0)
        subtotal = float(calc_row["toplam"])
        if is_sale:
            stock_item = stock_manager.get_stock_by_type_and_depot(product_name, depot_name)
            available = stock_item.quantity if stock_item else 0.0
            if stock_type == "Scrap" and available < weight_to_deduct:
                 QMessageBox.warning(self, "Yetersiz Stok", f"Yetersiz Stok!\nMevcut: {available:.2f}\nistenen: {weight_to_deduct:.2f}")
                 return
            elif stock_type == "Ziynet" and available < input_qty:
                 QMessageBox.warning(self, "Yetersiz Stok", f"Yetersiz Stok!\nMevcut: {available}\nistenen: {input_qty}")
                 return
        cart.append({
             'product_id': product_def.id,
             'type': product_name,
             'quantity': input_qty,
             'quantity_gr': weight_to_deduct,
            'unit_price': unit_price,
            'currency': currency,
            'subtotal': subtotal,
            'depot_name': depot_name,
            'is_product_mode': False
        })
        self._update_cart_display(stock_type, transaction_type)
        if stock_type == "Ziynet":
            self._show_status_message(f"Eklendi: {input_qty} adet {product_name}")
        else:
            self._show_status_message(f"Eklendi: {input_qty} {product_name}")
        widgets['product_combo'].setCurrentIndex(-1)
        widgets['quantity_entry'].setText("1")
        widgets['price_entry'].clear()
    def _update_cart_display(self, stock_type: str, transaction_type: str) -> None:
        """Updates the POS cart table and total amount."""
        if stock_type not in self.stock_widgets: return
        if transaction_type not in self.stock_widgets[stock_type]: return
        widgets = self.stock_widgets[stock_type][transaction_type]
        pos_key = f"{stock_type}_{transaction_type}"
        cart = self.pos_carts.get(pos_key, [])
        cart_table = widgets['cart_table']
        cart_table.setRowCount(len(cart))
        for row, item in enumerate(cart):
            display_name = item['type']
            barcodes = item.get('unique_barcode', [])
            if barcodes and isinstance(barcodes, list) and len(barcodes) > 0:
                display_name = f"[{barcodes[0]}] {display_name}"
            cart_table.setItem(row, 0, QTableWidgetItem(display_name))
            if stock_type == "Product":
                qty_val = int(item['quantity']) if item['quantity'] else 0
                cart_table.setItem(row, 1, QTableWidgetItem(str(qty_val)))
                gram_val = item.get('quantity_gr', 0.0)
                cart_table.setItem(row, 2, QTableWidgetItem(f"{format_number(gram_val, 3)}"))
                price_val = item.get('unit_price', 0.0)
                cart_table.setItem(row, 3, QTableWidgetItem(f"{format_number(price_val)}"))
                cart_table.setItem(row, 4, QTableWidgetItem(item.get('currency', '')))
                subtotal_val = item.get('subtotal', 0.0)
                cart_table.setItem(row, 5, QTableWidgetItem(f"{format_number(subtotal_val)} {item.get('currency', '')}"))
            else:
                qty_val = item.get('quantity', 0.0)
                if stock_type == "Ziynet":
                    try:
                        qty_display = str(int(qty_val))
                    except Exception:
                        qty_display = str(qty_val)
                    cart_table.setItem(row, 1, QTableWidgetItem(qty_display))
                else:
                    cart_table.setItem(row, 1, QTableWidgetItem(f"{format_number(qty_val, 3)}"))
                price_val = item.get('unit_price', 0.0)
                cart_table.setItem(row, 2, QTableWidgetItem(f"{format_number(price_val)}"))
                cart_table.setItem(row, 3, QTableWidgetItem(item.get('currency', '')))
                subtotal_val = item.get('subtotal', 0.0)
                cart_table.setItem(row, 4, QTableWidgetItem(f"{format_number(subtotal_val)} {item.get('currency', '')}"))
        self.fiyat_hesapla(stock_type, transaction_type)

    def _serialize_cart_details(self, cart: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        details: List[Dict[str, Any]] = []
        for item in cart or []:
            calc_row = hesapla(item.get("quantity", 0), item.get("unit_price", 0), 0, 0)
            detail = {
                "urun": item.get("type", ""),
                "miktar": float(item.get("quantity", 0) or 0),
                "birim_fiyat": float(item.get("unit_price", 0) or 0),
                "para_birimi": str(item.get("currency", self.display_currency) or self.display_currency),
                "net": float(calc_row.get("net", 0) or 0),
                "toplam": float(calc_row.get("toplam", 0) or 0),
            }
            barcodes = item.get("unique_barcode") or []
            if isinstance(barcodes, list) and barcodes:
                detail["barkod"] = str(barcodes[0])
            gram_val = float(item.get("quantity_gr", 0) or 0)
            if gram_val > 0:
                detail["gram"] = gram_val
            karat_val = str(item.get("karat") or "").strip()
            if karat_val:
                detail["saflik"] = karat_val
            details.append(detail)
        return grid_to_json(details)

    def _build_pos_tax_snapshot(
        self,
        stock_type: str,
        transaction_type: str,
        cart: List[Dict[str, Any]],
        widgets: Dict[str, Any],
    ) -> Dict[str, Any]:
        """
        POS ekranındaki anlık verileri ve SEÇİLEN VERGİLERİ toplayarak 
        TaxEngine için bir veri paketi (snapshot) oluşturur.
        """
        is_sale = transaction_type == "Sale"
        
        # 1. Müşteri Adını Al
        person_name = ""
        try:
            if widgets.get("person_combo"):
                person_name = str(widgets.get("person_combo").currentText() or "").strip()
        except Exception:
            pass

        # 2. Seçili Vergileri Al (YENİ SİSTEM)
        # Checkbox işaretli mi?
        chk = widgets.get('apply_taxes_checkbox')
        apply_taxes = bool(chk.isChecked()) if chk else False
        
        # Seçim penceresinden dönen vergi listesi
        selected_taxes = widgets.get('selected_taxes', [])
        
        # Eğer checkbox kapalıysa, seçili liste olsa bile vergiyi sıfırla
        if not apply_taxes:
            selected_taxes = []

        # 3. Not Oluştur
        note = f"{stock_type} {'Satış' if is_sale else 'Alış'}"

        # 4. Sepetteki Ürünleri Vergi Motoruna Uygun Hale Getir
        from modules.calculations import hesapla # Import edildiğinden emin olun
        try:
            self._normalize_cart_to_display_currency(cart)
        except Exception as e:
            logger.error(f"Sepet para birimi normalize edilemedi: {e}")
        
        tax_items = []
        # Fallback hesaplama için manuel toplam (Motor çalışmazsa kullanılır)
        fallback_subtotal = 0.0 
        
        for item in cart or []:
            qty = float(item.get("quantity", 0))
            price = float(item.get("unit_price", 0))
            name = str(item.get("type", ""))
            
            # Basit hesaplama (Vergisiz)
            row_calc = hesapla(qty, price, 0, 0)
            fallback_subtotal += float(row_calc.get("toplam", 0))
            
            tax_items.append({
                "name": name,
                "qty": qty,
                "unit_price": price,
            })

        cart_currency = self._get_cart_currency(cart)

        # 5. Vergi Motorunu Çağır
        try:
            # TaxEngine artık profil ID değil, vergi listesi bekliyor
            snapshot = self.tax_engine.compute_snapshot(
                items=tax_items,
                currency=cart_currency,
                selected_taxes=selected_taxes, # <-- KRİTİK DEĞİŞİKLİK BURADA
                customer_name=person_name,
                note=note
            )
            return snapshot
            
        except Exception as e:
            # Eğer hesaplama motorunda bir hata olursa, programın çökmemesi için
            # vergisiz ham hali döndür.
            logger.error(f"Vergi hesaplama hatası (_build_pos_tax_snapshot): {e}")
            
            return {
                "version": 1,
                "apply_taxes": False,
                "currency": cart_currency,
                "customer_name": person_name,
                "note": note,
                "subtotal": fallback_subtotal,
                "tax_total": 0.0,
                "grand_total": fallback_subtotal,
                "applied_taxes_summary": "",
                "taxes": [],
                "fixed_items": [],
                "lines": [] # Detayları boş geçiyoruz hata durumunda
            }

    def fiyat_hesapla(self, stock_type: str, transaction_type: str) -> float:
        if stock_type not in self.stock_widgets or transaction_type not in self.stock_widgets[stock_type]:
            return 0.0
        widgets = self.stock_widgets[stock_type][transaction_type]
        cart = self.pos_carts.get(f"{stock_type}_{transaction_type}", [])
        if not cart:
            if "total_amount_label" in widgets:
                widgets["total_amount_label"].setText(self.currency_manager.format_currency(0.0, self.display_currency))
            return 0.0
        snapshot = self._build_pos_tax_snapshot(stock_type, transaction_type, cart, widgets)
        payable_total = float(snapshot.get("grand_total") or 0.0)
        payable_currency = str(snapshot.get("currency") or self._get_cart_currency(cart) or self.display_currency)
        if "total_amount_label" in widgets:
            widgets["total_amount_label"].setText(self.currency_manager.format_currency(payable_total, payable_currency))
        return payable_total

    def _remove_from_cart(self, pos_key: str) -> None:
        """Removes selected products from the POS cart."""
        stock_type, transaction_type = pos_key.split('_')
        widgets = self.stock_widgets[stock_type][transaction_type]
        cart = self.pos_carts[pos_key]
        cart_table = widgets['cart_table']
        selected_items = cart_table.selectedItems()
        if not selected_items: return
        rows_to_remove = sorted(set(item.row() for item in selected_items), reverse=True)
        for row in rows_to_remove:
            if row < len(cart):
                item = cart.pop(row)
                self._show_status_message(f"Removed {item['type']} from list.")
        self._update_cart_display(stock_type, transaction_type)
    def _complete_transaction(self, stock_type: str, transaction_type: str) -> None:
        """
        Complete POS sale/purchase flow asynchronously to keep UI responsive.
        """
        widgets = self.stock_widgets[stock_type][transaction_type]
        pos_key = f"{stock_type}_{transaction_type}"
        cart = self.pos_carts[pos_key]
        is_sale = transaction_type == "Sale"
        if not cart:
            msg = QMessageBox(self)
            msg.setIcon(QMessageBox.Icon.Warning)
            msg.setWindowTitle(self.lang.get("warning", "Warning"))
            msg.setText(self.lang.get("empty_cart", "Cart is empty!"))
            ok_btn = msg.addButton(self.lang.get("ok", "OK"), QMessageBox.ButtonRole.AcceptRole)
            ok_btn.setDefault(True)
            msg.exec()
            return

        if self._tx_worker is not None and self._tx_worker.isRunning():
            QMessageBox.information(self, "Bilgi", "Devam eden bir işlem var. Lütfen bitmesini bekleyin.")
            return

        person_name = widgets['person_combo'].currentText().strip() or "N/A"
        if person_name != "N/A" and not self.person_manager.find_by_attribute('full_name', person_name):
            if self._show_confirm_dialog(self.lang["info"], f"Kişi '{person_name}' bulunamadı. Eklensin mi?"):
                self.person_manager.add_item(Person(full_name=person_name))
                self.crm_manager.update_person_comboboxes()
            else:
                return

        products_sold_data: List[Dict[str, Any]] = []
        if is_sale and stock_type == "Product":
            curr_stock_mgr = self.stock_manager
            curr_def_mgr = self.product_manager
            available_barcodes_by_key: Dict[tuple, set] = defaultdict(set)
            for stock_row in curr_stock_mgr.get_all():
                code = str(getattr(stock_row, "unique_barcode", "") or "").strip()
                if not code:
                    continue
                if float(getattr(stock_row, "quantity", 0) or 0) <= 0:
                    continue
                key = (str(getattr(stock_row, "item_type", "") or ""), str(getattr(stock_row, "depot_name", "") or ""))
                available_barcodes_by_key[key].add(code)

            for item in cart:
                depot_name = item['depot_name']
                quantity_int = int(item['quantity'])
                existing_raw = item.get('unique_barcode')
                if isinstance(existing_raw, list):
                    existing_barcodes = [str(b).strip() for b in existing_raw if str(b).strip()]
                elif isinstance(existing_raw, str) and existing_raw.strip():
                    existing_barcodes = [existing_raw.strip()]
                else:
                    existing_barcodes = []

                valid_for_stock = available_barcodes_by_key.get((item['type'], depot_name), set())
                valid_existing = [code for code in existing_barcodes if code in valid_for_stock]
                if len(valid_existing) == quantity_int and quantity_int > 0:
                    item['unique_barcode'] = valid_existing
                    products_sold_data.append(item)
                    continue
                invalid_entered = [code for code in existing_barcodes if code not in valid_for_stock]
                if invalid_entered:
                    QMessageBox.warning(self, "Hata", "Girdiğiniz barkod stokta bulunamadı!")
                    return
                if not valid_for_stock:
                    QMessageBox.warning(self, "Hata", "Bu ürün stokta yok!")
                    return
                if len(valid_for_stock) < quantity_int:
                    QMessageBox.warning(
                        self,
                        "Hata",
                        f"Yetersiz barkodlu stok! İstenen: {quantity_int}, Mevcut: {len(valid_for_stock)}",
                    )
                    return

                from modules.stock import BarcodeSaleSelectionDialog
                barcode_dialog = BarcodeSaleSelectionDialog(
                    item['type'], quantity_int, curr_stock_mgr, depot_name, self.lang, curr_def_mgr, self
                )
                if barcode_dialog.exec() != QDialog.DialogCode.Accepted:
                    return
                selected_barcodes = barcode_dialog.get_selected_barcodes()
                if len(selected_barcodes) != quantity_int:
                    QMessageBox.warning(self, "Hata", "Seçilen barkod adedi ürün adedi ile eşleşmiyor.")
                    return
                item['unique_barcode'] = selected_barcodes
                products_sold_data.append(item)
        else:
            products_sold_data = list(cart)

        tax_snapshot = self._build_pos_tax_snapshot(stock_type, transaction_type, cart, widgets)
        payable_total = float(tax_snapshot.get("grand_total") or 0.0)
        transaction_currency = str(tax_snapshot.get("currency") or self._get_cart_currency(cart) or self.display_currency)
        if payable_total <= 0:
            fallback_total = 0.0
            for item in cart:
                calc_row = hesapla(item.get("quantity", 0), item.get("unit_price", 0), 0, 0)
                fallback_total += float(calc_row.get("toplam", 0) or 0)
            payable_total = float(fallback_total)

        details_json = self._serialize_cart_details(products_sold_data)
        for d in details_json:
            log_islem(
                f"{stock_type}_{transaction_type}",
                d.get("miktar", 0),
                d.get("toplam", 0),
                d.get("vergi", 0),
                d.get("net", 0),
            )

        payment_dialog = PaymentDialog(
            total_amount=payable_total,
            display_currency=transaction_currency,
            person_name=person_name,
            cash_accounts=self._get_accessible_cash_accounts(),
            card_accounts=self._get_accessible_card_accounts(),
            depot_manager=self.depot_manager,
            product_manager=self.product_manager,
            stock_manager=self.stock_manager,
            lang=self.lang,
            currency_manager=self.currency_manager,
            allowed_depots=self._get_accessible_depots(),
            product_trade_direction="in" if is_sale else "out",
            parent=self
        )
        if not payment_dialog.exec():
            return

        payment_breakdown = payment_dialog.get_payment_breakdown()
        self._show_loading("İşlem VPS'e kaydediliyor, lütfen bekleyiniz...")

        transaction_context = {
            'cart': deepcopy(list(cart)),
            'person_name': person_name,
            'stock_type': stock_type,
            'is_sale': is_sale,
            'payment_breakdown': deepcopy(payment_breakdown),
            'products_sold_data': deepcopy(list(products_sold_data)),
            'display_currency': transaction_currency,
            'tax_snapshot': deepcopy(tax_snapshot),
            'details_json': deepcopy(details_json),
        }

        self._tx_worker = TransactionWorker(self.transaction_service, transaction_context)
        self._tx_worker.finished_success.connect(
            lambda result: self._on_transaction_worker_success(
                result, stock_type, transaction_type, list(products_sold_data)
            )
        )
        self._tx_worker.error_occurred.connect(self._on_transaction_worker_error)
        self._tx_worker.finished.connect(self._on_transaction_worker_finished)
        self._tx_worker.start()

    def _on_transaction_worker_success(
        self,
        result: Dict[str, Any],
        stock_type: str,
        transaction_type: str,
        products_sold_data: List[Dict[str, Any]],
    ) -> None:
        """Runs on UI thread after background POS transaction succeeds."""
        pos_key = f"{stock_type}_{transaction_type}"
        cart = self.pos_carts[pos_key]
        cart.clear()
        self._update_cart_display(stock_type, transaction_type)
        self.update_all_displays()

        is_sale = transaction_type == "Sale"
        generated_barcodes = []
        if isinstance(result, dict):
            generated_barcodes = result.get("generated_barcodes", []) or []

        if not is_sale and generated_barcodes:
            barcode_dialog = BarcodeCreatedDialog(generated_barcodes, self)
            barcode_dialog.exec()

        product_names = [item.get("type", "") for item in products_sold_data]
        desc_preview = ", ".join(product_names)[:30]
        self._show_status_message(f"İşlem tamamlandı: {desc_preview}...")

    def _on_transaction_worker_error(self, error_msg: str) -> None:
        """Runs on UI thread after background POS transaction fails."""
        logger.error(f"Transaction worker error: {error_msg}")
        QMessageBox.critical(self, "Hata", f"İşlem kaydedilemedi:\n{error_msg}")

    def _on_transaction_worker_finished(self) -> None:
        """Cleans up worker and loading state after completion."""
        self._hide_loading()
        worker = self._tx_worker
        self._tx_worker = None
        if worker is not None:
            try:
                worker.deleteLater()
            except Exception:
                pass
    def _execute_stock_transfer(self, stock_type: str):
        """Executes the transfer of a product from one depot to another."""
        widgets = self.stock_widgets[stock_type]['transfer']
        stock_manager = self._get_stock_manager(stock_type)
        source_depot = widgets['source_combo'].currentText()
        dest_depot = widgets['dest_combo'].currentText()
        product_name = widgets['product_combo'].currentText()
        qty_field_name = "Adet" if stock_type in ("Product", "Ziynet") else self.lang['quantity']
        qty_precision = 3 if stock_type == "Scrap" else None
        quantity = self.get_float_from_entry(
            widgets['quantity_entry'],
            qty_field_name,
            allow_zero=False,
            precision=qty_precision,
        )
        if not all([source_depot, dest_depot, product_name, quantity is not None]):
            QMessageBox.warning(self, self.lang["warning"], self.lang["missing_info"])
            return
        if source_depot == dest_depot:
            QMessageBox.warning(self, self.lang["warning"], self.lang["depots_must_be_different"])
            return
        if stock_type == "Product":
            qty_int = int(quantity)
            if qty_int != quantity:
                QMessageBox.warning(self, "Hata", "Altın transferi tam sayı (Adet) olmalıdır.")
                return
            from modules.stock import BarcodeSaleSelectionDialog # Transfer için de kullanılabilir
            barcode_dialog = BarcodeSaleSelectionDialog(
                product_name, qty_int, stock_manager, source_depot, self.lang, self.product_manager, self
            )
            if barcode_dialog.exec() == QDialog.DialogCode.Accepted:
                selected_barcodes = barcode_dialog.get_selected_barcodes()
                for barcode in selected_barcodes:
                    item = next((i for i in stock_manager.get_all() if i.unique_barcode == barcode), None)
                    if item:
                        stock_manager.update_item(item.id, {"depot_name": dest_depot})
                        self.barcode_history_manager.log_movement(
                            barcode=barcode,
                            from_loc=source_depot,
                            to_loc=dest_depot,
                            desc=f"Depo Transferi ({product_name})",
                            user=self.current_user['name']
                        )
                self.stock_movement_manager.add_log(
                    item_type=stock_type, product_name=product_name, op_type="Transfer",
                    qty=qty_int, source=source_depot, dest=dest_depot,
                    desc=f"Barkodlu Transfer: {len(selected_barcodes)} adet", user=self.current_user['name']
                )
                widgets['quantity_entry'].clear()
                self.update_all_displays()
                self._show_status_message("Transfer Başarılı.")
            return
        if stock_type == "Ziynet":
            qty_int = int(quantity)
            if qty_int != quantity:
                QMessageBox.warning(self, "Hata", "Ziynet transferi tam sayı (Adet) olmalıdır.")
                return
            quantity = qty_int
        source_stock = stock_manager.get_stock_by_type_and_depot(product_name, source_depot)
        if not source_stock or source_stock.quantity < quantity:
            QMessageBox.warning(self, self.lang["warning"], "Yetersiz Stok")
            return
        if stock_manager.remove_stock(product_name, quantity, source_depot):
            stock_manager.add_or_update_stock(product_name, quantity, source_stock.purchase_price_base, dest_depot)
            self.stock_movement_manager.add_log(
                item_type=stock_type, product_name=product_name, op_type="Transfer",
                qty=quantity, source=source_depot, dest=dest_depot,
                desc="Yığın Transferi", user=self.current_user['name']
            )
            widgets['quantity_entry'].clear()
            self.update_all_displays()
            quantity_fmt = str(int(quantity)) if stock_type == "Ziynet" else format_number(quantity, 3)
            self._show_status_message(self.lang["depot_transfer_successful"].format(
                quantity=quantity_fmt, product=product_name, source=source_depot, dest=dest_depot))
        else:
            QMessageBox.critical(self, "Hata", "Transfer başarısız.")
    def _show_add_debt_dialog(self, record_type: str, affect_default: bool) -> None:
        """Shows a dialog to add a new receivable or payable record."""
        pass
    def _collect_payment(self): self.crm_manager._handle_debt_payment("Receivable")
    def _make_payment(self): self.crm_manager._handle_debt_payment("Payable")
    def _receive_product_payment(self): self.crm_manager._handle_product_payment("Receivable")
    def _return_product_payment(self): self.crm_manager._handle_product_payment("Payable")
    def _add_repair_record(self) -> None:
        """Adds a new repair record."""
        customer_full_name = self.repair_customer_combo.currentText().strip()
        if not customer_full_name:
            QMessageBox.warning(self, self.lang["missing_info"], f"{self.lang['customer_name']} {self.lang['missing_info'].lower()}.")
            return
        if not self.person_manager.find_by_attribute('full_name', customer_full_name):
            if self._show_confirm_dialog(self.lang["info"], f"Customer '{customer_full_name}' not found. Add new customer?"):
                self.person_manager.add_item(Person(full_name=customer_full_name))
                self.crm_manager.update_person_comboboxes()
            else: return
        product_description = self.repair_product_description_entry.text().strip()
        if not product_description:
            QMessageBox.warning(self, self.lang["missing_info"], f"{self.lang['product_desc']} {self.lang['missing_info'].lower()}.")
            return
        estimated_fee = self.get_float_from_entry(self.repair_estimated_fee_entry, self.lang["estimated_fee"], allow_zero=True)
        if estimated_fee is None: return
        new_record = RepairRecord(
            customer_full_name=customer_full_name, product_description=product_description,
            complaint_request=self.repair_complaint_entry.toPlainText().strip(), receipt_date=datetime.now().strftime("%d.%m.%Y"),
            estimated_fee=estimated_fee, fee_currency=self.repair_fee_currency_combo.currentText(), status="Received"
        )
        self.repair_manager.add_item(new_record)
        self.update_all_displays()
        self._show_status_message(f"Repair record added for {customer_full_name}.")
        self._clear_repair_form()
    def _clear_repair_form(self) -> None:
        """Clears the repair record form fields."""
        self.repair_customer_combo.setCurrentIndex(-1); self.repair_customer_combo.setEditText("")
        self.repair_product_description_entry.clear(); self.repair_complaint_entry.clear()
        self.repair_estimated_fee_entry.clear(); self.repair_table.clearSelection()
    def _repair_record_selected(self) -> None:
        """Loads selected repair record's details into the form."""
        selected_items = self.repair_table.selectedItems()
        if not selected_items:
            self._clear_repair_form()
            return
        repair_id = self.repair_table.item(selected_items[0].row(), 0).data(Qt.ItemDataRole.UserRole)
        record = self.repair_manager.find_by_id(repair_id)
        if record:
            self.repair_customer_combo.setEditText(record.customer_full_name)
            self.repair_product_description_entry.setText(record.product_description)
            self.repair_complaint_entry.setPlainText(record.complaint_request)
            self.repair_estimated_fee_entry.setText(f"{record.estimated_fee:.2f}")
            self.repair_fee_currency_combo.setCurrentText(record.fee_currency)
            self.repair_status_combo.setCurrentText(self.lang.get(record.status.lower(), record.status))
    def _update_repair_status(self) -> None:
        """Updates the status of a selected repair record."""
        selected_items = self.repair_table.selectedItems()
        if not selected_items: return
        repair_id = self.repair_table.item(selected_items[0].row(), 0).data(Qt.ItemDataRole.UserRole)
        record = self.repair_manager.find_by_id(repair_id)
        if not record: return
        new_status_text = self.repair_status_combo.currentText()
        new_status = next((k for k, v in self.lang.items() if v == new_status_text), new_status_text).title().replace('_', ' ')
        if record.status != new_status:
            new_data = {"status": new_status}
            if new_status == "Delivered": new_data["delivery_date"] = datetime.now().strftime("%d.%m.%Y")
            self.repair_manager.update_item(repair_id, new_data)
            self.update_all_displays()
            self._show_status_message(f"Repair record status updated to {new_status_text}.")
    def _deliver_and_collect_repair(self) -> None:
        """
        Tamiri teslim eder ve ücreti tahsil eder.
        Oluşan kayıtlar 'Tamir' etiketiyle işaretlenir ki Cari hesaba karışmasın.
        """
        selected_items = self.repair_table.selectedItems()
        if not selected_items: return
        repair_id = self.repair_table.item(selected_items[0].row(), 0).data(Qt.ItemDataRole.UserRole)
        record = self.repair_manager.find_by_id(repair_id)
        if not record or record.status == "Delivered": return
        if record.estimated_fee <= 0:
            self.repair_manager.update_item(repair_id, {"status": "Delivered", "delivery_date": datetime.now().strftime("%d.%m.%Y")})
            self.update_all_displays()
            self._show_status_message(f"Repair delivered for {record.customer_full_name} (No Fee).")
            return
        payment_dialog = PaymentDialog(
            total_amount=record.estimated_fee,
            display_currency=record.fee_currency,
            person_name=record.customer_full_name,
            cash_accounts=self._get_accessible_cash_accounts(),
            card_accounts=self._get_accessible_card_accounts(),
            depot_manager=self.depot_manager,
            product_manager=self.product_manager,
            stock_manager=self.stock_manager,
            lang=self.lang,
            currency_manager=self.currency_manager,
            allowed_depots=self._get_accessible_depots(),
            product_trade_direction="in",
            parent=self
        )
        if not payment_dialog.exec(): return
        payment_breakdown = payment_dialog.get_payment_breakdown()
        transaction_date = datetime.now().strftime("%d.%m.%Y")
        event_date_iso = datetime.now().isoformat()
        service_desc = f"Tamir Hizmet Bedeli - {record.product_description}"
        self.customer_history_manager.add_item(CustomerHistoryEvent(
            person_full_name=record.customer_full_name, event_date=event_date_iso,
            event_type="Tamir Hizmeti (Borç)",
            debt_original=record.estimated_fee, currency=record.fee_currency,
            description=service_desc, related_id=record.id
        ))
        for payment in payment_breakdown:
            method_type = payment['method_type']
            manager = None
            if method_type == 'cash': manager = self.cash_managers.get(payment['account_id'])
            elif method_type == 'card': manager = self.card_managers.get(payment['account_id'])
            if manager:
                payment_base = float(
                    payment.get(
                        'base_amount',
                        self.currency_manager._convert_to_base_currency(payment['amount'], payment['currency'])
                    ) or 0.0
                )
                fin_desc = f"Tamir Tahsilatı - {record.customer_full_name}"
                new_fin_trans = FinancialTransaction(
                    transaction_date, "Income", payment['amount'], payment['currency'],
                    payment_base, fin_desc, person_full_name=record.customer_full_name
                )
                manager.add_item(new_fin_trans)
                self.customer_history_manager.add_item(CustomerHistoryEvent(
                    person_full_name=record.customer_full_name, event_date=event_date_iso,
                    event_type="Tamir Tahsilatı",
                    credit_original=payment['amount'], currency=payment['currency'],
                    description=f"Tamir ödemesi Alındı ({payment['account_name']})",
                    related_id=new_fin_trans.id
                ))
            elif method_type == "On Credit":
                remaining_in_display = self.currency_manager._convert_to_display_currency(payment['amount_base'], self.display_currency)
                desc_credit = f"Tamir Bakiyesi (Veresiye) - {record.product_description}"
                new_debt = DebtRecord("Receivable", record.customer_full_name, transaction_date, "Cash",
                                    total_amount_base=payment['amount_base'],
                                    original_amount=remaining_in_display,
                                    currency=self.display_currency,
                                    description=desc_credit)
                self.debt_manager.add_item(new_debt)
            elif method_type == "product":
                prod_name = payment.get('account_id')
                qty_pay = float(payment.get('amount') or 0)
                depot_name = payment.get('depot_name') or get_default_depot_name()
                gold_details = payment.get('gold_details') or []
                product_def = self.product_manager.find_by_attribute('product_name', prod_name) if prod_name else None
                p_base = 0.0
                if product_def:
                    p_base = self.currency_manager._convert_to_base_currency(product_def.default_purchase_price, product_def.purchase_currency)
                if gold_details:
                    for detail in gold_details:
                        barcode = str(detail.get('barcode') or '').strip()
                        gram_val = float(detail.get('gram') or 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=prod_name,
                            quantity=1.0,
                            quantity_gr=gram_val,
                            unique_barcode=barcode,
                            depot_name=depot_name,
                            purchase_price_base=p_base,
                        ))
                        self.barcode_history_manager.register_buy(
                            barcode=barcode,
                            p_type=prod_name,
                            gram=gram_val,
                            karat=karat_val,
                            source=record.customer_full_name,
                            price=p_base,
                            method="Tamir Tahsilatı (Takas)",
                        )
                elif prod_name and qty_pay > 0:
                    self.stock_manager.add_or_update_stock(prod_name, qty_pay, p_base, depot_name)
                self.customer_history_manager.add_item(CustomerHistoryEvent(
                    person_full_name=record.customer_full_name,
                    event_date=event_date_iso,
                    event_type="Tamir Tahsilatı (Takas)",
                    credit_original=payment.get('total_val', 0),
                    currency=payment.get('currency', self.display_currency),
                    description=f"{qty_pay:.3f} {prod_name} alındı ({depot_name})",
                    related_id=None
                ))
        self.repair_manager.update_item(repair_id, {"status": "Delivered", "delivery_date": transaction_date})
        self.update_all_displays()
        self._show_status_message(f"Repair delivered and payment processed for {record.customer_full_name}.")
    def _delete_repair_record(self) -> None:
        if self.current_user["role"] != "admin" and "delete_all" not in self.current_user.get("permissions", []):
            QMessageBox.critical(self, "Yetki Hatası", "Bu kaydı silmeye yetkiniz yok.\n(Yönetici onayı gerekir)")
            return
        """Deletes a selected repair record."""
        selected_items = self.repair_table.selectedItems()
        if not selected_items: return
        repair_id = self.repair_table.item(selected_items[0].row(), 0).data(Qt.ItemDataRole.UserRole)
        if self._show_confirm_dialog(self.lang["delete"], self.lang['confirm_delete_prompt']):
            if self.repair_manager.delete_item(repair_id):
                self.update_all_displays()
                self._show_status_message(f"Repair record deleted.")
                self._clear_repair_form()
    def _add_scheduled_transaction(self):
        due_date = self.st_due_date.date().toString("dd.MM.yyyy")
        trans_type_text = self.st_type_combo.currentText()
        trans_type = "Income" if trans_type_text == self.lang["income"] else "Expense"
        amount = self.get_float_from_entry(self.st_amount_entry, self.lang["amount"], allow_zero=False)
        if amount is None: return
        currency = self.st_currency_combo.currentText()
        description = self.st_description_entry.text().strip()
        if not description:
            QMessageBox.warning(self, self.lang["warning"], self.lang["missing_info"])
            return
        target_data = self.st_target_account_combo.currentData()
        if not target_data:
            QMessageBox.warning(self, self.lang["warning"], self.lang["no_selection"].format(item_type=self.lang["source_account"]))
            return
        target_id = target_data[1]
        person_name = self.st_person_combo.currentText().strip() or None
        new_trans = ScheduledTransaction(
            due_date=due_date, transaction_type=trans_type, amount=amount,
            currency=currency, description=description, target=target_id,
            person_full_name=person_name
        )
        self.scheduled_transaction_manager.add_item(new_trans)
        self.update_scheduled_transactions_display()
        self._show_status_message("Scheduled transaction added.")
        self.st_amount_entry.clear()
        self.st_description_entry.clear()
    def _edit_scheduled_transaction(self):
        selected_items = self.scheduled_transactions_table.selectedItems()
        if not selected_items:
            return
        row = selected_items[0].row()
        item_id = self.scheduled_transactions_table.item(row, 0).data(Qt.ItemDataRole.UserRole)
        transaction = self.scheduled_transaction_manager.find_by_id(item_id)
        if not transaction:
            return
        accounts = []
        for acc in sorted(self.cash_account_manager.get_all(), key=lambda x: x.name):
            accounts.append((f"{self.lang['cash']} - {acc.name}", "cash", acc.id))
        for acc in sorted(self.card_account_manager.get_all(), key=lambda x: x.name):
            accounts.append((f"{self.lang['card']} - {acc.name}", "card", acc.id))
        dialog = EditScheduledTransactionDialog(transaction, self.lang, accounts, self)
        if dialog.exec():
            data = dialog.get_data()
            if data:
                self.scheduled_transaction_manager.update_item(transaction.id, data)
                self.update_scheduled_transactions_display()
                self._show_status_message("Scheduled transaction updated.")
            else:
                QMessageBox.warning(self, self.lang["warning"], self.lang["missing_info"])
    def _delete_scheduled_transaction(self):
        if self.current_user["role"] != "admin" and "delete_all" not in self.current_user.get("permissions", []):
            QMessageBox.critical(self, "Yetki Hatası", "Bu kaydı silmeye yetkiniz yok.\n(Yönetici onayı gerekir)")
            return
        selected_items = self.scheduled_transactions_table.selectedItems()
        if not selected_items: return
        row = selected_items[0].row()
        item_id = self.scheduled_transactions_table.item(row, 0).data(Qt.ItemDataRole.UserRole)
        if self._show_confirm_dialog(self.lang["delete"], self.lang["confirm_delete_prompt"]):
            self.scheduled_transaction_manager.delete_item(item_id)
            self.update_scheduled_transactions_display()
            self._show_status_message("Scheduled transaction deleted.")
    def _complete_selected_scheduled_transaction(self):
        """Manually completes the selected scheduled transaction from the table."""
        selected_items = self.scheduled_transactions_table.selectedItems()
        if not selected_items:
            return
        row = selected_items[0].row()
        item_id = self.scheduled_transactions_table.item(row, 0).data(Qt.ItemDataRole.UserRole)
        transaction = self.scheduled_transaction_manager.find_by_id(item_id)
        if not transaction or transaction.status == "Completed":
            return
        if self._show_confirm_dialog(self.lang["complete_now"], f"'{transaction.description}' - {self.lang['confirm_delete_prompt']}"):
            self._complete_scheduled_transaction(transaction)
    def update_home_tab_display(self) -> None:
        """Updates the Home tab with summary data."""
        if not hasattr(self, 'home_accounts_summary_table'):
            return
        account_balances = {}
        all_currencies = set()
        for account in sorted(self.cash_account_manager.get_all(), key=lambda a: a.name):
            if account.id in self.cash_managers:
                balances = self.cash_managers[account.id].calculate_balance()
                account_balances[(account.name, self.lang["cash"])] = balances
                all_currencies.update(balances.keys())
        for account in sorted(self.card_account_manager.get_all(), key=lambda a: a.name):
            if account.id in self.card_managers:
                balances = self.card_managers[account.id].calculate_balance()
                account_balances[(account.name, self.lang["card"])] = balances
                all_currencies.update(balances.keys())
        sorted_currencies = sorted(list(all_currencies))
        if not sorted_currencies:
            sorted_currencies.append(self.display_currency)
        table = self.home_accounts_summary_table
        table.clear()
        table.setRowCount(len(account_balances))
        table.setColumnCount(1 + len(sorted_currencies))
        headers = [self.lang["account_name_col_header"]] + sorted_currencies
        table.setHorizontalHeaderLabels(headers)
        row_idx = 0
        for (acc_name, acc_type), balances in account_balances.items():
            name_item = QTableWidgetItem(f"{acc_name} ({acc_type})")
            name_item.setTextAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
            table.setItem(row_idx, 0, name_item)
            for col_idx, currency in enumerate(sorted_currencies):
                balance = balances.get(currency, 0.0)
                item = QTableWidgetItem(format_number(balance))
                item.setTextAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
                table.setItem(row_idx, col_idx + 1, item)
            row_idx += 1
        if table.columnCount() > 0:
            table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
            pixel_width = 150
            for i in range(1, table.columnCount()):
                table.setColumnWidth(i, pixel_width)
        self.recent_transactions_table.setRowCount(0)
        all_transactions = []
        for acc_id, manager in self.cash_managers.items():
            account = self.cash_account_manager.find_by_id(acc_id)
            for t in manager.get_all():
                all_transactions.append((t, f"{self.lang['cash']} - {account.name if account else 'N/A'}"))
        for acc_id, manager in self.card_managers.items():
            account = self.card_account_manager.find_by_id(acc_id)
            for t in manager.get_all():
                all_transactions.append((t, f"{self.lang['card']} - {account.name if account else 'N/A'}"))
        sorted_transactions = sorted(all_transactions, key=lambda x: (parse_date_from_display(x[0].transaction_date) or datetime.min, x[0].created_at), reverse=True)[:10]
        self.recent_transactions_table.setRowCount(len(sorted_transactions))
        for row, (transaction, account_name) in enumerate(sorted_transactions):
            self.recent_transactions_table.setItem(row, 0, QTableWidgetItem(transaction.transaction_date))
            self.recent_transactions_table.setItem(row, 1, QTableWidgetItem(self.lang.get(transaction.transaction_type.lower(), transaction.transaction_type)))
            self.recent_transactions_table.setItem(row, 2, QTableWidgetItem(self.currency_manager.format_currency(transaction.original_amount, transaction.currency)))
            self.recent_transactions_table.setItem(row, 3, QTableWidgetItem(transaction.description))
            self.recent_transactions_table.setItem(row, 4, QTableWidgetItem(account_name))
    def update_product_comboboxes(self, force: bool = False) -> None:
        """ürün ve kategori seçim kutularını SİNYAL KORUMALI şekilde günceller."""
        if not force and hasattr(self, "tabs"):
            # Gereksiz ağ/db yükünü azalt: sadece ilgili sekmelerde güncelle.
            if self.tabs.currentIndex() not in [1, 3]:
                return

        # 1. KATEGORİ KUTULARINI GÜNCELLE
        if hasattr(self, 'category_manager'):
            cat_names = self.category_manager.get_category_names()
            for st in ["Product", "Scrap", "Ziynet"]:
                if st in self.stock_widgets and 'def_form' in self.stock_widgets[st]:
                    cat_combo = self.stock_widgets[st]['def_form'].get('category')
                    if isinstance(cat_combo, QComboBox):
                        curr = cat_combo.currentText()
                        cat_combo.blockSignals(True)
                        cat_combo.clear()
                        cat_combo.addItems(cat_names)
                        if curr in cat_names:
                            cat_combo.setCurrentText(curr)
                        cat_combo.blockSignals(False)

        for stock_type in ["Product", "Scrap", "Ziynet"]:
            if stock_type not in self.stock_widgets: continue
            manager = getattr(self, f"{stock_type.lower()}_manager", None)
            if not manager:
                manager = self.product_manager # Fallback
            product_names = manager.get_product_names()
            if 'type_filter_combo' in self.stock_widgets[stock_type]:
                try:
                    combo = self.stock_widgets[stock_type]['type_filter_combo']
                    current_data = combo.currentData() # Text yerine Data kullan (Güvenli)
                    combo.blockSignals(True) # Sinyali Kes
                    combo.clear()
                    combo.addItem("Tümü", None) # None data = Tümü
                    for name in product_names:
                        combo.addItem(name, name)
                    idx = combo.findData(current_data)
                    if idx != -1: combo.setCurrentIndex(idx)
                    else: combo.setCurrentIndex(0)
                    combo.blockSignals(False) # Sinyali Aç
                except RuntimeError: pass
            for transaction_type in ["Sale", "Purchase"]:
                if transaction_type not in self.stock_widgets[stock_type]: continue
                widgets = self.stock_widgets[stock_type][transaction_type]
                if 'product_combo' in widgets:
                    try:
                        combo = widgets['product_combo']
                        current_text = combo.currentText()
                        combo.blockSignals(True)
                        combo.clear()
                        combo.addItem("")
                        combo.addItems(product_names)
                        if current_text in product_names:
                            combo.setCurrentText(current_text)
                        combo.blockSignals(False)
                    except RuntimeError: pass
    def update_transaction_list_display(self, table: QTableWidget, manager: FinancialTransactionManager) -> None:
        """Finansal işlem tablolarını dondurmadan doldurur."""
        sorted_transactions = sorted(manager.get_all(), key=lambda x: (parse_date_from_display(x.transaction_date) or datetime.min, x.created_at), reverse=True)
        def fill_row(row, transaction):
            chk_item = QTableWidgetItem()
            chk_item.setFlags(Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsEnabled)
            chk_item.setCheckState(Qt.CheckState.Unchecked)
            table.setItem(row, 0, chk_item)
            date_item = QTableWidgetItem(transaction.transaction_date)
            date_item.setData(Qt.ItemDataRole.UserRole, transaction.id)
            table.setItem(row, 1, date_item)
            type_trans = self.lang.get(transaction.transaction_type.lower(), transaction.transaction_type)
            table.setItem(row, 2, QTableWidgetItem(type_trans))
            amount_str = self.currency_manager.format_currency(transaction.original_amount, transaction.currency)
            amount_item = QTableWidgetItem(amount_str)
            amount_item.setTextAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
            if transaction.transaction_type == "Income":
                amount_item.setForeground(QColor("green"))
            else:
                amount_item.setForeground(QColor("red"))
            table.setItem(row, 3, amount_item)
            table.setItem(row, 4, QTableWidgetItem(transaction.description))
        self.utility_manager._populate_table_in_batches(table, sorted_transactions, fill_row)
    def _update_generic_product_definition_list(self, stock_type: str):
        """
        ürün/Hurda/Ziynet tanımlarını listeler.
        DüZELTME: Ayar (Karat) sütunu eklendi ve veriler doğru eşleştirildi.
        """
        if stock_type not in self.stock_widgets: return
        if 'def_form' not in self.stock_widgets[stock_type]: return
        if 'table' not in self.stock_widgets[stock_type]['def_form']: return
        is_product = stock_type == "Product"
        if stock_type == "Product":
            manager = self.product_manager
        elif stock_type == "Ziynet":
            manager = self.ziynet_manager
        else:  # Scrap
            manager = self.scrap_manager
        table = self.stock_widgets[stock_type]['def_form']['table']
        table.setRowCount(0)
        if is_product:
            headers = [
                self.lang["product_name_type"],
                "Barkod ön Eki",
                self.lang["product_category"],
                self.lang["product_karat"], # <--- EKLENDi
                self.lang["default_purchase_price"],
                self.lang["default_sale_price"]
            ]
        else:
            headers = [
                self.lang["product_name_type"],
                self.lang["product_karat"],
                self.lang["default_purchase_price"],
                self.lang["default_sale_price"]
            ]
        table.setColumnCount(len(headers))
        table.setHorizontalHeaderLabels(headers)
        table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        all_defs = manager.get_all()
        active_defs = [p for p in all_defs if p.is_active]
        sorted_defs = sorted(active_defs, key=lambda p: p.product_name.lower())
        table.setRowCount(len(sorted_defs))
        for row, product in enumerate(sorted_defs):
            name_item = QTableWidgetItem(product.product_name)
            name_item.setData(Qt.ItemDataRole.UserRole, product.id)
            table.setItem(row, 0, name_item)
            if is_product:
                table.setItem(row, 1, QTableWidgetItem(getattr(product, 'barcode_header', '') or ''))
                table.setItem(row, 2, QTableWidgetItem(product.category))
                table.setItem(row, 3, QTableWidgetItem(product.karat_purity)) # <--- AYAR VERiSi
                table.setItem(row, 4, QTableWidgetItem(f"{format_number(product.default_purchase_price)} {product.purchase_currency}"))
                table.setItem(row, 5, QTableWidgetItem(f"{format_number(product.default_sale_price)} {product.sale_currency}"))
            else:
                table.setItem(row, 1, QTableWidgetItem(product.karat_purity))
                table.setItem(row, 2, QTableWidgetItem(f"{format_number(product.default_purchase_price)} {product.purchase_currency}"))
                table.setItem(row, 3, QTableWidgetItem(f"{format_number(product.default_sale_price)} {product.sale_currency}"))
    def _open_stock_barcode_list(self, product_name, depot_name):
        """Seçilen ürün ve depo için barkod listesi penceresini açar."""
        from modules.stock import StockBarcodeListDialog
        all_items = self.stock_manager.get_all()
        barcodes = [
            item.unique_barcode
            for item in all_items
            if item.item_type == product_name
            and item.depot_name == depot_name
            and item.quantity > 0
            and item.unique_barcode
        ]
        if not barcodes:
            QMessageBox.information(self, "Bilgi", "Bu kayıt için listelenecek barkod bulunamadı.")
            return
        dialog = StockBarcodeListDialog(product_name, depot_name, sorted(barcodes), self)
        dialog.exec()
    def update_repair_list_display(self) -> None:
        """Updates the repair records table with filtering."""
        if not hasattr(self, 'repair_filter_combo') or not hasattr(self, 'repair_table'):
            return
        filter_person = self.repair_filter_combo.currentText()
        all_repairs = self.repair_manager.get_all()
        filtered_repairs = [r for r in all_repairs if filter_person == self.lang["show_all"] or r.customer_full_name == filter_person]
        self.repair_table.setRowCount(0)
        sorted_repairs = sorted(filtered_repairs, key=lambda r: (parse_date_from_display(r.receipt_date) or datetime.min, r.created_at), reverse=True)
        self.repair_table.setRowCount(len(sorted_repairs))
        for row, record in enumerate(sorted_repairs):
            customer_item = QTableWidgetItem(record.customer_full_name); customer_item.setData(Qt.ItemDataRole.UserRole, record.id)
            self.repair_table.setItem(row, 0, customer_item)
            self.repair_table.setItem(row, 1, QTableWidgetItem(record.product_description))
            self.repair_table.setItem(row, 2, QTableWidgetItem(record.complaint_request))
            self.repair_table.setItem(row, 3, QTableWidgetItem(record.receipt_date))
            self.repair_table.setItem(row, 4, QTableWidgetItem(self.currency_manager.format_currency(record.estimated_fee, record.fee_currency)))
            self.repair_table.setItem(row, 5, QTableWidgetItem(self.lang.get(record.status.lower(), record.status)))
            color = QColor('gray') if record.status == "Delivered" else QColor(0, 128, 0) if record.status == "Ready" else None
            if color:
                for col in range(6):
                    if self.repair_table.item(row, col): self.repair_table.item(row, col).setForeground(color)
    def update_depot_displays(self):
        """Updates all depot-related UI elements with FILTERED access and SAFETY check."""
        accessible_depot_names = self._get_accessible_depots()
        depots_objects = [d for d in self.depot_manager.get_all() if d.name in accessible_depot_names]
        def safe_fill(combo_attr_name, items, parent_obj=self):
            if hasattr(parent_obj, combo_attr_name):
                combo = getattr(parent_obj, combo_attr_name)
                try:
                    current_text = combo.currentText()
                    combo.blockSignals(True)
                    combo.clear()
                    combo.addItems(items)
                    if current_text in items:
                        combo.setCurrentText(current_text)
                    combo.blockSignals(False)
                except RuntimeError:
                    pass
        if hasattr(self, 'depot_selector_combo'):
            try:
                combo = self.depot_selector_combo
                current_id = combo.currentData()
                combo.blockSignals(True)
                combo.clear()
                for d in depots_objects:
                    combo.addItem(d.name, d.id)
                if current_id:
                    idx = combo.findData(current_id)
                    if idx != -1: combo.setCurrentIndex(idx)
                    else: combo.setCurrentIndex(0 if depots_objects else -1)
                combo.blockSignals(False)
                self.update_depot_content_display()
            except RuntimeError: pass
        safe_fill('report_depot_combo', [self.lang.get("show_all", "Tümünü Göster")] + accessible_depot_names)
        for st in ['Product', 'Scrap', 'Ziynet']:
            if st not in self.stock_widgets: continue
            widgets = self.stock_widgets[st]
            if 'depot_filter_combo' in widgets:
                try:
                    combo = widgets['depot_filter_combo']
                    current_text = combo.currentText()
                    combo.blockSignals(True)
                    combo.clear()
                    combo.addItem(self.lang["show_all"])
                    combo.addItems(accessible_depot_names)
                    if current_text in accessible_depot_names: combo.setCurrentText(current_text)
                    else: combo.setCurrentIndex(0)
                    combo.blockSignals(False)
                except RuntimeError: pass
            for trans_type in ['Sale', 'Purchase']:
                if trans_type in widgets and 'pos_depot_combo' in widgets[trans_type]:
                    try:
                        self._fill_combo_preserve_selection(widgets[trans_type]['pos_depot_combo'], accessible_depot_names)
                    except RuntimeError: pass
            if 'transfer' in widgets:
                try:
                    if 'source_combo' in widgets['transfer']:
                        self._fill_combo_preserve_selection(widgets['transfer']['source_combo'], accessible_depot_names)
                    if 'dest_combo' in widgets['transfer']:
                        self._fill_combo_preserve_selection(widgets['transfer']['dest_combo'], accessible_depot_names)
                    self._update_transfer_product_combo(st)
                except RuntimeError: pass
        try:
            if hasattr(self, 'depot_trans_dest_combo'):
                self._fill_combo_preserve_selection(self.depot_trans_dest_combo, accessible_depot_names)
            if hasattr(self, '_update_depot_transfer_products_list'):
                self._update_depot_transfer_products_list()
        except RuntimeError: pass
    def _fill_combo_preserve_selection(self, combo: QComboBox, items: list):
        """Yardımcı Metod: Combobox içeriğini seçimi koruyarak günceller."""
        current_text = combo.currentText()
        combo.clear()
        combo.addItems(items)
        if current_text in items:
            combo.setCurrentText(current_text)
    def _update_transfer_product_combo(self, stock_type: str):
        """Updates the product combobox in the transfer tab based on selected source depot."""
        widgets = self.stock_widgets[stock_type]['transfer']
        if stock_type == "Product":
            stock_manager = self.stock_manager
        elif stock_type == "Ziynet":
            stock_manager = self.ziynet_stock_manager
        else:  # Scrap
            stock_manager = self.scrap_stock_manager
        source_depot = widgets['source_combo'].currentText()
        product_combo = widgets['product_combo']
        product_combo.clear()
        if source_depot:
            products_in_depot = [item.item_type for item in stock_manager.get_all() if item.depot_name == source_depot and item.quantity > 0]
            product_combo.addItems(sorted(products_in_depot))
    def update_scheduled_transactions_display(self):
        """Updates the scheduled transactions table."""
        if not hasattr(self, 'scheduled_transactions_table'):
            return
        table = self.scheduled_transactions_table
        table.setRowCount(0)
        transactions = sorted(self.scheduled_transaction_manager.get_all(), key=lambda t: (t.status, t.due_date))
        table.setRowCount(len(transactions))
        for row, trans in enumerate(transactions):
            date_item = QTableWidgetItem(trans.due_date)
            date_item.setData(Qt.ItemDataRole.UserRole, trans.id)
            table.setItem(row, 0, date_item)
            table.setItem(row, 1, QTableWidgetItem(self.lang.get(trans.transaction_type.lower(), trans.transaction_type)))
            table.setItem(row, 2, QTableWidgetItem(self.currency_manager.format_currency(trans.amount, trans.currency)))
            table.setItem(row, 3, QTableWidgetItem(trans.person_full_name or ''))
            table.setItem(row, 4, QTableWidgetItem(trans.description))
            target_name = "Unknown"
            account_found = self.cash_account_manager.find_by_id(trans.target)
            if account_found:
                target_name = f"{self.lang['cash']} ({account_found.name})"
            else:
                account_found = self.card_account_manager.find_by_id(trans.target)
                if account_found:
                    target_name = f"{self.lang['card']} ({account_found.name})"
            table.setItem(row, 5, QTableWidgetItem(target_name))
            status_text = self.lang.get(f"status_{trans.status.lower()}", trans.status)
            status_item = QTableWidgetItem(status_text)
            if trans.status == 'Completed':
                status_item.setForeground(QColor('gray'))
            table.setItem(row, 6, status_item)
    def _fix_old_cash_labels(self):
        """Eski 'NAKiT iŞLEMi' etiketli kayıtları Giriş/çıkış olarak düzeltir."""
        if not self._show_confirm_dialog("Veri Onarımı", "Eski 'NAKiT iŞLEMi' etiketli kayıtlar, tutar yönüne göre 'GiRiŞ' veya 'çIKIŞ' olarak yeniden adlandırılacak.\nOnaylıyor musunuz?"):
            return
        events = self.customer_history_manager.get_all()
        count = 0
        for event in events:
            if event.event_type == "NAKiT iŞLEMi":
                new_type = None
                if event.credit_original > 0:
                    new_type = "NAKiT GiRiŞi"
                elif event.debt_original > 0:
                    new_type = "NAKiT çIKIŞI"
                if new_type:
                    try:
                        self.customer_history_manager.update_item(event.id, {"event_type": new_type})
                        count += 1
                    except Exception as e:
                        logger.error(f"Cash label fix failed (id={event.id}): {e}")
        if count > 0:
            self.update_all_displays()
            QMessageBox.information(self, "Başarılı", f"{count} adet eski kayıt düzeltildi.")
        else:
            QMessageBox.information(self, "Bilgi", "Düzeltilecek eski kayıt bulunamadı.")
    def update_all_displays_full(self) -> None:
        """Tüm UI componentlerini günceller (tam güncelleme - başlangıçta çalışır)."""
        logger.info("Full update of all application displays.")
        if hasattr(self, 'crm_manager'): self.crm_manager.update_person_comboboxes()
        if hasattr(self, 'finance_manager'): self.finance_manager.update_all_account_combos()
        self.update_home_tab_display()
        self._update_generic_stock_list("Product")
        self._update_generic_stock_list("Scrap")
        self._update_generic_stock_list("Ziynet")
        self._update_generic_product_definition_list("Product")
        self._update_generic_product_definition_list("Scrap")
        self._update_generic_product_definition_list("Ziynet")
        if hasattr(self, 'crm_manager'):
            self.crm_manager.update_person_list_display()
            self.crm_manager._update_excel_account_view()
        self.update_repair_list_display()
        if hasattr(self, 'finance_manager'):
            self.finance_manager.update_cash_transaction_display()
            self.finance_manager.update_card_transaction_display()
        self.update_depot_displays()
        self.update_scheduled_transactions_display()
        self.update_product_comboboxes()
        self._update_cart_display('Product', 'Sale')
        self._update_cart_display('Product', 'Purchase')
        self._update_cart_display('Scrap', 'Sale')
        self._update_cart_display('Scrap', 'Purchase')
        self._update_cart_display('Ziynet', 'Sale')
        self._update_cart_display('Ziynet', 'Purchase')
        if hasattr(self, 'report_manager'): self.report_manager._filter_report()
        self._update_calendar_markers()
    def _get_debt_summary_string(self, records: List[DebtRecord]) -> str:
        """Helper to generate summary string for a list of debt records (cash and product)."""
        cash_balances = defaultdict(float)
        product_balances = defaultdict(float)
        for record in records:
            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)
        return "<br>".join(summary_parts)
    def _show_status_message(self, message: str, timeout: int = 3000) -> None:
        """Displays a message in the status bar."""
        self.statusBar().showMessage(message, timeout)
        logger.info(f"Status message: {message}")
    def _add_search_result(self, result_type, name, details, data):
        """Helper to add a row to the search results table."""
        row = self.search_results_table.rowCount()
        self.search_results_table.insertRow(row)
        self.search_results_table.setItem(row, 0, QTableWidgetItem(result_type))
        self.search_results_table.setItem(row, 1, QTableWidgetItem(name))
        self.search_results_table.setItem(row, 2, QTableWidgetItem(details))
        go_btn = QPushButton(self._get_icon("SP_ArrowRight"), self.lang["go_to_record"])
        go_btn.clicked.connect(lambda: self.navigate_to_item(data))
        self.search_results_table.setCellWidget(row, 3, go_btn)
    def navigate_to_item(self, data: Dict[str, str]):
        """ Navigates to a specific item based on search results. """
        item_type, item_id = data.get("type"), data.get("id")
        if item_type == "navigation":
            main_idx = data.get("main_idx")
            sub_idx = data.get("sub_idx")
            self.tabs.setCurrentIndex(main_idx)
            if sub_idx is not None:
                current_widget = self.tabs.currentWidget()
                if isinstance(current_widget, QTabWidget):
                    current_widget.setCurrentIndex(sub_idx)
            return
        if item_type == "person":
            self.tabs.setCurrentWidget(self.contacts_tab_widget)
            for row in range(self.person_table.rowCount()):
                if self.person_table.item(row, 0).data(Qt.ItemDataRole.UserRole) == item_id:
                    self.person_table.selectRow(row)
                    self.person_table.scrollToItem(self.person_table.item(row, 0))
                    break
        elif item_type == "product_def" or item_type == "scrap_def" or item_type == "ziynet_def":
            is_product = item_type == "product_def"
            is_ziynet = item_type == "ziynet_def"
            if is_product:
                stock_type = "Product"
                tab_widget = self.stocks_tab_widget
                tab_index = 1
            elif is_ziynet:
                stock_type = "Ziynet"
                tab_widget = self.stocks_tab_widget
                tab_index = 5
            else:
                stock_type = "Scrap"
                tab_widget = self.stocks_tab_widget
                tab_index = 3
            table = self.stock_widgets[stock_type]['def_form']['table']
            self.tabs.setCurrentWidget(tab_widget)
            tab_widget.setCurrentIndex(tab_index)
            for row in range(table.rowCount()):
                if table.item(row, 0).data(Qt.ItemDataRole.UserRole) == item_id:
                    table.selectRow(row)
                    table.scrollToItem(table.item(row, 0))
                    break
        elif item_type == "repair":
            self.tabs.setCurrentWidget(self.repair_tab_widget)
            for row in range(self.repair_table.rowCount()):
                if self.repair_table.item(row, 0).data(Qt.ItemDataRole.UserRole) == item_id:
                    self.repair_table.selectRow(row)
                    self.repair_table.scrollToItem(self.repair_table.item(row, 0))
                    break
        elif item_type == "debt":
            self.tabs.setCurrentWidget(self.accounts_tab_widget)
            debt_record = self.debt_manager.find_by_id(item_id)
            if debt_record:
                table = self.receivables_table if debt_record.record_type == "Receivable" else self.payables_table
                for row in range(table.rowCount()):
                    if table.item(row, 0).data(Qt.ItemDataRole.UserRole) == item_id:
                        table.selectRow(row)
                        table.scrollToItem(table.item(row, 0))
                        break
        elif item_type == "transaction":
            manager_id = data.get("manager_id")
            if manager_id in self.cash_managers:
                self.tabs.setCurrentWidget(self.cash_tab_widget)
                self.cash_account_selector_combo.setCurrentIndex(self.cash_account_selector_combo.findData(manager_id))
                table = self.cash_transaction_table
                for row in range(table.rowCount()):
                    if table.item(row, 1).data(Qt.ItemDataRole.UserRole) == item_id:
                        table.selectRow(row)
                        table.scrollToItem(table.item(row, 1))
                        break
            elif manager_id in self.card_managers:
                cash_widget = getattr(self, "cash_tab_widget", None)
                if cash_widget:
                    index = self.tabs.indexOf(cash_widget)
                    if index != -1:
                        self.tabs.setCurrentIndex(index)
                        if isinstance(cash_widget, QTabWidget):
                            cash_widget.setCurrentIndex(1)  # 0: Nakit, 1: Kartlar
                self.card_account_selector_combo.setCurrentIndex(self.card_account_selector_combo.findData(manager_id))
                table = self.card_transaction_table
                for row in range(table.rowCount()):
                    if table.item(row, 1).data(Qt.ItemDataRole.UserRole) == item_id:
                        table.selectRow(row)
                        table.scrollToItem(table.item(row, 1))
                        break
    def _update_transaction_button_states(self, edit_btn: QPushButton, delete_btn: QPushButton, table: QTableWidget):
        has_selection = len(table.selectedItems()) > 0
        edit_btn.setEnabled(has_selection)
        delete_btn.setEnabled(has_selection)
    def _update_repair_button_states(self):
        has_selection = len(self.repair_table.selectedItems()) > 0
        self.repair_change_status_btn.setEnabled(has_selection)
        self.repair_deliver_collect_btn.setEnabled(has_selection)
        self.repair_delete_btn.setEnabled(has_selection)
        self._repair_record_selected()
    def _update_stock_button_states(self, stock_type: str):
        table = self.stock_widgets[stock_type]['stock_table']
        widgets = self.stock_widgets[stock_type]
        has_selection = len(table.selectedItems()) > 0
        widgets['delete_stock_btn'].setEnabled(has_selection)
        widgets['update_price_btn'].setEnabled(has_selection)
    def _update_product_def_button_states(self, stock_type: str):
        form = self.stock_widgets[stock_type]['def_form']
        has_selection = len(form['table'].selectedItems()) > 0
        form['update_btn'].setEnabled(has_selection)
        form['delete_btn'].setEnabled(has_selection)
    def _update_depot_button_states(self):
            """
            Eski depo listesi kaldırıldığı için bu fonksiyon artık yeni sekmeye yönlendirmeli
            veya boş bırakılmalıdır.
            """
            if hasattr(self, '_update_depot_manage_buttons'):
                self._update_depot_manage_buttons()
            else:
                pass
    def _update_accounts_button_states(self):
        receivable_selected = len(self.receivables_table.selectedItems()) > 0
        is_paid_receivable = True
        if receivable_selected:
            try:
                row = self.receivables_table.currentRow()
                debt_id = self.receivables_table.item(row, 0).data(Qt.ItemDataRole.UserRole)
                debt_record = self.debt_manager.find_by_id(debt_id)
                if debt_record:
                    is_paid_receivable = debt_record.is_paid
            except (AttributeError, IndexError):
                receivable_selected = False
        self.process_receivable_btn.setEnabled(receivable_selected and not is_paid_receivable)
        self.edit_receivable_btn.setEnabled(receivable_selected)
        self.delete_receivable_btn.setEnabled(receivable_selected)
        payable_selected = len(self.payables_table.selectedItems()) > 0
        is_paid_payable = True
        if payable_selected:
            try:
                row = self.payables_table.currentRow()
                debt_id = self.payables_table.item(row, 0).data(Qt.ItemDataRole.UserRole)
                debt_record = self.debt_manager.find_by_id(debt_id)
                if debt_record:
                    is_paid_payable = debt_record.is_paid
            except (AttributeError, IndexError):
                payable_selected = False
        self.process_payable_btn.setEnabled(payable_selected and not is_paid_payable)
        self.edit_payable_btn.setEnabled(payable_selected)
        self.delete_payable_btn.setEnabled(payable_selected)
    def _update_scheduled_transaction_button_states(self):
        """Updates the enable/disable state of buttons on the scheduled transactions tab."""
        selected = len(self.scheduled_transactions_table.selectedItems()) > 0
        self.st_edit_btn.setEnabled(selected)
        self.st_delete_btn.setEnabled(selected)
        is_pending = False
        if selected:
            row = self.scheduled_transactions_table.currentRow()
            status_item = self.scheduled_transactions_table.item(row, 6)
            if status_item and status_item.text() == self.lang["status_pending"]:
                is_pending = True
        self.st_complete_btn.setEnabled(selected and is_pending)
def apply_startup_theme(app_instance):
    """
    Program ilk açıldığında son kullanılan profilin temasını yükler.
    Bulamazsa varsayılan olarak 'Modern Dark Gold' temasını uygular.
    """
    default_theme = "Modern Dark Gold"
    theme_name = default_theme
    try:
        app_data_dir = get_app_data_directory()
        profiles_conf = Path(app_data_dir) / PROFILES_CONFIG_FILE
        if profiles_conf.exists():
            with open(profiles_conf, "r", encoding='utf-8') as f:
                p_data = json.load(f)
                last_prof = p_data.get("last_profile")
                if last_prof:
                    prof_path = p_data.get("profiles", {}).get(last_prof)
                    settings_path = Path(prof_path) / "config.json"
                    if settings_path.exists():
                        with open(settings_path, "r", encoding='utf-8') as sf:
                            s_data = json.load(sf)
                            theme_name = s_data.get("theme_name", default_theme)
    except Exception as e:
        print(f"Tema yükleme hatası: {e}")
        theme_name = default_theme
    custom_mgr = CustomThemeManager()
    all_themes_local = {**APP_THEMES, **custom_mgr.get_themes()}
    default_theme_data = APP_THEMES.get(default_theme, list(APP_THEMES.values())[0])
    theme_data = all_themes_local.get(theme_name, default_theme_data)
    if theme_name not in all_themes_local and theme_name in QStyleFactory.keys():
        app_instance.setStyle(QStyleFactory.create(theme_name))
        return
    colors = theme_data.copy()
    def adjust_color(hex_color, factor):
        try:
            c = QColor(hex_color)
            return c.lighter(int(factor)).name() if factor > 100 else c.darker(int(factor)).name()
        except: return hex_color
    bg_qcolor = QColor(colors.get('background', '#ffffff'))
    if 'background_alt' not in colors:
        colors['background_alt'] = bg_qcolor.lighter(103).name() if bg_qcolor.lightness() < 230 else bg_qcolor.darker(103).name()
    if 'tertiary' not in colors:
        colors['tertiary'] = adjust_color(QColor(colors.get('secondary', '#cccccc')), 120)
    if 'border' not in colors:
        colors['border'] = adjust_color(QColor(colors.get('background', '#ffffff')), 90)
    qss = f"""
        QDialog, QWidget {{ background-color: {colors.get('background')}; color: {colors.get('text_dark')}; }}
        QListWidget {{ background-color: {colors.get('background_alt')}; border: 1px solid {colors.get('border')}; color: {colors.get('text_dark')}; }}
        QLineEdit, QComboBox {{ background-color: {colors.get('background_alt')}; border: 1px solid {colors.get('border')}; color: {colors.get('text_dark')}; padding: 5px; border-radius: 4px; }}
        QPushButton {{ background-color: {colors.get('secondary')}; color: {colors.get('text_light')}; border: none; padding: 8px 16px; border-radius: 4px; font-weight: bold; }}
        QPushButton:hover {{ background-color: {colors.get('tertiary')}; }}
        QPushButton#dangerButton {{ background-color: {colors.get('danger')}; }}
        QLabel {{ color: {colors.get('text_dark')}; }}
    """
    app_instance.setStyle(QStyleFactory.create("Fusion"))
    app_instance.setStyleSheet("QMainWindow { background-color: #2d2d2d; } QDialog { background-color: #2d2d2d; }")
    app_instance.setStyleSheet(qss)





