from flask import render_template, request, redirect, url_for, flash, jsonify, Response
from flask_login import login_user, logout_user, login_required, current_user
from app import ProductUnit, Purchase, PurchaseItem, StockOpname, StockOpnameItem, Supplier, SupplierPayment, app, db, User, Customer, Product, Sale, SaleItem, Payment, CashFlow, StockMovement, Settings, InventoryLoss, RepairOrder, RepairAction
from datetime import datetime
from sqlalchemy import or_, func
from werkzeug.utils import secure_filename
import os
import subprocess
import sys
import csv
import io
from flask import jsonify

@app.route('/')
@login_required
def index():
    return redirect(url_for('dashboard'))

@app.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('dashboard'))
    
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        user = User.query.filter_by(username=username).first()
        
        if user and user.check_password(password):
            login_user(user)
            return redirect(url_for('dashboard'))
        else:
            flash('Username atau password salah!', 'error')
    
    return render_template('login.html')

@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('login'))

@app.route('/dashboard')
@login_required
def dashboard():
    total_customers = Customer.query.count()
    total_products = Product.query.count()
    total_sales = Sale.query.count()
    
    # Total pendapatan hari ini
    today = datetime.now().date()
    today_sales = db.session.query(func.sum(Sale.total_bayar)).filter(
        func.date(Sale.tanggal) == today
    ).scalar() or 0
    
    # Piutang belum lunas
    piutang = db.session.query(func.sum(Sale.total_bayar - func.coalesce(db.session.query(func.sum(Payment.jumlah)).filter(Payment.sale_id == Sale.id).scalar_subquery(), 0))).filter(
        Sale.status == 'Belum Lunas'
    ).scalar() or 0
    
    # Hutang belum lunas
    hutang_query = db.session.query(
        func.sum(Purchase.total - func.coalesce(db.session.query(func.sum(SupplierPayment.jumlah)).filter(SupplierPayment.purchase_id == Purchase.id).scalar_subquery(), 0))
    ).filter(Purchase.status == 'Belum Lunas')
    hutang = hutang_query.scalar() or 0
    
    # Produk stok rendah (Hanya untuk barang fisik)
    low_stock = Product.query.filter(Product.is_service == False, Product.stok <= Product.stok_minimum).count()
    
    # Recent sales
    recent_sales = Sale.query.order_by(Sale.tanggal.desc()).limit(5).all()

    # FITUR 6: Hitung Uang di Laci (Saldo Kas Real-time)
    # Total Masuk - Total Keluar
    kas_masuk = db.session.query(func.sum(CashFlow.jumlah)).filter(CashFlow.jenis == 'Masuk').scalar() or 0
    kas_keluar = db.session.query(func.sum(CashFlow.jumlah)).filter(CashFlow.jenis == 'Keluar').scalar() or 0
    uang_laci = kas_masuk - kas_keluar

    # Statistik reparasi
    repair_in_progress = RepairOrder.query.filter(
        RepairOrder.status.in_(['Masuk', 'Diagnosa', 'Perbaikan'])
    ).count()
    repair_ready = RepairOrder.query.filter_by(status='Selesai').count()

    return render_template('dashboard.html',
                         total_customers=total_customers,
                         total_products=total_products,
                         total_sales=total_sales,
                         today_sales=today_sales,
                         piutang=piutang,
                         hutang=hutang,
                         low_stock=low_stock,
                         recent_sales=recent_sales,
                         uang_laci=uang_laci,
                         repair_in_progress=repair_in_progress,
                         repair_ready=repair_ready)

# ==================== CUSTOMERS ====================
@app.route('/customers')
@login_required
def customers():
    page = request.args.get('page', 1, type=int)
    search = request.args.get('search', '')
    
    query = Customer.query
    if search:
        query = query.filter(or_(
            Customer.nama.like(f'%{search}%'),
            Customer.no_hp.like(f'%{search}%')
        ))
    
    customers = query.order_by(Customer.created_at.desc()).paginate(
        page=page, per_page=10, error_out=False
    )
    return render_template('customers.html', customers=customers, search=search)

@app.route('/customers/add', methods=['POST'])
@login_required
def add_customer():
    try:
        customer = Customer(
            nama=request.form['nama'],
            alamat=request.form['alamat'],
            no_hp=request.form['no_hp'],
            jenis=request.form['jenis'],
            limit_piutang=float(request.form.get('limit_piutang', 0))
        )
        db.session.add(customer)
        db.session.commit()
        flash('Pelanggan berhasil ditambahkan!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('customers'))

@app.route('/customers/edit/<int:id>', methods=['POST'])
@login_required
def edit_customer(id):
    try:
        customer = Customer.query.get_or_404(id)
        customer.nama = request.form['nama']
        customer.alamat = request.form['alamat']
        customer.no_hp = request.form['no_hp']
        customer.jenis = request.form['jenis']
        customer.limit_piutang = float(request.form.get('limit_piutang', 0))
        db.session.commit()
        flash('Pelanggan berhasil diupdate!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('customers'))

@app.route('/customers/delete/<int:id>')
@login_required
def delete_customer(id):
    try:
        customer = Customer.query.get_or_404(id)
        
        # CEK: Apakah pelanggan punya riwayat penjualan?
        if customer.sales:
            flash(f'Gagal menghapus! Pelanggan "{customer.nama}" memiliki riwayat transaksi. Sebaiknya biarkan saja atau ubah namanya.', 'error')
            return redirect(url_for('customers'))
        
        # Jika tidak ada transaksi, baru boleh hapus
        db.session.delete(customer)
        db.session.commit()
        flash('Pelanggan berhasil dihapus!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('customers'))

# ==================== PRODUCTS ====================
@app.route('/products')
@login_required
def products():
    page = request.args.get('page', 1, type=int)
    search = request.args.get('search', '')
    category_filter = request.args.get('category', '')
    
    categories_query = db.session.query(Product.kategori).distinct().filter(
        Product.kategori.isnot(None), 
        Product.kategori != ''
    ).order_by(Product.kategori.asc()).all()
    categories = [c[0] for c in categories_query]

    query = Product.query
    
    if search:
        query = query.filter(or_(
            Product.kode_barang.like(f'%{search}%'),
            Product.nama_barang.like(f'%{search}%'),
            Product.kategori.like(f'%{search}%')
        ))
    
    if category_filter:
        query = query.filter(Product.kategori == category_filter)
    
    products = query.order_by(Product.created_at.desc()).paginate(
        page=page, per_page=10, error_out=False
    )

    # FITUR 2: Hitung Total Aset (Hanya Barang Fisik)
    # Rumus: Sum(Stok * Harga Pokok) dimana stok > 0 dan bukan jasa
    total_asset_value = db.session.query(
        func.sum(Product.stok * Product.harga_pokok)
    ).filter(
        Product.is_service == False, 
        Product.stok > 0
    ).scalar() or 0
    
    return render_template('products.html', 
                           products=products, 
                           search=search, 
                           categories=categories,
                           selected_category=category_filter,
                           total_asset_value=total_asset_value)

@app.route('/products/add', methods=['POST'])
@login_required
def add_product():
    if current_user.role == 'Kasir':
        flash('Akses ditolak! Kasir tidak dapat menambah produk.', 'error')
        return redirect(url_for('products'))

    try:
        # 1. Ambil Data Dasar (Satuan Terkecil)
        jenis_produk = request.form.get('jenis_produk', 'Produk')
        is_service = True if jenis_produk == 'Jasa' else False
        
        # Bersihkan input angka (cegah error string kosong)
        stok_awal = int(request.form.get('stok', 0)) if request.form.get('stok') else 0
        stok_min = int(request.form.get('stok_minimum', 10)) if request.form.get('stok_minimum') else 10

        # === CEK DUPLIKAT KODE BARANG ===
        kode_input = request.form['kode_barang'].strip()
        produk_existing = Product.query.filter(
            func.lower(Product.kode_barang) == func.lower(kode_input)
        ).first()
        if produk_existing:
            flash(
                f'Kode barang "{kode_input}" sudah digunakan oleh produk "{produk_existing.nama_barang}". '
                f'Gunakan kode yang berbeda.',
                'error'
            )
            return redirect(url_for('products'))

        # Simpan Produk Utama (Base Unit)
        product = Product(
            kode_barang=kode_input,
            barcode=request.form.get('barcode') or None,
            nama_barang=request.form['nama_barang'],
            kategori=request.form.get('kategori'),
            
            # Ini adalah Satuan Terkecil (misal: Batang/Pcs)
            satuan=request.form['satuan'], 
            
            # Harga Dasar (Harga per satuan terkecil)
            harga_pokok=float(request.form['harga_pokok']),
            harga_ecer=float(request.form['harga_ecer']),
            harga_grosir=float(request.form['harga_grosir']),
            
            stok=0 if is_service else stok_awal,
            stok_minimum=stok_min,
            is_service=is_service
        )
        
        db.session.add(product)
        db.session.flush() # PENTING: Flush agar product.id terbentuk sebelum commit
        
        # 2. Simpan Satuan Konversi (Multi Unit) - JIKA ADA
        # Kita asumsikan di HTML ada input array: unit_nama[], unit_qty[], unit_harga[]
        
        if not is_service:
            unit_names = request.form.getlist('unit_nama[]')
            unit_qtys = request.form.getlist('unit_konversi[]') # Faktor pengali (misal: 12)
            unit_prices_pokok = request.form.getlist('unit_harga_pokok[]')
            unit_prices_ecer = request.form.getlist('unit_harga_ecer[]')
            unit_prices_grosir = request.form.getlist('unit_harga_grosir[]')
            unit_barcodes = request.form.getlist('unit_barcode[]')

            # Loop array input
            for i in range(len(unit_names)):
                # Pastikan nama satuan & konversi tidak kosong
                if unit_names[i] and unit_qtys[i]:
                    b_val = unit_barcodes[i].strip() if i < len(unit_barcodes) else ""
                    b_val = None if b_val == "" or b_val.lower() == "null" else b_val

                    new_unit = ProductUnit(
                        product_id=product.id,
                        nama_satuan=unit_names[i],
                        jumlah_konversi=int(unit_qtys[i]),
                        harga_pokok=float(unit_prices_pokok[i] or 0) if i < len(unit_prices_pokok) else 0,
                        harga_ecer=float(unit_prices_ecer[i] or 0) if i < len(unit_prices_ecer) else 0,
                        harga_grosir=float(unit_prices_grosir[i] or 0) if i < len(unit_prices_grosir) else 0,
                        barcode=b_val
                    )
                    db.session.add(new_unit)
                            
        # 3. Catat Stok Awal (Movement) - Hanya Base Unit
        if not is_service and product.stok > 0:
            movement = StockMovement(
                product_id=product.id,
                jenis='Masuk',
                qty=product.stok,
                keterangan='Stok awal (Base Unit)'
            )
            db.session.add(movement)
            
        db.session.commit()
        flash(f'Produk {product.nama_barang} berhasil ditambahkan!', 'success')

    except Exception as e:
        db.session.rollback()
        print(e) # Debugging di terminal
        flash(f'Error: {str(e)}', 'error')
        
    return redirect(url_for('products'))

@app.route('/products/edit/<int:id>', methods=['POST'])
@login_required
def edit_product(id):
    if current_user.role == 'Kasir':
        flash('Akses ditolak! Kasir tidak dapat mengubah produk.', 'error')
        return redirect(url_for('products'))

    try:
        product = Product.query.get_or_404(id)
        old_stok = product.stok
        
        jenis_produk = request.form.get('jenis_produk', 'Produk')
        is_service = True if jenis_produk == 'Jasa' else False

        product.is_service = is_service

        # === CEK DUPLIKAT KODE BARANG (kecualikan produk yang sedang di-edit) ===
        kode_input = request.form['kode_barang'].strip()
        produk_konflik = Product.query.filter(
            func.lower(Product.kode_barang) == func.lower(kode_input),
            Product.id != id
        ).first()
        if produk_konflik:
            flash(
                f'Kode barang "{kode_input}" sudah digunakan oleh produk "{produk_konflik.nama_barang}". '
                f'Gunakan kode yang berbeda.',
                'error'
            )
            return redirect(url_for('products'))

        product.kode_barang = kode_input
        product.barcode = request.form.get('barcode') or None
        product.nama_barang = request.form['nama_barang']
        product.kategori = request.form.get('kategori')
        product.satuan = request.form['satuan']
        product.harga_pokok = float(request.form['harga_pokok'])
        product.harga_ecer = float(request.form['harga_ecer'])
        product.harga_grosir = float(request.form['harga_grosir'])
        
        if is_service:
            new_stok = 0
            product.stok = 0
            product.stok_minimum = 0
        else:
            new_stok = int(request.form.get('stok', 0))
            product.stok = new_stok
            product.stok_minimum = int(request.form.get('stok_minimum', 10))

        # --- UPDATE MULTI SATUAN ---
        # Hapus seluruh satuan bertingkat lama untuk digantikan dengan list yang baru (berlaku untuk edit & hapus item form)
        ProductUnit.query.filter_by(product_id=product.id).delete()
        
        if not is_service:
            unit_names = request.form.getlist('unit_nama[]')
            unit_qtys = request.form.getlist('unit_konversi[]')
            unit_prices_pokok = request.form.getlist('unit_harga_pokok[]')
            unit_prices_ecer = request.form.getlist('unit_harga_ecer[]')
            unit_prices_grosir = request.form.getlist('unit_harga_grosir[]')
            unit_barcodes = request.form.getlist('unit_barcode[]')

            for i in range(len(unit_names)):
                # Pastikan input nama dan konversinya tidak kosong sebelum disimpan
                if unit_names[i] and unit_qtys[i]:
                    b_val = unit_barcodes[i].strip() if i < len(unit_barcodes) else ""
                    b_val = None if b_val == "" or b_val.lower() == "null" else b_val

                    new_unit = ProductUnit(
                        product_id=product.id,
                        nama_satuan=unit_names[i],
                        jumlah_konversi=int(unit_qtys[i]),
                        harga_pokok=float(unit_prices_pokok[i] or 0) if i < len(unit_prices_pokok) else 0,
                        harga_ecer=float(unit_prices_ecer[i] or 0) if i < len(unit_prices_ecer) else 0,
                        harga_grosir=float(unit_prices_grosir[i] or 0) if i < len(unit_prices_grosir) else 0,
                        barcode=b_val
                    )
                    db.session.add(new_unit)
        db.session.commit()
        
        # Add stock movement if changed
        if new_stok != old_stok:
            diff = new_stok - old_stok
            movement = StockMovement(
                product_id=product.id,
                jenis='Masuk' if diff > 0 else 'Keluar',
                qty=abs(diff),
                keterangan='Penyesuaian stok'
            )
            db.session.add(movement)
            db.session.commit()
        
        flash('Produk berhasil diupdate!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('products'))

@app.route('/products/delete/<int:id>')
@login_required
def delete_product(id):
    # PERUBAHAN: Blokir akses untuk Kasir
    if current_user.role == 'Kasir':
        flash('Akses ditolak! Kasir tidak dapat menghapus produk.', 'error')
        return redirect(url_for('products'))

    try:
        product = Product.query.get_or_404(id)
        db.session.delete(product)
        db.session.commit()
        flash('Produk berhasil dihapus!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('products'))

# ==================== SALES ====================
@app.route('/sales')
@login_required
def sales():
    page = request.args.get('page', 1, type=int)
    search = request.args.get('search', '')
    
    query = Sale.query
    if search:
        query = query.join(Customer).filter(or_(
            Sale.nomor_nota.like(f'%{search}%'),
            Customer.nama.like(f'%{search}%')
        ))
    
    sales = query.order_by(Sale.tanggal.desc()).paginate(
        page=page, per_page=10, error_out=False
    )
    
    customers = Customer.query.all()
    products = Product.query.all()
    return render_template('sales.html', sales=sales, search=search, customers=customers, products=products)

@app.route('/api/product/search', methods=['POST'])
@login_required
def search_product():
    """API untuk search produk by barcode (Scanner)"""
    query = request.json.get('query', '')
    
    # Cari di Produk Utama ATAU di Barcode Satuan (ProductUnit)
    # 1. Cek Produk Utama
    product = Product.query.filter(
        Product.is_service == False,
        or_(
            Product.barcode == query,
            Product.kode_barang == query
        )
    ).first()

    # 2. Jika tidak ketemu, Cek di ProductUnit (Barcode Satuan)
    selected_unit = None
    if not product:
        unit = ProductUnit.query.filter_by(barcode=query).first()
        if unit:
            product = unit.product
            selected_unit = unit.nama_satuan # Ingat satuan apa yang discan

    if product:
        # Siapkan daftar satuan untuk dropdown
        units_data = []
        for u in product.units:
            units_data.append({
                'nama': u.nama_satuan,
                'qty': u.jumlah_konversi,
                'ecer': u.harga_ecer,
                'grosir': u.harga_grosir
            })

        return jsonify({
            'success': True,
            'product': {
                'id': product.id,
                'kode': product.kode_barang,
                'nama': product.nama_barang,
                'satuan': selected_unit if selected_unit else product.satuan, # Prioritaskan satuan yang discan
                'harga_ecer': product.harga_ecer,
                'harga_grosir': product.harga_grosir,
                'stok': product.stok,
                'units': units_data # <--- INI PENTING DIKIRIM
            }
        })
    else:
        return jsonify({'success': False, 'message': 'Produk tidak ditemukan'})

@app.route('/api/products/autocomplete', methods=['POST'])
@login_required
def products_autocomplete():
    """API untuk autocomplete search (Ketik Manual)"""
    data = request.get_json()
    query = data.get('query', '')
    p_type = data.get('type', 'all')
    
    if len(query) < 2:
        return jsonify({'success': False, 'products': []})
    
    db_query = Product.query
    
    if p_type == 'product':
        db_query = db_query.filter(Product.is_service == False)
    elif p_type == 'service':
        db_query = db_query.filter(Product.is_service == True)
    
    products = db_query.filter(or_(
        Product.nama_barang.like(f'%{query}%'),
        Product.kode_barang.like(f'%{query}%'),
        Product.barcode.like(f'%{query}%')
    )).limit(10).all()
    
    results = []
    for p in products:
        # Siapkan units
        units_data = []
        for u in p.units:
            units_data.append({
                'nama': u.nama_satuan,
                'qty': u.jumlah_konversi,
                'ecer': u.harga_ecer,
                'grosir': u.harga_grosir
            })

        results.append({
            'id': p.id,
            'kode': p.kode_barang,
            'barcode': p.barcode,
            'nama': p.nama_barang,
            'satuan': p.satuan,
            'harga_ecer': p.harga_ecer,
            'harga_grosir': p.harga_grosir,
            'stok': p.stok,
            'units': units_data # <--- INI PENTING DIKIRIM
        })
    
    return jsonify({
        'success': True,
        'products': results
    })

@app.route('/sales/add', methods=['POST'])
@login_required
def add_sale():
    try:
        # 1. GENERATE NOMOR NOTA (Format: YYYYMMDD0001)
        today = datetime.now()
        prefix = today.strftime('%Y%m%d')
        last_sale = Sale.query.filter(Sale.nomor_nota.like(f'{prefix}%')).order_by(Sale.id.desc()).first()
        
        if last_sale:
            last_num = int(last_sale.nomor_nota[-4:])
            new_num = last_num + 1
        else:
            new_num = 1
        nomor_nota = f'{prefix}{new_num:04d}'
        
        # 2. AMBIL DATA DARI FORM
        customer_id = int(request.form['customer_id'])
        metode_pembayaran = request.form['metode_pembayaran']
        diskon = float(request.form.get('diskon', 0))
        uang_diterima = float(request.form.get('jumlah_uang_tunai', 0)) # Untuk Tunai
        uang_muka = float(request.form.get('uang_muka_dp', 0))          # Untuk Tempo/Cicilan
        
        # 3. INISIALISASI OBJEK PENJUALAN
        sale = Sale(
            nomor_nota=nomor_nota,
            customer_id=customer_id,
            total=0,
            diskon=diskon,
            total_bayar=0,
            metode_pembayaran=metode_pembayaran,
            status='Belum Lunas',
            jumlah_terima=0, # Akan diupdate di bawah
            kembalian=0,     # Akan diupdate di bawah
            user_id=current_user.id,
            tanggal=datetime.now()
        )
        db.session.add(sale)
        db.session.flush() # Agar sale.id tersedia
        
        # 4. PROSES ITEM KERANJANG
        product_ids = request.form.getlist('product_id[]')
        quantities = request.form.getlist('qty[]')
        satuan_list = request.form.getlist('satuan[]') 
        
        total_transaksi = 0
        
        for i in range(len(product_ids)):
            p_id = int(product_ids[i])
            qty_jual = int(quantities[i])
            satuan_pilihan = satuan_list[i]
            
            product = Product.query.get(p_id)
            if not product: continue
            
            # --- LOGIKA KONVERSI SATUAN ---
            faktor_konversi = 1
            harga_jual = 0
            
            # Cari apakah satuan yang dipilih adalah satuan konversi (Multi-Unit)
            unit_obj = ProductUnit.query.filter_by(product_id=product.id, nama_satuan=satuan_pilihan).first()
            
            if unit_obj:
                faktor_konversi = unit_obj.jumlah_konversi
                # Tentukan harga berdasarkan jenis pelanggan
                customer = Customer.query.get(customer_id)
                if customer.jenis == 'Sub Agen' and unit_obj.harga_grosir > 0:
                    harga_jual = unit_obj.harga_grosir
                else:
                    harga_jual = unit_obj.harga_ecer if unit_obj.harga_ecer > 0 else (product.harga_ecer * faktor_konversi)
            else:
                # Satuan Dasar
                faktor_konversi = 1
                customer = Customer.query.get(customer_id)
                harga_jual = product.harga_grosir if customer.jenis == 'Sub Agen' else product.harga_ecer

            # Hitung total penguraian stok (dalam unit terkecil)
            total_qty_base = qty_jual * faktor_konversi
            
            # --- CEK & POTONG STOK ---
            if not product.is_service:
                if product.stok < total_qty_base:
                    raise Exception(f"Stok {product.nama_barang} tidak cukup! Sisa: {product.stok_display}")
                
                product.stok -= total_qty_base
                
                # Catat Movement Stok
                movement = StockMovement(
                    product_id=product.id,
                    jenis='Keluar',
                    qty=total_qty_base,
                    keterangan=f'Penjualan {nomor_nota} ({qty_jual} {satuan_pilihan})',
                    tanggal=datetime.now()
                )
                db.session.add(movement)
            
            # --- SIMPAN DETAIL ITEM ---
            subtotal = harga_jual * qty_jual
            sale_item = SaleItem(
                sale_id=sale.id,
                product_id=product.id,
                qty=qty_jual,
                satuan=satuan_pilihan,
                qty_konversi=faktor_konversi,
                harga=harga_jual,
                subtotal=subtotal
            )
            db.session.add(sale_item)
            total_transaksi += subtotal
            
        # 5. FINALISASI PERHITUNGAN SALE
        sale.total = total_transaksi
        sale.total_bayar = total_transaksi - diskon
        
        # 6. LOGIKA PEMBAYARAN & CASHFLOW
        if metode_pembayaran in ('Tunai', 'Transfer/QRIS'):
            sale.status = 'Lunas'
            sale.jumlah_terima = uang_diterima
            sale.kembalian = uang_diterima - sale.total_bayar
            
            # Catat Uang Masuk
            kategori_kas = 'Penjualan' if metode_pembayaran == 'Tunai' else 'Transfer/QRIS'
            cash = CashFlow(
                jenis='Masuk',
                kategori=kategori_kas,
                jumlah=sale.total_bayar,
                keterangan=f'Penjualan {metode_pembayaran} {nomor_nota}',
                user_id=current_user.id,
                tanggal=datetime.now()
            )
            db.session.add(cash)
            
        else: # Tempo / Cicilan
            sale.jumlah_terima = uang_muka # Uang yang masuk saat ini (DP)
            sale.kembalian = 0
            
            if uang_muka > 0:
                # Catat sebagai cicilan pertama (Payment)
                payment = Payment(
                    sale_id=sale.id,
                    jumlah=uang_muka,
                    keterangan='Uang Muka (DP)',
                    tanggal=datetime.now()
                )
                db.session.add(payment)
                
                # Catat Arus Kas Masuk (Hanya sebesar DP)
                cash = CashFlow(
                    jenis='Masuk',
                    kategori='Uang Muka Penjualan',
                    jumlah=uang_muka,
                    keterangan=f'DP Penjualan {nomor_nota}',
                    user_id=current_user.id,
                    tanggal=datetime.now()
                )
                db.session.add(cash)

            # Jika DP mencukupi atau lebih, set Lunas
            if uang_muka >= sale.total_bayar:
                sale.status = 'Lunas'
            else:
                sale.status = 'Belum Lunas'

        db.session.commit()
        flash(f'Penjualan berhasil! No. Nota: {nomor_nota}', 'success')
        return redirect(url_for('sales', print_id=sale.id))

    except Exception as e:
        db.session.rollback()
        flash(f'Gagal Transaksi: {str(e)}', 'error')
        return redirect(url_for('sales'))

@app.route('/sales/detail/<int:id>')
@login_required
def sale_detail(id):
    sale = Sale.query.get_or_404(id)
    return jsonify({
        'nomor_nota': sale.nomor_nota,
        'tanggal': sale.tanggal.strftime('%d/%m/%Y %H:%M'),
        'customer': sale.customer.nama,
        'customer_jenis': sale.customer.jenis,
        'items': [{
            'nama': item.product.nama_barang,
            'qty': item.qty,
            'harga': item.harga,
            'subtotal': item.subtotal
        } for item in sale.items],
        'total': sale.total,
        'diskon': sale.diskon,
        'total_bayar': sale.total_bayar,
        'metode_pembayaran': sale.metode_pembayaran,
        'status': sale.status
    })

@app.route('/sales/print/<int:id>/<format>')
@login_required
def print_sale(id, format):
    sale = Sale.query.get_or_404(id)
    settings = Settings.query.first()
    if not settings: settings = Settings()
    
    if format == 'a5':
        return render_template('print_a5.html', sale=sale, settings=settings)
    elif format == 'thermal_web': # <--- TAMBAHAN BARU
        return render_template('print_58mm.html', sale=sale, settings=settings)
    else:
        return "Format tidak dikenal.", 400

@app.route('/sales/delete/<int:id>')
@login_required
def delete_sale(id):
    try:
        sale = Sale.query.get_or_404(id)
        nomor_nota_to_delete = sale.nomor_nota
        
        # 1. Kembalikan stok barang
        for item in sale.items:
            product = item.product # Perbaikan dari 'productz' ke 'product'
            if product and not product.is_service:
                # Kembalikan stok dalam satuan terkecil (Qty x Faktor Konversi)
                qty_yang_dikembalikan = item.qty * item.qty_konversi
                product.stok += qty_yang_dikembalikan
                
                # Catat Movement (Opsional, agar riwayat stok rapi)
                movement = StockMovement(
                    product_id=product.id,
                    jenis='Masuk',
                    qty=qty_yang_dikembalikan,
                    keterangan=f'Pembatalan Penjualan {nomor_nota_to_delete}',
                    tanggal=datetime.now()
                )
                db.session.add(movement)
        
        # 2. Hapus Arus Kas terkait nota ini
        # Kita cari CashFlow yang mengandung nomor nota di keterangannya
        CashFlow.query.filter(CashFlow.keterangan.like(f'%{nomor_nota_to_delete}%')).delete(synchronize_session=False)
        
        # 3. Hapus data penjualan (Item & Payment akan terhapus otomatis jika cascade aktif)
        db.session.delete(sale)
        
        db.session.commit()
        flash(f'Penjualan {nomor_nota_to_delete} berhasil dihapus dan stok dikembalikan.', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error saat menghapus penjualan: {str(e)}', 'error')
        
    return redirect(url_for('sales'))

# ==================== PAYMENTS ====================
@app.route('/payments')
@login_required
def payments():
    page = request.args.get('page', 1, type=int)
    search = request.args.get('search', '')
    
    query = Sale.query.filter(Sale.status == 'Belum Lunas')
    if search:
        query = query.join(Customer).filter(or_(
            Sale.nomor_nota.like(f'%{search}%'),
            Customer.nama.like(f'%{search}%')
        ))
    
    sales = query.order_by(Sale.tanggal.desc()).paginate(
        page=page, per_page=10, error_out=False
    )
    return render_template('payments.html', sales=sales, search=search)

@app.route('/payments/add/<int:sale_id>', methods=['POST'])
@login_required
def add_payment(sale_id):
    try:
        sale = Sale.query.get_or_404(sale_id)
        jumlah = float(request.form['jumlah'])
        keterangan = request.form.get('keterangan', '')
        
        payment = Payment(
            sale_id=sale.id,
            jumlah=jumlah,
            keterangan=keterangan
        )
        db.session.add(payment)
        
        # Check if fully paid
        total_paid = db.session.query(func.sum(Payment.jumlah)).filter(
            Payment.sale_id == sale.id
        ).scalar() or 0
        total_paid += jumlah
        
        if total_paid >= sale.total_bayar:
            sale.status = 'Lunas'
        
        # Add cash flow
        cash = CashFlow(
            jenis='Masuk',
            kategori='Pelunasan Piutang',
            jumlah=jumlah,
            keterangan=f'Pembayaran {sale.nomor_nota} - {keterangan}',
            user_id=current_user.id
        )
        db.session.add(cash)
        
        db.session.commit()
        flash('Pembayaran berhasil dicatat!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('payments'))

# ==================== CASH FLOW ====================
@app.route('/cashflow')
@login_required
def cashflow():
    # Cek Role
    if current_user.role == 'Kasir':
        flash('Akses ditolak! Kasir tidak dapat mengakses menu Cash Flow.', 'error')
        return redirect(url_for('dashboard'))

    page = request.args.get('page', 1, type=int)
    search = request.args.get('search', '')
    
    # 1. Setup Default Tanggal (Bulan Ini)
    today = datetime.now()
    default_start = today.replace(day=1).strftime('%Y-%m-%d')
    default_end = today.strftime('%Y-%m-%d')
    
    start_date = request.args.get('start_date', default_start)
    end_date = request.args.get('end_date', default_end)
    
    # 2. Base Query
    query = CashFlow.query
    
    # Filter Tanggal
    if start_date and end_date:
        query = query.filter(func.date(CashFlow.tanggal).between(start_date, end_date))
    
    # Filter Search
    if search:
        query = query.filter(or_(
            CashFlow.kategori.like(f'%{search}%'),
            CashFlow.keterangan.like(f'%{search}%')
        ))
    
    # 3. Hitung Total Summary (Berdasarkan filter yang aktif)
    # Kita perlu query terpisah menggunakan db.session untuk agregasi
    
    # Helper untuk membuat filter yang sama untuk query agregat
    def apply_filters(q):
        if start_date and end_date:
            q = q.filter(func.date(CashFlow.tanggal).between(start_date, end_date))
        if search:
            q = q.filter(or_(
                CashFlow.kategori.like(f'%{search}%'),
                CashFlow.keterangan.like(f'%{search}%')
            ))
        return q

    # Hitung Total Masuk
    q_masuk = db.session.query(func.sum(CashFlow.jumlah)).filter(CashFlow.jenis == 'Masuk')
    total_masuk = apply_filters(q_masuk).scalar() or 0
    
    # Hitung Total Keluar
    q_keluar = db.session.query(func.sum(CashFlow.jumlah)).filter(CashFlow.jenis == 'Keluar')
    total_keluar = apply_filters(q_keluar).scalar() or 0
    
    # Hitung Balance
    balance = total_masuk - total_keluar
    
    # 4. Pagination Data
    cashflows = query.order_by(CashFlow.tanggal.desc()).paginate(
        page=page, per_page=10, error_out=False
    )
    
    return render_template('cashflow.html', 
                           cashflows=cashflows, 
                           search=search,
                           start_date=start_date,
                           end_date=end_date,
                           total_masuk=total_masuk,
                           total_keluar=total_keluar,
                           balance=balance)

# ==================== KASIR SHIFT / TUTUP KASIR ====================
@app.route('/kasir-shift')
@login_required
def kasir_shift():
    from sqlalchemy import cast, Date as SADate, case

    today = datetime.now().date()
    
    # Ambil tanggal dari query param (untuk filter Admin/Owner melihat hari lain)
    tgl_str = request.args.get('tanggal', today.strftime('%Y-%m-%d'))
    try:
        tgl_filter = datetime.strptime(tgl_str, '%Y-%m-%d').date()
    except ValueError:
        tgl_filter = today

    # Jika role Kasir, hanya bisa lihat data dirinya sendiri dan hari ini
    if current_user.role == 'Kasir':
        tgl_filter = today
        kasir_filter_id = current_user.id
    else:
        kasir_filter_id = request.args.get('kasir_id', 'all')

    # --------------------------------------------------------
    # Query penjualan berdasarkan filter
    # --------------------------------------------------------
    q = db.session.query(
        User.id.label('user_id'),
        User.nama_lengkap.label('nama_kasir'),
        func.count(Sale.id).label('jumlah_transaksi'),
        func.sum(Sale.total_bayar).label('total_penjualan'),
        func.sum(
            case(
                (Sale.metode_pembayaran == 'Tunai', Sale.total_bayar),
                else_=0
            )
        ).label('total_tunai'),
        func.sum(
            case(
                (Sale.metode_pembayaran == 'Transfer/QRIS', Sale.total_bayar),
                else_=0
            )
        ).label('total_qris'),
        func.sum(
            case(
                (Sale.metode_pembayaran.notin_(['Tunai', 'Transfer/QRIS']), Sale.total_bayar),
                else_=0
            )
        ).label('total_non_tunai'),
        func.sum(Sale.diskon).label('total_diskon'),
    ).join(Sale, Sale.user_id == User.id)\
     .filter(func.date(Sale.tanggal) == tgl_filter)

    if current_user.role == 'Kasir':
        q = q.filter(Sale.user_id == kasir_filter_id)
    elif kasir_filter_id != 'all':
        try:
            q = q.filter(Sale.user_id == int(kasir_filter_id))
        except (ValueError, TypeError):
            pass

    ringkasan_kasir = q.group_by(User.id, User.nama_lengkap).all()

    # --------------------------------------------------------
    # Detail transaksi (untuk kasir sendiri / semua jika admin)
    # --------------------------------------------------------
    q_detail = Sale.query.filter(func.date(Sale.tanggal) == tgl_filter)\
                         .order_by(Sale.tanggal.asc())

    if current_user.role == 'Kasir':
        q_detail = q_detail.filter(Sale.user_id == current_user.id)
    elif kasir_filter_id != 'all':
        try:
            q_detail = q_detail.filter(Sale.user_id == int(kasir_filter_id))
        except (ValueError, TypeError):
            pass

    detail_transaksi = q_detail.all()

    # --------------------------------------------------------
    # Grand total
    # --------------------------------------------------------
    grand_total = sum(r.total_penjualan or 0 for r in ringkasan_kasir)
    grand_tunai = sum(r.total_tunai or 0 for r in ringkasan_kasir)
    grand_qris = sum(r.total_qris or 0 for r in ringkasan_kasir)
    grand_non_tunai = sum(r.total_non_tunai or 0 for r in ringkasan_kasir)
    grand_diskon = sum(r.total_diskon or 0 for r in ringkasan_kasir)
    grand_trx = sum(r.jumlah_transaksi or 0 for r in ringkasan_kasir)

    # Daftar semua kasir untuk filter (admin/owner)
    semua_kasir = User.query.order_by(User.nama_lengkap).all()

    return render_template(
        'kasir_shift.html',
        ringkasan_kasir=ringkasan_kasir,
        detail_transaksi=detail_transaksi,
        tgl_filter=tgl_filter,
        tgl_str=tgl_str,
        kasir_filter_id=kasir_filter_id,
        semua_kasir=semua_kasir,
        grand_total=grand_total,
        grand_tunai=grand_tunai,
        grand_qris=grand_qris,
        grand_non_tunai=grand_non_tunai,
        grand_diskon=grand_diskon,
        grand_trx=grand_trx,
    )

@app.route('/cashflow/add', methods=['POST'])
@login_required
def add_cashflow():
    # PERUBAHAN: Blokir akses untuk Kasir
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))

    try:
        cashflow = CashFlow(
            jenis=request.form['jenis'],
            kategori=request.form['kategori'],
            jumlah=float(request.form['jumlah']),
            keterangan=request.form.get('keterangan', ''),
            user_id=current_user.id
        )
        db.session.add(cashflow)
        db.session.commit()
        flash('Transaksi kas berhasil dicatat!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('cashflow'))

@app.route('/cashflow/edit/<int:id>', methods=['POST'])
@login_required
def edit_cashflow(id):
    # PERUBAHAN: Blokir akses untuk Kasir
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))

    try:
        cashflow = CashFlow.query.get_or_404(id)
        cashflow.jenis = request.form['jenis']
        cashflow.kategori = request.form['kategori']
        cashflow.jumlah = float(request.form['jumlah'])
        cashflow.keterangan = request.form.get('keterangan', '')
        db.session.commit()
        flash('Transaksi kas berhasil diupdate!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('cashflow'))

@app.route('/cashflow/delete/<int:id>')
@login_required
def delete_cashflow(id):
    # PERUBAHAN: Blokir akses untuk Kasir
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))

    try:
        cashflow = CashFlow.query.get_or_404(id)
        db.session.delete(cashflow)
        db.session.commit()
        flash('Transaksi kas berhasil dihapus!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('cashflow'))

# ==================== REPORTS ====================
@app.route('/reports')
@login_required
def reports():
    # 1. Ambil Rentang Tanggal (Default: Bulan ini berjalan)
    start_date = request.args.get('start_date', datetime.now().replace(day=1).strftime('%Y-%m-%d'))
    end_date = request.args.get('end_date', datetime.now().strftime('%Y-%m-%d'))
    
    # 2. Data Grafik Penjualan Harian
    sales_data = db.session.query(
        func.date(Sale.tanggal).label('tanggal'),
        func.sum(Sale.total_bayar).label('total')
    ).filter(
        func.date(Sale.tanggal).between(start_date, end_date)
    ).group_by(func.date(Sale.tanggal)).all()
    
    # 3. Ringkasan Arus Kas (Untuk Card Atas)
    kas_masuk = db.session.query(func.sum(CashFlow.jumlah)).filter(
        CashFlow.jenis == 'Masuk',
        func.date(CashFlow.tanggal).between(start_date, end_date)
    ).scalar() or 0
    
    kas_keluar = db.session.query(func.sum(CashFlow.jumlah)).filter(
        CashFlow.jenis == 'Keluar',
        func.date(CashFlow.tanggal).between(start_date, end_date)
    ).scalar() or 0
    
    # 4. Komponen Laba Rugi

    # A.1 Total Penjualan Barang (dari transaksi Sale)
    total_sales = db.session.query(func.sum(Sale.total_bayar)).filter(
        func.date(Sale.tanggal).between(start_date, end_date)
    ).scalar() or 0

    # A.2 Pendapatan Jasa Reparasi
    # Pakai akumulasi tindakan (biaya jasa + sparepart) pada order yang tidak Batal.
    # Ini mewakili nilai pekerjaan yang DIAKUI pada periode berdasarkan tanggal tindakan.
    repair_income = db.session.query(func.sum(RepairAction.total)).join(
        RepairOrder, RepairAction.repair_id == RepairOrder.id
    ).filter(
        RepairOrder.status != 'Batal',
        func.date(RepairAction.tanggal).between(start_date, end_date)
    ).scalar() or 0

    # Total pendapatan usaha (barang + jasa)
    total_pendapatan = (total_sales or 0) + (repair_income or 0)

    # B. HPP (Harga Pokok Penjualan)
    # Menghitung modal barang yang terjual
    hpp = db.session.query(func.sum(SaleItem.qty * Product.harga_pokok)).join(
        Product
    ).join(Sale).filter(
        func.date(Sale.tanggal).between(start_date, end_date)
    ).scalar() or 0
    
    # C. Biaya Operasional
    # Mengambil Kas Keluar yang kategorinya mengandung kata 'Biaya', 'Operasional', 'Gaji', 'Listrik', 'Sewa'
    # Ini untuk memisahkan antara "Beli Stok" (Aset) dengan "Biaya" (Pengeluaran Hangus)
    biaya_operasional = db.session.query(func.sum(CashFlow.jumlah)).filter(
        CashFlow.jenis == 'Keluar',
        or_(
            CashFlow.kategori.ilike('%Biaya%'),
            CashFlow.kategori.ilike('%Operasional%'),
            CashFlow.kategori.ilike('%Gaji%'),
            CashFlow.kategori.ilike('%Listrik%'),
            CashFlow.kategori.ilike('%Sewa%'),
            CashFlow.kategori.ilike('%Transport%')
        ),
        func.date(CashFlow.tanggal).between(start_date, end_date)
    ).scalar() or 0

    # D. Kerugian Barang (Inventory Loss - Fitur Baru)
    # Barang Expired, Rusak, atau Hilang
    total_loss = db.session.query(func.sum(InventoryLoss.total_rugi)).filter(
        func.date(InventoryLoss.tanggal).between(start_date, end_date)
    ).scalar() or 0
    
    # 5. Kalkulasi Akhir
    # Laba Kotor = (Penjualan Barang + Pendapatan Jasa) - HPP Barang
    laba_kotor = total_pendapatan - hpp
    # Laba Bersih = Laba Kotor - Biaya Operasional - Kerugian Barang
    laba_bersih = laba_kotor - biaya_operasional - total_loss

    # Statistik tambahan untuk info reparasi
    repair_count = db.session.query(func.count(RepairOrder.id)).filter(
        RepairOrder.status != 'Batal',
        func.date(RepairOrder.tanggal_masuk).between(start_date, end_date)
    ).scalar() or 0

    return render_template('reports.html',
                         start_date=start_date,
                         end_date=end_date,
                         sales_data=sales_data,
                         kas_masuk=kas_masuk,
                         kas_keluar=kas_keluar,
                         total_sales=total_sales,
                         repair_income=repair_income,
                         total_pendapatan=total_pendapatan,
                         repair_count=repair_count,
                         hpp=hpp,
                         biaya_operasional=biaya_operasional,
                         total_loss=total_loss,
                         laba_kotor=laba_kotor,
                         laba_bersih=laba_bersih)
# ==================== USERS ====================
@app.route('/users')
@login_required
def users():
    if current_user.role != 'Admin':
        flash('Akses ditolak! Hanya Admin yang dapat mengakses halaman ini.', 'error')
        return redirect(url_for('dashboard'))
    
    page = request.args.get('page', 1, type=int)
    search = request.args.get('search', '')
    
    query = User.query
    if search:
        query = query.filter(or_(
            User.username.like(f'%{search}%'),
            User.nama_lengkap.like(f'%{search}%')
        ))
    
    users = query.order_by(User.created_at.desc()).paginate(
        page=page, per_page=10, error_out=False
    )
    return render_template('users.html', users=users, search=search)

@app.route('/users/add', methods=['POST'])
@login_required
def add_user():
    if current_user.role != 'Admin':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))
    
    try:
        user = User(
            username=request.form['username'],
            nama_lengkap=request.form['nama_lengkap'],
            role=request.form['role']
        )
        user.set_password(request.form['password'])
        db.session.add(user)
        db.session.commit()
        flash('User berhasil ditambahkan!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('users'))

# ==================== SETTINGS ====================
UPLOAD_FOLDER = 'static/images'
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}

if not os.path.exists(UPLOAD_FOLDER):
    os.makedirs(UPLOAD_FOLDER)

app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/settings')
@login_required
def settings():
    # UBAH BARIS INI: Izinkan Admin dan Demo
    if current_user.role not in ['Admin', 'Demo']:
        flash('Akses ditolak! Anda tidak memiliki izin untuk mengakses pengaturan.', 'error')
        return redirect(url_for('dashboard'))
    
    settings = Settings.query.first()
    if not settings:
        settings = Settings()
        db.session.add(settings)
        db.session.commit()
    
    return render_template('settings.html', settings=settings)

@app.route('/settings/update', methods=['POST'])
@login_required
def update_settings():
    # UBAH BARIS INI: Izinkan Admin dan Demo
    if current_user.role not in ['Admin', 'Demo']:
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))
    
    try:
        settings = Settings.query.first()
        if not settings:
            settings = Settings()
            db.session.add(settings)
        
        settings.company_name = request.form['company_name']
        settings.company_address = request.form['company_address']
        settings.company_phone = request.form['company_phone']
        settings.company_wa = request.form['company_wa']
        settings.company_email = request.form.get('company_email', '')
        settings.company_website = request.form.get('company_website', '')
        settings.print_note = request.form.get('print_note', '')
        
        # Handle logo upload
        if 'logo' in request.files:
            file = request.files['logo']
            if file and file.filename != '' and allowed_file(file.filename):
                filename = secure_filename(file.filename)
                # Add timestamp to avoid overwrite
                name, ext = os.path.splitext(filename)
                filename = f"logo_{datetime.now().strftime('%Y%m%d%H%M%S')}{ext}"
                file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
                settings.logo_path = filename
        
        db.session.commit()
        flash('Pengaturan berhasil diupdate!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    
    return redirect(url_for('settings'))

@app.route('/users/edit/<int:id>', methods=['POST'])
@login_required
def edit_user(id):
    if current_user.role != 'Admin':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))
    
    try:
        user = User.query.get_or_404(id)
        user.username = request.form['username']
        user.nama_lengkap = request.form['nama_lengkap']
        user.role = request.form['role']
        
        if request.form.get('password'):
            user.set_password(request.form['password'])
        
        db.session.commit()
        flash('User berhasil diupdate!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('users'))

@app.route('/users/delete/<int:id>')
@login_required
def delete_user(id):
    if current_user.role != 'Admin':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))
    
    try:
        if id == current_user.id:
            flash('Tidak dapat menghapus akun sendiri!', 'error')
            return redirect(url_for('users'))
        
        user = User.query.get_or_404(id)
        InventoryLoss.query.filter_by(user_id=id).update({'user_id': None})
        Sale.query.filter_by(user_id=id).update({'user_id': None})
        db.session.delete(user)
        db.session.commit()
        flash('User berhasil dihapus!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('users'))

@app.route('/suppliers')
@login_required
def suppliers():
    # PERUBAHAN: Blokir akses untuk Kasir
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))

    page = request.args.get('page', 1, type=int)
    search = request.args.get('search', '')
    
    query = Supplier.query
    if search:
        query = query.filter(or_(
            Supplier.nama.like(f'%{search}%'),
            Supplier.kontak_person.like(f'%{search}%'),
            Supplier.no_hp.like(f'%{search}%')
        ))
    
    suppliers = query.order_by(Supplier.created_at.desc()).paginate(
        page=page, per_page=10, error_out=False
    )
    return render_template('suppliers.html', suppliers=suppliers, search=search)

@app.route('/suppliers/add', methods=['POST'])
@login_required
def add_supplier():
    # PERUBAHAN: Blokir akses untuk Kasir
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))

    try:
        supplier = Supplier(
            nama=request.form['nama'],
            kontak_person=request.form.get('kontak_person'),
            no_hp=request.form.get('no_hp'),
            alamat=request.form.get('alamat')
        )
        db.session.add(supplier)
        db.session.commit()
        flash('Supplier berhasil ditambahkan!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('suppliers'))

@app.route('/suppliers/edit/<int:id>', methods=['POST'])
@login_required
def edit_supplier(id):
    # PERUBAHAN: Blokir akses untuk Kasir
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))

    try:
        supplier = Supplier.query.get_or_404(id)
        supplier.nama = request.form['nama']
        supplier.kontak_person = request.form.get('kontak_person')
        supplier.no_hp = request.form.get('no_hp')
        supplier.alamat = request.form.get('alamat')
        db.session.commit()
        flash('Supplier berhasil diupdate!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('suppliers'))

@app.route('/suppliers/delete/<int:id>')
@login_required
def delete_supplier(id):
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))
    try:
        supplier = Supplier.query.get_or_404(id)
        if supplier.purchases:
            flash(f'Gagal menghapus! Supplier "{supplier.nama}" tidak bisa dihapus karena memiliki riwayat transaksi pembelian. Silakan biarkan saja atau ubah namanya menjadi Non-Aktif.', 'error')
            return redirect(url_for('suppliers'))
        db.session.delete(supplier)
        db.session.commit()
        flash('Supplier berhasil dihapus!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Gagal menghapus supplier: Terjadi kesalahan pada sistem database.', 'error')
    return redirect(url_for('suppliers'))

@app.route('/purchases')
@login_required
def purchases():
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))

    page = request.args.get('page', 1, type=int)
    search = request.args.get('search', '')
    
    query = Purchase.query
    if search:
        query = query.filter(Purchase.nomor_faktur.like(f'%{search}%'))
    
    purchases = query.order_by(Purchase.tanggal.desc()).paginate(
        page=page, per_page=10, error_out=False
    )
    
    suppliers = Supplier.query.all()
    today_date = datetime.now().strftime('%Y-%m-%d')
    
    return render_template('purchases.html', 
                            purchases=purchases, 
                            search=search, 
                            suppliers=suppliers, 
                            today_date=today_date)

@app.route('/purchases/add', methods=['POST'])
@login_required
def add_purchase():
    # PERUBAHAN: Blokir akses untuk Kasir
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))

    try:
        supplier_id = request.form['supplier_id']
        nomor_faktur = request.form['nomor_faktur']
        tanggal_str = request.form['tanggal']
        tanggal = datetime.strptime(tanggal_str, '%Y-%m-%d')
        metode_pembayaran = request.form['metode_pembayaran']
        uang_muka = float(request.form.get('uang_muka_dp', 0))

        purchase = Purchase(
            supplier_id=supplier_id,
            nomor_faktur=nomor_faktur,
            tanggal=tanggal,
            metode_pembayaran=metode_pembayaran,
            user_id=current_user.id,
            status='Belum Lunas'
        )
        db.session.add(purchase)
        db.session.flush()

        product_ids = request.form.getlist('product_id[]')
        quantities = request.form.getlist('qty[]')
        satuan_list = request.form.getlist('satuan[]')
        konversi_list = request.form.getlist('konversi[]')
        harga_belis = request.form.getlist('harga_beli[]')
        harga_ecers = request.form.getlist('harga_jual_ecer[]')
        harga_grosirs = request.form.getlist('harga_jual_grosir[]')
        while len(harga_ecers) < len(product_ids):
            harga_ecers.append('')
        while len(harga_grosirs) < len(product_ids):
            harga_grosirs.append('')

        total_purchase = 0
        for pid, qty, satuan, konv, harga, h_ecer, h_grosir in zip(product_ids, quantities, satuan_list, konversi_list, harga_belis, harga_ecers, harga_grosirs):
            product = Product.query.get(int(pid))
            qty_input = int(qty)
            konv_faktor = int(konv)
            harga_beli_satuan = float(harga)

            # Hitung penambahan stok ke satuan terkecil
            qty_tambah_stok = qty_input * konv_faktor

            # Hitung harga pokok baru PER SATUAN TERKECIL dan bulatkan
            # Contoh: Beli 1 Slop (120 batang) harga 120.000 -> Harga pokok per batang = 1.000
            harga_pokok_baru = round(harga_beli_satuan / konv_faktor)

            subtotal = qty_input * harga_beli_satuan

            item = PurchaseItem(
                purchase_id=purchase.id,
                product_id=product.id,
                qty=qty_input,
                satuan=satuan,
                qty_konversi=konv_faktor,
                harga_beli=harga_beli_satuan,
                subtotal=subtotal
            )
            db.session.add(item)

            # UPDATE STOK & HARGA POKOK PRODUK
            product.stok += qty_tambah_stok
            product.harga_pokok = harga_pokok_baru
            if h_ecer.strip():
                product.harga_ecer = float(h_ecer)
            if h_grosir.strip():
                product.harga_grosir = float(h_grosir)
            
            # Catat Movement
            movement = StockMovement(
                product_id=product.id,
                jenis='Masuk',
                qty=qty_tambah_stok,
                keterangan=f'Pembelian {nomor_faktur} ({qty_input} {satuan})'
            )
            db.session.add(movement)
            total_purchase += subtotal        
        purchase.total = total_purchase
        
        # Payment Logic
        if metode_pembayaran == 'Tunai':
            purchase.status = 'Lunas'
            cash = CashFlow(
                jenis='Keluar',
                kategori='Pembelian Stok',
                jumlah=total_purchase,
                keterangan=f'Pembelian {nomor_faktur}',
                user_id=current_user.id
            )
            db.session.add(cash)
        else: # Tempo or Cicilan
            if uang_muka > 0:
                payment = SupplierPayment(
                    purchase_id=purchase.id,
                    jumlah=uang_muka,
                    keterangan='Uang Muka (DP)'
                )
                db.session.add(payment)
                
                cash = CashFlow(
                    jenis='Keluar',
                    kategori='Pembayaran Hutang',
                    jumlah=uang_muka,
                    keterangan=f'DP Pembelian {nomor_faktur}',
                    user_id=current_user.id
                )
                db.session.add(cash)
            
            if uang_muka >= total_purchase:
                purchase.status = 'Lunas'
            else:
                purchase.status = 'Belum Lunas'

        db.session.commit()
        flash('Transaksi pembelian berhasil disimpan!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error saat menyimpan pembelian: {str(e)}', 'error')
    
    return redirect(url_for('purchases'))

@app.route('/purchases/detail/<int:id>')
@login_required
def purchase_detail(id):
    try:
        purchase = Purchase.query.get_or_404(id)
        
        # Hitung pembayaran yang sudah masuk (DP + Cicilan)
        total_paid = sum(p.jumlah for p in purchase.payments)
        remaining = purchase.total - total_paid
        
        # Susun data JSON
        data = {
            'success': True,
            'id': purchase.id,
            'nomor_faktur': purchase.nomor_faktur,
            'tanggal_raw': purchase.tanggal.strftime('%Y-%m-%d'), # Untuk input date
            'tanggal_indo': purchase.tanggal.strftime('%d/%m/%Y'), # Untuk tampilan text
            'supplier_id': purchase.supplier_id,
            'supplier_nama': purchase.supplier.nama,
            'supplier_contact': f"{purchase.supplier.kontak_person or '-'} ({purchase.supplier.no_hp or '-'})",
            'status': purchase.status,
            'metode': purchase.metode_pembayaran,
            'total': purchase.total,
            'terbayar': total_paid,
            'sisa': remaining,
            'items': []
        }
        
        for item in purchase.items:
            # Ambil list satuan konversi milik produk ini
            unit_list = []
            if item.product:
                for u in item.product.units:
                    unit_list.append({
                        'nama': u.nama_satuan,
                        'qty': u.jumlah_konversi
                    })
            
            data['items'].append({
                'product_id': item.product_id,
                'nama_barang': item.product.nama_barang if item.product else "Produk Terhapus",
                'kode_barang': item.product.kode_barang if item.product else "-",
                'qty': item.qty,
                'satuan_dipilih': item.satuan,
                'konversi_dipilih': item.qty_konversi,
                'harga_beli': item.harga_beli,
                'subtotal': item.subtotal,
                'satuan_base': item.product.satuan if item.product else "Pcs",
                'harga_ecer': item.product.harga_ecer if item.product else 0,
                'harga_grosir': item.product.harga_grosir if item.product else 0,
                'units_all': unit_list # Sangat penting untuk dropdown saat EDIT
            })
            
        return jsonify(data)
    except Exception as e:
        return jsonify({'success': False, 'message': str(e)}), 500

@app.route('/payables')
@login_required
def payables():
    # PERUBAHAN: Blokir akses untuk Kasir
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))

    page = request.args.get('page', 1, type=int)
    search = request.args.get('search', '')
    
    query = Purchase.query.filter(Purchase.status == 'Belum Lunas')
    if search:
        query = query.join(Supplier).filter(or_(
            Purchase.nomor_faktur.like(f'%{search}%'),
            Supplier.nama.like(f'%{search}%')
        ))
    
    purchases = query.order_by(Purchase.tanggal.desc()).paginate(
        page=page, per_page=10, error_out=False
    )
    return render_template('payables.html', purchases=purchases, search=search)

@app.route('/payables/add/<int:purchase_id>', methods=['POST'])
@login_required
def add_supplier_payment(purchase_id):
    # PERUBAHAN: Blokir akses untuk Kasir
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))

    try:
        purchase = Purchase.query.get_or_404(purchase_id)
        jumlah = float(request.form['jumlah'])
        keterangan = request.form.get('keterangan', '')
        
        payment = SupplierPayment(
            purchase_id=purchase.id,
            jumlah=jumlah,
            keterangan=keterangan
        )
        db.session.add(payment)
        
        # Check if fully paid
        total_paid = db.session.query(func.sum(SupplierPayment.jumlah)).filter(
            SupplierPayment.purchase_id == purchase.id
        ).scalar() or 0
        total_paid += jumlah
        
        if total_paid >= purchase.total:
            purchase.status = 'Lunas'
        
        # Add cash flow
        cash = CashFlow(
            jenis='Keluar',
            kategori='Pembayaran Hutang',
            jumlah=jumlah,
            keterangan=f'Pembayaran faktur {purchase.nomor_faktur} - {keterangan}',
            user_id=current_user.id
        )
        db.session.add(cash)
        
        db.session.commit()
        flash('Pembayaran hutang berhasil dicatat!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('payables'))

@app.route('/inventory/loss', methods=['GET', 'POST'])
@login_required
def inventory_loss():
    # 1. Cek Role (Hanya Admin/Owner)
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))

    # 2. Proses INPUT DATA (POST)
    if request.method == 'POST':
        try:
            prod_id = request.form.get('product_id')
            qty_input = int(request.form.get('qty', 0))
            satuan_pilihan = request.form.get('satuan')
            konv_faktor = int(request.form.get('konversi', 1))
            alasan = request.form.get('alasan')
            keterangan = request.form.get('keterangan', '')
            
            product = Product.query.get(prod_id)
            if not product:
                flash('Produk tidak ditemukan', 'error')
                return redirect(url_for('inventory_loss'))
            
            # Hitung total qty dalam satuan terkecil
            total_qty_base = qty_input * konv_faktor
            
            # Cek kecukupan stok
            if product.stok < total_qty_base:
                flash(f'Stok tidak cukup! Stok saat ini: {product.stok_display}', 'error')
                return redirect(url_for('inventory_loss'))
            
            # Kurangi Stok Dasar
            product.stok -= total_qty_base
            
            # Hitung Nilai Kerugian (Qty Base * HPP Dasar)
            nilai_rugi = total_qty_base * product.harga_pokok
            
            # Simpan Record Kerugian
            loss = InventoryLoss(
                product_id=product.id,
                qty=qty_input,
                satuan=satuan_pilihan,
                qty_konversi=konv_faktor,
                alasan=alasan,
                total_rugi=nilai_rugi,
                keterangan=keterangan,
                user_id=current_user.id
            )
            db.session.add(loss)
            
            # Catat Stock Movement
            movement = StockMovement(
                product_id=product.id,
                jenis='Keluar',
                qty=total_qty_base,
                keterangan=f'Loss: {alasan} ({qty_input} {satuan_pilihan})'
            )
            db.session.add(movement)
            
            db.session.commit()
            flash('Kerugian barang berhasil dicatat', 'success')
            
            # PENTING: Harus ada return redirect di sini
            return redirect(url_for('inventory_loss'))

        except Exception as e:
            db.session.rollback()
            flash(f'Error: {str(e)}', 'error')
            return redirect(url_for('inventory_loss'))

    # 3. TAMPILKAN HALAMAN (GET)
    # Ambil riwayat kerugian terbaru
    losses = InventoryLoss.query.order_by(InventoryLoss.tanggal.desc()).limit(50).all()
    # Ambil daftar produk fisik saja
    products = Product.query.filter_by(is_service=False).order_by(Product.nama_barang).all()
    
    return render_template('inventory_loss.html', losses=losses, products=products)

@app.route('/reports/customer_profit')
@login_required
def customer_profit_report():
    if current_user.role == 'Kasir':
        flash('Akses ditolak! Fitur ini tidak tersedia untuk role Kasir.', 'error')
        return redirect(url_for('index'))
    start_date = request.args.get('start_date', datetime.now().replace(day=1).strftime('%Y-%m-%d'))
    end_date = request.args.get('end_date', datetime.now().strftime('%Y-%m-%d'))
    
    # 1. Ambil semua data penjualan dalam range tanggal
    sales = Sale.query.filter(func.date(Sale.tanggal).between(start_date, end_date)).all()

    customers_data = {}
    total_belanja_semua = 0
    total_profit_semua = 0

    for sale in sales:
        cid = sale.customer_id
        cname = sale.customer.nama
        tgl = sale.tanggal.strftime('%Y-%m-%d')
        
        # Inisialisasi data pelanggan jika belum ada
        if cid not in customers_data:
            customers_data[cid] = {
                'nama': cname,
                'per_tanggal': {},
                'grand_total_belanja': 0,
                'grand_total_profit': 0
            }
        
        # Inisialisasi data tanggal jika belum ada
        if tgl not in customers_data[cid]['per_tanggal']:
            customers_data[cid]['per_tanggal'][tgl] = {
                'detail_barang': [],
                'total_harian': 0,
                'profit_harian': 0
            }

        # Hitung Profit Nota Ini: (Total Subtotal Item - Total HPP Item) - Diskon Nota
        total_hpp_nota = 0
        for item in sale.items:
            hpp_item = (item.product.harga_pokok * item.qty_konversi * item.qty)
            profit_item = item.subtotal - hpp_item
            total_hpp_nota += hpp_item
            
            # Tambah ke detail tabel
            customers_data[cid]['per_tanggal'][tgl]['detail_barang'].append({
                'produk': item.product.nama_barang,
                'qty': item.qty,
                'satuan': item.satuan,
                'subtotal': item.subtotal,
                'profit': profit_item # Profit bruto per item
            })

        # --- KOREKSI OMSET & PROFIT ---
        # Omset yang benar adalah total_bayar (Net)
        omset_net_nota = sale.total_bayar
        # Profit yang benar adalah (Total Jual - Total HPP) - Diskon Nota
        profit_net_nota = (sale.total - total_hpp_nota) - sale.diskon

        # Update hitungan harian
        customers_data[cid]['per_tanggal'][tgl]['total_harian'] += omset_net_nota
        customers_data[cid]['per_tanggal'][tgl]['profit_harian'] += profit_net_nota
        
        # Update hitungan grand total per pelanggan
        customers_data[cid]['grand_total_belanja'] += omset_net_nota
        customers_data[cid]['grand_total_profit'] += profit_net_nota
        
        # Untuk Ringkasan Footer Laporan
        total_belanja_semua += omset_net_nota
        total_profit_semua += profit_net_nota

    return render_template('report_customer_profit.html', 
                           customers_data=customers_data, 
                           start_date=start_date, 
                           end_date=end_date,
                           total_belanja_semua=total_belanja_semua,
                           total_profit_semua=total_profit_semua)

@app.route('/sales/open_print_dialog/<int:id>')
@login_required
def open_print_dialog(id):
    """
    Membuka aplikasi Tkinter (print_gui.py) di komputer SERVER (Localhost).
    """
    try:
        # Panggil script python eksternal
        # sys.executable memastikan kita menggunakan python environment yang sama
        subprocess.Popen([sys.executable, 'print_gui.py', str(id)])
        
        return jsonify({'success': True, 'message': 'Dialog print dibuka di layar server'})
    except Exception as e:
        return jsonify({'success': False, 'message': f'Gagal membuka dialog: {str(e)}'})

@app.route('/purchases/delete/<int:id>')
@login_required
def delete_purchase(id):
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('purchases'))

    try:
        purchase = Purchase.query.get_or_404(id)
        faktur = purchase.nomor_faktur

        # 1. Kembalikan/Kurangi Stok yang pernah ditambah
        for item in purchase.items:
            product = item.product
            # Hitung total qty dalam satuan terkecil yang dulu masuk
            qty_masuk = item.qty * item.qty_konversi
            product.stok -= qty_masuk
            
            # Tambahkan catatan movement keluar (karena pembatalan)
            movement = StockMovement(
                product_id=product.id,
                jenis='Keluar',
                qty=qty_masuk,
                keterangan=f'Pembatalan Pembelian {faktur}'
            )
            db.session.add(movement)

        # 2. Hapus data Arus Kas terkait faktur ini
        CashFlow.query.filter(CashFlow.keterangan.like(f'%{faktur}%')).delete(synchronize_session=False)

        # 3. Hapus Purchase (Items & SupplierPayments akan terhapus otomatis karena cascade)
        db.session.delete(purchase)
        
        db.session.commit()
        flash(f'Transaksi {faktur} berhasil dihapus dan stok telah disesuaikan.', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error saat menghapus: {str(e)}', 'error')
    
    return redirect(url_for('purchases'))

@app.route('/purchases/edit/<int:id>', methods=['POST'])
@login_required
def edit_purchase(id):
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('purchases'))

    try:
        purchase = Purchase.query.get_or_404(id)
        old_faktur = purchase.nomor_faktur

        # --- LANGKAH 1: KEMBALIKAN KONDISI STOK LAMA ---
        for old_item in purchase.items:
            old_product = old_item.product
            old_product.stok -= (old_item.qty * old_item.qty_konversi)
        
        # Hapus item lama dan cashflow lama
        PurchaseItem.query.filter_by(purchase_id=purchase.id).delete()
        CashFlow.query.filter(CashFlow.keterangan.like(f'%{old_faktur}%')).delete(synchronize_session=False)

        # --- LANGKAH 2: UPDATE DATA HEADER ---
        purchase.supplier_id = request.form['supplier_id']
        purchase.nomor_faktur = request.form['nomor_faktur']
        purchase.tanggal = datetime.strptime(request.form['tanggal'], '%Y-%m-%d')
        purchase.metode_pembayaran = request.form['metode_pembayaran']
        
        # --- LANGKAH 3: PROSES ITEM BARU (Sama seperti add_purchase) ---
        product_ids = request.form.getlist('product_id[]')
        quantities = request.form.getlist('qty[]')
        satuan_list = request.form.getlist('satuan[]')
        konversi_list = request.form.getlist('konversi[]')
        harga_belis = request.form.getlist('harga_beli[]')
        harga_ecers = request.form.getlist('harga_jual_ecer[]')
        harga_grosirs = request.form.getlist('harga_jual_grosir[]')
        while len(harga_ecers) < len(product_ids):
            harga_ecers.append('')
        while len(harga_grosirs) < len(product_ids):
            harga_grosirs.append('')

        total_new = 0
        for pid, qty, sat, konv, harga, h_ecer, h_grosir in zip(product_ids, quantities, satuan_list, konversi_list, harga_belis, harga_ecers, harga_grosirs):
            product = Product.query.get(int(pid))
            qty_val = int(qty)
            konv_val = int(konv)
            harga_val = float(harga)

            subtotal = qty_val * harga_val
            total_new += subtotal

            # Tambah ke tabel Item
            new_item = PurchaseItem(
                purchase_id=purchase.id,
                product_id=product.id,
                qty=qty_val,
                satuan=sat,
                qty_konversi=konv_val,
                harga_beli=harga_val,
                subtotal=subtotal
            )
            db.session.add(new_item)

            # Update Stok & HPP baru (Dibulatkan)
            product.stok += (qty_val * konv_val)
            product.harga_pokok = round(harga_val / konv_val)
            if h_ecer.strip():
                product.harga_ecer = float(h_ecer)
            if h_grosir.strip():
                product.harga_grosir = float(h_grosir)

        purchase.total = total_new

        # --- LANGKAH 4: UPDATE CASHFLOW ---
        if purchase.metode_pembayaran == 'Tunai':
            purchase.status = 'Lunas'
            db.session.add(CashFlow(
                jenis='Keluar', kategori='Pembelian Stok', jumlah=total_new,
                keterangan=f'Pembelian {purchase.nomor_faktur}', user_id=current_user.id
            ))
        else:
            purchase.status = 'Belum Lunas'
            dp = float(request.form.get('uang_muka_dp', 0))
            if dp > 0:
                db.session.add(CashFlow(
                    jenis='Keluar', kategori='Pembayaran Hutang', jumlah=dp,
                    keterangan=f'DP Pembelian {purchase.nomor_faktur}', user_id=current_user.id
                ))

        db.session.commit()
        flash('Transaksi pembelian berhasil diperbarui!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Gagal Update: {str(e)}', 'error')

    return redirect(url_for('purchases'))

@app.route('/inventory/opname')
@login_required
def stock_opname():
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))
    
    # Ambil semua produk fisik (bukan jasa)
    products = Product.query.filter_by(is_service=False).order_by(Product.nama_barang).all()
    return render_template('stock_opname.html', products=products)

@app.route('/inventory/opname/process', methods=['POST'])
@login_required
def process_opname():
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))

    try:
        # Gunakan nama 'stok_aktual[]' agar sama dengan HTML
        product_ids = request.form.getlist('product_id[]')
        stok_aktuals = request.form.getlist('stok_aktual[]')
        keterangan_umum = request.form.get('keterangan_umum', 'Stok Opname Rutin')

        opname = StockOpname(keterangan=keterangan_umum, user_id=current_user.id)
        db.session.add(opname)
        db.session.flush()

        for p_id, s_aktual in zip(product_ids, stok_aktuals):
            # Cek jika input kosong atau bukan angka, jangan diproses
            if s_aktual == "" or s_aktual is None:
                continue
            
            product = Product.query.get(int(p_id))
            aktual = int(s_aktual)
            sistem = product.stok
            selisih = aktual - sistem # Rumus: Fisik - Sistem

            detail = StockOpnameItem(
                opname_id=opname.id,
                product_id=product.id,
                stok_sistem=sistem,
                stok_aktual=aktual,
                selisih=selisih
            )
            db.session.add(detail)

            if selisih != 0:
                product.stok = aktual
                db.session.add(StockMovement(
                    product_id=product.id,
                    jenis='Masuk' if selisih > 0 else 'Keluar',
                    qty=abs(selisih),
                    keterangan=f'Adjustment Opname (Selisih {selisih})'
                ))

        db.session.commit()
        flash('Stok Berhasil Disesuaikan!', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Gagal: {str(e)}', 'error')

    return redirect(url_for('stock_opname'))

@app.route('/inventory/opname/history')
@login_required
def stock_opname_history():
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('dashboard'))
    
    # Ambil semua data opname, yang terbaru di atas
    history = StockOpname.query.order_by(StockOpname.tanggal.desc()).all()
    return render_template('stock_opname_history.html', history=history)

@app.route('/inventory/opname/detail/<int:id>')
@login_required
def stock_opname_detail(id):
    opname = StockOpname.query.get_or_404(id)

    items_data = []
    for item in opname.items:
        items_data.append({
            'nama': item.product.nama_barang,
            'kode': item.product.kode_barang,
            'satuan': item.product.satuan,
            'sistem': item.stok_sistem,
            'aktual': item.stok_aktual,
            'selisih': item.selisih
        })

    return jsonify({
        'id': opname.id,
        'tanggal': opname.tanggal.strftime('%d/%m/%Y %H:%M'),
        'keterangan': opname.keterangan,
        'petugas': opname.user.nama_lengkap if opname.user else 'System',
        'items': items_data
    })

# ==================== CSV EXPORT / IMPORT ====================
def _csv_response(filename, header, rows):
    """Helper: bangun response CSV dengan BOM supaya Excel membaca UTF-8."""
    buf = io.StringIO()
    buf.write('\ufeff')  # UTF-8 BOM untuk Excel
    writer = csv.writer(buf)
    writer.writerow(header)
    writer.writerows(rows)
    return Response(
        buf.getvalue(),
        mimetype='text/csv; charset=utf-8',
        headers={'Content-Disposition': f'attachment; filename="{filename}"'}
    )

@app.route('/customers/export')
@login_required
def customers_export():
    header = ['nama', 'no_hp', 'alamat', 'jenis', 'limit_piutang']
    rows = [(c.nama, c.no_hp or '', c.alamat or '', c.jenis, c.limit_piutang or 0)
            for c in Customer.query.order_by(Customer.nama.asc()).all()]
    fname = f"pelanggan_{datetime.now().strftime('%Y%m%d_%H%M')}.csv"
    return _csv_response(fname, header, rows)

@app.route('/customers/import/template')
@login_required
def customers_import_template():
    """Unduh template CSV untuk impor pelanggan."""
    header = ['nama', 'no_hp', 'alamat', 'jenis', 'limit_piutang']
    contoh = [
        ('Budi Santoso', '081234567890', 'Jl. Merdeka No. 10, Jakarta', 'Biasa', 0),
        ('Toko Makmur', '0812345678', 'Jl. Sudirman No. 5, Surabaya', 'Sub Agen', 5000000),
    ]
    return _csv_response('template_pelanggan.csv', header, contoh)

@app.route('/customers/import', methods=['POST'])
@login_required
def customers_import():
    file = request.files.get('csv_file')
    if not file or file.filename == '':
        flash('File CSV tidak dipilih.', 'error')
        return redirect(url_for('customers'))
    if not file.filename.lower().endswith('.csv'):
        flash('File harus berformat .csv', 'error')
        return redirect(url_for('customers'))

    try:
        raw = file.stream.read()
        text = None
        for enc in ('utf-8-sig', 'utf-8', 'cp1252', 'latin-1'):
            try:
                text = raw.decode(enc)
                break
            except UnicodeDecodeError:
                continue
        if text is None:
            raise ValueError('Encoding file tidak dikenal.')

        sample = text[:2048]
        try:
            dialect = csv.Sniffer().sniff(sample, delimiters=',;\t')
        except csv.Error:
            class _D(csv.excel):
                delimiter = ','
            dialect = _D

        reader = csv.DictReader(io.StringIO(text), dialect=dialect)
        reader.fieldnames = [((h or '').strip().lower().lstrip('﻿'))
                             for h in (reader.fieldnames or [])]

        if 'nama' not in reader.fieldnames:
            flash(f'Header wajib: nama. Ditemukan: {", ".join(reader.fieldnames)}', 'error')
            return redirect(url_for('customers'))

        def _num(v, default=0):
            if v is None:
                return default
            s = str(v).strip()
            if ',' in s and s.count(',') == 1 and s.count('.') >= 1:
                s = s.replace('.', '').replace(',', '.')
            if s == '':
                return default
            try:
                return float(s)
            except ValueError:
                return default

        created = 0
        skipped = 0
        for row in reader:
            nama = (row.get('nama') or '').strip()
            if not nama:
                skipped += 1
                continue

            no_hp = (row.get('no_hp') or '').strip()
            alamat = (row.get('alamat') or '').strip()
            jenis_val = (row.get('jenis') or '').strip().lower()
            jenis = 'Sub Agen' if jenis_val in ('sub agen', 'sub_agen', 'subagen', 'agen') else 'Biasa'
            limit = _num(row.get('limit_piutang'))

            db.session.add(Customer(
                nama=nama,
                no_hp=no_hp,
                alamat=alamat,
                jenis=jenis,
                limit_piutang=limit
            ))
            created += 1

        db.session.commit()
        flash(f'Import selesai. {created} pelanggan ditambahkan, {skipped} baris dilewati.', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Gagal import CSV: {str(e)}', 'error')

    return redirect(url_for('customers'))

@app.route('/suppliers/export')
@login_required
def suppliers_export():
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('suppliers'))
    header = ['nama', 'kontak_person', 'no_hp', 'alamat']
    rows = [(s.nama, s.kontak_person or '', s.no_hp or '', s.alamat or '')
            for s in Supplier.query.order_by(Supplier.nama.asc()).all()]
    fname = f"supplier_{datetime.now().strftime('%Y%m%d_%H%M')}.csv"
    return _csv_response(fname, header, rows)

@app.route('/products/export')
@login_required
def products_export():
    header = ['kode_barang', 'barcode', 'nama_barang', 'kategori', 'satuan',
              'harga_pokok', 'harga_ecer', 'harga_grosir', 'stok',
              'stok_minimum', 'is_service']
    rows = []
    for p in Product.query.order_by(Product.nama_barang.asc()).all():
        rows.append((
            p.kode_barang, p.barcode or '', p.nama_barang, p.kategori or '',
            p.satuan or '', p.harga_pokok or 0, p.harga_ecer or 0,
            p.harga_grosir or 0, p.stok or 0, p.stok_minimum or 0,
            'Ya' if p.is_service else 'Tidak'
        ))
    fname = f"produk_{datetime.now().strftime('%Y%m%d_%H%M')}.csv"
    return _csv_response(fname, header, rows)

@app.route('/products/import/template')
@login_required
def products_import_template():
    """Unduh template CSV untuk impor produk."""
    header = ['kode_barang', 'barcode', 'nama_barang', 'kategori', 'satuan',
              'harga_pokok', 'harga_ecer', 'harga_grosir', 'stok',
              'stok_minimum', 'is_service']
    contoh = [
        ('BRG001', '8991234567890', 'Contoh Produk Fisik', 'Umum', 'Pcs',
         10000, 12000, 11000, 50, 5, 'Tidak'),
        ('JSA001', '', 'Contoh Jasa Service', 'Service', 'Layanan',
         0, 50000, 45000, 0, 0, 'Ya'),
    ]
    return _csv_response('template_produk.csv', header, contoh)

@app.route('/products/import', methods=['POST'])
@login_required
def products_import():
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('products'))

    file = request.files.get('csv_file')
    if not file or file.filename == '':
        flash('File CSV tidak dipilih.', 'error')
        return redirect(url_for('products'))
    if not file.filename.lower().endswith('.csv'):
        flash('File harus berformat .csv', 'error')
        return redirect(url_for('products'))

    try:
        raw = file.stream.read()
        # Coba beberapa encoding umum
        text = None
        for enc in ('utf-8-sig', 'utf-8', 'cp1252', 'latin-1'):
            try:
                text = raw.decode(enc)
                break
            except UnicodeDecodeError:
                continue
        if text is None:
            raise ValueError('Encoding file tidak dikenal.')

        # Deteksi delimiter (comma / semicolon)
        sample = text[:2048]
        try:
            dialect = csv.Sniffer().sniff(sample, delimiters=',;\t')
        except csv.Error:
            class _D(csv.excel):
                delimiter = ','
            dialect = _D

        reader = csv.DictReader(io.StringIO(text), dialect=dialect)
        reader.fieldnames = [((h or '').strip().lower().lstrip('\ufeff'))
                             for h in (reader.fieldnames or [])]

        required = {'kode_barang', 'nama_barang'}
        if not required.issubset(set(reader.fieldnames)):
            flash(f'Header wajib: {", ".join(required)}. Ditemukan: {", ".join(reader.fieldnames)}', 'error')
            return redirect(url_for('products'))

        def _num(v, default=0):
            if v is None:
                return default
            s = str(v).strip().replace('.', '').replace(',', '.') \
                if (',' in str(v) and str(v).count(',') == 1 and str(v).count('.') >= 1) \
                else str(v).strip()
            if s == '':
                return default
            try:
                return float(s)
            except ValueError:
                return default

        def _int(v, default=0):
            return int(_num(v, default))

        def _bool(v):
            s = (str(v) if v is not None else '').strip().lower()
            return s in ('1', 'true', 'ya', 'y', 'yes', 'jasa', 'service')

        created = 0
        updated = 0
        skipped = 0
        for row in reader:
            kode = (row.get('kode_barang') or '').strip()
            nama = (row.get('nama_barang') or '').strip()
            if not kode or not nama:
                skipped += 1
                continue

            is_service = _bool(row.get('is_service'))
            barcode = (row.get('barcode') or '').strip() or None
            kategori = (row.get('kategori') or '').strip() or None
            satuan = (row.get('satuan') or '').strip() or ('Layanan' if is_service else 'Pcs')

            harga_pokok = _num(row.get('harga_pokok'))
            harga_ecer = _num(row.get('harga_ecer'))
            harga_grosir = _num(row.get('harga_grosir'))
            stok = 0 if is_service else _int(row.get('stok'))
            stok_min = _int(row.get('stok_minimum'), 10)

            existing = Product.query.filter_by(kode_barang=kode).first()
            if existing:
                existing.nama_barang = nama
                existing.kategori = kategori
                existing.satuan = satuan
                existing.harga_pokok = harga_pokok
                existing.harga_ecer = harga_ecer
                existing.harga_grosir = harga_grosir
                existing.stok_minimum = stok_min
                existing.is_service = is_service
                if barcode:
                    existing.barcode = barcode
                # Stok hanya di-update bila kolom diisi dan bukan jasa
                if not is_service and (row.get('stok') or '').strip() != '':
                    selisih = stok - (existing.stok or 0)
                    existing.stok = stok
                    if selisih != 0:
                        db.session.add(StockMovement(
                            product_id=existing.id,
                            jenis='Masuk' if selisih > 0 else 'Keluar',
                            qty=abs(selisih),
                            keterangan='Import CSV (penyesuaian stok)'
                        ))
                updated += 1
            else:
                new_p = Product(
                    kode_barang=kode,
                    barcode=barcode,
                    nama_barang=nama,
                    kategori=kategori,
                    satuan=satuan,
                    harga_pokok=harga_pokok,
                    harga_ecer=harga_ecer,
                    harga_grosir=harga_grosir,
                    stok=stok,
                    stok_minimum=stok_min,
                    is_service=is_service
                )
                db.session.add(new_p)
                db.session.flush()
                if not is_service and stok > 0:
                    db.session.add(StockMovement(
                        product_id=new_p.id,
                        jenis='Masuk',
                        qty=stok,
                        keterangan='Stok awal (Import CSV)'
                    ))
                created += 1

        db.session.commit()
        flash(f'Import CSV selesai. Baru: {created}, Update: {updated}, Dilewati: {skipped}.', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Gagal import CSV: {str(e)}', 'error')

    return redirect(url_for('products'))

# ==================== REPAIR / JASA REPARASI ====================
def _generate_nomor_service():
    prefix = 'SV' + datetime.now().strftime('%y%m%d')
    last = RepairOrder.query.filter(RepairOrder.nomor_service.like(prefix + '%')) \
        .order_by(RepairOrder.id.desc()).first()
    seq = 1
    if last and last.nomor_service.startswith(prefix):
        try:
            seq = int(last.nomor_service[len(prefix):]) + 1
        except ValueError:
            seq = 1
    return f"{prefix}{seq:03d}"

@app.route('/repairs')
@login_required
def repairs():
    page = request.args.get('page', 1, type=int)
    search = request.args.get('search', '').strip()
    status_filter = request.args.get('status', '').strip()

    query = RepairOrder.query
    if search:
        query = query.join(Customer, RepairOrder.customer_id == Customer.id, isouter=True).filter(or_(
            RepairOrder.nomor_service.like(f'%{search}%'),
            RepairOrder.nama_barang.like(f'%{search}%'),
            RepairOrder.serial_number.like(f'%{search}%'),
            RepairOrder.merk.like(f'%{search}%'),
            Customer.nama.like(f'%{search}%')
        ))
    if status_filter:
        query = query.filter(RepairOrder.status == status_filter)

    repairs_paginated = query.order_by(RepairOrder.tanggal_masuk.desc()).paginate(
        page=page, per_page=15, error_out=False
    )
    customers = Customer.query.order_by(Customer.nama.asc()).all()

    stats = {
        'masuk': RepairOrder.query.filter(RepairOrder.status.in_(['Masuk', 'Diagnosa', 'Perbaikan'])).count(),
        'selesai': RepairOrder.query.filter_by(status='Selesai').count(),
        'diambil': RepairOrder.query.filter_by(status='Diambil').count(),
    }

    return render_template('repairs.html',
                           repairs=repairs_paginated,
                           customers=customers,
                           search=search,
                           status_filter=status_filter,
                           stats=stats)

@app.route('/repairs/add', methods=['POST'])
@login_required
def add_repair():
    try:
        customer_id = int(request.form['customer_id'])
        repair = RepairOrder(
            nomor_service=_generate_nomor_service(),
            customer_id=customer_id,
            nama_barang=request.form['nama_barang'].strip(),
            merk=(request.form.get('merk') or '').strip() or None,
            tipe=(request.form.get('tipe') or '').strip() or None,
            serial_number=(request.form.get('serial_number') or '').strip() or None,
            kelengkapan=(request.form.get('kelengkapan') or '').strip() or None,
            kondisi_fisik=(request.form.get('kondisi_fisik') or '').strip() or None,
            keluhan=request.form['keluhan'].strip(),
            diagnosa=(request.form.get('diagnosa') or '').strip() or None,
            teknisi=(request.form.get('teknisi') or '').strip() or None,
            estimasi_biaya=float(request.form.get('estimasi_biaya') or 0),
            status=request.form.get('status') or 'Masuk',
            catatan=(request.form.get('catatan') or '').strip() or None,
            user_id=current_user.id
        )
        db.session.add(repair)
        db.session.commit()
        flash(f'Order reparasi {repair.nomor_service} berhasil dibuat.', 'success')
        return redirect(url_for('repair_detail', id=repair.id))
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('repairs'))

@app.route('/repairs/<int:id>')
@login_required
def repair_detail(id):
    repair = RepairOrder.query.get_or_404(id)
    return render_template('repair_detail.html', repair=repair)

@app.route('/repairs/<int:id>/update', methods=['POST'])
@login_required
def update_repair(id):
    repair = RepairOrder.query.get_or_404(id)
    try:
        repair.nama_barang = request.form['nama_barang'].strip()
        repair.merk = (request.form.get('merk') or '').strip() or None
        repair.tipe = (request.form.get('tipe') or '').strip() or None
        repair.serial_number = (request.form.get('serial_number') or '').strip() or None
        repair.kelengkapan = (request.form.get('kelengkapan') or '').strip() or None
        repair.kondisi_fisik = (request.form.get('kondisi_fisik') or '').strip() or None
        repair.keluhan = request.form['keluhan'].strip()
        repair.diagnosa = (request.form.get('diagnosa') or '').strip() or None
        repair.teknisi = (request.form.get('teknisi') or '').strip() or None
        repair.estimasi_biaya = float(request.form.get('estimasi_biaya') or 0)
        new_status = request.form.get('status') or repair.status
        if new_status in ('Selesai', 'Diambil') and not repair.tanggal_selesai:
            repair.tanggal_selesai = datetime.now()
        if new_status not in ('Selesai', 'Diambil'):
            repair.tanggal_selesai = None
        repair.status = new_status
        repair.catatan = (request.form.get('catatan') or '').strip() or None
        db.session.commit()
        flash('Data reparasi diperbarui.', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('repair_detail', id=id))

@app.route('/repairs/<int:id>/action', methods=['POST'])
@login_required
def add_repair_action(id):
    repair = RepairOrder.query.get_or_404(id)
    try:
        biaya_jasa = float(request.form.get('biaya_jasa') or 0)
        biaya_sp = float(request.form.get('biaya_sparepart') or 0)
        action = RepairAction(
            repair_id=repair.id,
            tindakan=request.form['tindakan'].strip(),
            sparepart=(request.form.get('sparepart') or '').strip() or None,
            biaya_jasa=biaya_jasa,
            biaya_sparepart=biaya_sp,
            total=biaya_jasa + biaya_sp,
            teknisi=(request.form.get('teknisi') or repair.teknisi or '').strip() or None,
            catatan=(request.form.get('catatan') or '').strip() or None,
        )
        db.session.add(action)
        # Update total biaya order berdasarkan akumulasi tindakan
        repair.total_biaya = (repair.total_biaya or 0) + action.total
        if repair.status == 'Masuk':
            repair.status = 'Perbaikan'
        db.session.commit()
        flash('Tindakan berhasil dicatat.', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('repair_detail', id=id))

@app.route('/repairs/<int:id>/action/<int:aid>/delete')
@login_required
def delete_repair_action(id, aid):
    repair = RepairOrder.query.get_or_404(id)
    action = RepairAction.query.get_or_404(aid)
    try:
        if action.repair_id != repair.id:
            flash('Tindakan tidak sesuai order.', 'error')
            return redirect(url_for('repair_detail', id=id))
        repair.total_biaya = max(0, (repair.total_biaya or 0) - (action.total or 0))
        db.session.delete(action)
        db.session.commit()
        flash('Tindakan dihapus.', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('repair_detail', id=id))

@app.route('/repairs/<int:id>/bayar', methods=['POST'])
@login_required
def bayar_repair(id):
    repair = RepairOrder.query.get_or_404(id)
    try:
        jumlah = float(request.form.get('jumlah') or 0)
        if jumlah <= 0:
            flash('Jumlah bayar harus > 0.', 'error')
            return redirect(url_for('repair_detail', id=id))
        repair.jumlah_dibayar = (repair.jumlah_dibayar or 0) + jumlah
        if repair.total_biaya > 0 and repair.jumlah_dibayar >= repair.total_biaya:
            repair.status_bayar = 'Lunas'
        elif repair.jumlah_dibayar > 0:
            repair.status_bayar = 'DP'
        # Catat di arus kas
        db.session.add(CashFlow(
            jenis='Masuk',
            kategori='Jasa Reparasi',
            jumlah=jumlah,
            keterangan=f'Pembayaran reparasi {repair.nomor_service} - {repair.nama_barang}',
            user_id=current_user.id
        ))
        db.session.commit()
        flash(f'Pembayaran Rp {jumlah:,.0f} dicatat.', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('repair_detail', id=id))

@app.route('/repairs/<int:id>/delete')
@login_required
def delete_repair(id):
    if current_user.role == 'Kasir':
        flash('Akses ditolak!', 'error')
        return redirect(url_for('repairs'))
    repair = RepairOrder.query.get_or_404(id)
    try:
        if repair.jumlah_dibayar and repair.jumlah_dibayar > 0:
            flash('Tidak dapat menghapus order yang sudah ada pembayaran.', 'error')
            return redirect(url_for('repairs'))
        db.session.delete(repair)
        db.session.commit()
        flash('Order reparasi dihapus.', 'success')
    except Exception as e:
        db.session.rollback()
        flash(f'Error: {str(e)}', 'error')
    return redirect(url_for('repairs'))

@app.route('/customers/<int:id>/repairs')
@login_required
def customer_repair_history(id):
    customer = Customer.query.get_or_404(id)
    history = RepairOrder.query.filter_by(customer_id=id) \
        .order_by(RepairOrder.tanggal_masuk.desc()).all()
    return render_template('customer_repairs.html', customer=customer, history=history)

# ==================== BACKUP & RESTORE ====================
from flask import send_file
import shutil
import time

@app.route('/backup')
@login_required
def backup_db():
    if current_user.role == 'Demo':
        flash('Fitur Backup Database tidak tersedia di akun Demo. Hubungi pemilik toko untuk mengakses fitur ini.', 'warning')
        return redirect(url_for('settings'))
    if current_user.role not in ['Admin', 'Owner']:
        flash('Akses ditolak! Hanya Admin atau Owner yang dapat melakukan backup.', 'error')
        return redirect(url_for('settings'))
    
    try:
        base_dir = os.path.abspath(os.path.dirname(__file__))
        db_path = os.path.join(base_dir, 'oshacomp.db')
        
        if not os.path.exists(db_path):
            flash('File database tidak ditemukan untuk dibackup. (Hanya untuk SQLite)', 'error')
            return redirect(url_for('settings'))
            
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_filename = f"backup_oshacomp_{timestamp}.db"
        
        return send_file(db_path, as_attachment=True, download_name=backup_filename)
        
    except Exception as e:
        flash(f'Gagal membuat backup: {str(e)}', 'error')
        return redirect(url_for('settings'))

@app.route('/restore', methods=['POST'])
@login_required
def restore_db():
    if current_user.role == 'Demo':
        flash('Fitur Restore Database tidak tersedia di akun Demo. Hubungi pemilik toko untuk mengakses fitur ini.', 'warning')
        return redirect(url_for('settings'))
    if current_user.role not in ['Admin', 'Owner']:
        flash('Akses ditolak! Hanya Admin atau Owner yang dapat melakukan restore.', 'error')
        return redirect(url_for('settings'))
        
    try:
        if 'backup_file' not in request.files:
            flash('Tidak ada file yang diunggah', 'error')
            return redirect(url_for('settings'))
            
        file = request.files['backup_file']
        if file.filename == '':
            flash('Tidak ada file yang dipilih', 'error')
            return redirect(url_for('settings'))
            
        if not file.filename.endswith('.db'):
            flash('Hanya file dengan ekstensi .db yang diperbolehkan!', 'error')
            return redirect(url_for('settings'))
            
        base_dir = os.path.abspath(os.path.dirname(__file__))
        db_path = os.path.join(base_dir, 'oshacomp.db')
        temp_path = os.path.join(base_dir, 'temp_restore.db')
        
        file.save(temp_path)
        
        # Free up database lock
        db.session.remove()
        db.engine.dispose()
        time.sleep(1)
        
        shutil.copy2(temp_path, db_path)
        os.remove(temp_path)
        
        flash('Database berhasil di-restore! Perubahan akan segera aktif.', 'success')
        return redirect(url_for('settings'))
        
    except Exception as e:
        flash(f'Gagal melakukan restore: {str(e)}', 'error')
        return redirect(url_for('settings'))