<?php
    
    defined('BASEPATH') or exit('No direct script access allowed');
    
    use PhpOffice\PhpSpreadsheet\Spreadsheet;
    use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
    use PhpOffice\PhpSpreadsheet\Style\Alignment;
    use PhpOffice\PhpSpreadsheet\Style\Border;
    use PhpOffice\PhpSpreadsheet\Style\Fill;
    use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
    use Escpos\EscposImage;
    use Escpos\PrintConnectors\WindowsPrintConnector;
    use Escpos\Printer;
    
    class Produk extends CI_Controller
    {
        public function __construct()
        {
            parent::__construct();
            // cek_tabel();
            cek_session_login();
            $this->load->model('Masterdata_model', 'masterdata');
            $this->load->model('Journal_model', 'journal');
            $this->load->dbforge();
            $this->info = info();
            $this->back = $this->agent->referrer();
            $this->iduser = $this->session->idu;
            $this->perPage = 12;
            $this->perPageBahan = 10;
        }
        
        /**
            * data.
            *
            * @return void
        */
        public function data()
        {
            cek_menu_akses();
            $data['title'] = 'Data Produk | '.info()['title'];
            $data['judul'] = 'Data Produk';
            
            $conditions['where'] = [
            'produk.kunci' => 0,
            ];
            $conditions['returnType'] = 'count';
            $totalRec = $this->model_data->getProduk($conditions);
            
            // Pagination configuration
            $config['target'] = '#dataProduk';
            $config['base_url'] = base_url('produk/cariProduk');
            $config['total_rows'] = $totalRec;
            $config['per_page'] = $this->perPage;
            $config['link_func'] = 'search_Produk';
            // Initialize pagination library
            $this->ajax_pagination->initialize($config);
            
            // Get records
            $conditions = [
            'limit' => $this->perPage,
            ];
            $conditions['where'] = [
            'produk.kunci' => 0,
            ];
            $data['jenis'] = $this->model_app->view_where('jenis_cetakan', ['pub' => 'Y', 'kunci' => 0])->result();
            $data['result'] = $this->model_data->getProduk($conditions);
            $this->template->load('main/themes', 'produk/produk', $data);
        }
        
        /**
            * cari_invoice_page.
            *
            * @return void
        */
        public function cari_invoice_page()
        {
            cek_nput_post('GET');
            $page = $this->input->post('page');
            if (!$page) {
                $offset = 0;
                } else {
                $offset = $page;
            }
            
            $limit = 10;
            $keywords = $this->input->post('keywords');
            
            if (!empty($keywords)) {
                $conditions['search']['keywords'] = $keywords;
            }
            
            // Get record count
            $conditions['returnType'] = 'count';
            $totalRec = $this->model_data->getCari($conditions);
            
            // Pagination configuration
            $config['target'] = '#hasil_cari';
            $config['base_url'] = base_url('produk/cari_invoice');
            $config['total_rows'] = $totalRec;
            $config['per_page'] = $limit;
            $config['link_func'] = 'cariFilterIn';
            
            // Initialize pagination library
            $this->ajax_pagination->initialize($config);
            
            // Get records
            $conditions['start'] = $offset;
            $conditions['limit'] = $limit;
            unset($conditions['returnType']);
            $data['posts'] = $this->model_data->getCari($conditions);
            $data['pilihan'] = $this->model_app->view('tb_users');
            // Load the data list view
            $this->load->view('produk/cari_invoice', $data, false);
        }
        
        /**
            * modal_popup.
            *
            * @return void
        */
        public function modal_popup()
        {
            $data['title'] = 'Data Produk | '.info()['title'];
            $this->load->view('produk/modal_popup');
        }
        
        /**
            * cari_harga.
            *
            * @return void
        */
        public function cari_harga()
        {
            cek_nput_post('POST');
            
            $id_bahan = xss_filter($this->input->get('id'), 'xss');
            $jumlah = xss_filter($this->input->get('jml'), 'xss');
            $totukuran = xss_filter($this->input->get('totukuran'), 'xss');
            $id_member = xss_filter($this->input->get('id_member'), 'xss');
            $id_satuan = xss_filter($this->input->get('satuan'), 'xss');
            
            $this->ensure_bahan_komisi_desain_schema();
            $cek_status = $this->model_app->pilih_where('status_stok,type_harga,id_satuan,status,margin_finishing_aktif,lebar_bahan_tersedia,pembulatan_di_bawah_satu_meter', 'bahan', ['id' => $id_bahan])->row();
            $cek_satuan = $this->model_app->pilih_where('satuan', 'satuan', ['id' => $cek_status->id_satuan])->row();
            $type_harga = $cek_status->type_harga;
            
            if ($cek_status->status_stok == 'Y') {
                $this->cek_stok($id_bahan, $jumlah);
            }
            
            $status = $cek_status->status;
            
            if ($status > 0) {
                $jml = $totukuran;
                } else {
                $jml = $jumlah;
            }

            if ((int) $type_harga === 6) {
                $type_harga = resolve_semua_metode_harga($id_bahan, $id_member, $id_satuan, $jml);
            }
            
            if ($type_harga == 1) {
                $sql1 = $this->db->query("SELECT 
                `satuan`.`id` AS idsatuan,
                `satuan`.`satuan`,
                `satu_harga`.`harga_jual`,
                `bahan`.`title`
                FROM
                `bahan`
                INNER JOIN `satu_harga` ON (`bahan`.`id` = `satu_harga`.`id_bahan`)
                INNER JOIN `satuan` ON (`satu_harga`.`id_satuan` = `satuan`.`id`)
                WHERE
                `bahan`.`id` = $id_bahan");
                $harga_jual = $sql1->row()->harga_jual;
                $satuan = $sql1->row()->idsatuan;
                $data = ['status' => true, 'harga' => $harga_jual, 'satuan' => $satuan, 'type_harga' => $type_harga];
                } elseif ($type_harga == 2) {
                $id_satuan = !empty($id_satuan) ? $id_satuan : $cek_status->id_satuan;
                if ((string) $id_satuan === (string) $cek_status->id_satuan) {
                    $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                    $harga_jual = $sql->row()->harga_jual;
                    $satuan = $sql->row()->id_satuan;
                    $data = ['status' => true, 'harga' => $harga_jual, 'satuan' => $satuan, 'type_harga' => $type_harga];
                } else {
                $sql2 = $this->db->query("SELECT 
                `satuan`.`id` AS idsatuan,
                `satuan`.`satuan`,
                `harga_satuan`.`id_satuan`,
                `bahan`.`title`,
                `harga_satuan`.`harga_jual`,
                `harga_satuan`.`id_bahan`
                FROM
                `satuan`
                INNER JOIN `harga_satuan` ON (`satuan`.`id` = `harga_satuan`.`id_satuan`)
                INNER JOIN `bahan` ON (`harga_satuan`.`id_bahan` = `bahan`.`id`)
                WHERE
                `harga_satuan`.`id_bahan` = $id_bahan AND `harga_satuan`.`id_satuan` = $id_satuan");
                if ($sql2->num_rows() > 0) {
                    $harga_jual = $sql2->row()->harga_jual;
                    $satuan = $sql2->row()->idsatuan;
                } else {
                    $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                    $harga_jual = $sql->row()->harga_jual;
                    $satuan = $sql->row()->id_satuan;
                }
                $data = ['status' => true, 'harga' => $harga_jual, 'satuan' => $satuan, 'type_harga' => $type_harga];
                }
                } elseif ($type_harga == 3) {
                $sql3 = $this->db->query("SELECT 
                `harga_member`.`id_bahan`,
                `harga_member`.`id_satuan`,
                `harga_member`.`id_member`,
                `harga_member`.`harga_jual`
                FROM
                `satuan`
                INNER JOIN `harga_member` ON (`satuan`.`id` = `harga_member`.`id_satuan`)
                INNER JOIN `bahan` ON (`harga_member`.`id_bahan` = `bahan`.`id`)
                INNER JOIN `konsumen` ON (`harga_member`.`id_member` = `konsumen`.`jenis_member`)
                WHERE
                `harga_member`.`id_member` = $id_member AND 
                `harga_member`.`id_bahan` = $id_bahan
                GROUP BY
                `harga_member`.`id_bahan`,
                `harga_member`.`id_satuan`,
                `harga_member`.`id_member`,
                `harga_member`.`harga_jual`");
                if ($sql3->num_rows() > 0) {
                    $harga_jual = $sql3->row()->harga_jual;
                    $satuan = $sql3->row()->id_satuan;
                    } else {
                    $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                    $harga_jual = $sql->row()->harga_jual;
                    $satuan = $sql->row()->id_satuan;
                }
                $data = ['status' => true, 'harga' => $harga_jual, 'satuan' => $satuan, 'type_harga' => $type_harga];
                } elseif ($type_harga == 4) {
                $sql = $this->db->query("select id_satuan,harga_jual from range_harga where id_bahan=$id_bahan AND $jml between jumlah_minimal and jumlah_maksimal");
                if ($sql->num_rows() > 0) {
                    $harga_jual = $sql->row()->harga_jual;
                    $satuan = $sql->row()->id_satuan;
                    } else {
                    $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                    $harga_jual = $sql->row()->harga_jual;
                    $satuan = $sql->row()->id_satuan;
                }
                $data = ['status' => true, 'harga' => $harga_jual, 'satuan' => $satuan, 'type_harga' => $type_harga];
                
                // cari type harga 5
                } elseif ($type_harga == 5) {
                $sql4 = $this->db->query("select id_satuan,harga_jual from harga_range_member where id_bahan=$id_bahan AND id_member='$id_member' AND $jml between jumlah_minimal and jumlah_maksimal");
                if ($sql4->num_rows() > 0) {
                    $harga_jual = $sql4->row()->harga_jual;
                    $satuan = $sql4->row()->id_satuan;
                    $msg = 1;
                    } else {
                    $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                    $harga_jual = $sql->row()->harga_jual;
                    $satuan = $sql->row()->id_satuan;
                    $msg = 2;
                }
                $data = ['status' => true, 'harga' => $harga_jual, 'satuan' => $satuan, 'type_harga' => $type_harga, 'msg' => $msg];
                } else {
                $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                $harga_jual = $sql->row()->harga_jual;
                $satuan = $sql->row()->id_satuan;
                $data = ['status' => true, 'harga' => $harga_jual, 'satuan' => $satuan, 'type_harga' => $type_harga, 'msg' => 3];
            }
            // Nama kolom lama dipertahankan agar data yang sudah ada tetap kompatibel.
            $data['pembulatan_ukuran_aktif'] = isset($cek_status->margin_finishing_aktif) ? (int) $cek_status->margin_finishing_aktif : 0;
            $data['margin_finishing_aktif'] = $data['pembulatan_ukuran_aktif'];
            $data['lebar_bahan_tersedia'] = !empty($cek_status->lebar_bahan_tersedia) ? (string) $cek_status->lebar_bahan_tersedia : '1,1.5,2,3';
            $data['pembulatan_di_bawah_satu_meter'] = isset($cek_status->pembulatan_di_bawah_satu_meter) ? (int) $cek_status->pembulatan_di_bawah_satu_meter : 0;
            
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * cek_stok.
            *
            * @return void
        */
        private function cek_stok($id, $jumlah)
        {
            $jml_masuk = stok_masuk($id);
            $jml_keluar = stok_keluar($id);
            $total = $jml_masuk - $jml_keluar;
            return $total;
        }
        
        /**
            * tambah_produk.
            *
            * @return void
        */
        public function tambah_produk()
        {
            cek_nput_post('GET');
            $tipe = xss_filter($this->input->post('tipe'), 'xss');
            $where = ['kunci' => 0];
            $data['tipe'] = $tipe;
            $data['jenis'] = $this->model_app->view_where('jenis_cetakan', $where)->result_array();
            $this->load->view('produk/add_produk', $data);
        }
        
        /**
            * edit_produk.
            *
            * @return void
        */
        /**
            * edit_produk.
            *
            * @return void
        */
        public function edit_produk()
        {
            cek_nput_post('GET');
            
            $noid = xss_filter($this->input->post('id'), 'xss');
            $tipe = xss_filter($this->input->post('tipe'), 'xss');
            $where = ['id' => $noid];
            $record = $this->model_app->edit('produk', $where)->row_array();
            $data['back'] = $this->back;
            $data['tipe'] = $tipe;
            $data['record'] = $record;
            $data['jenis'] = $this->model_app->view_where('jenis_cetakan', ['kunci' => 0])->result_array();
            $this->load->view('produk/edit_produk', $data);
        }
        
        /**
            * number.
            *
            * @return void
        */
        private function number()
        {
            return random_string('numeric', 13);
        }
        
        /**
            * save_produk.
            *
            * @return void
        */
        public function save_produk()
        {
            cek_nput_post('GET');
            simpan_demo('Simpan');
            
            $id = xss_filter($this->input->post('id'), 'xss');
            $type = xss_filter($this->input->post('type'), 'xss');
            $bahan = $this->input->post('bahan', true);
            $jenis = xss_filter($this->input->post('jenis'), 'xss');
            $ukuran = xss_filter($this->input->post('ukuran'), 'xss');
            $jumlah = xss_filter($this->input->post('jumlah'), 'xss');
            $lock_harga = xss_filter($this->input->post('lock_harga'), 'xss');
            $barcode = xss_filter($this->input->post('barcode'), 'xss');
            
            if (!empty($bahan)) {
                $bahan = implode(',', array_unique($bahan));
                } else {
                $bahan = '';
            }
            if (empty($barcode)) {
                $barcode = $this->number();
            }
            
            if ($id > 0 and $type == 'edit') {
                $this->form_validation->set_rules([
                [
                'field' => 'barcode',
                'label' => 'Barcode',
                'rules' => 'required|trim|numeric|min_length[13]|max_length[13]|callback__cek_barcode['.$id.']',
                'errors' => [
                'required' => '%s. Harus di isi',
                'numeric' => '%s. Harus angka',
                'min_length' => '%s minimal 12 digit.',
                'max_length' => '%s maksimal 12 digit.',
                ],
                ],
                
                [
                'field' => 'nama',
                'label' => 'Nama Produk',
                'rules' => 'required|trim|min_length[3]|max_length[100]|callback__cek_nama['.$id.']',
                'errors' => [
                'required' => '%s Harus diisi.',
                'min_length' => '%s minimal 3 digit.',
                'max_length' => '%s minimal 100 digit.',
                ],
                ],
                ]);
                
                if ($this->form_validation->run() == false) {
                    $response['status'] = 400;
                    $response['msg'] = validation_errors();
                    } else {
                    $data = [
                    'title' => xss_filter($this->input->post('nama'), 'xss'),
                    'id_jenis' => $jenis,
                    'ukuran' => $ukuran,
                    'jumlah' => $jumlah,
                    'barcode' => $barcode,
                    'lock_harga' => $lock_harga,
                    ];
                    $res = $this->model_app->update('produk', $data, ['id' => $id]);
                    if ($res['status'] == 'ok') {
                        $response = ['status' => 200, 'msg' => 'Data berhasil disimpan'];
                        } else {
                        $response = ['status' => 400, 'msg' => 'Data gagal disimpan'];
                    }
                }
                } else {
                
                $this->form_validation->set_rules([
                [
                'field' => 'nama',
                'label' => 'Nama Produk',
                'rules' => 'required|trim|min_length[3]|max_length[100]|is_unique[produk.title]',
                'errors' => [
                'required' => '%s Harus diisi.',
                'min_length' => '%s minimal 3 digit.',
                'max_length' => '%s minimal 100 digit.',
                'is_unique'     => '%s sudah ada.'
                ],
                ],
                ]);
                if ($this->form_validation->run() != FALSE){
                    $data = [
                    'title' => xss_filter($this->input->post('nama'), 'xss'),
                    'id_jenis' => $jenis,
                    // 'id_bahan' => $bahan,
                    'ukuran' => $ukuran,
                    'jumlah' => $jumlah,
                    'barcode' => $barcode,
                    'lock_harga' => $lock_harga,
                    ];
                    
                    $res = $this->model_app->input('produk', $data);
                    if ($res['status'] == 'ok') {
                        $response = ['status' => 200, 'msg' => 'Data berhasil disimpan'];
                        } else {
                        $response = ['status' => 400, 'msg' => 'Data gagal disimpan'];
                    }
                    }else{
                    $response = array('hasil'=>400,'msg'=>validation_errors());
                }
            }
            $this->output
            ->set_status_header(200)
            ->set_content_type('application/json', 'utf-8')
            ->set_output(json_encode($response, JSON_HEX_APOS | JSON_HEX_QUOT))
            ->_display();
            exit;
        }
        
        public function _cek_barcode_bahan($barcode,$id) 
        {
            $cek = $this->model_data->cek_barcode_bahan($id, $barcode);
            if ($cek == FALSE) 
            {
                $this->form_validation->set_message('_cek_barcode_bahan', 'Barcode sudah ada');
                return FALSE;
            }
            else 
            {
                return TRUE;
            }
        }
        public function _cek_title($nama,$id) 
        {
            $cek = $this->model_data->cek_title($id, $nama);
            if ($cek == FALSE) 
            {
                $this->form_validation->set_message('_cek_title', 'Barang/Merk '.$nama.' sudah ada');
                return FALSE;
            }
            else 
            {
                return TRUE;
            }
        }
        public function _cek_barcode($barcode,$id) 
        {
            $cek = $this->model_data->cek_barcode($id, $barcode);
            if ($cek == FALSE) 
            {
                $this->form_validation->set_message('_cek_barcode', 'Barcode sudah ada');
                return FALSE;
            }
            else 
            {
                return TRUE;
            }
        }
        public function _cek_nama($nama,$id) 
        {
            $cek = $this->model_data->cek_nama($id, $nama);
            if ($cek == FALSE) 
            {
                $this->form_validation->set_message('_cek_nama', 'Produk '.$nama.' sudah ada');
                return FALSE;
            }
            else 
            {
                return TRUE;
            }
        }
        /**
            * hapus_produk.
            *
            * @return void
        */
        public function hapus_produk()
        {
            cek_nput_post('GET');
            simpan_demo('Hapus');
            cek_crud_akses(10);
            
            $id = xss_filter($this->input->post('id'), 'xss');
            $type = xss_filter($this->input->post('type'), 'xss');
            
            if ($id > 0 and $type == 'hapus') {
                $cek = $this->model_app->view_where('invoice_detail', ['id_produk' => $id]);
                if ($cek->num_rows() > 0) {
                    $data = ['status' => 400, 'msg' => 'Data tidak dapat dihapus'];
                    } else {
                    $res = $this->model_app->hapus('produk', ['id' => $id]);
                    if ($res['status'] == 'ok') {
                        $data = ['status' => 200, 'msg' => 'Data berhasil dihapus'];
                        } else {
                        $data = ['status' => 400, 'msg' => 'Data gagal dihapus'];
                    }
                }
                } else {
                $data = ['status' => 400, 'msg' => 'Data tida ditemukan'];
            }
            
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * edit_satuan.
            *
            * @return void
        */
        public function edit_satuan()
        {
            cek_nput_post('GET');
            $id = xss_filter($this->input->post('id'), 'xss');
            if ($id > 0) {
                $where = ['id' => $id];
                $row = $this->model_app->edit('satuan', $where)->row_array();
                
                $data = [
                'id' => $id,
                'judul' => $row['satuan'],
                'nama' => $row['nama_satuan'],
                'jumlah' => $row['jumlah'],
                'aktif' => $row['pub'],
                ];
                } else {
                $data = ['id' => 0, 'judul' => '', 'nama' => '', 'jumlah' => 0, 'aktif' => ''];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * save_satuan.
            *
            * @return void
        */
        public function save_satuan()
        {
            cek_nput_post('GET');
            simpan_demo('Simpan');
            cek_crud_akses(9);
            
            $id = xss_filter($this->input->post('id'), 'xss');
            $type = xss_filter($this->input->post('type'), 'xss');
            $judul = $this->db->escape_str($this->input->post('judul'));
            $nama = change_case($this->db->escape_str($this->input->post('nama')));
            $jumlah = change_case($this->db->escape_str($this->input->post('jumlah')));
            $aktif = xss_filter($this->input->post('aktif'), 'xss');
            
            $_data = ['satuan' => $judul, 'nama_satuan' => $nama, 'jumlah' => $jumlah, 'pub' => $aktif];
            if ($id == 0 and $type == 'add') {
                // /data baru
                $res = $this->model_app->input('satuan', $_data);
                if ($res['status'] == 'ok') {
                    $data = ['status' => 200, 'msg' => 'Data berhasil disimpan'];
                    } else {
                    $data = ['status' => 400];
                }
                } elseif ($type == 'edit') {
                // /data update
                $res = $this->model_app->update('satuan', $_data, ['id' => $id]);
                if ($res['status'] == 'ok') {
                    $data = ['status' => 200, 'msg' => 'Data berhasil update'];
                    } else {
                    $data = ['status' => 400];
                }
                } else {
                $data = ['status' => 400, 'msg' => 'Data tidak ditemukan'];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * hapus_satuan.
            *
            * @return void
        */
        public function hapus_satuan()
        {
            cek_nput_post('GET');
            simpan_demo('Hapus');
            cek_crud_akses(10);
            
            $id = xss_filter($this->input->post('id'), 'xss');
            
            $cek = $this->model_app->view_where('bahan', ['id_satuan' => $id]);
            if ($cek->num_rows() > 0) {
                $data = ['status' => 400, 'msg' => 'Data tidak dapat dihapus'];
                } else {
                $res = $this->model_app->hapus('satuan', ['id' => $id]);
                if ($res['status'] == 'ok') {
                    $data = ['status' => 200, 'msg' => 'Data berhasil dihapus'];
                    } else {
                    $data = ['status' => 400, 'msg' => 'Data gagal dihapus'];
                }
            }
            
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * cari_bahan.
            *
            * @return void
        */
        public function cari_bahan()
        {
            cek_nput_post('GET');
            cek_crud_akses(8);
            
            $name = xss_filter($this->input->post('name'), 'xss');
            $result = $this->model_app->view_like('bahan', ['title' => $name]);
            
            $data = [];
            foreach ($result->result_array() as $row) {
                $data[] = ['id' => $row['id'], 'name' => $row['title']];
            }
            
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * bahan.
            *
            * @return void
        */
        public function bahan()
        {
            cek_menu_akses();
            cek_crud_akses(8);
            $this->ensure_bahan_komposisi_schema();
            $this->ensure_bahan_foto_schema();
            $data['title'] = 'Data Bahan | '.info()['title'];
            $data['judul'] = 'Data Bahan';
            
            $data['kategori'] = $this->model_app->view_where('jenis_cetakan', ['kunci' => 0, 'pub' => 'Y'])->result();
            $data['produk'] = $this->model_app->view_where_ordering('produk',['pub' => 1],'title','ASC')->result();
            $data['satuan'] = $this->model_app->view_where('satuan', ['pub' => 0])->result();
            $data['bahan_komposisi_options'] = $this->get_bahan_komposisi_options();
            
            $this->template->load('main/themes', 'produk/bahan', $data);
        }

        private function ensure_bahan_ukuran_default_schema()
        {
            if (!$this->db->table_exists('bahan')) {
                return;
            }

            if ($this->db->field_exists('ukuran_default', 'bahan')) {
                return;
            }

            $this->dbforge->add_column('bahan', [
                'ukuran_default' => [
                    'type' => 'VARCHAR',
                    'constraint' => 100,
                    'null' => true,
                    'default' => '',
                    'after' => 'barcode',
                ],
            ]);

            if (
                $this->db->table_exists('produk') &&
                $this->db->field_exists('id_produk', 'bahan') &&
                $this->db->field_exists('ukuran', 'produk')
            ) {
                $this->db->query('
                    UPDATE `bahan`
                    INNER JOIN `produk` ON `produk`.`id` = `bahan`.`id_produk`
                    SET `bahan`.`ukuran_default` = `produk`.`ukuran`
                    WHERE (`bahan`.`ukuran_default` IS NULL OR `bahan`.`ukuran_default` = "")
                ');
            }
        }

        private function ensure_bahan_foto_schema()
        {
            if (!$this->db->table_exists('bahan') || $this->db->field_exists('foto', 'bahan')) {
                return;
            }

            $this->dbforge->add_column('bahan', [
                'foto' => [
                    'type' => 'VARCHAR',
                    'constraint' => 255,
                    'null' => true,
                    'default' => '',
                    'after' => 'barcode',
                ],
            ]);
        }

        private function ensure_bahan_komposisi_schema()
        {
            $this->ensure_bahan_ukuran_default_schema();
            $this->ensure_bahan_komisi_desain_schema();
            $this->ensure_bahan_varian_schema();
            $this->ensure_batas_minimum_stok_schema();

            if (!$this->db->table_exists('bahan_komposisi')) {
                $this->dbforge->add_field([
                    'id' => [
                        'type' => 'INT',
                        'constraint' => 11,
                        'null' => false,
                        'auto_increment' => true,
                    ],
                    'id_bahan_parent' => [
                        'type' => 'INT',
                        'constraint' => 11,
                        'null' => false,
                    ],
                    'id_bahan_komponen' => [
                        'type' => 'INT',
                        'constraint' => 11,
                        'null' => false,
                    ],
                    'jumlah' => [
                        'type' => 'FLOAT',
                        'null' => false,
                        'default' => 0,
                    ],
                    'panjang' => [
                        'type' => 'FLOAT',
                        'null' => false,
                        'default' => 0,
                    ],
                    'lebar' => [
                        'type' => 'FLOAT',
                        'null' => false,
                        'default' => 0,
                    ],
                    'create_date' => [
                        'type' => 'DATETIME',
                        'null' => false,
                    ],
                ]);
                $this->dbforge->add_key('id', true);
                $this->dbforge->create_table('bahan_komposisi', true);
                $this->db->query('ALTER TABLE `bahan_komposisi` ENGINE = InnoDB');
            }

            if (!$this->db->field_exists('panjang', 'bahan_komposisi')) {
                $this->dbforge->add_column('bahan_komposisi', [
                    'panjang' => [
                        'type' => 'FLOAT',
                        'null' => false,
                        'default' => 0,
                        'after' => 'jumlah',
                    ],
                ]);
            }

            if (!$this->db->field_exists('lebar', 'bahan_komposisi')) {
                $this->dbforge->add_column('bahan_komposisi', [
                    'lebar' => [
                        'type' => 'FLOAT',
                        'null' => false,
                        'default' => 0,
                        'after' => 'panjang',
                    ],
                ]);
            }

            $this->seed_bahan_komposisi_awal();
        }

        private function ensure_batas_minimum_stok_schema()
        {
            if (!$this->db->table_exists('bahan') || $this->db->field_exists('batas_minimum_stok', 'bahan')) {
                return;
            }

            $this->dbforge->add_column('bahan', [
                'batas_minimum_stok' => [
                    'type' => 'DECIMAL',
                    'constraint' => '15,2',
                    'null' => false,
                    'default' => '0.00',
                    'after' => 'status_stok',
                ],
            ]);
        }

        private function ensure_bahan_komisi_desain_schema()
        {
            if (!$this->db->table_exists('bahan')) {
                return;
            }

            if (!$this->db->field_exists('komisi_desain_aktif', 'bahan')) {
                $this->dbforge->add_column('bahan', [
                    'komisi_desain_aktif' => [
                        'type' => 'INT',
                        'constraint' => 1,
                        'null' => false,
                        'default' => 0,
                    ],
                ]);
            }

            if (!$this->db->field_exists('margin_finishing_aktif', 'bahan')) {
                $this->dbforge->add_column('bahan', [
                    'margin_finishing_aktif' => [
                        'type' => 'INT',
                        'constraint' => 1,
                        'null' => false,
                        'default' => 0,
                        'after' => 'komisi_desain_aktif',
                    ],
                ]);
            }

            if (!$this->db->field_exists('lebar_bahan_tersedia', 'bahan')) {
                $this->dbforge->add_column('bahan', [
                    'lebar_bahan_tersedia' => [
                        'type' => 'VARCHAR',
                        'constraint' => 255,
                        'null' => false,
                        'default' => '1,1.5,2,3',
                        'after' => 'margin_finishing_aktif',
                    ],
                ]);
            }

            if (!$this->db->field_exists('pembulatan_di_bawah_satu_meter', 'bahan')) {
                $this->dbforge->add_column('bahan', [
                    'pembulatan_di_bawah_satu_meter' => [
                        'type' => 'INT',
                        'constraint' => 1,
                        'null' => false,
                        'default' => 0,
                        'after' => 'lebar_bahan_tersedia',
                    ],
                ]);
            }

            $salesFields = [];
            if (!$this->db->field_exists('komisi_penjualan_aktif', 'bahan')) $salesFields['komisi_penjualan_aktif'] = ['type'=>'INT','constraint'=>1,'null'=>false,'default'=>0];
            if (!$this->db->field_exists('komisi_penjualan_tipe', 'bahan')) $salesFields['komisi_penjualan_tipe'] = ['type'=>'VARCHAR','constraint'=>10,'null'=>false,'default'=>'persen'];
            if (!$this->db->field_exists('komisi_penjualan_nilai', 'bahan')) $salesFields['komisi_penjualan_nilai'] = ['type'=>'DECIMAL','constraint'=>'15,2','null'=>false,'default'=>'0.00'];
            if ($salesFields) $this->dbforge->add_column('bahan', $salesFields);
        }

        private function ensure_bahan_varian_schema()
        {
            if (!$this->db->table_exists('bahan')) {
                return;
            }

            $fields = [];

            if (!$this->db->field_exists('parent_bahan_id', 'bahan')) {
                $fields['parent_bahan_id'] = [
                    'type' => 'INT',
                    'constraint' => 11,
                    'null' => false,
                    'default' => 0,
                    'after' => 'id',
                ];
            }

            if (!$this->db->field_exists('varian_aktif', 'bahan')) {
                $fields['varian_aktif'] = [
                    'type' => 'INT',
                    'constraint' => 1,
                    'null' => false,
                    'default' => 0,
                    'after' => 'komisi_desain_aktif',
                ];
            }

            if (!$this->db->field_exists('varian_warna', 'bahan')) {
                $fields['varian_warna'] = [
                    'type' => 'VARCHAR',
                    'constraint' => 100,
                    'null' => true,
                    'default' => '',
                    'after' => 'varian_aktif',
                ];
            }

            if (!$this->db->field_exists('varian_ukuran', 'bahan')) {
                $fields['varian_ukuran'] = [
                    'type' => 'VARCHAR',
                    'constraint' => 100,
                    'null' => true,
                    'default' => '',
                    'after' => 'varian_warna',
                ];
            }

            if (!empty($fields)) {
                $this->dbforge->add_column('bahan', $fields);
            }
        }

        private function seed_bahan_komposisi_awal()
        {
            if (!$this->db->table_exists('bahan') || !$this->db->table_exists('bahan_komposisi')) {
                return;
            }

            $parent = $this->find_bahan_by_normalized_title('X banner 60 x 160cm 1 set Tiang');
            $flexy = $this->find_bahan_by_normalized_title('Flexy China 280gr');
            $tiang = $this->find_bahan_by_normalized_title('Tiang X banner 60 cm');

            if (empty($parent) || empty($flexy) || empty($tiang)) {
                return;
            }

            $parentRows = $this->db
                ->from('bahan_komposisi')
                ->where('id_bahan_parent', (int) $parent->id)
                ->count_all_results();

            if ($parentRows == 0) {
                $this->db->insert_batch('bahan_komposisi', [
                    [
                        'id_bahan_parent' => (int) $parent->id,
                        'id_bahan_komponen' => (int) $flexy->id,
                        'jumlah' => 0.96,
                        'panjang' => 60,
                        'lebar' => 160,
                        'create_date' => date('Y-m-d H:i:s'),
                    ],
                    [
                        'id_bahan_parent' => (int) $parent->id,
                        'id_bahan_komponen' => (int) $tiang->id,
                        'jumlah' => 1,
                        'panjang' => 0,
                        'lebar' => 0,
                        'create_date' => date('Y-m-d H:i:s'),
                    ],
                ]);
                return;
            }

            $flexyRow = $this->db
                ->select('id,jumlah,panjang,lebar')
                ->from('bahan_komposisi')
                ->where('id_bahan_parent', (int) $parent->id)
                ->where('id_bahan_komponen', (int) $flexy->id)
                ->limit(1)
                ->get()
                ->row();

            if (!empty($flexyRow) && (float) $flexyRow->panjang == 0 && (float) $flexyRow->lebar == 0 && abs((float) $flexyRow->jumlah - 0.96) < 0.0001) {
                $this->db->update('bahan_komposisi', ['panjang' => 60, 'lebar' => 160], ['id' => (int) $flexyRow->id]);
            }
        }

        private function get_bahan_komposisi_options()
        {
            if (!$this->db->table_exists('bahan')) {
                return [];
            }

            $query = $this->db
                ->select('bahan.id,bahan.title,bahan.status AS hitung_ukuran,bahan.status_stok,bahan.pub,IFNULL(satuan.satuan, "-") AS satuan', false)
                ->from('bahan')
                ->join('satuan', 'satuan.id = bahan.id_satuan', 'left');

            if ($this->db->field_exists('parent_bahan_id', 'bahan')) {
                $query
                    ->group_start()
                    ->where('bahan.parent_bahan_id', 0)
                    ->or_where('bahan.pub', 1)
                    ->group_end();
            }

            return $query
                ->order_by('bahan.id', 'DESC')
                ->get()
                ->result();
        }

        public function bahan_komposisi_options()
        {
            cek_nput_post('GET');
            cek_crud_akses(8);
            $this->ensure_bahan_komposisi_schema();

            $this->output
                ->set_content_type('application/json')
                ->set_output(json_encode($this->get_bahan_komposisi_options()));
        }

        private function bahan_has_komposisi($id)
        {
            if (!$this->db->table_exists('bahan_komposisi')) {
                return false;
            }

            return $this->db
                ->from('bahan_komposisi')
                ->where('id_bahan_parent', (int) $id)
                ->count_all_results() > 0;
        }

        private function get_bahan_komposisi($id)
        {
            if (!$this->db->table_exists('bahan_komposisi')) {
                return [];
            }

            return $this->db
                ->select('bahan_komposisi.id_bahan_komponen,bahan_komposisi.jumlah,bahan_komposisi.panjang,bahan_komposisi.lebar,bahan.status AS hitung_ukuran,bahan.title,IFNULL(satuan.satuan, "-") AS satuan', false)
                ->from('bahan_komposisi')
                ->join('bahan', 'bahan.id = bahan_komposisi.id_bahan_komponen', 'left')
                ->join('satuan', 'satuan.id = bahan.id_satuan', 'left')
                ->where('bahan_komposisi.id_bahan_parent', (int) $id)
                ->order_by('bahan.title', 'ASC')
                ->get()
                ->result_array();
        }

        private function save_bahan_komposisi($id_parent, $aktif, $komponen, $jumlah, $panjang = [], $lebar = [])
        {
            $id_parent = (int) $id_parent;
            $aktif = (int) $aktif === 1;

            if ($id_parent <= 0 || !$this->db->table_exists('bahan_komposisi')) {
                return ['status' => 200, 'msg' => 'Komposisi dilewati'];
            }

            if (!$aktif) {
                $this->db->delete('bahan_komposisi', ['id_bahan_parent' => $id_parent]);
                return ['status' => 200, 'msg' => 'Komposisi nonaktif'];
            }

            $komponen = is_array($komponen) ? $komponen : [];
            $jumlah = is_array($jumlah) ? $jumlah : [];
            $panjang = is_array($panjang) ? $panjang : [];
            $lebar = is_array($lebar) ? $lebar : [];
            $rows = [];
            $used = [];

            foreach ($komponen as $idx => $id_komponen) {
                $id_komponen = (int) $id_komponen;
                $qty = $this->parse_komposisi_number($jumlah[$idx] ?? 0);
                $panjangKomponen = $this->parse_komposisi_number($panjang[$idx] ?? 0);
                $lebarKomponen = $this->parse_komposisi_number($lebar[$idx] ?? 0);

                if ($id_komponen <= 0 && $qty <= 0 && $panjangKomponen <= 0 && $lebarKomponen <= 0) {
                    continue;
                }
                if ($id_komponen <= 0) {
                    return ['status' => 400, 'msg' => 'Bahan komponen belum dipilih'];
                }
                if ($id_komponen === $id_parent) {
                    return ['status' => 400, 'msg' => 'Item tidak boleh menjadi komponen dirinya sendiri'];
                }
                if (isset($used[$id_komponen])) {
                    return ['status' => 400, 'msg' => 'Bahan komponen tidak boleh duplikat'];
                }

                $bahan = $this->db
                    ->select('id,status_stok,status')
                    ->from('bahan')
                    ->where('id', $id_komponen)
                    ->limit(1)
                    ->get()
                    ->row();

                if (empty($bahan)) {
                    return ['status' => 400, 'msg' => 'Bahan komponen tidak ditemukan'];
                }
                if (((float) $panjangKomponen > 0 || (float) $lebarKomponen > 0) && ((float) $panjangKomponen <= 0 || (float) $lebarKomponen <= 0)) {
                    return ['status' => 400, 'msg' => 'Ukuran komposisi harus lengkap'];
                }
                if ((int) $bahan->status > 0 && $panjangKomponen > 0 && $lebarKomponen > 0) {
                    $qty = (int) $bahan->status === 2
                        ? $lebarKomponen / 100
                        : ($panjangKomponen / 100) * ($lebarKomponen / 100);
                } else {
                    $panjangKomponen = 0;
                    $lebarKomponen = 0;
                }
                if ($qty <= 0) {
                    return ['status' => 400, 'msg' => 'Jumlah komposisi harus lebih dari 0'];
                }

                $used[$id_komponen] = true;
                $rows[] = [
                    'id_bahan_parent' => $id_parent,
                    'id_bahan_komponen' => $id_komponen,
                    'jumlah' => $qty,
                    'panjang' => $panjangKomponen,
                    'lebar' => $lebarKomponen,
                    'create_date' => date('Y-m-d H:i:s'),
                ];
            }

            if (empty($rows)) {
                return ['status' => 400, 'msg' => 'Komposisi aktif, tetapi bahan komponen belum diisi'];
            }

            $this->db->delete('bahan_komposisi', ['id_bahan_parent' => $id_parent]);
            $this->db->insert_batch('bahan_komposisi', $rows);

            return ['status' => 200, 'msg' => 'Komposisi tersimpan'];
        }

        private function parse_komposisi_number($value)
        {
            $value = str_replace(',', '.', (string) $value);
            return is_numeric($value) ? (float) $value : 0;
        }

        private function resolve_ukuran_default($row, $fallback = '')
        {
            if (is_object($row)) {
                $value = isset($row->ukuran_default) ? $row->ukuran_default : '';
            } else {
                $value = is_array($row) && isset($row['ukuran_default']) ? $row['ukuran_default'] : '';
            }

            $value = trim((string) $value);
            return $value !== '' ? $value : (string) $fallback;
        }

        private function find_bahan_by_normalized_title($title)
        {
            $target = $this->normalize_bahan_title($title);
            $rows = $this->db
                ->select('id,title')
                ->from('bahan')
                ->get()
                ->result();

            foreach ($rows as $row) {
                if ($this->normalize_bahan_title($row->title) === $target) {
                    return $row;
                }
            }

            return null;
        }

        private function normalize_bahan_title($title)
        {
            $title = strtolower((string) $title);
            $title = preg_replace('/[^a-z0-9]+/', ' ', $title);
            return trim(preg_replace('/\s+/', ' ', $title));
        }

        private function normalize_varian_rows($warna, $ukuran, $harga_jual = [])
        {
            $warna = is_array($warna) ? $warna : [];
            $ukuran = is_array($ukuran) ? $ukuran : [];
            $harga_jual = is_array($harga_jual) ? $harga_jual : [];
            $rows = [];
            $used = [];
            $max = max(count($warna), count($ukuran), count($harga_jual));

            for ($i = 0; $i < $max; $i++) {
                $w = trim((string) ($warna[$i] ?? ''));
                $u = trim((string) ($ukuran[$i] ?? ''));
                $harga = rp_to_int((string) ($harga_jual[$i] ?? '0'));

                if ($w === '' && $u === '') {
                    continue;
                }

                $key = strtolower($w).'|'.strtolower($u);
                if (isset($used[$key])) {
                    continue;
                }

                $used[$key] = true;
                $rows[] = [
                    'warna' => $w,
                    'ukuran' => $u,
                    'harga_jual' => $harga,
                    'label' => trim($w.($w !== '' && $u !== '' ? ' / ' : '').$u),
                    'key' => $key,
                ];
            }

            return $rows;
        }

        private function get_bahan_varian($id_parent)
        {
            if (!$this->db->table_exists('bahan') || !$this->db->field_exists('parent_bahan_id', 'bahan')) {
                return [];
            }

            $rows = $this->db
                ->select('id,varian_warna,varian_ukuran,harga_jual,pub')
                ->from('bahan')
                ->where('parent_bahan_id', (int) $id_parent)
                ->where('pub', 1)
                ->order_by('id', 'ASC')
                ->get()
                ->result();

            $data = [];
            foreach ($rows as $row) {
                $data[] = [
                    'id' => (int) $row->id,
                    'warna' => (string) $row->varian_warna,
                    'ukuran' => (string) $row->varian_ukuran,
                    'harga_jual' => (float) $row->harga_jual,
                    'aktif' => (int) $row->pub,
                ];
            }

            return $data;
        }

        private function save_bahan_varian($id_parent, $aktif, array $rows, array $base_data)
        {
            $id_parent = (int) $id_parent;
            if ($id_parent <= 0) {
                return ['status' => 400, 'msg' => 'Data bahan tidak ditemukan'];
            }

            if ((int) $aktif !== 1) {
                $this->model_app->update('bahan', ['varian_aktif' => 0], ['id' => $id_parent]);
                $this->model_app->update('bahan', ['pub' => 0], ['parent_bahan_id' => $id_parent]);
                return ['status' => 200];
            }

            if (empty($rows)) {
                return ['status' => 400, 'msg' => 'Varian aktif, tetapi daftar varian belum diisi'];
            }

            $parent = $this->model_app->view_where('bahan', ['id' => $id_parent])->row();
            if (!$parent) {
                return ['status' => 400, 'msg' => 'Data bahan tidak ditemukan'];
            }

            $existing = $this->db
                ->select('id,varian_warna,varian_ukuran')
                ->from('bahan')
                ->where('parent_bahan_id', $id_parent)
                ->get()
                ->result();

            $existingMap = [];
            foreach ($existing as $row) {
                $key = strtolower(trim((string) $row->varian_warna)).'|'.strtolower(trim((string) $row->varian_ukuran));
                $existingMap[$key] = (int) $row->id;
            }

            $activeIds = [];
            foreach ($rows as $row) {
                $variantTitle = $parent->title.' - '.$row['label'];
                $data = $base_data;
                $data['title'] = $variantTitle;
                $data['parent_bahan_id'] = $id_parent;
                $data['varian_aktif'] = 0;
                $data['varian_warna'] = $row['warna'];
                $data['varian_ukuran'] = $row['ukuran'];
                $data['ukuran_default'] = $row['ukuran'];
                $data['harga_jual'] = $row['harga_jual'];
                $data['pub'] = 1;
                $data['featured'] = 0;
                unset($data['barcode']);

                if (isset($existingMap[$row['key']])) {
                    $variantId = $existingMap[$row['key']];
                    $this->model_app->update('bahan', $data, ['id' => $variantId]);
                    $satuHarga = [
                        'id_satuan' => $data['id_satuan'],
                        'harga_beli' => $data['harga_modal'],
                        'harga_pokok' => $data['harga_modal'],
                        'harga_jual' => $row['harga_jual'],
                    ];
                    $cekSatuHarga = $this->model_app->view_where('satu_harga', ['id_bahan' => $variantId]);
                    if ($cekSatuHarga->num_rows() > 0) {
                        $this->model_app->update('satu_harga', $satuHarga, ['id_bahan' => $variantId]);
                    } else {
                        $satuHarga['id_bahan'] = $variantId;
                        $this->model_app->input('satu_harga', $satuHarga);
                    }
                } else {
                    $data['barcode'] = $this->number();
                    $insert = $this->model_app->input('bahan', $data);
                    if ($insert['status'] != 'ok') {
                        return ['status' => 400, 'msg' => 'Varian gagal disimpan'];
                    }

                    $variantId = (int) $insert['id'];
                    $this->model_app->input('satu_harga', [
                        'id_satuan' => $data['id_satuan'],
                        'id_bahan' => $variantId,
                        'harga_beli' => $data['harga_modal'],
                        'harga_pokok' => $data['harga_modal'],
                        'harga_jual' => $row['harga_jual'],
                    ]);
                    $this->model_app->update('bahan', ['type_harga' => 1], ['id' => $variantId]);
                }

                $activeIds[] = $variantId;
            }

            if (!empty($activeIds)) {
                $this->db->where('parent_bahan_id', $id_parent);
                $this->db->where_not_in('id', $activeIds);
                $this->db->update('bahan', ['pub' => 0]);
            }

            $this->model_app->update('bahan', ['varian_aktif' => 1], ['id' => $id_parent]);

            return ['status' => 200];
        }
        
        /**
            * edit_bahan.
            *
            * @return void
        */
        public function edit_bahan()
        {
            $this->ensure_bahan_foto_schema();
            cek_nput_post('GET');
            cek_crud_akses(8);
            $this->ensure_bahan_komposisi_schema();
            $this->ensure_bahan_foto_schema();
            $this->ensure_bahan_decimal_schema();
            $id = xss_filter($this->input->post('id'), 'xss');
            if ($id > 0) {
                $masuk = $this->model_app->sum_stok('stok_masuk', ['id_bahan' => $id])->row();
                $keluar = $this->model_app->sum_stok('stok_keluar', ['id_bahan' => $id])->row();
                $total = $masuk->total - $keluar->total;
                $select = [
                '`bahan`.`title`',
                '`bahan`.`id`',
                '`bahan`.`id_jenis`',
                '`bahan`.`id_produk`',
                '`bahan`.`harga_modal`',
                '`bahan`.`harga_jual`',
                '`bahan`.`harga`',
                '`bahan`.`id_satuan`',
                '`bahan`.`status`',
                '`bahan`.`status_stok`',
                '`bahan`.`batas_minimum_stok`',
                '`bahan`.`barcode`',
                '`bahan`.`foto`',
                '`bahan`.`ukuran_default`',
                '`bahan`.`komisi_desain_aktif`',
                '`bahan`.`komisi_penjualan_aktif`',
                '`bahan`.`komisi_penjualan_tipe`',
                '`bahan`.`komisi_penjualan_nilai`',
                '`bahan`.`margin_finishing_aktif`',
                '`bahan`.`lebar_bahan_tersedia`',
                '`bahan`.`pembulatan_di_bawah_satu_meter`',
                '`bahan`.`varian_aktif`',
                '`bahan`.`varian_warna`',
                '`bahan`.`varian_ukuran`',
                '`bahan`.`kunci`',
                '`satuan`.`satuan`',
                '`bahan`.`pub`',
                ];
                $where = ['bahan.id' => $id];
                
                $row = $this->model_app->join_where($select, 'satuan', 'bahan', 'id', 'id_satuan', $where)->row_array();
                // dump($row);
                $data = [
                'id' => $id,
                'judul' => $row['title'],
                'kategori' => $row['id_jenis'],
                'produk' => $row['id_produk'],
                'modal' => $this->harga_decimal_display($row['harga_modal']),
                'harga_jual' => $this->harga_decimal_display($row['harga_jual']),
                'harga_pokok' => $this->harga_decimal_display($row['harga']),
                'id_satuan' => $row['id_satuan'],
                'satuan' => $row['satuan'],
                'ukuran' => $row['status'],
                'stok' => $row['status_stok'],
                'batas_minimum_stok' => rtrim(rtrim(number_format((float) $row['batas_minimum_stok'], 2, '.', ''), '0'), '.'),
                'jenis' => $row['kunci'],
                'barcode' => $row['barcode'],
                'foto' => isset($row['foto']) ? $row['foto'] : '',
                'total' => $total,
                'aktif' => $row['pub'],
                'komisi_desain_aktif' => isset($row['komisi_desain_aktif']) ? (int) $row['komisi_desain_aktif'] : 0,
                'komisi_penjualan_aktif' => isset($row['komisi_penjualan_aktif']) ? (int) $row['komisi_penjualan_aktif'] : 0,
                'komisi_penjualan_tipe' => isset($row['komisi_penjualan_tipe']) ? $row['komisi_penjualan_tipe'] : 'persen',
                'komisi_penjualan_nilai' => isset($row['komisi_penjualan_nilai']) ? (float)$row['komisi_penjualan_nilai'] : 0,
                'margin_finishing_aktif' => isset($row['margin_finishing_aktif']) ? (int) $row['margin_finishing_aktif'] : 0,
                'lebar_bahan_tersedia' => !empty($row['lebar_bahan_tersedia']) ? (string) $row['lebar_bahan_tersedia'] : '1,1.5,2,3',
                'pembulatan_di_bawah_satu_meter' => isset($row['pembulatan_di_bawah_satu_meter']) ? (int) $row['pembulatan_di_bawah_satu_meter'] : 0,
                'varian_aktif' => isset($row['varian_aktif']) ? (int) $row['varian_aktif'] : 0,
                'varian' => $this->get_bahan_varian($id),
                'ukuran_default' => $this->resolve_ukuran_default($row),
                'komposisi_aktif' => $this->bahan_has_komposisi($id) ? 1 : 0,
                'komposisi' => $this->get_bahan_komposisi($id),
                ];
                } else {
                $data = ['id' => 0, 'judul' => '', 'harga' => '', 'aktif' => '', 'msg' => 'error'];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * atur_harga.
            *
            * @return void
        */
        public function atur_harga()
        {
            cek_nput_post('GET');
            cek_crud_akses(8);
            $id = xss_filter($this->input->post('id'), 'xss');
            if ($id > 0) {
                $select = [
                '`bahan`.`title`',
                '`bahan`.`id`',
                '`bahan`.`id_jenis`',
                '`bahan`.`harga_modal`',
                '`bahan`.`id_satuan`',
                '`bahan`.`status`',
                '`bahan`.`status_stok`',
                '`bahan`.`type_harga`',
                '`satuan`.`satuan`',
                '`bahan`.`pub`',
                ];
                $where = ['bahan.id' => $id];
                
                $row = $this->model_app->join_where($select, 'satuan', 'bahan', 'id', 'id_satuan', $where)->row_array();
                $data = [
                'id' => $id,
                'judul' => $row['title'],
                'type_harga' => $row['type_harga'],
                'id_satuan' => $row['id_satuan'],
                'modal' => $row['harga_modal'],
                ];
                } else {
                $data = ['id' => 0, 'judul' => '', 'harga' => 0, 'aktif' => 'N', 'msg' => 'error'];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * save_tab.
            *
            * @return void
        */
        public function save_tab()
        {
            cek_nput_post('GET');
            
            $id = xss_filter($this->input->post('id'), 'xss');
            $tab = xss_filter($this->input->post('idtab'), 'xss');
            
            $current = $this->model_app->pilih_where('type_harga', 'bahan', ['id' => $id])->row();
            $typeHarga = $current && (int) $current->type_harga === 6 ? 6 : $tab;
            $res = $this->model_app->update('bahan', ['type_harga' => $typeHarga], ['id' => $id]);
            if ($res['status'] == 'ok') {
                $cek = $this->model_app->view_where('satu_harga', ['id_bahan' => $id]);
                if ($cek->num_rows() > 0) {
                    $data = ['status' => 200, 'id' => $cek->row()->id_satuan, 'msg' => 'Data berhasil update'];
                    } else {
                    $data = ['status' => 200, 'id' => 0, 'msg' => 'Data berhasil update'];
                }
                } else {
                $data = ['status' => 400, 'id' => 0];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * save_bahan.
            *
            * @return void
        */
        public function save_bahan()
        {
            cek_nput_post('GET');
            simpan_demo('Simpan');
            cek_crud_akses(9);
            $this->ensure_bahan_komposisi_schema();
            $this->ensure_bahan_decimal_schema();
            $this->ensure_bahan_foto_schema();
            
            $id = xss_filter($this->input->post('id'), 'xss');
            $type = xss_filter($this->input->post('type'), 'xss');
            $judul = xss_filter($this->input->post('judul'), 'xss');
            $kategori = xss_filter($this->input->post('kategori'), 'xss');
            $produk = xss_filter($this->input->post('produk'), 'xss');
            $modal = xss_filter($this->input->post('modal'), 'xss');
            $harga_jual = xss_filter($this->input->post('harga_jual'), 'xss');
            if ($harga_jual === '' || $harga_jual === null) {
                $harga_jual = $modal;
            }
            $harga_pokok = xss_filter($this->input->post('harga_pokok'), 'xss');
            $foto = trim((string) xss_filter($this->input->post('foto'), 'xss'));
            if ($foto !== '' && !preg_match('#^uploads/produk/[A-Za-z0-9._-]+$#', $foto)) {
                $foto = '';
            }
            if (!product_item_image_enabled()) {
                $foto = '';
                if ((int) $id > 0 && $this->db->field_exists('foto', 'bahan')) {
                    $existingPhoto = $this->db
                        ->select('foto')
                        ->from('bahan')
                        ->where('id', (int) $id)
                        ->limit(1)
                        ->get()
                        ->row_array();
                    $foto = isset($existingPhoto['foto']) ? (string) $existingPhoto['foto'] : '';
                }
            }
            $modal = $this->harga_decimal_value($modal);
            $harga_jual = $this->harga_decimal_value($harga_jual);
            $harga_pokok = $this->harga_decimal_value($harga_pokok);
            $satuan = xss_filter($this->input->post('satuan'), 'xss');
            $ukuran = xss_filter($this->input->post('ukuran'), 'xss');
            $ukuran = in_array((int) $ukuran, [0, 1, 2], true) ? (int) $ukuran : 0;
            $stok = xss_filter($this->input->post('stok'), 'xss');
            $batas_minimum_stok_raw = trim((string) $this->input->post('batas_minimum_stok'));
            if (!preg_match('/^\d+(?:[\.,]\d+)?$/', $batas_minimum_stok_raw)) {
                $data = ['status' => 400, 'msg' => 'Batas minimum stok harus berupa angka 0 atau lebih'];
                $this->output
                ->set_content_type('application/json')
                ->set_output(json_encode($data));
                return;
            }
            $batas_minimum_stok = str_replace(',', '.', $batas_minimum_stok_raw);
            $kunci = xss_filter($this->input->post('jenis'), 'xss');
            $barcode = xss_filter($this->input->post('barcode'), 'xss');
            $aktif = xss_filter($this->input->post('aktif'), 'xss');
            $ukuran_default_input = $this->input->post('ukuran_default', true);
            $ukuran_default_is_posted = $ukuran_default_input !== null;
            $ukuran_default = $ukuran_default_is_posted ? xss_filter($ukuran_default_input, 'xss') : '';
            $komposisi_aktif = ((int) $this->input->post('komposisi_aktif') === 1) ? 1 : 0;
            $komisi_desain_aktif = ((int) $this->input->post('komisi_desain_aktif') === 1) ? 1 : 0;
            $komisi_penjualan_aktif = ((int)$this->input->post('komisi_penjualan_aktif') === 1) ? 1 : 0;
            $komisi_penjualan_tipe = $this->input->post('komisi_penjualan_tipe') === 'nominal' ? 'nominal' : 'persen';
            $komisi_penjualan_nilai = str_replace(',', '.', trim((string)$this->input->post('komisi_penjualan_nilai')));
            $komisi_penjualan_nilai = is_numeric($komisi_penjualan_nilai) ? max(0, (float)$komisi_penjualan_nilai) : 0;
            if ($komisi_penjualan_tipe === 'persen') $komisi_penjualan_nilai = min(100, $komisi_penjualan_nilai);
            $margin_finishing_aktif = ((int) $this->input->post('margin_finishing_aktif') === 1) ? 1 : 0;
            $pembulatan_di_bawah_satu_meter = ((int) $this->input->post('pembulatan_di_bawah_satu_meter') === 1) ? 1 : 0;
            $lebar_bahan_raw = trim((string) $this->input->post('lebar_bahan_tersedia'));
            // Nilai dari form sudah dinormalisasi: koma/titik koma adalah pemisah,
            // sedangkan pecahan memakai titik (contoh: 1,1.5,2,3).
            $lebar_bahan_parts = preg_split('/[,;\s]+/', $lebar_bahan_raw);
            $lebar_bahan_valid = [];
            foreach ($lebar_bahan_parts as $lebar_bahan_value) {
                $lebar_bahan_value = str_replace(',', '.', trim($lebar_bahan_value));
                if ($lebar_bahan_value === '' || !is_numeric($lebar_bahan_value) || (float) $lebar_bahan_value <= 0) {
                    continue;
                }
                $normalized = rtrim(rtrim(number_format((float) $lebar_bahan_value, 3, '.', ''), '0'), '.');
                $lebar_bahan_valid[$normalized] = (float) $normalized;
            }
            asort($lebar_bahan_valid, SORT_NUMERIC);
            $lebar_bahan_tersedia = implode(',', array_keys($lebar_bahan_valid));
            if ($margin_finishing_aktif === 1 && $lebar_bahan_tersedia === '') {
                $data = ['status' => 400, 'msg' => 'Lebar bahan yang tersedia harus diisi minimal satu ukuran'];
                $this->output->set_content_type('application/json')->set_output(json_encode($data));
                return;
            }
            if ($lebar_bahan_tersedia === '') {
                $lebar_bahan_tersedia = '1,1.5,2,3';
            }
            $varian_aktif = ((int) $this->input->post('varian_aktif') === 1) ? 1 : 0;
            $varian_rows = $this->normalize_varian_rows(
                $this->input->post('varian_warna'),
                $this->input->post('varian_ukuran'),
                $this->input->post('varian_harga_jual')
            );
            $komponen = $this->input->post('komponen');
            $komponen_jumlah = $this->input->post('komponen_jumlah');
            $komponen_panjang = $this->input->post('komponen_panjang');
            $komponen_lebar = $this->input->post('komponen_lebar');

            if (($type == 'add' || $type == 'edit') && $varian_aktif !== 1 && trim((string) $ukuran_default) === '') {
                $data = ['status' => 400, 'msg' => 'Ukuran default masih kosong'];
                $this->output
                ->set_content_type('application/json')
                ->set_output(json_encode($data));
                return;
            }

            if ($varian_aktif === 1) {
                foreach ($varian_rows as $row) {
                    if (trim((string) $row['ukuran']) === '') {
                        $data = ['status' => 400, 'msg' => 'Ukuran varian harus diisi'];
                        $this->output
                        ->set_content_type('application/json')
                        ->set_output(json_encode($data));
                        return;
                    }
                }

                if (trim((string) $ukuran_default) === '') {
                    $ukuran_default = '-';
                }
            }
            
            $_data = [
            'id_jenis' => $kategori,
            'id_produk' => $produk,
            'title' => $judul,
            'harga_modal' => $modal,
            'harga_jual' => $harga_jual,
            'harga' => $harga_pokok,
            'id_satuan' => $satuan,
            'status' => $ukuran,
            'status_stok' => $stok,
            'batas_minimum_stok' => $batas_minimum_stok,
            'kunci' => $kunci,
            'barcode' => $barcode,
            'foto' => $foto,
            'pub' => $aktif,
            'komisi_desain_aktif' => $komisi_desain_aktif,
            'komisi_penjualan_aktif' => $komisi_penjualan_aktif,
            'komisi_penjualan_tipe' => $komisi_penjualan_tipe,
            'komisi_penjualan_nilai' => $komisi_penjualan_nilai,
            'margin_finishing_aktif' => $margin_finishing_aktif,
            'lebar_bahan_tersedia' => $lebar_bahan_tersedia,
            'pembulatan_di_bawah_satu_meter' => $pembulatan_di_bawah_satu_meter,
            'varian_aktif' => $varian_aktif,
            ];

            if ($ukuran_default_is_posted) {
                $_data['ukuran_default'] = $ukuran_default;
            }
            
            if (empty($id) and $type == 'add') {
                $this->form_validation->set_rules([
                [
                'field' => 'judul',
                'label' => 'Nama Barang/Merk',
                'rules' => 'required|trim|min_length[3]|max_length[50]|is_unique[bahan.title]',
                'errors' => [
                'required' => '%s. Harus di isi',
                'min_length' => '%s minimal 3 digit.',
                'max_length' => '%s minimal 50 digit.',
                'is_unique'     => '%s sudah ada.'
                ],
                ],
                [
                'field' => 'barcode',
                'label' => 'Barcode',
                'rules' => 'required|trim|min_length[12]|max_length[13]|is_unique[bahan.barcode]',
                'errors' => [
                'required' => '%s. Harus di isi',
                'min_length' => '%s minimal 12 digit.',
                'max_length' => '%s minimal 13 digit.',
                'is_unique'     => '%s sudah ada.'
                ],
                ],
                ]);
                
                if ($this->form_validation->run() == false) {
                    $data = ['status' => 400, 'msg' => validation_errors()];
                    
                    $this->output
                    ->set_status_header(200)
                    ->set_content_type('application/json', 'utf-8')
                    ->set_output(json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES))
                    ->_display();
                    exit;
                }
                // /data baru
                $res = $this->model_app->input('bahan', $_data);
                if ($res['status'] == 'ok') {
                    $cek = $this->model_app->view_where('satu_harga', ['id_bahan' => $id]);
                    if ($cek->num_rows() == 0) {
                        $this->hapus_harga($id);
                        
                    }
                    $array = [
                    'id_satuan' => $satuan,
                    'id_bahan' => $res['id'],
                    'harga_beli' => $modal,
                    'harga_pokok' => $modal,
                    'harga_jual' => $harga_jual,
                    ];
                    $this->model_app->input('satu_harga', $array);
                    $this->model_app->update('bahan', ['type_harga' => 1], ['id' => $res['id']]);
                    $komposisi = $this->save_bahan_komposisi($res['id'], $komposisi_aktif, $komponen, $komponen_jumlah, $komponen_panjang, $komponen_lebar);
                    $varian = ($komposisi['status'] == 200)
                        ? $this->save_bahan_varian($res['id'], $varian_aktif, $varian_rows, $_data)
                        : $komposisi;
                    if ($varian['status'] == 200) {
                        $data = ['status' => 200, 'msg' => 'Data berhasil disimpan'];
                        } else {
                        $data = $varian;
                    }
                    } else {
                    $data = ['status' => 400];
                }
            }
            if ($id > 0 and $type == 'edit') {
                $this->form_validation->set_rules([
                [
                'field' => 'barcode',
                'label' => 'Barcode',
                'rules' => 'required|trim|numeric|min_length[13]|max_length[13]|callback__cek_barcode_bahan['.$id.']',
                'errors' => [
                'required' => '%s. Harus di isi',
                'numeric' => '%s. Harus angka',
                'min_length' => '%s minimal 12 digit.',
                'max_length' => '%s maksimal 12 digit.',
                ],
                ],
                
                [
                'field' => 'judul',
                'label' => 'Nama Barang/Merk',
                'rules' => 'required|trim|min_length[3]|max_length[100]|callback__cek_title['.$id.']',
                'errors' => [
                'required' => '%s Harus diisi.',
                'min_length' => '%s minimal 3 digit.',
                'max_length' => '%s minimal 100 digit.',
                ],
                ],
                ]);
                
                if ($this->form_validation->run() == false) {
                    $data['status'] = 400;
                    $data['msg'] = validation_errors();
                    } else {
                    // /data update
                    $res = $this->model_app->update('bahan', $_data, ['id' => $id]);
                    if ($res['status'] == 'ok') {
                        $satuHarga = [
                            'id_satuan' => $satuan,
                            'harga_beli' => $modal,
                            'harga_pokok' => $modal,
                            'harga_jual' => $harga_jual,
                        ];
                        $cekSatuHarga = $this->model_app->view_where('satu_harga', ['id_bahan' => $id]);
                        if ($cekSatuHarga->num_rows() > 0) {
                            $this->model_app->update('satu_harga', $satuHarga, ['id_bahan' => $id]);
                        } else {
                            $satuHarga['id_bahan'] = $id;
                            $this->model_app->input('satu_harga', $satuHarga);
                        }
                        $komposisi = $this->save_bahan_komposisi($id, $komposisi_aktif, $komponen, $komponen_jumlah, $komponen_panjang, $komponen_lebar);
                        $varian = ($komposisi['status'] == 200)
                            ? $this->save_bahan_varian($id, $varian_aktif, $varian_rows, $_data)
                            : $komposisi;
                        if ($varian['status'] == 200) {
                            $data = ['status' => 200, 'msg' => 'Data berhasil update'];
                            } else {
                            $data = $varian;
                        }
                        } else {
                        $data = ['status' => 400];
                    }
                }
            }
            
            if ($id > 0 and $type == 'featured') {
                $featured = xss_filter($this->input->post('featured'), 'xss');
                $kategori = xss_filter($this->input->post('kategori'), 'xss');
                $produk = xss_filter($this->input->post('produk'), 'xss');
                $array = [
                'featured' => $featured,
                ];
                
                $res = $this->model_app->update('bahan', $array, ['id_produk' => $produk, 'id' => $id]);
                
                if ($res['status'] == 'ok') {
                    $this->model_app->update('bahan', ['featured' => 0], ['id_produk' => $produk, 'id !=' => $id]);
                    $data = ['status' => 200, 'msg' => 'Data berhasil update'];
                    } else {
                    $data = ['status' => 400];
                }
            }

            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }

        public function upload_foto_bahan()
        {
            cek_nput_post('GET');
            cek_crud_akses(9);
            $this->ensure_bahan_foto_schema();

            if (!product_item_image_enabled()) {
                $this->output
                    ->set_status_header(403)
                    ->set_content_type('application/json')
                    ->set_output(json_encode([
                        'status' => 403,
                        'msg' => 'Fitur gambar item produk/bahan sedang tidak aktif',
                    ]));
                return;
            }

            $file = isset($_FILES['foto_produk']) ? $_FILES['foto_produk'] : null;
            if (!$file || (int) $file['error'] !== UPLOAD_ERR_OK) {
                $this->output->set_content_type('application/json')->set_output(json_encode([
                    'status' => 400,
                    'msg' => 'Foto produk belum dipilih atau gagal diunggah',
                ]));
                return;
            }

            if ((int) $file['size'] > 4 * 1024 * 1024) {
                $this->output->set_content_type('application/json')->set_output(json_encode([
                    'status' => 400,
                    'msg' => 'Ukuran foto maksimal 4 MB',
                ]));
                return;
            }

            $extension = strtolower(pathinfo((string) $file['name'], PATHINFO_EXTENSION));
            $allowedExtensions = ['jpg', 'jpeg', 'png', 'webp'];
            $allowedMime = ['image/jpeg', 'image/pjpeg', 'image/png', 'image/webp'];
            $mime = function_exists('mime_content_type') ? mime_content_type($file['tmp_name']) : '';
            if (!in_array($extension, $allowedExtensions, true) || ($mime && !in_array($mime, $allowedMime, true))) {
                $this->output->set_content_type('application/json')->set_output(json_encode([
                    'status' => 400,
                    'msg' => 'Format foto harus JPG, PNG, atau WEBP',
                ]));
                return;
            }

            if (!function_exists('imagecreatefromstring') || !function_exists('imagewebp')) {
                $this->output->set_content_type('application/json')->set_output(json_encode([
                    'status' => 400,
                    'msg' => 'Server belum mendukung konversi gambar ke WEBP',
                ]));
                return;
            }

            $imageInfo = @getimagesize($file['tmp_name']);
            $sourceWidth = isset($imageInfo[0]) ? (int) $imageInfo[0] : 0;
            $sourceHeight = isset($imageInfo[1]) ? (int) $imageInfo[1] : 0;
            if ($sourceWidth < 1 || $sourceHeight < 1 || ($sourceWidth * $sourceHeight) > 20000000) {
                $this->output->set_content_type('application/json')->set_output(json_encode([
                    'status' => 400,
                    'msg' => 'Resolusi foto tidak valid atau terlalu besar (maksimal 20 megapiksel)',
                ]));
                return;
            }

            $sourceData = @file_get_contents($file['tmp_name']);
            $image = $sourceData !== false ? @imagecreatefromstring($sourceData) : false;
            unset($sourceData);
            if (!$image) {
                $this->output->set_content_type('application/json')->set_output(json_encode([
                    'status' => 400,
                    'msg' => 'Isi file tidak dapat dibaca sebagai gambar',
                ]));
                return;
            }

            if (in_array($extension, ['jpg', 'jpeg'], true) && function_exists('exif_read_data')) {
                $exif = @exif_read_data($file['tmp_name']);
                $orientation = isset($exif['Orientation']) ? (int) $exif['Orientation'] : 1;
                if ($orientation === 3) {
                    $rotated = imagerotate($image, 180, 0);
                } elseif ($orientation === 6) {
                    $rotated = imagerotate($image, -90, 0);
                } elseif ($orientation === 8) {
                    $rotated = imagerotate($image, 90, 0);
                }
                if (isset($rotated) && $rotated) {
                    imagedestroy($image);
                    $image = $rotated;
                }
            }

            $sourceWidth = imagesx($image);
            $sourceHeight = imagesy($image);
            $maxDimension = 1600;
            $scale = min(1, $maxDimension / max($sourceWidth, $sourceHeight));
            $targetWidth = max(1, (int) round($sourceWidth * $scale));
            $targetHeight = max(1, (int) round($sourceHeight * $scale));

            if ($targetWidth !== $sourceWidth || $targetHeight !== $sourceHeight) {
                $resized = imagecreatetruecolor($targetWidth, $targetHeight);
                imagealphablending($resized, false);
                imagesavealpha($resized, true);
                $transparent = imagecolorallocatealpha($resized, 0, 0, 0, 127);
                imagefilledrectangle($resized, 0, 0, $targetWidth, $targetHeight, $transparent);
                imagecopyresampled($resized, $image, 0, 0, 0, 0, $targetWidth, $targetHeight, $sourceWidth, $sourceHeight);
                imagedestroy($image);
                $image = $resized;
            } else {
                imagepalettetotruecolor($image);
                imagealphablending($image, false);
                imagesavealpha($image, true);
            }

            $directory = FCPATH.'uploads/produk/';
            if (!is_dir($directory)) {
                @mkdir($directory, 0775, true);
            }
            if (!is_dir($directory) || !is_writable($directory)) {
                imagedestroy($image);
                $this->output->set_content_type('application/json')->set_output(json_encode([
                    'status' => 400,
                    'msg' => 'Folder foto produk tidak dapat ditulis',
                ]));
                return;
            }

            $filename = 'produk_'.date('YmdHis').'_'.bin2hex(random_bytes(5)).'.webp';
            $destination = $directory.$filename;
            $converted = @imagewebp($image, $destination, 80);
            imagedestroy($image);
            if (!$converted || !is_file($destination) || filesize($destination) < 1) {
                if (is_file($destination)) {
                    @unlink($destination);
                }
                $this->output->set_content_type('application/json')->set_output(json_encode([
                    'status' => 400,
                    'msg' => 'Foto produk gagal dikonversi ke WEBP',
                ]));
                return;
            }

            $path = 'uploads/produk/'.$filename;
            $savedSize = (int) filesize($destination);
            $this->output->set_content_type('application/json')->set_output(json_encode([
                'status' => 200,
                'path' => $path,
                'url' => base_url($path),
                'format' => 'webp',
                'width' => $targetWidth,
                'height' => $targetHeight,
                'size' => $savedSize,
                'msg' => 'Foto berhasil dikonversi ke WEBP ('.number_format($savedSize / 1024, 0, ',', '.').' KB)',
            ]));
        }

        public function save_semua_metode_harga()
        {
            cek_nput_post('GET');
            cek_crud_akses(8);
            $id = (int) xss_filter($this->input->post('id'), 'xss');
            $aktif = (int) xss_filter($this->input->post('aktif'), 'xss') === 1;
            $tab = (int) xss_filter($this->input->post('tab'), 'xss');
            $tab = in_array($tab, [1, 2, 3, 4, 5], true) ? $tab : 1;
            $typeHarga = $aktif ? 6 : $tab;

            $res = $this->model_app->update('bahan', ['type_harga' => $typeHarga], ['id' => $id]);
            $data = $res['status'] === 'ok'
                ? [
                    'status' => 200,
                    'type_harga' => $typeHarga,
                    'msg' => $aktif ? 'Semua metode harga aktif' : 'Metode harga tunggal aktif',
                ]
                : ['status' => 400, 'type_harga' => $typeHarga, 'msg' => 'Pengaturan gagal disimpan'];

            $this->output
                ->set_content_type('application/json')
                ->set_output(json_encode($data));
        }

        public function toggle_komisi_desain_bahan()
        {
            cek_nput_post('GET');
            simpan_demo('Simpan');
            cek_crud_akses(9);
            $this->ensure_bahan_komposisi_schema();

            $id = (int) xss_filter($this->input->post('id'), 'xss');
            $value = ((int) $this->input->post('value') === 1) ? 1 : 0;

            if ($id <= 0) {
                $data = ['status' => 400, 'msg' => 'Data bahan tidak ditemukan'];
            } else {
                $res = $this->model_app->update('bahan', ['komisi_desain_aktif' => $value], ['id' => $id]);
                $data = ($res['status'] == 'ok')
                    ? ['status' => 200, 'msg' => 'Komisi desain berhasil diupdate']
                    : ['status' => 400, 'msg' => 'Komisi desain gagal diupdate'];
            }

            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * hapus_bahan.
            *
            * @return void
        */
        public function hapus_bahan()
        {
            cek_nput_post('GET');
            simpan_demo('Hapus');
            cek_crud_akses(10);
            
            $id = xss_filter($this->input->post('id'), 'xss');
            $type = xss_filter($this->input->post('type'), 'xss');
            
            if ($id > 0 and $type == 'hapus') {
                $cek = $this->model_app->view_where('invoice_detail', ['id_bahan' => $id]);
                if ($cek->num_rows() == 0) {
                    $search = $this->model_app->view_where('invoice_detail', ['id_bahan' => $id]);
                    if ($search->num_rows() > 0) {
                        $data = ['status' => 400, 'msg' => 'Bahan tidak bisa dihapus'];
                        } else {
                        $res = $this->model_app->hapus('bahan', ['id' => $id]);
                        if ($res['status'] == 'ok') {
                            $this->hapus_harga($id);
                            $data = ['status' => 200, 'msg' => 'Data berhasil dihapus'];
                            } else {
                            $data = ['status' => 400, 'msg' => 'Data gagal dihapus'];
                        }
                    }
                    } else {
                    $data = ['status' => 400, 'msg' => 'Data tidak bisa dihapus'];
                }
                } else {
                $data = ['status' => 400, 'msg' => 'Bad Request'];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * hapus_harga.
            *
            * @return void
        */
        private function hapus_harga($id)
        {
            $satu_harga = $this->model_app->view_where('satu_harga', ['id_bahan' => $id]);
            if ($satu_harga->num_rows() > 0) {
                $this->model_app->hapus('satu_harga', ['id_bahan' => $id]);
            }
            
            $harga_satuan = $this->model_app->view_where('harga_satuan', ['id_bahan' => $id]);
            if ($harga_satuan->num_rows() > 0) {
                $this->model_app->hapus('harga_satuan', ['id_bahan' => $id]);
            }
            
            $harga_member = $this->model_app->view_where('harga_member', ['id_bahan' => $id]);
            if ($harga_member->num_rows() > 0) {
                $this->model_app->hapus('harga_member', ['id_bahan' => $id]);
            }
            $range_harga = $this->model_app->view_where('range_harga', ['id_bahan' => $id]);
            if ($range_harga->num_rows() > 0) {
                $this->model_app->hapus('range_harga', ['id_bahan' => $id]);
            }
            
            $harga_range_member = $this->model_app->view_where('harga_range_member', ['id_bahan' => $id]);
            if ($harga_range_member->num_rows() > 0) {
                $this->model_app->hapus('harga_range_member', ['id_bahan' => $id]);
            }
            $stok_masuk = $this->model_app->view_where('stok_masuk', ['id_bahan' => $id]);
            if ($stok_masuk->num_rows() > 0) {
                $this->model_app->hapus('stok_masuk', ['id_bahan' => $id]);
            }
            $stok_keluar = $this->model_app->view_where('stok_keluar', ['id_bahan' => $id]);
            if ($stok_masuk->num_rows() > 0) {
                $this->model_app->hapus('stok_keluar', ['id_bahan' => $id]);
            }
        }
        
        /**
            * jenis.
            *
            * @return void
        */
        public function jenis()
        {
            cek_menu_akses();
            $data['title'] = 'Jenis Produk | '.info()['title'];
            $data['judul'] = 'Jenis Produk';
            
            $conditions['where'] = [
            'jenis_cetakan.kunci' => 0,
            ];
            
            $conditions['returnType'] = 'count';
            $totalRec = $this->model_data->getJenis($conditions);
            
            // Pagination configuration
            $config['target'] = '#dataJenis';
            $config['base_url'] = base_url('produk/cariJenis');
            $config['total_rows'] = $totalRec;
            $config['per_page'] = $this->perPage;
            $config['link_func'] = 'search_Jenis';
            // Initialize pagination library
            $this->ajax_pagination->initialize($config);
            
            // Get records
            $conditions = [
            'limit' => $this->perPage,
            ];
            
            $conditions['where'] = [
            'jenis_cetakan.kunci' => 0,
            ];
            // $this->db->where_in('kelompok_akun.id_kelompok_akun', 6);
            $data['dt_akun'] = $this->masterdata->getAccountData();
            $data['result'] = $this->model_data->getJenis($conditions);
            $data['limit_default'] = (int) $this->perPage;
            $this->template->load('main/themes', 'produk/jenis', $data);
        }
        
        /**
            * satuan.
            *
            * @return void
        */
        public function satuan()
        {
            cek_menu_akses();
            cek_crud_akses(8);
            $data['title'] = 'Satuan Produk | '.info()['title'];
            
            $conditions['returnType'] = 'count';
            $totalRec = $this->model_data->getSatuan($conditions);
            
            // Pagination configuration
            $config['target'] = '#dataSatuan';
            $config['base_url'] = base_url('produk/cariSatuan');
            $config['total_rows'] = $totalRec;
            $config['per_page'] = $this->perPage;
            $config['link_func'] = 'search_Satuan';
            // Initialize pagination library
            $this->ajax_pagination->initialize($config);
            
            // Get records
            $conditions = [
            'limit' => $this->perPage,
            ];
            
            $data['result'] = $this->model_data->getSatuan($conditions);
            $this->template->load('main/themes', 'produk/satuan', $data);
        }
        
        /**
            * edit_jenis.
            *
            * @return void
        */
        public function edit_jenis()
        {
            cek_nput_post('GET');
            $id = xss_filter($this->input->post('id'), 'xss');
            if ($id > 0) {
                $where = ['id_jenis' => $id];
                $row = $this->model_app->edit('jenis_cetakan', $where)->row_array();
                $data = ['id' => $id, 'judul' => $row['jenis_cetakan'], 'kelompok' => $row['status'], 'id_akun' => $row['id_akun'], 'aktif' => $row['pub']];
                } else {
                $data = ['id' => 0, 'judul' => '', 'kelompok' => '', 'aktif' => ''];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * save_jenis.
            *
            * @return void
        */
        public function save_jenis()
        {
            cek_nput_post('GET');
            simpan_demo('Simpan');
            $id = $this->db->escape_str($this->input->post('id'));
            $id_akun = $this->db->escape_str($this->input->post('id_akun'));
            $type = $this->db->escape_str($this->input->post('type'));
            $judul = change_case($this->db->escape_str($this->input->post('judul')));
            $status = change_case($this->db->escape_str($this->input->post('kelompok')));
            $aktif = $this->db->escape_str($this->input->post('aktif'));
            if (!empty($id_akun)) {
                $_data = ['jenis_cetakan' => $judul, 'id_akun' => $id_akun, 'status' => $status, 'pub' => $aktif];
                } else {
                $_data = ['jenis_cetakan' => $judul, 'status' => $status, 'pub' => $aktif];
            }
            if ($id == 0 and $type == 'add') {
                // /data baru
                $res = $this->model_app->input('jenis_cetakan', $_data);
                if ($res['status'] == 'ok') {
                    $this->cek_akun($judul, $res['id']);
                    $data = ['status' => 200, 'msg' => 'Data berhasil disimpan'];
                    } else {
                    $data = ['status' => 400];
                }
                } else {
                // /data update
                $res = $this->model_app->update('jenis_cetakan', $_data, ['id_jenis' => $id]);
                if ($res['status'] == 'ok') {
                    $this->cek_akun($judul, $id);
                    $data = ['status' => 200, 'msg' => 'Data berhasil update'];
                    } else {
                    $data = ['status' => 400];
                }
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        public function cek_akun($nama, $val)
        {
            $kelompok = $this->db->escape_str($this->input->post('kelompok'));
            if ($kelompok == 6) {
                $id_perkiraan = 2;
                } else {
                $id_perkiraan = 1;
            }
            $cek = $this->model_app->view_where('akun', ['nama' => $nama]);
            if ($cek->num_rows() > 0) {
                } else {
                $this->db->select_max('noakun');
                $this->db->from('akun');
                $this->db->where('id_kelompok_akun', $kelompok);
                $query = $this->db->get()->row()->noakun;
                $max = $query + 1;
                
                $_data = [
                'noakun' => $max,
                'nama' => $nama,
                'id_kelompok_akun' => $kelompok,
                'id_perkiraan' => $id_perkiraan,
                'saldo_awal' => 0,
                'keterangan' => '',
                'id_user' => $this->iduser,
                ];
                
                $input = $this->model_app->input('akun', $_data);
                $this->model_app->update('jenis_cetakan', ['id_akun' => $input['id']], ['id_jenis' => $val]);
            }
        }
        
        /**
            * hapus_jenis.
            *
            * @return void
        */
        public function hapus_jenis()
        {
            cek_nput_post('GET');
            simpan_demo('Hapus');
            $id = xss_filter($this->input->post('id'), 'xss');
            $type = xss_filter($this->input->post('type'), 'xss');
            
            if ($id > 0 and $type == 'hapus') {
                if ($id == 9) {
                    $data = ['status' => 400, 'msg' => 'Data desain jangan dihapus'];
                    echo json_encode($data);
                    exit;
                }
                $cek = $this->model_app->view_where('invoice_detail', ['id_bahan' => $id]);
                if ($cek->num_rows() == 0) {
                    $cek2 = $this->model_app->view_where('pengeluaran_detail', ['id_biaya' => $id]);
                    if ($cek2->num_rows() == 0) {
                        $search = $this->model_app->view_where('invoice_detail', ['jenis_cetakan' => $id]);
                        if ($search->num_rows() > 0) {
                            $data = ['status' => 400, 'msg' => 'Jenis tidak bisa dihapus'];
                            } else {
                            $where = ['id_jenis' => $id];
                            $res = $this->model_app->hapus('jenis_cetakan', $where);
                            if ($res['status'] == 'ok') {
                                $data = ['status' => 200, 'msg' => 'Data berhasil dihapus'];
                                } else {
                                $data = ['status' => 400, 'msg' => 'Data gagal dihapus'];
                            }
                        }
                        } else {
                        $data = ['status' => 400, 'msg' => 'Data tidak bisa dihapus'];
                    }
                    } else {
                    $data = ['status' => 400, 'msg' => 'Data tidak bisa dihapus'];
                }
                } else {
                $data = ['status' => 400, 'msg' => 'Bad request'];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * print_cek.
            *
            * @return void
        */
        public function print_cek()
        {
            cek_nput_post('GET');
            $id = xss_filter($this->input->post('id'), 'xss');
            
            if ($this->session->level == 'desain') {
                $this->model_app->update('invoice', ['pos' => 'Y', 'status' => 'simpan', 'id_desain' => $this->iduser], ['id_invoice' => $id]);
                $this->session->unset_userdata('cart');
            }
            
            $data = ['status' => 200, 'id' => $id, 'encrypt_url' => encrypt_url($id)];
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }

        public function active_printers()
        {
            $this->_ensure_one_active_printer();
            $printers = $this->db
                ->select('id,name,shared_name,slug,ukuran_kertas,posisi')
                ->order_by('id', 'DESC')
                ->get_where('printer', ['pub' => 1])
                ->result_array();

            $data = [];
            foreach ($printers as $printer) {
                $data[] = [
                    'id' => isset($printer['id']) ? (int) $printer['id'] : 0,
                    'name' => isset($printer['name']) ? (string) $printer['name'] : '',
                    'shared_name' => isset($printer['shared_name']) ? (string) $printer['shared_name'] : '',
                    'slug' => isset($printer['slug']) ? (string) $printer['slug'] : '',
                    'ukuran_kertas' => isset($printer['ukuran_kertas']) ? (string) $printer['ukuran_kertas'] : '',
                    'posisi' => isset($printer['posisi']) ? (string) $printer['posisi'] : '',
                ];
            }

            $this->output
                ->set_content_type('application/json')
                ->set_output(json_encode(['status' => 200, 'printers' => $data]));
        }
        
        /**
            * print_invoice.
            *
            * @return void
        */
        public function print_invoice($noid = null)
        {
            $noid = decrypt_url($noid);
            $id = ['id_invoice' => $noid];
            $printer_id = (int) $this->input->get('printer_id', true);
            if ($printer_id > 0) {
                $cek_printer = $this->db->get_where('printer', ['id' => $printer_id, 'slug' => 'in', 'pub' => 1]);
            } else {
                $cek_printer = $this->db->get_where('printer', ['slug' => 'in', 'pub' => 1]);
            }
            if ($cek_printer->num_rows() < 1) {
                $cek_printer = $this->model_app->view_where('printer', ['slug' => 'in']);
            }
            $rowc = $cek_printer->row_array();
            if (empty($rowc)) {
                $rowc = [
                    'ukuran_kertas' => 'A4',
                    'ukuran_font' => 11,
                    'posisi' => 'potrait',
                    'max_item' => 33,
                    'slug' => 'in',
                    'pub' => 0,
                    'show_footer' => 0
                ];
            }
            
            $search = $this->model_app->view_where('invoice', $id);
            $app_info = info();
            
            // icon
            $data['waicon'] = ['color' => FCPATH.'assets/img/wa_icon.png', 'bw' => FCPATH.'assets/img/wa_icon_bw.png'];
            $data['mail'] = ['color' => FCPATH.'assets/img/gmail_icon.png', 'bw' => FCPATH.'assets/img/gmail_icon_bw.png'];
            $data['fbicon'] = ['color' => FCPATH.'assets/img/fb_icon.png', 'bw' => FCPATH.'assets/img/fb_icon_bw.png'];
            $data['igicon'] = ['color' => FCPATH.'assets/img/ig_icon.png', 'bw' => FCPATH.'assets/img/ig_icon_bw.png'];
            
            $data['logo_lunas'] = FCPATH.'uploads/'.$app_info['logo'];
            $data['logo_blunas'] = FCPATH.'uploads/'.$app_info['logo_bw'];
            $data['lunas'] = FCPATH.'uploads/'.$app_info['stamp_l'];
            $data['blunas'] = FCPATH.'uploads/'.$app_info['stamp_b'];
            $data['favicon'] = FCPATH.'uploads/'.$app_info['favicon'];
            $data['html'] = 'N';
            
            if ($search->num_rows() > 0) {
                $this->session->unset_userdata('cart');
                $row = $search->row();
				$invoice_qr = invoice_qr_context($noid, $app_info['kode_qris']);
				$data['qris'] = $invoice_qr['content'];
				$data['kode_qris'] = $invoice_qr['path'];
				$data['qr_available'] = $invoice_qr['available'];
				$data['qr_is_tracking'] = $invoice_qr['is_tracking'];
				$data['qr_label'] = $invoice_qr['label'];
                $jml = $row->cetak + 1;
                $this->model_app->update('tb_users', ['last_invoice' => 0], ['id_user' => $this->session->idu]);
                if ($row->status != 'batal') {
                    $this->model_app->update('invoice', ['cetak' => $jml, 'status' => 'simpan', 'pos' => 'Y', 'oto' => 6], ['id_invoice' => $noid]);
                }
                $data['cetak'] = $row;
                $data['info'] = $app_info;
                $konsumen = $this->model_app->view_where('konsumen', ['id' => $row->id_konsumen])->row_array();
                
                if ($konsumen['max_utang'] > 0 and $jml <= 1) {
                    $max_utang = $konsumen['max_utang'] - 1;
                    $this->model_app->update('konsumen', ['max_utang' => $max_utang], ['id' => $row->id_konsumen]);
                }
                
                $data['konsumen'] = $konsumen;
                $data['marketing'] = $this->model_app->view_where('tb_users', ['id_user' => $row->id_marketing])->row_array();
                $data['detail'] = $this->model_app->produk_cart(['invoice_detail.id_invoice' => $noid]);
                $detail_subtotal = 0;
                $detail_diskon = 0;
                foreach ($data['detail'] as $detail_row) {
                    $jumlah = isset($detail_row['jumlah']) ? (float) $detail_row['jumlah'] : 0;
                    $harga = isset($detail_row['harga']) ? (float) $detail_row['harga'] : 0;
                    $diskon = isset($detail_row['diskon']) ? (float) $detail_row['diskon'] : 0;
                    $subtotal = $jumlah * $harga;
                    $detail_subtotal += $subtotal - ($subtotal * ($diskon / 100));
                    $detail_diskon += $diskon;
                }
                $data['sisanya'] = round($detail_subtotal);
                $data['total'] = $this->model_app->total_bayar(['bayar_invoice_detail.id_invoice' => $noid])->row();
                $data['tdetail'] = $row->total_bayar;
                $data['pajak'] = $row->pajak;
                $data['jumlah_bayar'] = $row->jumlah_bayar;
                $data['potongan_harga'] = $row->potongan_harga;
                $data['cashback'] = $row->cashback;
                $data['kembalian'] = $row->kembalian;
                $data['cdiskon'] = $detail_diskon;
                // bayar detail
                $data['bdetail'] = $this->model_app->view_where('bayar_invoice_detail', $id)->result_array();
                $data['cdetail'] = count($data['bdetail']);
                // /
                $data['cara'] = $this->model_app->cara_bayar(['`bayar_invoice_detail`.`id_invoice`' => $noid]);
                
                $data['bank'] = $this->model_app->view_where('rekening_bank', ['footer_invoice' => 1, 'publish' => 'Y'])->result();
                
                $paperSize = !empty($rowc['ukuran_kertas']) ? $rowc['ukuran_kertas'] : 'A4';
                $paperOrientation = strtolower(trim($rowc['posisi']));
                $paperOrientation = ($paperOrientation == 'landscape') ? 'landscape' : 'portrait';
                $data['paper_size'] = $paperSize;
                $data['paper_orientation'] = $paperOrientation;
                $data['max_item'] = isset($rowc['max_item']) ? (int)$rowc['max_item'] : 33;

                $data['show_footer'] = $rowc['show_footer'];
                if ($rowc['slug'] == 'dotmatrix' and $rowc['pub'] == 1) {
                    $data['font_size'] = $rowc['ukuran_font'];
                    $data['font_wight'] = 'bold';
                    $pdf_view = 'produk/print_invoice_dotmatrix';
                    } else {
                    $data['font_size'] = $rowc['ukuran_font'];
                    $data['font_wight'] = 'normal';
                    $pdf_view = 'produk/print_invoice';
                }

                $pdf_name = 'invoice_'.$noid.'_'.$row->tgl_trx.'.pdf';
                $cache_dir = FCPATH.'uploads/cache/invoice_pdf/';
                if (!is_dir($cache_dir)) {
                    @mkdir($cache_dir, 0775, true);
                }

                $cache_invoice = [
                    'id_invoice' => $row->id_invoice,
                    'id_transaksi' => $row->id_transaksi,
                    'total_bayar' => $row->total_bayar,
                    'jumlah_bayar' => $row->jumlah_bayar,
                    'kembalian' => $row->kembalian,
                    'potongan_harga' => $row->potongan_harga,
                    'cashback' => $row->cashback,
                    'pajak' => $row->pajak,
                    'lunas' => $row->lunas,
                    'tgl_trx' => $row->tgl_trx,
                    'jam_order' => $row->jam_order,
                    'tgl_ambil' => $row->tgl_ambil,
                    'id_user' => $row->id_user,
                    'id_marketing' => $row->id_marketing,
                    'id_desain' => $row->id_desain,
                    'status' => $row->status,
                    'history' => $row->history,
                    'data_json' => $row->data_json,
                    'id_konsumen' => $row->id_konsumen,
                    'id_dropshiper' => $row->id_dropshiper,
                    'external_order_id' => $row->external_order_id,
                    'channel' => $row->channel,
                    'total_pajak' => $row->total_pajak,
                ];
                $cache_payload = [
                    'view' => $pdf_view,
                    'paper' => [$paperSize, $paperOrientation],
                    'printer' => [
                        'id' => isset($rowc['id']) ? $rowc['id'] : null,
                        'slug' => isset($rowc['slug']) ? $rowc['slug'] : null,
                        'ukuran_kertas' => isset($rowc['ukuran_kertas']) ? $rowc['ukuran_kertas'] : null,
                        'ukuran_font' => isset($rowc['ukuran_font']) ? $rowc['ukuran_font'] : null,
                        'posisi' => isset($rowc['posisi']) ? $rowc['posisi'] : null,
                        'max_item' => isset($rowc['max_item']) ? $rowc['max_item'] : null,
                        'show_footer' => isset($rowc['show_footer']) ? $rowc['show_footer'] : null,
                    ],
                    'invoice' => $cache_invoice,
                    'info' => [
                        'logo' => isset($app_info['logo']) ? $app_info['logo'] : null,
                        'logo_bw' => isset($app_info['logo_bw']) ? $app_info['logo_bw'] : null,
                        'stamp_l' => isset($app_info['stamp_l']) ? $app_info['stamp_l'] : null,
                        'stamp_b' => isset($app_info['stamp_b']) ? $app_info['stamp_b'] : null,
                        'favicon' => isset($app_info['favicon']) ? $app_info['favicon'] : null,
                        'kode_qris' => isset($app_info['kode_qris']) ? $app_info['kode_qris'] : null,
                        'footer_invoice' => isset($app_info['footer_invoice']) ? $app_info['footer_invoice'] : null,
                        'phone' => isset($app_info['phone']) ? $app_info['phone'] : null,
                        'email' => isset($app_info['email']) ? $app_info['email'] : null,
                        'fb' => isset($app_info['fb']) ? $app_info['fb'] : null,
                        'ig' => isset($app_info['ig']) ? $app_info['ig'] : null,
                    ],
                    'konsumen' => $data['konsumen'],
                    'marketing' => $data['marketing'],
                    'detail' => $data['detail'],
                    'total' => isset($data['total']->total) ? $data['total']->total : null,
                    'bdetail' => $data['bdetail'],
                    'cara' => $data['cara'],
                    'bank' => $data['bank'],
                    'show_footer' => $data['show_footer'],
                    'font_size' => $data['font_size'],
                    'font_style_version' => 2,
                    'payment_method_display_version' => 1,
                    'font_wight' => $data['font_wight'],
					'qr' => [
						'content' => $invoice_qr['content'],
						'is_tracking' => $invoice_qr['is_tracking'],
						'file_hash' => $invoice_qr['available'] ? @md5_file($invoice_qr['path']) : null,
					],
                ];
                $cache_key = md5(json_encode($cache_payload));
                $cache_file = $cache_dir.'invoice_'.$row->id_transaksi.'_'.$cache_key.'.pdf';
                if (is_file($cache_file)) {
                    $cached_pdf = @file_get_contents($cache_file);
                    if ($cached_pdf !== false && $cached_pdf !== '') {
                        $this->output
                            ->set_content_type('application/pdf')
                            ->set_header('Content-Disposition: inline; filename="'.$pdf_name.'"')
                            ->set_output($cached_pdf);
                        return;
                    }
                }

                $this->load->library('pdf');
                $html = $this->load->view($pdf_view, $data, true);
                $pdf = new Pdf();
                $pdf->getOptions()->setChroot(FCPATH);
                $pdf->getOptions()->isHtml5ParserEnabled(true);
                $pdf->getOptions()->isRemoteEnabled(true);
                $pdf->setPaper($paperSize, $paperOrientation);
                $pdf->load_html($html);
                $pdf->render();
                $pdf_output = $pdf->output();
                if ($pdf_output !== '') {
                    @file_put_contents($cache_file, $pdf_output);
                }
                $this->output
                    ->set_content_type('application/pdf')
                    ->set_header('Content-Disposition: inline; filename="'.$pdf_name.'"')
                    ->set_output($pdf_output);
                } else {
                $data['cetak'] = 'error';
                $this->load->view('errors/404', $data);
            }
        }
        
        
        /**
            * print_invoice_html.
            *
            * @return void
        */
        public function print_invoice_html($noid = null)
        {
            $noid = decrypt_url($noid);
            $id = ['id_invoice' => $noid];
            $pub = ['pub' => 1]; // printer aktif
            $search = $this->model_app->view_where('invoice', $id);
            
            $printer_id = (int) $this->input->get('printer_id', true);
            if ($printer_id > 0) {
                $cek_printer = $this->db->get_where('printer', ['id' => $printer_id, 'pub' => 1]);
                if ($cek_printer->num_rows() < 1) {
                    $cek_printer = $this->model_app->view_where('printer', $pub);
                }
            } else {
                $cek_printer = $this->model_app->view_where('printer', $pub);
            }
            if ($cek_printer->num_rows() > 0) {
                $rowc = $cek_printer->row_array();
                $data['show_footer'] = $rowc['show_footer'];
                $paperSize = !empty($rowc['ukuran_kertas']) ? $rowc['ukuran_kertas'] : 'A4';
                $paperOrientation = strtolower(trim($rowc['posisi']));
                $paperOrientation = ($paperOrientation == 'landscape') ? 'landscape' : 'portrait';
                $data['paper_size'] = $paperSize;
                $data['paper_orientation'] = $paperOrientation;
                $data['max_item'] = isset($rowc['max_item']) ? (int)$rowc['max_item'] : 33;
                // print thermal
                if ($rowc['slug'] == 'th' and $rowc['pub'] == 1) {
                    if ($search->num_rows() > 0) {
                        $this->session->unset_userdata('cart');
                        $row = $search->row_array();
                        $jml = $row['cetak'] + 1;
                        $this->model_app->update(
                        'tb_users',
                        ['last_invoice' => 0],
                        ['id_user' => $this->iduser]
                        );
                        
                        if ($row['status'] != 'batal'
                        ) {
                            $this->model_app->update('invoice', ['cetak' => $jml, 'status' => 'simpan', 'pos' => 'Y', 'oto' => 6], ['id_invoice' => $noid]);
                        }
                        
                        // marketing
                        $marketing = $this->model_app->view_where('tb_users', ['id_user' => $row['id_marketing']])->row_array();
                        // bayar detail
                        $detail = $this->model_app->produk_cart(['invoice_detail.id_invoice' => $noid]);
                        // total bayar
                        $total = $this->model_app->total_bayar(['bayar_invoice_detail.id_invoice' => $noid])->row();
                        $bdetail = $this->model_app->view_where('bayar_invoice_detail', $id)->result_array();
                        
                        // load library
                        $this->load->library('escpos');
                        // membuat connector printer ke shared printer bernama "printer_a" (yang telah disetting sebelumnya)
                        $connector = new Escpos\PrintConnectors\WindowsPrintConnector($rowc['shared_name']);
                        
                        // membuat objek $printer agar dapat di lakukan fungsinya
                        $printer = new Escpos\Printer($connector);
                        // Get status
                        $printer->initialize();
                        $printer->selectPrintMode(Escpos\Printer::MODE_DOUBLE_HEIGHT); // Setting teks menjadi lebih besar
                        $printer->setJustification(Escpos\Printer::JUSTIFY_CENTER); // Setting teks menjadi rata tengah
                        $printer->text($this->info['title']."\n");
                        
                        $printer->initialize();
                        $printer->setJustification(Escpos\Printer::JUSTIFY_CENTER);
                        $printer->text(strip_word_html(base64_decode($this->info['deskripsi']))."\n");
                        $printer->text("\n");
                        
                        // Data transaksi
                        $printer->initialize();
                        $printer->text('Invoice : #'.$row['id_transaksi']."\n");
                        $printer->text('Kasir : '.$marketing['nama_lengkap']."\n");
                        $printer->text('Waktu : '.tgl_indo($row['tgl_trx'])."\n");
                        
                        // Membuat tabel
                        $printer->initialize(); // Reset bentuk/jenis teks
                        
                        $printer->text("-----------------------------------------------\n");
                        $printer->text(buatBaris4Kolom('Deskripsi', 'QTY', 'Harga', 'Subtotal'));
                        $printer->text("-----------------------------------------------\n");
                        $no = 1;
                        $totalb = 0;
                        $subtotal = 0;
                        $sisa = 0;
                        $diskon = 0;
                        foreach ($detail as $val) {
                            $diskon = $val['jumlah'] * $val['harga'] * $val['diskon'] / 100;
                            $totalb = $val['jumlah'] * $val['harga'] - $diskon;
                            $subtotal += $totalb;
                            
                            $printer->text(buatBaris4Kolom($val['title'].' '.$val['ukuran'], $val['jumlah'].get_satuan($val['satuan']), rp($val['harga']), rp($totalb)));
                            $printer->text('Ket: '.$val['nbahan'].' '.$val['keterangan']."\n");
                            
                            $pajak = ($subtotal * $row['pajak']) / 100;
                            $sisa = $pajak + $subtotal - $total->total;
                            $cek_bayar = $pajak + $subtotal;
                        }
                        
                        $printer->text("-----------------------------------------------\n");
                        $printer->text(buatBaris4Kolom('', '', 'TOTAL', rp($subtotal)));
                        $select = 'pajak, total_bayar AS total, jumlah_bayar, kembalian';
                        $where = ['id_invoice' => $noid];
                        $tdetail = $this->model_app->cek_total('invoice', $select, $where);
                        
                        if ($sisa == 0) {
                            if ($tdetail->jumlah_bayar > $total->total) {
                                $jumlah_bayar = $tdetail->jumlah_bayar;
                                $sisa = $tdetail->kembalian;
                                } else {
                                $jumlah_bayar = $total->total;
                            }
                            $printer->text(buatBaris4Kolom('', '', 'BAYAR', rp($total->total)));
                            $printer->text(buatBaris4Kolom('', '', 'KEMBALIAN', rp($sisa)));
                            } else {
                            $printer->text("\n");
                            $printer->text(buatBaris4Kolom('', '', 'BAYAR', rp($total->total)));
                            $printer->text(buatBaris4Kolom('', '', 'SISA', rp($sisa)));
                        }
                        
                        $printer->text("\n");
                        // Pesan penutup
                        $printer->initialize();
                        $printer->setJustification(Escpos\Printer::JUSTIFY_CENTER);
                        $printer->text("Terima kasih\n");
                        $printer->text('HP: '.$this->info['phone']."\n");
                        $printer->text(' EMAIL : '.$this->info['email']."\n");
                        $printer->feed(3); // mencetak 5 baris kosong agar terangkat (pemotong kertas saya memiliki jarak 5 baris dari toner)
                        // echo "ok";
                        
                        $printer->close();
                        } else {
                        echo 'gagal';
                    }
                    // print thermal 58
                    } elseif ($rowc['slug'] == 'th58' and $rowc['pub'] == 1) {
                        if ($search->num_rows() > 0) {
                            $this->session->unset_userdata('cart');
                            $row = $search->row_array();
                            $jml = $row['cetak'] + 1;
                            $this->model_app->update(
                                'tb_users',
                                ['last_invoice' => 0],
                                ['id_user' => $this->iduser]
                            );
                            
                            if ($row['status'] != 'batal') {
                                $this->model_app->update(
                                    'invoice',
                                    ['cetak' => $jml, 'status' => 'simpan', 'pos' => 'Y', 'oto' => 6],
                                    ['id_invoice' => $noid]
                                );
                            }
                            
                            // marketing
                            $marketing = $this->model_app->view_where('tb_users', ['id_user' => $row['id_marketing']])->row_array();
                            // detail item
                            $detail    = $this->model_app->produk_cart(['invoice_detail.id_invoice' => $noid]);
                            // total bayar
                            $total     = $this->model_app->total_bayar(['bayar_invoice_detail.id_invoice' => $noid])->row();
                            $bdetail   = $this->model_app->view_where('bayar_invoice_detail', $id)->result_array();
                            
                            // load library
                            $this->load->library('escpos');
                            // membuat connector printer ke shared printer yang sudah di-share di Windows
                            $connector = new Escpos\PrintConnectors\WindowsPrintConnector($rowc['shared_name']);
                            
                            // membuat objek $printer agar dapat di lakukan fungsinya
                            $printer = new Escpos\Printer($connector);

                            // cetak logo
                            try {
                                $logoPath = FCPATH . 'uploads/logo struk.png'; // file logo
                                $logo = EscposImage::load($logoPath, false);

                                // Rata tengah logo
                                $printer->setJustification(Printer::JUSTIFY_CENTER);
                                $printer->bitImage($logo);

                                // Kembali ke rata kiri
                                $printer->setJustification(Printer::JUSTIFY_LEFT);
                            } catch (Exception $e) {
                                $printer->text("Gagal load logo: " . $e->getMessage() . "\n");
                            }

                            // Inisialisasi printer (reset gaya teks)
                            $printer->initialize();
                            $printer->text("================================\n");

                            // Header toko
                            $printer->initialize();
                            $printer->setJustification(Escpos\Printer::JUSTIFY_CENTER);
                            $printer->text($this->info['title']."\n");

                            $printer->initialize();
                            $printer->setJustification(Escpos\Printer::JUSTIFY_CENTER);
                            $printer->text(strip_word_html(base64_decode($this->info['deskripsi']))."\n");

                            $printer->initialize();
                            $printer->setJustification(Escpos\Printer::JUSTIFY_CENTER);
                            $printer->text($this->info['phone']."\n");
                            $printer->text("\n");

                            // Nomor transaksi gede
                            $printer->initialize();
                            $printer->setJustification(Escpos\Printer::JUSTIFY_CENTER);
                            $printer->selectPrintMode(Escpos\Printer::MODE_DOUBLE_HEIGHT);
                            $printer->selectPrintMode(Escpos\Printer::MODE_DOUBLE_HEIGHT);
                            $printer->selectPrintMode(Escpos\Printer::MODE_DOUBLE_WIDTH);
                            $printer->setEmphasis(true);
                            $printer->text(''.$row['id_transaksi']."\n");
                            $printer->setEmphasis(true);
                            $printer->text("\n");

                            // Ambil data pelanggan
                            $konsumen = $this->model_app->view_where('konsumen', ['id' => $row['id_konsumen']])->row_array();
                            $nama_pelanggan = $konsumen ? $konsumen['nama'] : 'Umum';

                            // Data transaksi (kasir, tanggal, jam, pelanggan)
                            $printer->initialize();
                            $printer->text('Kasir     : '.$marketing['nama_lengkap']."\n");
                            $printer->text('Tanggal   : '.tgl_indo($row['tgl_trx'])."\n");
                            $printer->text('Jam       : '.invoice_order_time($row)."\n");

                            $idkonsumen = $row['id_konsumen'];
                            $cek = cekKonsumen($idkonsumen); // fungsi helper yg balikin array info konsumen
                            $panggilan = isset($cek['panggilan']) ? $cek['panggilan'] : '';
                            $printer->text('Pelanggan : '.$panggilan.' '.$nama_pelanggan."\n");

                            // Garis sebelum detail item
                            $printer->initialize();
                            $printer->text("================================\n");

                            $totalb    = 0;
                            $subtotal  = 0;
                            $sisa      = 0;
                            $diskon    = 0;

                            $itemCount    = count($detail); // jumlah total item
                            $currentIndex = 0;

                            foreach ($detail as $val) {
                                $currentIndex++;

                                // Hitung diskon dan total per item
                                $diskon  = $val['jumlah'] * $val['harga'] * $val['diskon'] / 100;
                                $totalb  = $val['jumlah'] * $val['harga'] - $diskon;
                                $subtotal += $totalb;

                                // --- Ambil data FINISHING / CATATAN persis dari kirim_invoice.php terbaru ---
                                // decode $val['detail'] -> object { data: [ {title, isi}, ... ] }
                                $finishingData = [];
                                if (isset($val['detail']) && $val['detail'] !== '') {
                                    $decoded = json_decode($val['detail']);
                                    if (is_object($decoded) && isset($decoded->data) && is_array($decoded->data)) {
                                        $finishingData = $decoded->data; // array of objects
                                    }
                                }
                                $hasNotes = !empty($finishingData);

                                // simpan keterangan utama
                                $ket = trim($val['keterangan']);

                                // === Tampilkan nama bahan dan ukuran/qty ===
                                $printer->text(Text::fill($val['nbahan'], 32)."\n");
                                $printer->text(
                                    Text::fill('Ukuran:'.$val['ukuran'], 20)
                                    . 'Qty:'.$val['jumlah']
                                    ."\n"
                                );

                                // === KETERANGAN (opsional, hanya jika ada) ===
                                if ($ket !== '') {
                                    $printer->text('Ket:'.$ket."\n");
                                }

                                // === CATATAN FINISHING (WAJIB SAMA POLA DENGAN kirim_invoice.php terbaru) ===
                                if ($hasNotes) {
                                    // header "Catatan:"
                                    $printer->text("Catatan:\n");

                                    // looping setiap detail finishing
                                    foreach ($finishingData as $fn) {
                                        $t = isset($fn->title) ? trim($fn->title) : '';
                                        $i = isset($fn->isi)   ? trim($fn->isi)   : '';
                                        $lineTxt = trim($t . ($i !== '' ? ' '.$i : ''));
                                        if ($lineTxt === '') continue;

                                        // tiap baris catatan finishing dicetak seperti di kirim_invoice.php
                                        $printer->text($lineTxt."\n");
                                    }
                                }

                                // === RINCIAN HARGA ITEM ===
                                $printer->text(
                                    Text::fill('  '.$val['jumlah'].' '.get_satuan($val['satuan']), 12).
                                    Text::fill('x', 3).
                                    Text::fill(number_format($val['harga'], 0, ',', '.'), 7).
                                    Text::fillRight(number_format($totalb, 0, ',', '.'), 10)."\n"
                                );

                                // Garis pemisah antar item (bukan setelah item terakhir)
                                if ($currentIndex < $itemCount) {
                                    $printer->text("________________________________\n");
                                }
                            }

                            // Hitung pajak dan sisa
                            $pajak     = ($subtotal * $row['pajak']) / 100;
                            $sisa      = $pajak + $subtotal - $total->total;
                            $cek_bayar = $pajak + $subtotal;

                            // Helper rata kiri/kanan 32 kolom
                            function rataKiriKanan($kiri, $kanan, $lebar = 32) {
                                $kiri  = trim($kiri);
                                $kanan = trim($kanan);
                                $spasi = $lebar - strlen($kiri) - strlen($kanan);
                                if ($spasi < 0) $spasi = 1; // minimal 1 spasi
                                return $kiri . str_repeat(" ", $spasi) . $kanan . "\n";
                            }

                            $printer->text("================================\n");
                            $printer->setEmphasis(true);
                            $printer->text(rataKiriKanan('TOTAL', rp($subtotal)));
                            $printer->setEmphasis(false);
                            $printer->selectPrintMode(); // reset ke normal

                            $select = 'pajak, total_bayar AS total, jumlah_bayar, kembalian';
                            $where  = ['id_invoice' => $noid];
                            $tdetail = $this->model_app->cek_total('invoice', $select, $where);

                            if ($sisa == 0) {
                                if ($tdetail->jumlah_bayar > $total->total) {
                                    $jumlah_bayar = $tdetail->jumlah_bayar;
                                    $sisa         = $tdetail->kembalian;
                                } else {
                                    $jumlah_bayar = $total->total;
                                    $sisa         = $tdetail->kembalian;
                                }
                                $printer->text(rataKiriKanan('BAYAR', rp($total->total)));
                                $printer->text(rataKiriKanan('KEMBALIAN', rp($sisa)));
                            } else {
                                $printer->text(rataKiriKanan('BAYAR', rp($total->total)));
                                $printer->text(rataKiriKanan('SISA', rp($sisa)));
                            }

                            $printer->text("================================\n");

							// Cetak QRIS atau QR tracking order ketika Kode QRIS dikosongkan.
                            try {
								$invoiceQr = invoice_qr_context($noid, info()['kode_qris']);
								$logoPath = $invoiceQr['path'];
								$resizedPath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'qr_print_'.uniqid('', true).'.png';

                                // Resize gambar ke 200x200 px pakai GD
                                list($orig_width, $orig_height) = getimagesize($logoPath);
								$src_image = imagecreatefromstring(file_get_contents($logoPath));

                                $new_width  = 200;
                                $new_height = 200;

                                $resized_image = imagecreatetruecolor($new_width, $new_height);
                                imagecopyresampled(
                                    $resized_image,
                                    $src_image,
                                    0, 0, 0, 0,
                                    $new_width, $new_height,
                                    $orig_width, $orig_height
                                );

                                // Simpan hasil resize
                                imagepng($resized_image, $resizedPath);
                                imagedestroy($src_image);
                                imagedestroy($resized_image);

                                // Load dan cetak QRIS
								$logo = EscposImage::load($resizedPath, false);
								@unlink($resizedPath);

                                $printer->setJustification(Printer::JUSTIFY_CENTER);
								if ($invoiceQr['is_tracking']) {
									$printer->text("Scan QR Code untuk\nTracking Pesanan\n");
								}
                                $printer->bitImage($logo);
                                $printer->setJustification(Printer::JUSTIFY_LEFT);

                            } catch (Exception $e) {
                                $printer->text("Gagal load logo: " . $e->getMessage() . "\n");
                            }

                            // Footer / disclaimer
                            $printer->initialize();
                            $printer->text("================================\n");

                            $printer->text("\n");
                            $printer->initialize();
                            $printer->setJustification(Escpos\Printer::JUSTIFY_CENTER);
                            $printer->text("Terima kasih\n");
                            $printer->text("Barang Yang Sudah Dibeli\n");
                            $printer->text("Tidak Dapat Di Batalkan !\n");
                            $printer->text("Barang tidak di ambil lebih dari 1 bulan\n");
                            $printer->text("BUKAN TANGGUNG JAWAB KAMI!!!\n");
                            $printer->feed(3);
                            
                            $printer->close();
                        } else {
                            echo 'gagal';
                        }

                    // lanjut kondisi lain (direct58 / direct85)
                    } elseif (($rowc['slug'] == 'direct58' or $rowc['slug'] == 'direct85') and $rowc['pub'] == 1) {
                        $data['waicon'] = ['color' => base_url().'assets/img/wa_icon.png', 'bw' => base_url().'assets/img/wa_icon_bw.png'];
                        $data['mail']   = ['color' => base_url().'assets/img/gmail_icon.png', 'bw' => base_url().'assets/img/gmail_icon_bw.png'];
                        $data['fbicon'] = ['color' => base_url().'assets/img/fb_icon.png', 'bw' => base_url().'assets/img/fb_icon_bw.png'];
                        $data['igicon'] = ['color' => base_url().'assets/img/ig_icon.png', 'bw' => base_url().'assets/img/ig_icon_bw.png'];

                        $data['logo_lunas']  = base_url().'uploads/'.info()['logo'];
                        $data['logo_blunas'] = base_url().'uploads/'.info()['logo_bw'];
                        $data['lunas']       = base_url().'uploads/'.info()['stamp_l'];
                        $data['blunas']      = base_url().'uploads/'.info()['stamp_b'];
                        $data['favicon']     = base_url().'uploads/'.info()['favicon'];
						$invoiceQr = invoice_qr_context($noid, info()['kode_qris']);
						$data['qris'] = $invoiceQr['content'];
						$data['kode_qris'] = $invoiceQr['url'];
						$data['qr_available'] = $invoiceQr['available'];
						$data['qr_is_tracking'] = $invoiceQr['is_tracking'];
						$data['qr_label'] = $invoiceQr['label'];
                        $data['html']        = 'Y';
                        //...
                        // (kode lanjutan direct58/direct85 tetap sama seperti sebelumnya)

                    // print dotmatrix
                    } elseif ($rowc['slug'] == 'dotmatrix' and $rowc['pub'] == 1) {
                    // icon
                    $data['waicon'] = ['color' => base_url().'assets/img/wa_icon.png', 'bw' => base_url().'assets/img/wa_icon_bw.png'];
                    
                    $data['mail'] = ['color' => base_url().'assets/img/gmail_icon.png', 'bw' => base_url().'assets/img/gmail_icon_bw.png'];
                    $data['fbicon'] = ['color' => base_url().'assets/img/fb_icon.png', 'bw' => base_url().'assets/img/fb_icon_bw.png'];
                    $data['igicon'] = ['color' => base_url().'assets/img/ig_icon.png', 'bw' => base_url().'assets/img/ig_icon_bw.png'];
                    
                    $data['logo_lunas'] = base_url().'uploads/'.info()['logo'];
                    $data['logo_blunas'] = base_url().'uploads/'.info()['logo_bw'];
                    $data['lunas'] = base_url().'uploads/'.info()['stamp_l'];
                    $data['blunas'] = base_url().'uploads/'.info()['stamp_b'];
                    $data['favicon'] = base_url().'uploads/'.info()['favicon'];
                    $data['html'] = 'Y';
                    $data['max_item'] = $rowc['max_item'];
                    $data['font_size'] = $rowc['ukuran_font'];
                    $data['font_wight'] = 'normal';
                    if ($search->num_rows() > 0) {
                        $this->session->unset_userdata('cart');
                        $row = $search->row();
                        $jml = $row->cetak + 1;
                        $this->model_app->update('tb_users', ['last_invoice' => 0], ['id_user' => $this->session->idu]);
                        if ($row->status != 'batal') {
                            $this->model_app->update('invoice', ['cetak' => $jml, 'status' => 'simpan', 'pos' => 'Y', 'oto' => 6], ['id_invoice' => $noid]);
                        }
                        // cek sisa
                        $_total = 'ROUND(SUM((`invoice_detail`.`jumlah` * harga) - (`invoice_detail`.`jumlah` * harga) * (`invoice_detail`.`diskon`/100))) AS sisa';
                        $cari_total = $this->model_app->cek_total('invoice_detail', $_total, ['id_invoice' => $noid]);
                        $data['sisanya'] = $cari_total->sisa;
                        
                        $data['cetak'] = $row;
                        $data['info'] = info();
                        $konsumen = $this->model_app->view_where('konsumen', ['id' => $row->id_konsumen])->row_array();
                        
                        if ($konsumen['max_utang'] > 0 and $jml <= 1) {
                            $max_utang = $konsumen['max_utang'] - 1;
                            $this->model_app->update('konsumen', ['max_utang' => $max_utang], ['id' => $row->id_konsumen]);
                        }
                        
                        $data['konsumen'] = $konsumen;
                        $data['marketing'] = $this->model_app->view_where('tb_users', ['id_user' => $row->id_marketing])->row_array();
                        $data['detail'] = $this->model_app->produk_cart(['invoice_detail.id_invoice' => $noid]);
                        $data['total'] = $this->model_app->total_bayar(['bayar_invoice_detail.id_invoice' => $noid])->row();
                        $select = 'pajak,potongan_harga,cashback, total_bayar AS total, jumlah_bayar, kembalian';
                        $where = ['id_invoice' => $noid];
                        $tdetail = $this->model_app->cek_total('invoice', $select, $where);
                        $data['tdetail'] = $tdetail->total;
                        $data['pajak'] = $tdetail->pajak;
                        $data['jumlah_bayar'] = $tdetail->jumlah_bayar;
                        $data['potongan_harga'] = $tdetail->potongan_harga;
                        $data['cashback'] = $tdetail->cashback;
                        $data['kembalian'] = $tdetail->kembalian;
                        $_diskon = 'SUM(diskon) AS `disc`';
                        $cdiskon = $this->model_app->cek_total('invoice_detail', $_diskon, $where);
                        $data['cdiskon'] = $cdiskon->disc;
                        $_select = 'COUNT(id) AS `jml`';
                        $cdetail = $this->model_app->cek_total('bayar_invoice_detail', $_select, $where);
                        $data['cdetail'] = $cdetail->jml;
                        // bayar detail
                        $data['bdetail'] = $this->model_app->view_where('bayar_invoice_detail', $id)->result_array();
                        // /
                        $data['cara'] = $this->model_app->cara_bayar(['`bayar_invoice_detail`.`id_invoice`' => $noid]);
                        $data['bank'] = $this->model_app->view_where('rekening_bank', ['footer_invoice' => 1, 'publish' => 'Y'])->result();
                        
                        $this->load->view('produk/print_invoice', $data);
                        } else {
                        $data['cetak'] = 'error';
                        $this->load->view('errors/404', $data);
                    }
                    // print html
                    } else {
                    // icon
                    $data['waicon'] = ['color' => base_url().'assets/img/wa_icon.png', 'bw' => base_url().'assets/img/wa_icon_bw.png'];
                    $data['mail'] = ['color' => base_url().'assets/img/gmail_icon.png', 'bw' => base_url().'assets/img/gmail_icon_bw.png'];
                    $data['fbicon'] = ['color' => base_url().'assets/img/fb_icon.png', 'bw' => base_url().'assets/img/fb_icon_bw.png'];
                    $data['igicon'] = ['color' => base_url().'assets/img/ig_icon.png', 'bw' => base_url().'assets/img/ig_icon_bw.png'];
                    
                    $data['logo_lunas'] = base_url().'uploads/'.info()['logo'];
                    $data['logo_blunas'] = base_url().'uploads/'.info()['logo_bw'];
                    $data['lunas'] = base_url().'uploads/'.info()['stamp_l'];
                    $data['blunas'] = base_url().'uploads/'.info()['stamp_b'];
                    $data['favicon'] = base_url().'uploads/'.info()['favicon'];
					$invoice_qr = invoice_qr_context($noid, info()['kode_qris']);
					$data['qris'] = $invoice_qr['content'];
					$data['kode_qris'] = $invoice_qr['url'];
					$data['qr_available'] = $invoice_qr['available'];
					$data['qr_is_tracking'] = $invoice_qr['is_tracking'];
					$data['qr_label'] = $invoice_qr['label'];
                    
                    $data['html'] = 'Y';
                    $data['font_size'] = $rowc['ukuran_font'];
                    $data['font_wight'] = 'normal';
                    if ($search->num_rows() > 0) {
                        $this->session->unset_userdata('cart');
                        $row = $search->row();
                        $jml = $row->cetak + 1;
                        $this->model_app->update('tb_users', ['last_invoice' => 0], ['id_user' => $this->session->idu]);
                        if ($row->status != 'batal') {
                            $this->model_app->update('invoice', ['cetak' => $jml, 'status' => 'simpan', 'pos' => 'Y', 'oto' => 6], ['id_invoice' => $noid]);
                        }
                        // cek sisa
                        $_total = 'ROUND(SUM((`invoice_detail`.`jumlah` * harga) - (`invoice_detail`.`jumlah` * harga) * (`invoice_detail`.`diskon`/100))) AS sisa';
                        $cari_total = $this->model_app->cek_total('invoice_detail', $_total, ['id_invoice' => $noid]);
                        $data['sisanya'] = $cari_total->sisa;
                        
                        $data['cetak'] = $row;
                        $data['info'] = info();
                        $konsumen = $this->model_app->view_where('konsumen', ['id' => $row->id_konsumen])->row_array();
                        
                        if ($konsumen['max_utang'] > 0 and $jml <= 1) {
                            $max_utang = $konsumen['max_utang'] - 1;
                            $this->model_app->update('konsumen', ['max_utang' => $max_utang], ['id' => $row->id_konsumen]);
                        }
                        
                        $data['konsumen'] = $konsumen;
                        $data['marketing'] = $this->model_app->view_where('tb_users', ['id_user' => $row->id_marketing])->row_array();
                        $data['detail'] = $this->model_app->produk_cart(['invoice_detail.id_invoice' => $noid]);
                        $data['total'] = $this->model_app->total_bayar(['bayar_invoice_detail.id_invoice' => $noid])->row();
                        $select = 'pajak,potongan_harga,cashback, total_bayar AS total, jumlah_bayar, kembalian';
                        $where = ['id_invoice' => $noid];
                        $tdetail = $this->model_app->cek_total('invoice', $select, $where);
                        $data['tdetail'] = $tdetail->total;
                        $data['pajak'] = $tdetail->pajak;
                        $data['jumlah_bayar'] = $tdetail->jumlah_bayar;
                        $data['potongan_harga'] = $tdetail->potongan_harga;
                        $data['cashback'] = $tdetail->cashback;
                        $data['kembalian'] = $tdetail->kembalian;
                        $_diskon = 'SUM(diskon) AS `disc`';
                        $cdiskon = $this->model_app->cek_total('invoice_detail', $_diskon, $where);
                        $data['cdiskon'] = $cdiskon->disc;
                        $_select = 'COUNT(id) AS `jml`';
                        $cdetail = $this->model_app->cek_total('bayar_invoice_detail', $_select, $where);
                        $data['cdetail'] = $cdetail->jml;
                        // bayar detail
                        $data['bdetail'] = $this->model_app->view_where('bayar_invoice_detail', $id)->result_array();
                        // /
                        $data['cara'] = $this->model_app->cara_bayar(['`bayar_invoice_detail`.`id_invoice`' => $noid]);
                        $data['bank'] = $this->model_app->view_where('rekening_bank', ['footer_invoice' => 1, 'publish' => 'Y'])->result();
                        
                        $this->load->view('produk/print_invoice', $data);
                        } else {
                        $data['cetak'] = 'error';
                        $this->load->view('errors/404', $data);
                    }
                }
            }
        }
        
        /**
            * finishing.
            *
            * @return void
        */
        public function finishing()
        {
            $data['title'] = 'Data Produk | '.info()['title'];
            $cari_finishing = $this->model_app->view_where('invoice_detail', ['id_rincianinvoice' => $this->input->post('id')]);
            $finishing = '';
            $nama_produk = '-';
            if ($cari_finishing->num_rows() > 0) {
                $rows = $cari_finishing->row();
                $finishing = $rows->detail;
                $row = $this->model_app->view_where('produk', ['id' => $rows->id_produk])->row();
                $nama_produk = $row->title;
            }
            
            $data['detail'] = [
            'invoice' => xss_filter($this->input->post('invoice'), 'xss'),
            'idr' => xss_filter($this->input->post('id'), 'xss'),
            'kode' => xss_filter($this->input->post('kode'), 'xss'),
            'jenis' => xss_filter($this->input->post('jenis'), 'xss'),
            ];
            
            $data['nama_produk'] = $nama_produk;
            $data['desain'] = $this->model_app->view_where('tb_users', ['aktif' => 'Y'])->result();
            $data['finishing'] = json_decode($finishing);
            $this->load->view('produk/detail', $data);
        }
        
        /**
            * hapus_finishing.
            *
            * @return void
        */
        public function hapus_finishing()
        {
            cek_nput_post('GET');
            simpan_demo('Hapus');
            $kode = xss_filter($this->input->post('kode'), 'xss');
            $id_finish = xss_filter($this->input->post('id'), 'xss');
            
            $cari = $this->model_app->view_where('invoice_detail', ['id_rincianinvoice' => $kode]);
            $data = [];
            if ($cari->num_rows() > 0) {
                $row = $cari->row();
                $detail = $row->detail;
                if (!empty($detail)) {
                    $json = json_decode($detail);
                    foreach ($json->data as $key => $entry) {
                        if ($entry->id == $id_finish) {
                            unset($entry);
                            } else {
                            $new[] = $entry;
                        }
                    }
                    $data['kode'] = $kode;
                    $data['tipe'] = 'finishing';
                    $data['data'] = array_values($new);
                    $updateData = json_encode($data);
                    $res = $this->model_app->update('invoice_detail', ['detail' => $updateData], ['id_rincianinvoice' => $kode]);
                    if ($res['status'] == 'ok') {
                        $data = ['status' => 200];
                        } else {
                        $data = ['status' => 400];
                    }
                    } else {
                    $data = ['status' => 400];
                }
                } else {
                $data = ['status' => 400];
            }
            echo json_encode($data);
        }
        
        /**
            * update_finishing.
            *
            * @return void
        */
        public function update_finishing()
        {
            cek_nput_post('GET');
            // simpan_demo('Simpan');
            $id_finish = $this->input->post('id_finish', true);
            $title = $this->input->post('title', true);
            $isi = $this->input->post('isi', true);
            $gambar = $this->input->post('gambar', true);
            $tipe = $this->input->post('tipe');
            $kode = xss_filter($this->input->post('kode'), 'sql');
            $kode_invoice = xss_filter($this->input->post('kode_invoice'), 'sql');
            if ($tipe != 'desain') {
                // dump($kode_invoice);
                $this->form_validation->set_rules([
                [
                'field' => 'isi[]',
                'label' => 'Keterangan',
                'rules' => 'required|trim|min_length[3]|max_length[50]',
                'errors' => [
                'required' => '%s. Harus diisi.',
                'min_length' => '%s minimal 3 digit.',
                'max_length' => '%s minimal 50 digit.',
                ],
                ],
                ]);
                
                if ($this->form_validation->run() == false) {
                    $response['status'] = false;
                    $response['msg'] = validation_errors();
                    $this->output
                    ->set_status_header(200)
                    ->set_content_type('application/json', 'utf-8')
                    ->set_output(json_encode($response, JSON_HEX_APOS | JSON_HEX_QUOT))
                    ->_display();
                    exit;
                }
            }
            if ($tipe == 'desain') {
                $id_desain = xss_filter($this->input->post('id_desain'), 'xss');
                $where = ['id_invoice' => $kode_invoice];
                $_data = ['id_desain' => $id_desain];
                $_where = ['id_rincianinvoice' => $kode];
                $res = $this->model_app->update('invoice', $_data, $where);
                $this->model_app->update('invoice_detail', $_data, $_where);
                if ($res['status'] == 'ok') {
                    $data = ['ok' => 'ok', 'msg' => 'ok'];
                    } else {
                    $data = ['ok' => 'err', 'msg' => 'error'];
                }
                echo json_encode($data);
                } else {
                $data = [];
                $data['kode'] = $kode;
                $data['tipe'] = 'finishing';
                $index = 0;
                foreach ($id_finish as $str) {
                    $gambar_path = '';
                    if (is_array($gambar) && isset($gambar[$index])) {
                        $gambar_path = xss_filter($gambar[$index], 'xss');
                    }
                    $data['data'][] = [
                    'id' => $str,
                    'isi' => xss_filter($isi[$index], 'xss'),
                    'gambar' => $gambar_path,
                    ];
                    
                    ++$index;
                }
                
                $json_update = json_encode($data);
                
                if (!empty($json_update)) {
                    $_data = ['detail' => $json_update];
                    $where = ['id_rincianinvoice' => $kode];
                    $res = $this->model_app->update('invoice_detail', $_data, $where);
                    if ($res['status'] == 'ok') {
                        $data = ['ok' => 'ok', 'msg' => 'ok'];
                        } else {
                        $data = ['ok' => 'err', 'msg' => 'error'];
                    }
                    echo json_encode($data);
                }
            }
        }

        /**
            * upload_finishing_image.
            *
            * @return void
        */
        public function upload_finishing_image()
        {
            cek_nput_post('GET');
            $this->output->set_content_type('application/json', 'utf-8');

            if (empty($_FILES['gambar']['name']) || empty($_FILES['gambar']['tmp_name'])) {
                echo json_encode(['status' => false, 'msg' => 'File gambar belum dipilih']);
                return;
            }

            if (!isset($_FILES['gambar']['error']) || (int) $_FILES['gambar']['error'] !== UPLOAD_ERR_OK) {
                echo json_encode(['status' => false, 'msg' => 'Upload gagal, coba ulangi']);
                return;
            }

            $max_bytes = 4 * 1024 * 1024; // 4MB
            if (!empty($_FILES['gambar']['size']) && (int) $_FILES['gambar']['size'] > $max_bytes) {
                echo json_encode(['status' => false, 'msg' => 'Ukuran gambar maksimal 4MB']);
                return;
            }

            $original_name = basename((string) $_FILES['gambar']['name']);
            $ext = strtolower(pathinfo($original_name, PATHINFO_EXTENSION));
            $allowed_ext = ['jpg', 'jpeg', 'png', 'webp'];
            if (!in_array($ext, $allowed_ext, true)) {
                echo json_encode(['status' => false, 'msg' => 'Format gambar harus jpg/jpeg/png/webp']);
                return;
            }

            $tmp_file = (string) $_FILES['gambar']['tmp_name'];
            if (!is_uploaded_file($tmp_file)) {
                echo json_encode(['status' => false, 'msg' => 'File upload tidak valid']);
                return;
            }

            $mime = '';
            if (function_exists('finfo_open')) {
                $finfo = @finfo_open(FILEINFO_MIME_TYPE);
                $mime = $finfo ? @finfo_file($finfo, $tmp_file) : '';
                if ($finfo) {
                    @finfo_close($finfo);
                }
                $allowed_mime = ['image/jpeg', 'image/pjpeg', 'image/png', 'image/webp'];
                if (!empty($mime) && !in_array($mime, $allowed_mime, true)) {
                    echo json_encode(['status' => false, 'msg' => 'File bukan gambar yang didukung']);
                    return;
                }
            }

            $upload_dir = FCPATH . 'uploads/finishing/';
            if (!is_dir($upload_dir)) {
                @mkdir($upload_dir, 0775, true);
            }
            if (!is_dir($upload_dir) || !is_writable($upload_dir)) {
                echo json_encode(['status' => false, 'msg' => 'Folder upload tidak bisa ditulis']);
                return;
            }

            $token = function_exists('random_bytes')
                ? bin2hex(random_bytes(16))
                : md5(uniqid((string) mt_rand(), true));
            $filename = $token . '.webp';
            $dest = $upload_dir . $filename;

            $converted = bm_convert_image_path_to_webp($tmp_file, $dest, 80, $ext, $mime);
            if (!$converted['status']) {
                if (is_file($dest)) {
                    @unlink($dest);
                }
                echo json_encode(['status' => false, 'msg' => $converted['message']]);
                return;
            }

            $rel_path = 'uploads/finishing/' . $filename;
            $display_name = pathinfo($original_name, PATHINFO_FILENAME) . '.webp';
            echo json_encode([
                'status' => true,
                'msg' => 'Upload berhasil',
                'path' => $rel_path,
                'url' => base_url($rel_path),
                'name' => $filename,
                'display_name' => $display_name,
            ]);
        }
        
        /**
            * ajax.
            *
            * @return void
        */
        public function ajax()
        {
            cek_nput_post('GET');
            $type = $this->db->escape_str($this->input->post('type'));
            if (in_array($type, ['bahan_table', 'produk_table', 'produk_table_a', 'produk_table_qr', 'produk_table_tiga', 'produk_table_dua'], true)) {
                $this->ensure_bahan_ukuran_default_schema();
            }
            if ($type == 'jenis_table') {
                $row_num = $_POST['row_num'];
                $name = $_POST['name_startsWith'];
                $query = "SELECT * FROM jenis_cetakan where UPPER(jenis_cetakan) LIKE '%".strtoupper($name)."%' AND pub='Y' AND status='0'";
                $result = $this->db->query($query);
                $data = [];
                foreach ($result->result_array() as $row) {
                    $name = $row['jenis_cetakan'].'|'.$row['id_jenis'].'|'.$row['status'].'|'.$row_num;
                    array_push($data, $name);
                }
                if (!empty($data)) {
                    echo json_encode($data);
                    } else {
                    array_push($data, 'NONE');
                    echo json_encode($data);
                }
            }
            if ($type == 'jenis_pengeluaran') {
                $row_num = $_POST['row_num'];
                $name = $_POST['name_startsWith'];
                $query = "SELECT * FROM jenis_pengeluaran where UPPER(title) LIKE '%".strtoupper($name)."%' AND pub='Y' AND id_akun!='102'";
                $result = $this->db->query($query);
                $data = [];
                foreach ($result->result_array() as $row) {
                    $name = $row['title'].'|'.$row['id_jenis'].'|'.$row_num;
                    array_push($data, $name);
                }
                if (!empty($data)) {
                    echo json_encode($data);
                    } else {
                    array_push($data, 'NONE');
                    echo json_encode($data);
                }
            }
            
            if ($type == 'jenis_pembelian') {
                $row_num = $_POST['row_num'];
                $name = $_POST['name_startsWith'];
                $query = "SELECT * FROM jenis_pengeluaran where UPPER(title) LIKE '%".strtoupper($name)."%'";
                $result = $this->db->query($query);
                $data = [];
                foreach ($result->result_array() as $row) {
                    $name = $row['title'].'|'.$row['id_jenis'].'|'.$row_num;
                    array_push($data, $name);
                }
                if (!empty($data)) {
                    echo json_encode($data);
                    } else {
                    array_push($data, 'NONE');
                    echo json_encode($data);
                }
            }
            if ($type == 'bahan_table') {
                $idprod = $this->input->post('idprod');
                $id_konsumen = $this->input->post('id_konsumen');
                $cek = "SELECT * FROM `produk` where id='$idprod'";
                $res = $this->db->query($cek);
                $rows = $res->row_array();
                $row_num = $this->input->post('row_num');
                $name = $this->input->post('name_startsWith');
                
                $query = "SELECT * FROM bahan where UPPER(title) LIKE '%".strtoupper($name)."%' AND id_produk=$idprod";
                $result = $this->db->query($query);
                $data = [];
                $name = '';
                $satuan = '-';
                foreach ($result->result_array() as $row) {
                    $id_bahan = $row['id'];
                    if ($row['type_harga'] == 1) {
                        $sql1 = $this->db->query("SELECT 
                        `satuan`.`id` AS idsatuan,
                        `satuan`.`satuan`,
                        `satu_harga`.`id_satuan`,
                        `satu_harga`.`harga_jual`,
                        `bahan`.`title`
                        FROM
                        `bahan`
                        INNER JOIN `satu_harga` ON (`bahan`.`id` = `satu_harga`.`id_bahan`)
                        INNER JOIN `satuan` ON (`satu_harga`.`id_satuan` = `satuan`.`id`)
                        WHERE `bahan`.`id` = $id_bahan");
                        if ($sql1->num_rows() > 0) {
                            $harga_jual = $sql1->row()->harga_jual;
                            $satuan = $sql1->row()->id_satuan;
                            } else {
                            $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                            $harga_jual = $sql->row()->harga_jual;
                            $satuan = $sql->row()->id_satuan;
                        }
                        } elseif ($row['type_harga'] == 2) {
                        $sql2 = $this->db->query("SELECT 
                        `satuan`.`id` AS idsatuan,
                        `satuan`.`satuan`,
                        `harga_satuan`.`id_satuan`,
                        `bahan`.`title`,
                        `harga_satuan`.`harga_jual`,
                        `harga_satuan`.`id_bahan`
                        FROM
                        `satuan`
                        INNER JOIN `harga_satuan` ON (`satuan`.`id` = `harga_satuan`.`id_satuan`)
                        INNER JOIN `bahan` ON (`harga_satuan`.`id_bahan` = `bahan`.`id`)
                        WHERE  `harga_satuan`.`id_bahan` = $id_bahan");
                        $harga_jual = $sql2->row()->harga_jual;
                        $satuan = $sql2->row()->idsatuan;
                        } elseif ($row['type_harga'] == 3) {
                        $id_member = $this->model_app->pilih_where('jenis_member', 'konsumen', ['id' => $id_konsumen])->row()->jenis_member;
                        $sql = $this->db->query("SELECT 
                        `harga_member`.`id_bahan`,
                        `harga_member`.`id_satuan`,
                        `harga_member`.`id_member`,
                        `harga_member`.`harga_jual`
                        FROM
                        `satuan`
                        INNER JOIN `harga_member` ON (`satuan`.`id` = `harga_member`.`id_satuan`)
                        INNER JOIN `bahan` ON (`harga_member`.`id_bahan` = `bahan`.`id`)
                        INNER JOIN `konsumen` ON (`harga_member`.`id_member` = `konsumen`.`jenis_member`)
                        WHERE
                        `harga_member`.`id_member` = $id_member AND 
                        `harga_member`.`id_bahan` = $id_bahan
                        GROUP BY
                        `harga_member`.`id_bahan`,
                        `harga_member`.`id_satuan`,
                        `harga_member`.`id_member`,
                        `harga_member`.`harga_jual`");
                        
                        if ($sql->num_rows() > 0) {
                            $harga_jual = $sql->row()->harga_jual;
                            $satuan = $sql->row()->id_satuan;
                            } else {
                            $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                            $harga_jual = $sql->row()->harga_jual;
                            $satuan = $sql->row()->id_satuan;
                        }
                        } elseif ($row['type_harga'] == 4) {
                        $jumlah = $this->input->post('jumlah');
                        $sql = $this->db->query("select id_satuan,harga_jual from range_harga where id_bahan=$id_bahan AND $jumlah between jumlah_minimal and jumlah_maksimal");
                        if ($sql->num_rows() > 0) {
                            $harga_jual = $sql->row()->harga_jual;
                            $satuan = $sql->row()->id_satuan;
                            } else {
                            $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                            $harga_jual = $sql->row()->harga_jual;
                            $satuan = $sql->row()->id_satuan;
                        }
                        } elseif ($row['type_harga'] == 5) {
                        $id_member = $this->model_app->pilih_where('jenis_member', 'konsumen', ['id' => $id_konsumen])->row()->jenis_member;
                        $sql = $this->db->query("select id_satuan,harga_jual from harga_range_member where id_bahan=$id_bahan AND id_member='$id_member' AND 1 between jumlah_minimal and jumlah_maksimal");
                        
                        if ($sql->num_rows() > 0) {
                            $harga_jual = $sql->row()->harga_jual;
                            $satuan = $sql->row()->id_satuan;
                            } else {
                            $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                            $harga_jual = $sql->row()->harga_jual;
                            $satuan = $sql->row()->id_satuan;
                        }
                        } else {
                        $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id_bahan=$id_bahan");
                        $harga_jual = $sql->row()->harga_jual;
                        $satuan = $sql->row()->id_satuan;
                    }
                    $name =
                    $row['title'].'|'.
                    $row['id'].'|'.
                    $satuan.'|'.
                    $harga_jual.'|'.
                    $row['status'].'|'.
                    $row['type_harga'].'|'.
                    $this->resolve_ukuran_default($row, $rows['ukuran'] ?? '').'|'.
                    $row_num;
                    array_push($data, $name);
                }
                if (!empty($data)) {
                    echo json_encode($data);
                    } else {
                    array_push($data, 'Data tidak ditemukan');
                    echo json_encode($data);
                }
            }
            if ($type == 'bahan_table_mod') {
                $idprod = $this->input->post('idprod');
                $id_konsumen = $this->input->post('id_konsumen');
                $cek = "SELECT * FROM `produk` where id='$idprod'";
                $res = $this->db->query($cek);
                $rows = $res->row_array();
                $row_num = $this->input->post('row_num');
                $name = $this->input->post('name_startsWith');
                
                $query = "SELECT * FROM bahan where UPPER(title) LIKE '%".strtoupper($name)."%' AND id_jenis=$rows[id_jenis]";
                $result = $this->db->query($query);
                $data = [];
                $name = '';
                $satuan = '-';
                foreach ($result->result_array() as $row) {
                    $id_bahan = $row['id'];
                    if ($row['type_harga'] == 1) {
                        $sql1 = $this->db->query("SELECT 
                        `satuan`.`id` AS idsatuan,
                        `satuan`.`satuan`,
                        `satu_harga`.`id_satuan`,
                        `satu_harga`.`harga_jual`,
                        `bahan`.`title`
                        FROM
                        `bahan`
                        INNER JOIN `satu_harga` ON (`bahan`.`id` = `satu_harga`.`id_bahan`)
                        INNER JOIN `satuan` ON (`satu_harga`.`id_satuan` = `satuan`.`id`)
                        WHERE `bahan`.`id` = $id_bahan");
                        if ($sql1->num_rows() > 0) {
                            $harga_jual = $sql1->row()->harga_jual;
                            $satuan = $sql1->row()->id_satuan;
                            } else {
                            $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                            $harga_jual = $sql->row()->harga_jual;
                            $satuan = $sql->row()->id_satuan;
                        }
                        } elseif ($row['type_harga'] == 2) {
                        $sql2 = $this->db->query("SELECT 
                        `satuan`.`id` AS idsatuan,
                        `satuan`.`satuan`,
                        `harga_satuan`.`id_satuan`,
                        `bahan`.`title`,
                        `harga_satuan`.`harga_jual`,
                        `harga_satuan`.`id_bahan`
                        FROM
                        `satuan`
                        INNER JOIN `harga_satuan` ON (`satuan`.`id` = `harga_satuan`.`id_satuan`)
                        INNER JOIN `bahan` ON (`harga_satuan`.`id_bahan` = `bahan`.`id`)
                        WHERE  `harga_satuan`.`id_bahan` = $id_bahan");
                        $harga_jual = $sql2->row()->harga_jual;
                        $satuan = $sql2->row()->idsatuan;
                        } elseif ($row['type_harga'] == 3) {
                        $id_member = $this->model_app->pilih_where('jenis_member', 'konsumen', ['id' => $id_konsumen])->row()->jenis_member;
                        $sql = $this->db->query("SELECT 
                        `harga_member`.`id_bahan`,
                        `harga_member`.`id_satuan`,
                        `harga_member`.`id_member`,
                        `harga_member`.`harga_jual`
                        FROM
                        `satuan`
                        INNER JOIN `harga_member` ON (`satuan`.`id` = `harga_member`.`id_satuan`)
                        INNER JOIN `bahan` ON (`harga_member`.`id_bahan` = `bahan`.`id`)
                        INNER JOIN `konsumen` ON (`harga_member`.`id_member` = `konsumen`.`jenis_member`)
                        WHERE
                        `harga_member`.`id_member` = $id_member AND 
                        `harga_member`.`id_bahan` = $id_bahan
                        GROUP BY
                        `harga_member`.`id_bahan`,
                        `harga_member`.`id_satuan`,
                        `harga_member`.`id_member`,
                        `harga_member`.`harga_jual`");
                        
                        if ($sql->num_rows() > 0) {
                            $harga_jual = $sql->row()->harga_jual;
                            $satuan = $sql->row()->id_satuan;
                            } else {
                            $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                            $harga_jual = $sql->row()->harga_jual;
                            $satuan = $sql->row()->id_satuan;
                        }
                        } elseif ($row['type_harga'] == 4) {
                        $jumlah = $this->input->post('jumlah');
                        $sql = $this->db->query("select id_satuan,harga_jual from range_harga where id_bahan=$id_bahan AND $jumlah between jumlah_minimal and jumlah_maksimal");
                        if ($sql->num_rows() > 0) {
                            $harga_jual = $sql->row()->harga_jual;
                            $satuan = $sql->row()->id_satuan;
                            } else {
                            $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                            $harga_jual = $sql->row()->harga_jual;
                            $satuan = $sql->row()->id_satuan;
                        }
                        } elseif ($row['type_harga'] == 5) {
                        $id_member = $this->model_app->pilih_where('jenis_member', 'konsumen', ['id' => $id_konsumen])->row()->jenis_member;
                        $sql = $this->db->query("select id_satuan,harga_jual from harga_range_member where id_bahan=$id_bahan AND id_member='$id_member' AND 1 between jumlah_minimal and jumlah_maksimal");
                        
                        if ($sql->num_rows() > 0) {
                            $harga_jual = $sql->row()->harga_jual;
                            $satuan = $sql->row()->id_satuan;
                            } else {
                            $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                            $harga_jual = $sql->row()->harga_jual;
                            $satuan = $sql->row()->id_satuan;
                        }
                        } else {
                        $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id_bahan=$id_bahan");
                        $harga_jual = $sql->row()->harga_jual;
                        $satuan = $sql->row()->id_satuan;
                    }
                    $name =
                    $row['title'].'|'.
                    $row['id'].'|'.
                    $satuan.'|'.
                    $harga_jual.'|'.
                    $row['status'].'|'.
                    $row['type_harga'].'|'.
                    $row_num;
                    array_push($data, $name);
                }
                if (!empty($data)) {
                    echo json_encode($data);
                    } else {
                    array_push($data, 'Data tidak ditemukan');
                    echo json_encode($data);
                }
            }
            if ($type == 'bahan_table_tiga') {
                $idprod = $_POST['idprod'];
                $cek = "SELECT * FROM `produk` where id='$idprod'";
                $res = $this->db->query($cek);
                $rows = $res->row_array();
                $row_num = $_POST['row_num'];
                $name = $_POST['name_startsWith'];
                // $query = "SELECT * FROM bahan where UPPER(title) LIKE '%".strtoupper($name)."%'";
                $query = "SELECT * FROM bahan where id_jenis=$rows[id_jenis]";
                $result = $this->db->query($query);
                $data = [];
                $name = '';
                $satuan = '-';
                foreach ($result->result_array() as $row) {
                    $name =
                    $row['title'].'|'.
                    $row['id'].'|'.
                    $row['id_satuan'].'|'.
                    $row['harga'].'|'.
                    $row['status'].'|'.
                    $row['type_harga'].'|'.
                    $row_num;
                    array_push($data, $name);
                }
                if (!empty($data)) {
                    echo json_encode($data);
                    } else {
                    array_push($data, 'Data tidak ditemukan');
                    echo json_encode($data);
                }
            }
            
            if ($type == 'pembelian_bahan_table') {
                $row_num = $_POST['row_num'];
                $name = $_POST['name_startsWith'];
                $query = "SELECT * FROM bahan where UPPER(title) LIKE '%".strtoupper($name)."%'";
                $result = $this->db->query($query);
                $data = [];
                $name = '';
                $satuan = '-';
                foreach ($result->result_array() as $row) {
                    $name = $row['title'].'|'.$row['id'].'|'.$row['harga_modal'].'|'.$row_num;
                    array_push($data, $name);
                }
                if (!empty($data)) {
                    echo json_encode($data);
                    } else {
                    array_push($data, 'Data tidak ditemukan');
                    echo json_encode($data);
                }
            }
            if ($type == 'bahan_table_dua') {
                $idprod = $_POST['idprod'];
                $cek = "SELECT * FROM `produk` where id='$idprod'";
                $res = $this->db->query($cek);
                $rows = $res->row_array();
                $row_num = $_POST['row_num'];
                $name = $_POST['name_startsWith'];
                // $query = "SELECT * FROM bahan where UPPER(title) LIKE '%".strtoupper($name)."%'";
                $query = "SELECT * FROM bahan where id IN ($rows[id_bahan])";
                $result = $this->db->query($query);
                $data = [];
                $name = '';
                $satuan = '-';
                foreach ($result->result_array() as $row) {
                    $res = $this->db->query('SELECT satuan FROM satuan where id='.$row['id_satuan']);
                    if ($res->num_rows() > 0) {
                        $rowx = $res->row_array();
                        $satuan = $rowx['satuan'];
                    }
                    $name = $row['title'].'|'.$row['id'].'|'.$row['harga'].'|'.$satuan.'|'.$rows['id_bahan'].'|'.$row_num;
                    array_push($data, $name);
                }
                if (!empty($data)) {
                    echo json_encode($data);
                    } else {
                    array_push($data, 'Data tidak ditemukan');
                    echo json_encode($data);
                }
            }
            if ($type == 'satuan_table') {
                $row_num = $_POST['row_num'];
                $name = $_POST['name_startsWith'];
                $like = ['satuan' => $name];
                $result = $this->model_app->view_like('satuan', $like);
                $data = [];
                foreach ($result->result_array() as $row) {
                    $name = $row['satuan'].'|'.$row['id'].'|'.$row['jumlah'].'|'.$row_num;
                    array_push($data, $name);
                }
                if (!empty($data)) {
                    echo json_encode($data);
                    } else {
                    array_push($data, $name);
                    echo json_encode($data);
                }
            }
            
            if ($type == 'harga_table') {
                $row_num = $_POST['row_num'];
                $name = $_POST['name_startsWith'];
                $like = ['title' => $name];
                $result = $this->model_app->view_like('harga', $like);
                $data = [];
                foreach ($result->result_array() as $row) {
                    $name = $row['title'].'|'.$row['id'].'|'.$row_num;
                    array_push($data, $name);
                }
                if (!empty($data)) {
                    echo json_encode($data);
                    } else {
                    array_push($data, $name);
                    echo json_encode($data);
                }
            }
            
            if ($type == 'ukuran_table') {
                $row_num = $_POST['row_num'];
                $name = $_POST['name_startsWith'];
                $like = ['title' => $name];
                $result = $this->model_app->view_like('ukuran', $like);
                $data = [];
                foreach ($result->result_array() as $row) {
                    $name = $row['title'].'|'.$row['id'].'|'.$row_num;
                    array_push($data, $name);
                }
                if (!empty($data)) {
                    echo json_encode($data);
                    } else {
                    array_push($data, $name);
                    echo json_encode($data);
                }
            }
            
            if ($type == 'ket_table') {
                $row_num = $_POST['row_num'];
                $name = $_POST['name_startsWith'];
                $like = ['keterangan' => $name];
                $result = $this->model_app->view_like_group('invoice_detail', $like, 'keterangan');
                $data = [];
                foreach ($result->result_array() as $row) {
                    $name = $row['keterangan'].'|'.$row_num;
                    array_push($data, $name);
                }
                if (!empty($data)) {
                    echo json_encode($data);
                    } else {
                    array_push($data, $name);
                    echo json_encode($data);
                }
            }
            if ($type == 'supplier') {
                $row_num = $_POST['row_num'];
                $name = $_POST['name_startsWith'];
                $like = ['nama_perusahaan' => $name];
                $result = $this->model_app->view_like('supplier', $like);
                $data = [];
                foreach ($result->result_array() as $row) {
                    $name = $row['nama_perusahaan'].'|'.$row['id_supplier'].'|'.$row_num;
                    array_push($data, $name);
                }
                if (!empty($data)) {
                    echo json_encode($data);
                    } else {
                    array_push($data, 'NONE');
                    echo json_encode($data);
                }
            }
            if ($type == 'marketing_table') {
                $row_num = $_POST['row_num'];
                $name = $_POST['name_startsWith'];
                $like = ['nama_lengkap' => $name];
                $result = $this->model_app->view_like('tb_users', $like);
                $data = [];
                foreach ($result->result_array() as $row) {
                    $name = $row['nama_lengkap'].'|'.$row['id_user'].'|'.$row_num;
                    array_push($data, $name);
                }
                if (!empty($data)) {
                    echo json_encode($data);
                    } else {
                    array_push($data, 'NONE');
                    echo json_encode($data);
                }
            }
            if ($type == 'designer_table') {
                $row_num = $_POST['row_num'];
                $name = $_POST['name_startsWith'];
                $like = ['nama_lengkap' => $name];
                $result = $this->model_app->view_like('tb_users', $like);
                $data = [];
                foreach ($result->result_array() as $row) {
                    $name = $row['nama_lengkap'].'|'.$row['id_user'].'|'.$row_num;
                    array_push($data, $name);
                }
                if (!empty($data)) {
                    echo json_encode($data);
                    } else {
                    array_push($data, 'NONE');
                    echo json_encode($data);
                }
            }
            
            if ($type == 'produk_table') {
                $row_num = $_POST['row_num'];
                $id_member = $this->input->post('id_member');
                $name = $this->input->post('name_startsWith');
                if (!empty($name)) {
                    $query = "SELECT 
                    `jenis_cetakan`.`jenis_cetakan`,
                    `produk`.`barcode`,
                    `produk`.`title`,
                    `produk`.`ukuran`,
                    `produk`.`id`,
                    `produk`.`id_jenis`,
                    `produk`.`id_bahan`,
                    `produk`.`jumlah`,
                    `produk`.`lock_harga`,
                    `bahan`.`type_harga`,
                    `bahan`.`id_produk`,
                    `bahan`.`ukuran_default`,
                    `bahan`.`id` AS id_bahan
                    FROM
                    `produk`
                    INNER JOIN `bahan` ON (`produk`.`id` = `bahan`.`id_produk`)
                    INNER JOIN `jenis_cetakan` ON (`produk`.`id_jenis` = `jenis_cetakan`.`id_jenis`)
                    where (
                        UPPER(`produk`.`title`) LIKE '%".strtoupper($name)."%' OR
                        UPPER(`produk`.`barcode`) LIKE '%".strtoupper($name)."%' OR
                        UPPER(`bahan`.`title`) LIKE '%".strtoupper($name)."%'
                    ) AND produk.pub='1'";
                    $result = $this->db->query($query);
                    $data = [];
                    
                    $idb = 0;
                    $title = '-';
                    $harga_jual = 0;
                    $status = 0;
                    $type_harga = 1;
                    $id_satuan = 1;
                    $data = [];
                    if ($result->num_rows() > 0) {
                        foreach ($result->result_array() as $row) {
                            $ukuran_default = $this->resolve_ukuran_default($row, $row['ukuran']);
                            if (!empty($row['id_produk'])) {
                                $res = $this->db->query("SELECT * FROM bahan where id=$row[id_bahan] AND featured=1");
                                if ($res->num_rows() > 0) {
                                    $rowx = $res->row_array();
                                    $id_bahan = $rowx['id'];
                                    $title = $rowx['title'];
                                    $harga_jual = $rowx['harga_jual'];
                                    $status = $rowx['status'];
                                    $type_harga = $rowx['type_harga'];
                                    $id_satuan = $rowx['id_satuan'];
                                    $ukuran_default = $this->resolve_ukuran_default($rowx, $row['ukuran']);
                                    } else {
                                    $res = $this->db->query("SELECT * FROM bahan where id=$row[id_bahan] order by id desc");
                                    if ($res->num_rows() > 0) {
                                        $rowz = $res->row_array();
                                        $id_bahan = $rowz['id'];
                                        $title = $rowz['title'];
                                        $harga_jual = $rowz['harga_jual'];
                                        $status = $rowz['status'];
                                        $type_harga = $rowz['type_harga'];
                                        $id_satuan = $rowz['id_satuan'];
                                        $ukuran_default = $this->resolve_ukuran_default($rowz, $row['ukuran']);
                                        } else {
                                        $id_bahan = 0;
                                        $title = '-';
                                        $harga_jual = 0;
                                        $status = 0;
                                        $type_harga = 0;
                                        $id_satuan = 0;
                                    }
                                }
                                } else {
                                array_push($data, 'NONE');
                                echo json_encode($data);
                                exit;
                            }
                            $output_type_harga = $type_harga;
                            $satuan = $id_satuan;
                            if ((int) $type_harga === 6) {
                                $type_harga = resolve_semua_metode_harga(
                                    $id_bahan,
                                    $id_member,
                                    $id_satuan,
                                    1
                                );
                            }
                            if ($type_harga == 1) {
                                $sql1 = $this->db->query("SELECT 
                                `satuan`.`id` AS idsatuan,
                                `satuan`.`satuan`,
                                `satu_harga`.`harga_jual`,
                                `bahan`.`title`
                                FROM
                                `bahan`
                                INNER JOIN `satu_harga` ON (`bahan`.`id` = `satu_harga`.`id_bahan`)
                                INNER JOIN `satuan` ON (`satu_harga`.`id_satuan` = `satuan`.`id`)
                                WHERE
                                `bahan`.`id` = $id_bahan");
                                if ($sql1->num_rows() > 0) {
                                    $harga_jual = $sql1->row()->harga_jual;
                                    $satuan = $sql1->row()->idsatuan;
                                    } else {
                                    $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                                    $harga_jual = $sql->row()->harga_jual;
                                    $satuan = $sql->row()->id_satuan;
                                }
                            }
                            if ($type_harga == 2) {
                                $sql2 = $this->db->query("SELECT 
                                `satuan`.`id` AS idsatuan,
                                `satuan`.`satuan`,
                                `harga_satuan`.`id_satuan`,
                                `bahan`.`title`,
                                `harga_satuan`.`harga_jual`,
                                `harga_satuan`.`id_bahan`
                                FROM
                                `satuan`
                                INNER JOIN `harga_satuan` ON (`satuan`.`id` = `harga_satuan`.`id_satuan`)
                                INNER JOIN `bahan` ON (`harga_satuan`.`id_bahan` = `bahan`.`id`)
                                WHERE
                                `harga_satuan`.`id_bahan` = $id_bahan");
                                if ($sql2->num_rows() > 0) {
                                    $harga_jual = $sql2->row()->harga_jual;
                                    $satuan = $sql2->row()->id_satuan;
                                    } else {
                                    $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                                    $harga_jual = $sql->row()->harga_jual;
                                    $satuan = $sql->row()->id_satuan;
                                }
                            }
                            if ($type_harga == 3) {
                                $sql3 = $this->db->query("SELECT 
                                `harga_member`.`id_bahan`,
                                `harga_member`.`id_satuan`,
                                `harga_member`.`id_member`,
                                `harga_member`.`harga_jual`
                                FROM
                                `satuan`
                                INNER JOIN `harga_member` ON (`satuan`.`id` = `harga_member`.`id_satuan`)
                                INNER JOIN `bahan` ON (`harga_member`.`id_bahan` = `bahan`.`id`)
                                INNER JOIN `konsumen` ON (`harga_member`.`id_member` = `konsumen`.`jenis_member`)
                                WHERE
                                `harga_member`.`id_member` = $id_member AND 
                                `harga_member`.`id_bahan` = $id_bahan
                                GROUP BY
                                `harga_member`.`id_bahan`,
                                `harga_member`.`id_satuan`,
                                `harga_member`.`id_member`,
                                `harga_member`.`harga_jual`");
                                if ($sql3->num_rows() > 0) {
                                    $harga_jual = $sql3->row()->harga_jual;
                                    $satuan = $sql3->row()->id_satuan;
                                    } else {
                                    $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                                    $harga_jual = $sql->row()->harga_jual;
                                    $satuan = $sql->row()->id_satuan;
                                }
                            }
                            if ($type_harga == 4) {
                                $sql = $this->db->query("select id_satuan,harga_jual from range_harga where id_bahan=$id_bahan AND 1 between jumlah_minimal and jumlah_maksimal");
                                if ($sql->num_rows() > 0) {
                                    $harga_jual = $sql->row()->harga_jual;
                                    $satuan = $sql->row()->id_satuan;
                                    } else {
                                    $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                                    $harga_jual = $sql->row()->harga_jual;
                                    $satuan = $sql->row()->id_satuan;
                                }
                            }
                            if ($type_harga == 5) {
                                $sql4 = $this->db->query("select id_satuan,harga_jual from harga_range_member where id_bahan=$id_bahan AND id_member='$id_member' AND 1 between jumlah_minimal and jumlah_maksimal");
                                if ($sql4->num_rows() > 0) {
                                    $harga_jual = $sql4->row()->harga_jual;
                                    $satuan = $sql4->row()->id_satuan;
                                    } else {
                                    $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                                    $harga_jual = $sql->row()->harga_jual;
                                    $satuan = $sql->row()->id_satuan;
                                }
                            }
                            $name =
                            $row['title'].' - '.$title.'|'.
                            rp($harga_jual).'|'.
                            $row['id'].'|'.
                            $row['jenis_cetakan'].'|'.
                            $title.'|'.
                            $row['id_jenis'].'|'.
                            $id_bahan.'|'.
                            $status.'|'.
                            $satuan.'|'.
                            $ukuran_default.'|'.
                            $row['jumlah'].'|'.
                            $row['lock_harga'].'|'.
                            $output_type_harga.'|'.
                            $row['barcode'].'|'.
                            $row_num;
                            array_push($data, $name);
                        }
                        } else {
                        
                        $query = "SELECT 
                        `produk`.`title` AS `judul`,
                        `produk`.`id`,
                        `produk`.`id_jenis`,
                        `produk`.`ukuran`,
                        `produk`.`jumlah`,
                        `produk`.`lock_harga`,
                        `produk`.`harga_beli`,
                        `produk`.`kunci`,
                        `bahan`.`title`,
                        `bahan`.`id_satuan`,
                        `bahan`.`harga_jual`,
                        `bahan`.`type_harga`,
                        `bahan`.`status`,
                        `bahan`.`id` AS `id_bahan`,
                        `bahan`.`ukuran_default`,
                        `bahan`.`barcode`,
                        `bahan`.`status_stok`,
                        `satuan`.`id` AS `satuan`,
                        `jenis_cetakan`.`jenis_cetakan`
                        FROM
                        `produk`
                        INNER JOIN `bahan` ON (`produk`.`id` = `bahan`.`id_produk`)
                        INNER JOIN `satuan` ON (`bahan`.`id_satuan` = `satuan`.`id`)
                        INNER JOIN `jenis_cetakan` ON (`produk`.`id_jenis` = `jenis_cetakan`.`id_jenis`)
                        WHERE  `bahan`.`barcode` LIKE '%".$name."%' AND produk.pub='1'";
                        $result = $this->db->query($query);
                        if ($result->num_rows() > 0) {
                            foreach ($result->result_array() as $row) {
                                $name =
                                $row['judul'].' - '.$row['title'].'|'.
                                rp($row['harga_jual']).'|'.
                                $row['id'].'|'.
                                $row['jenis_cetakan'].'|'.
                                $row['title'].'|'.
                                $row['id_jenis'].'|'.
                                $row['id_bahan'].'|'.
                                $row['status'].'|'.
                                $row['satuan'].'|'.
                                $this->resolve_ukuran_default($row, $row['ukuran']).'|'.
                                $row['jumlah'].'|'.
                                $row['lock_harga'].'|'.
                                $row['type_harga'].'|'.
                                $row['barcode'].'|'.
                                $row_num;
                                array_push($data, $name);
                            }
                        }
                    }
                    if (!empty($data)) {
                        echo json_encode($data);
                        } else {
                        array_push($data, 'NONE');
                        echo json_encode($data);
                    }
                    } else {
                    echo json_encode(['NONE']);
                }
            }
            
            if ($type == 'produk_table_a') {
                $row_num = $_POST['row_num'];
                $id_member = $this->input->post('id_member');
                $name = $this->input->post('name_startsWith');
                if (!empty($name)) {
                    $query = "SELECT 
                    `jenis_cetakan`.`jenis_cetakan`,
                    `produk`.`barcode`,
                    `produk`.`title`,
                    `produk`.`ukuran`,
                    `produk`.`id`,
                    `produk`.`id_jenis`,
                    `produk`.`id_bahan`,
                    `produk`.`jumlah`,
                    `produk`.`lock_harga`
                    FROM
                    `jenis_cetakan`
                    INNER JOIN `produk` ON (`jenis_cetakan`.`id_jenis` = `produk`.`id_jenis`)
                    where (UPPER(`produk`.`title`) LIKE '%".strtoupper($name)."%' OR UPPER(`produk`.`barcode`) LIKE '%".strtoupper($name)."%') AND produk.pub='1'";
                    $result = $this->db->query($query);
                    $data = [];
                    
                    $idb = 0;
                    $title = '-';
                    $harga_jual = 0;
                    $status = 0;
                    $type_harga = 1;
                    $id_satuan = 1;
                    $data = [];
                    if ($result->num_rows() > 0) {
                        foreach ($result->result_array() as $row) {
                            $ukuran_default = $row['ukuran'];
                            if (!empty($row['id_jenis'])) {
                                $res = $this->db->query("SELECT * FROM bahan where id_jenis IN ($row[id_jenis]) AND featured=1");
                                if ($res->num_rows() > 0) {
                                    $rowx = $res->row_array();
                                    $id_bahan = $rowx['id'];
                                    $title = $rowx['title'];
                                    $harga_jual = $rowx['harga_jual'];
                                    $status = $rowx['status'];
                                    $type_harga = $rowx['type_harga'];
                                    $id_satuan = $rowx['id_satuan'];
                                    $ukuran_default = $this->resolve_ukuran_default($rowx, $row['ukuran']);
                                    } else {
                                    $res = $this->db->query("SELECT * FROM bahan where id_jenis IN ($row[id_jenis]) order by id desc");
                                    if ($res->num_rows() > 0) {
                                        $rowz = $res->row_array();
                                        $id_bahan = $rowz['id'];
                                        $title = $rowz['title'];
                                        $harga_jual = $rowz['harga_jual'];
                                        $status = $rowz['status'];
                                        $type_harga = $rowz['type_harga'];
                                        $id_satuan = $rowz['id_satuan'];
                                        $ukuran_default = $this->resolve_ukuran_default($rowz, $row['ukuran']);
                                        } else {
                                        $id_bahan = 0;
                                        $title = '-';
                                        $harga_jual = 0;
                                        $status = 0;
                                        $type_harga = 0;
                                        $id_satuan = 0;
                                    }
                                }
                                } else {
                                array_push($data, 'NONE');
                                echo json_encode($data);
                                exit;
                            }
                            $output_type_harga = $type_harga;
                            $satuan = $id_satuan;
                            if ((int) $type_harga === 6) {
                                $type_harga = resolve_semua_metode_harga(
                                    $id_bahan,
                                    $id_member,
                                    $id_satuan,
                                    1
                                );
                            }
                            if ($type_harga == 1) {
                                $sql1 = $this->db->query("SELECT 
                                `satuan`.`id` AS idsatuan,
                                `satuan`.`satuan`,
                                `satu_harga`.`harga_jual`,
                                `bahan`.`title`
                                FROM
                                `bahan`
                                INNER JOIN `satu_harga` ON (`bahan`.`id` = `satu_harga`.`id_bahan`)
                                INNER JOIN `satuan` ON (`satu_harga`.`id_satuan` = `satuan`.`id`)
                                WHERE
                                `bahan`.`id` = $id_bahan");
                                if ($sql1->num_rows() > 0) {
                                    $harga_jual = $sql1->row()->harga_jual;
                                    $satuan = $sql1->row()->idsatuan;
                                    } else {
                                    $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                                    $harga_jual = $sql->row()->harga_jual;
                                    $satuan = $sql->row()->id_satuan;
                                }
                            }
                            if ($type_harga == 2) {
                                $sql2 = $this->db->query("SELECT 
                                `satuan`.`id` AS idsatuan,
                                `satuan`.`satuan`,
                                `harga_satuan`.`id_satuan`,
                                `bahan`.`title`,
                                `harga_satuan`.`harga_jual`,
                                `harga_satuan`.`id_bahan`
                                FROM
                                `satuan`
                                INNER JOIN `harga_satuan` ON (`satuan`.`id` = `harga_satuan`.`id_satuan`)
                                INNER JOIN `bahan` ON (`harga_satuan`.`id_bahan` = `bahan`.`id`)
                                WHERE
                                `harga_satuan`.`id_bahan` = $id_bahan");
                                if ($sql2->num_rows() > 0) {
                                    $harga_jual = $sql2->row()->harga_jual;
                                    $satuan = $sql2->row()->id_satuan;
                                    } else {
                                    $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                                    $harga_jual = $sql->row()->harga_jual;
                                    $satuan = $sql->row()->id_satuan;
                                }
                            }
                            if ($type_harga == 3) {
                                $sql3 = $this->db->query("SELECT 
                                `harga_member`.`id_bahan`,
                                `harga_member`.`id_satuan`,
                                `harga_member`.`id_member`,
                                `harga_member`.`harga_jual`
                                FROM
                                `satuan`
                                INNER JOIN `harga_member` ON (`satuan`.`id` = `harga_member`.`id_satuan`)
                                INNER JOIN `bahan` ON (`harga_member`.`id_bahan` = `bahan`.`id`)
                                INNER JOIN `konsumen` ON (`harga_member`.`id_member` = `konsumen`.`jenis_member`)
                                WHERE
                                `harga_member`.`id_member` = $id_member AND 
                                `harga_member`.`id_bahan` = $id_bahan
                                GROUP BY
                                `harga_member`.`id_bahan`,
                                `harga_member`.`id_satuan`,
                                `harga_member`.`id_member`,
                                `harga_member`.`harga_jual`");
                                if ($sql3->num_rows() > 0) {
                                    $harga_jual = $sql3->row()->harga_jual;
                                    $satuan = $sql3->row()->id_satuan;
                                    } else {
                                    $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                                    $harga_jual = $sql->row()->harga_jual;
                                    $satuan = $sql->row()->id_satuan;
                                }
                            }
                            if ($type_harga == 4) {
                                $sql = $this->db->query("select id_satuan,harga_jual from range_harga where id_bahan=$id_bahan AND 1 between jumlah_minimal and jumlah_maksimal");
                                if ($sql->num_rows() > 0) {
                                    $harga_jual = $sql->row()->harga_jual;
                                    $satuan = $sql->row()->id_satuan;
                                    } else {
                                    $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                                    $harga_jual = $sql->row()->harga_jual;
                                    $satuan = $sql->row()->id_satuan;
                                }
                            }
                            if ($type_harga == 5) {
                                $sql4 = $this->db->query("select id_satuan,harga_jual from harga_range_member where id_bahan=$id_bahan AND id_member='$id_member' AND 1 between jumlah_minimal and jumlah_maksimal");
                                if ($sql4->num_rows() > 0) {
                                    $harga_jual = $sql4->row()->harga_jual;
                                    $satuan = $sql4->row()->id_satuan;
                                    } else {
                                    $sql = $this->db->query("select id,id_satuan,harga_jual from bahan where id=$id_bahan");
                                    $harga_jual = $sql->row()->harga_jual;
                                    $satuan = $sql->row()->id_satuan;
                                }
                            }
                            $name =
                            $row['title'].'|'.
                            rp($harga_jual).'|'.
                            $row['id'].'|'.
                            $row['jenis_cetakan'].'|'.
                            $title.'|'.
                            $row['id_jenis'].'|'.
                            $id_bahan.'|'.
                            $status.'|'.
                            $satuan.'|'.
                            $ukuran_default.'|'.
                            $row['jumlah'].'|'.
                            $row['lock_harga'].'|'.
                            $output_type_harga.'|'.
                            $row['barcode'].'|'.
                            $row_num;
                            array_push($data, $name);
                        }
                        } else {
                        $query = "SELECT 
                        `produk`.`title` AS `judul`,
                        `produk`.`jumlah`,
                        `produk`.`lock_harga`,
                        `produk`.`kunci`,
                        `produk`.`id_jenis`,
                        `bahan`.`harga_jual`,
                        `bahan`.`title`,
                        `bahan`.`id` AS id_bahan,
                        `bahan`.`type_harga`,
                        `bahan`.`id_satuan`,
                        `bahan`.`status`,
                        `bahan`.`barcode`,
                        `bahan`.`ukuran_default`,
                        `bahan`.`status_stok`,
                        `jenis_cetakan`.`jenis_cetakan`,
                        `produk`.`id`,
                        `produk`.`ukuran`,
                        `satuan`.`id` AS satuan
                        FROM
                        `jenis_cetakan`
                        INNER JOIN `produk` ON (`jenis_cetakan`.`id_jenis` = `produk`.`id_jenis`)
                        INNER JOIN `bahan` ON (`jenis_cetakan`.`id_jenis` = `bahan`.`id_jenis`)
                        INNER JOIN `satuan` ON (`bahan`.`id_satuan` = `satuan`.`id`)
                        WHERE  `bahan`.`barcode` LIKE '%".$name."%' AND produk.pub='1'";
                        $result = $this->db->query($query);
                        if ($result->num_rows() > 0) {
                            foreach ($result->result_array() as $row) {
                                $name =
                                $row['judul'].' - '.$row['title'].'|'.
                                rp($row['harga_jual']).'|'.
                                $row['id'].'|'.
                                $row['jenis_cetakan'].'|'.
                                $row['title'].'|'.
                                $row['id_jenis'].'|'.
                                $row['id_bahan'].'|'.
                                $row['status'].'|'.
                                $row['satuan'].'|'.
                                $this->resolve_ukuran_default($row, $row['ukuran']).'|'.
                                $row['jumlah'].'|'.
                                $row['lock_harga'].'|'.
                                $row['type_harga'].'|'.
                                $row['barcode'].'|'.
                                $row_num;
                                array_push($data, $name);
                            }
                        }
                    }
                    if (!empty($data)) {
                        echo json_encode($data);
                        } else {
                        array_push($data, 'NONE');
                        echo json_encode($data);
                    }
                    } else {
                    echo json_encode(['NONE']);
                }
            }
            
            if ($type == 'produk_table_qr') {
                $row_num = $_POST['row_num'];
                $name = $_POST['name_startsWith'];
                
                $query = "SELECT 
                `jenis_cetakan`.`jenis_cetakan`,
                `produk`.`barcode`,
                `produk`.`title`,
                `produk`.`ukuran`,
                `produk`.`id`,
                `produk`.`id_jenis`,
                `produk`.`id_bahan`,
                `produk`.`jumlah`,
                `produk`.`lock_harga`
                FROM
                `jenis_cetakan`
                INNER JOIN `produk` ON (`jenis_cetakan`.`id_jenis` = `produk`.`id_jenis`)
                where `produk`.`barcode` ='$name' AND produk.pub='1'";
                $result = $this->db->query($query);
                $data = [];
                
                $idb = 0;
                $title = '-';
                $harga_dasar = 0;
                $status = 0;
                $type_harga = 1;
                $id_satuan = 'pcs';
                
                foreach ($result->result_array() as $row) {
                    $ukuran_default = $row['ukuran'];
                    $res = $this->db->query("SELECT * FROM bahan where id IN ($row[id_bahan])");
                    if ($res->num_rows() > 0) {
                        $rowx = $res->row_array();
                        $idb = $rowx['id'];
                        $title = $rowx['title'];
                        $harga_dasar = $rowx['harga'];
                        $status = $rowx['status'];
                        $type_harga = $rowx['type_harga'];
                        $id_satuan = $rowx['id_satuan'];
                        $ukuran_default = $this->resolve_ukuran_default($rowx, $row['ukuran']);
                    }
                    $name =
                    $row['title'].'|'.
                    rp($harga_dasar).'|'.
                    $row['id'].'|'.
                    $row['jenis_cetakan'].'|'.
                    $title.'|'.
                    $row['id_jenis'].'|'.
                    $idb.'|'.
                    $status.'|'.
                    $id_satuan.'|'.
                    $ukuran_default.'|'.
                    $row['jumlah'].'|'.
                    $row['lock_harga'].'|'.
                    $type_harga.'|'.
                    $row['barcode'].'|'.
                    $row_num;
                    array_push($data, $name);
                }
                if (!empty($data)) {
                    echo json_encode($data);
                    } else {
                    array_push($data, 'NONE');
                    echo json_encode($data);
                }
            }
            
            if ($type == 'produk_table_tiga') {
                $row_num = $_POST['row_num'];
                $name = $_POST['name_startsWith'];
                
                $query = "SELECT 
                `bahan`.`title` AS `nbahan`,
                `jenis_cetakan`.`jenis_cetakan`,
                `produk`.`title`,
                `produk`.`ukuran`,
                `bahan`.`ukuran_default`,
                `bahan`.`id` AS idb,
                `bahan`.`harga_modal`,
                `bahan`.`harga_jual`,
                `bahan`.`status`,
                `bahan`.`harga` AS harga_dasar,
                `produk`.`id`,
                `produk`.`id_jenis`,
                `produk`.`jumlah`,
                `produk`.`lock_harga`,
                `bahan`.`id_satuan`
                FROM
                `jenis_cetakan`
                INNER JOIN `produk` ON (`jenis_cetakan`.`id_jenis` = `produk`.`id_jenis`)
                INNER JOIN `bahan` ON (`jenis_cetakan`.`id_jenis` = `bahan`.`id_jenis`)
                where UPPER(`produk`.`title`) LIKE '%".strtoupper($name)."%' AND produk.pub='1' GROUP BY bahan.id_jenis";
                $result = $this->db->query($query);
                $data = [];
                foreach ($result->result_array() as $row) {
                    $name = $row['title'].'|'.rp($row['harga_dasar']).'|'.$row['id'].'|'.$row['jenis_cetakan'].'|'.$row['nbahan'].'|'.$row['id_jenis'].'|'.$row['idb'].'|'.$row['status'].'|'.get_satuan($row['id_satuan']).'|'.$this->resolve_ukuran_default($row, $row['ukuran']).'|'.$row['jumlah'].'|'.$row['lock_harga'].'|'.$row_num;
                    array_push($data, $name);
                }
                if (!empty($data)) {
                    echo json_encode($data);
                    } else {
                    array_push($data, 'NONE');
                    echo json_encode($data);
                }
            }
            
            if ($type == 'produk_table_dua') {
                $row_num = $_POST['row_num'];
                $name = $_POST['name_startsWith'];
                $query = "SELECT 
                `produk`.`id`,
                `produk`.`id_jenis`,
                `produk`.`title`,
                `produk`.`ukuran`,
                `bahan`.`ukuran_default`,
                `jenis_cetakan`.`id_jenis`,
                `jenis_cetakan`.`jenis_cetakan`,
                `jenis_cetakan`.`status`,
                `bahan`.`id` AS idb,
                `bahan`.`title` AS nbahan,
                `bahan`.`harga` AS harga_dasar,
                `bahan`.`id_satuan`
                FROM
                `jenis_cetakan`
                INNER JOIN `produk` ON (`jenis_cetakan`.`id_jenis` = `produk`.`id_jenis`)
                INNER JOIN `bahan` ON (`produk`.`id_bahan` = `bahan`.`id`)
                where UPPER(`produk`.`title`) LIKE '%".strtoupper($name)."%' AND produk.pub='1'";
                $result = $this->db->query($query);
                $data = [];
                foreach ($result->result_array() as $row) {
                    $name = $row['title'].'|'.rp($row['harga_dasar']).'|'.$row['id'].'|'.$row['jenis_cetakan'].'|'.$row['nbahan'].'|'.$row['id_jenis'].'|'.$row['idb'].'|'.$row['status'].'|'.get_satuan($row['id_satuan']).'|'.$this->resolve_ukuran_default($row, $row['ukuran']).'|'.$row_num;
                    array_push($data, $name);
                }
                if (!empty($data)) {
                    echo json_encode($data);
                    } else {
                    array_push($data, 'NONE');
                    echo json_encode($data);
                }
            }
        }
        
        public function cari_nominal()
        {
            cek_nput_post('GET');
            $idorder = $this->db->escape_str($this->input->post('idorder'));
            $idorder = id_transaksi($idorder);
            $idedit = $this->db->escape_str($this->input->post('idedit'));
            $idkasir = $this->db->escape_str($this->input->post('idkasir'));
            $jml = $this->db->escape_str($this->input->post('jml'));
            $search = $this->model_app->view_where('bayar_invoice_detail', ['jml_bayar' => $jml, 'id_invoice' => $idorder]);
            if ($search->num_rows() > 0) {
                $row = $search->row_array();
                if ($row['kunci'] == 1) {
                    $data = ['status' => 'edit', 'idorder' => $idorder, 'idedit' => $idedit, 'idkasir' => $idkasir, 'jml' => $jml];
                    } else {
                    $data = ['status' => 200, 'idorder' => $idorder, 'idedit' => $idedit, 'idkasir' => $idkasir, 'jml' => $jml];
                }
                } else {
                $data = ['status' => 400];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * save_cari_invoice.
            *
            * @return void
        */
        public function save_cari_invoice()
        {
            cek_nput_post('GET');
            $idorder = $this->db->escape_str($this->input->post('idorder'));
            $idorder = strtoupper($idorder);
            if ((int) $idorder) {
                $id_invoice = $idorder;
                } else {
                $id_invoice = id_transaksi($idorder);
            }
            $idkasir = $this->db->escape_str($this->input->post('idkasir'));
            $idedit = $this->db->escape_str($this->input->post('idedit'));
            $jml = $this->db->escape_str($this->input->post('jml'));
            $id = ['id_user' => $this->session->idu];
            
            $typenya = [$idedit];
            $cek_batal = $this->model_app->view_where('invoice', ['status' => 'batal', 'id_transaksi' => $idorder]);
            if ($cek_batal->num_rows() > 0) {
                $data = ['status' => 400, 'msg' => 'Maaf Order Sudah dibatalkan'];
                echo json_encode($data);
                exit;
            }
            $search = $this->model_app->view_where('tb_users', $id);
            if ($search->num_rows() > 0) {
                $row = $search->row_array();
                $query = "SELECT * FROM tb_users where FIND_IN_SET('$idedit', CONCAT(type_akses, ',')) AND id_user=".$this->session->idu;
                $result = $this->db->query($query);
                if ($result->num_rows() > 0) {
                    if ($idedit == 1) {
                        $search = $this->model_app->view_where('invoice', ['lunas' => 1, 'id_transaksi' => $idorder]);
                        if ($search->num_rows() > 0) {
                            $data = ['status' => 400, 'msg' => 'Maaf Order Sudah Lunas'];
                            } else {
                            $res = $this->model_app->update('invoice', ['status' => 'edit', 'oto' => $idedit], ['id_invoice' => $id_invoice]);
                            if ($res['status'] == 'ok') {
                                $data = ['status' => 200, 'msg' => 1];
                                } else {
                                $data = ['status' => 400];
                            }
                        }
                        } elseif ($idedit == 2) { // hapus pembayaran + edit
                        $res = $this->model_app->update('bayar_invoice_detail', ['kunci' => 1], ['id_invoice' => $id_invoice, 'jml_bayar' => $jml]);
                        $this->model_app->update('invoice', ['oto' => 1], ['id_invoice' => $id_invoice]);
                        if ($res['status'] == 'ok') {
                            $_where = ['pemasukan' => $jml, 'catatan' => 'Pendapatan INVOICE NO.#'.$id_invoice];
                            $this->model_app->delete('kas_masuk', $_where);
                            $this->model_app->delete('bayar_invoice_detail', ['jml_bayar' => $jml, 'id_invoice' => $id_invoice]);
                            $data = ['status' => 200, 'msg' => 2];
                            } else {
                            $data = ['status' => 400];
                        }
                        } elseif ($idedit == 3) { // edit lunas
                        $search = $this->model_app->view_where('invoice', ['lunas' => 1, 'id_transaksi' => $idorder]);
                        if ($search->num_rows() > 0) {
                            $this->model_app->update('invoice', ['oto' => 3], ['id_transaksi' => $idorder]);
                            $data = ['status' => 200, 'msg' => 3];
                            } else {
                            $data = ['status' => 400, 'msg' => 'Order belum Lunas'];
                        }
                        } elseif ($idedit == 4) { // pending
                        $_select = 'SUM(jml_bayar) AS `jml`';
                        $cdetail = $this->model_app->cek_total('bayar_invoice_detail', $_select, ['id_invoice' => $id_invoice]);
                        if ($cdetail->jml == 0) {
                            $this->model_app->update('invoice', ['status' => 'pending', 'oto' => 1], ['id_transaksi' => $idorder]);
                            $data = ['status' => 200, 'msg' => 4];
                            $this->model_app->update('tb_users', ['last_invoice' => 0], ['id_user' => $idkasir]);
                            } else {
                            $data = ['status' => 400, 'msg' => 'Transaksi tidak bisa di pending'];
                        }
                        } elseif ($idedit == 5) { // batal
                        $data = ['status' => 400, 'msg' => 'batal di menu transaksi'];
                    }
                    } else {
                    $data = ['ok' => 'error'];
                }
                } else {
                $data = ['ok' => 'error'];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        // ajak cari data
        /**
            * cariProduk.
            *
            * @return void
        */
        public function cariProduk()
        {
            cek_nput_post('GET');
            $page = $this->input->post('page');
            if (!$page) {
                $offset = 0;
                } else {
                $offset = $page;
            }
            
            $limits = $this->input->post('limits');
            
            if (!empty($limits)) {
                $limit = $limits;
                } else {
                $limit = $this->perPage;
            }
            $sortBy = $this->input->post('sortBy');
            if (!empty($sortBy)) {
                $conditions['search']['sortBy'] = $sortBy;
            }
            
            $keywords = $this->input->post('keywords');
            if (!empty($keywords)) {
                $conditions['search']['keywords'] = $keywords;
            }
            $data['id_member'] = (int) $this->input->post('id_member');
            
            $jenis = $this->input->post('jenis');
            if (!empty($jenis)) {
                $conditions['where'] = [
                'jenis_cetakan.id_jenis' => $jenis,
                'produk.kunci' => 0,
                ];
                } else {
                $conditions['where'] = [
                'produk.kunci' => 0,
                ];
            }
            // Get record count
            $conditions['returnType'] = 'count';
            $totalRec = $this->model_data->getProduk($conditions);
            
            // Pagination configuration
            $config['target'] = '#dataProduk';
            $config['base_url'] = base_url('produk/cariProduk');
            $config['total_rows'] = $totalRec;
            $config['per_page'] = $limit;
            $config['link_func'] = 'search_Produk';
            
            // Initialize pagination library
            $this->ajax_pagination->initialize($config);
            
            // Get records
            $conditions['start'] = $offset;
            $conditions['limit'] = $limit;
            
            if (!empty($sortBy)) {
                $conditions['search']['sortBy'] = $sortBy;
            }
            
            if (!empty($keywords)) {
                $conditions['search']['keywords'] = $keywords;
            }
            
            if (!empty($jenis)) {
                $conditions['where'] = [
                'jenis_cetakan.id_jenis' => $jenis,
                'produk.kunci' => 0,
                ];
                } else {
                $conditions['where'] = [
                'produk.kunci' => 0,
                ];
            }
            
            unset($conditions['returnType']);
            $data['result'] = $this->model_data->getProduk($conditions);
            // Load the data list view
            $this->load->view('produk/cari-produk', $data, false);
        }
        
        // jenis
        /**
            * cariJenis.
            *
            * @return void
        */
        public function cariJenis()
        {
            cek_nput_post('GET');

            $page = $this->input->post('page');
            $offset = ($page) ? (int)$page : 0;

            // Ambil LIMIT dari dropdown (POST 'limits')
            $limits = $this->input->post('limits');
            $limit  = (!empty($limits)) ? (int)$limits : (int)$this->perPage;

            // OPTIONAL: amankan limit biar tidak aneh-aneh
            $allowedLimits = [10, 20, 50, 100];
            if (!in_array($limit, $allowedLimits, true)) {
                $limit = (int)$this->perPage;
            }

            $sortBy = $this->input->post('sortBy');
            if (!empty($sortBy)) {
                $conditions['search']['sortBy'] = $sortBy;
            }

            $keywords = $this->input->post('keywords');
            if (!empty($keywords)) {
                $conditions['search']['keywords'] = $keywords;
            }

            // (tetap seperti aslinya)
            $jenis = $this->input->post('jenis');
            if (!empty($jenis)) {
                $conditions['where'] = [
                    'jenis_cetakan.id_jenis' => $jenis,
                ];
            }

            // (tetap seperti aslinya)
            $conditions['where'] = [
                'jenis_cetakan.kunci' => 0,
            ];

            // Get record count
            $conditions['returnType'] = 'count';
            $totalRec = $this->model_data->getJenis($conditions);

            // Pagination configuration
            $config['target']     = '#dataJenis';
            $config['base_url']   = base_url('produk/cariJenis');
            $config['total_rows'] = $totalRec;

            // ✅ PENTING: pakai $limit dari dropdown
            $config['per_page']   = $limit;

            $config['link_func']  = 'search_Jenis';

            // Initialize pagination library
            $this->ajax_pagination->initialize($config);

            // Get records
            $conditions['start'] = $offset;

            // ✅ PENTING: pakai $limit dari dropdown
            $conditions['limit'] = $limit;

            $conditions['where'] = [
                'jenis_cetakan.kunci' => 0,
            ];
            unset($conditions['returnType']);

            $data['result'] = $this->model_data->getJenis($conditions);

            $conditions['offset'] = $offset; // (tetap)
            $this->load->view('produk/cari-jenis', $data, false);
        }

        
        // bahan
        /**
            * cariBahan.
            *
            * @return void
        */
        public function cariBahan()
        {
            cek_nput_post('GET');
            $this->ensure_bahan_komposisi_schema();
            $this->ensure_bahan_foto_schema();
            $page = $this->input->post('page');
            if (!$page) {
                $offset = 0;
                } else {
                $offset = $page;
            }
            
            $limits = $this->input->post('limits');
            if (!empty($limits)) {
                $limit = $limits;
                } else {
                $limit = $this->perPageBahan;
            }
            $sortBy = $this->input->post('sortBy');
            if (!empty($sortBy)) {
                $conditions['search']['sortBy'] = $sortBy;
            }
            
            $keywords = $this->input->post('keywords');
            if (!empty($keywords)) {
                $conditions['search']['keywords'] = $keywords;
            }
            
            $kategori = $this->input->post('kategori');
            if (!empty($kategori)) {
                $conditions['where'] = [
                '`bahan`.`id_jenis`' => $kategori,
                ];
                } else {
                $conditions['where'] = [
                ];
            }
            
            // dump($conditions);
            // Get record count
            $conditions['returnType'] = 'count';
            $totalRec = $this->model_data->getBahan($conditions);
            
            // Pagination configuration
            $config['target'] = '#dataBahan';
            $config['base_url'] = base_url('produk/cariBahan');
            $config['total_rows'] = $totalRec;
            $config['per_page'] = $limit;
            $config['link_func'] = 'search_Bahan';
            
            // Initialize pagination library
            $this->ajax_pagination->initialize($config);
            
            // Get records
            $conditions['start'] = $offset;
            $conditions['limit'] = $limit;
            if (!empty($kategori)) {
                $conditions['where'] = [
                '`bahan`.`id_jenis`' => $kategori,
                ];
                } else {
                $conditions['where'] = [
                ];
            }
            
            unset($conditions['returnType']);
            $data['result'] = $this->model_data->getBahan($conditions);
            
            // $conditions['offset'] = $offset;
            // Load the data list view
            $this->load->view('produk/cari-bahan', $data, false);
        }
        
        // harga
        /**
            * daftar_harga.
            *
            * @return void
        */
        public function daftar_harga()
        {
            $data['kategori'] = $this->model_app->view_where('jenis_cetakan', ['kunci' => 0, 'pub' => 'Y'])->result();
            $this->load->view('produk/daftar_harga', $data, false);
        }
        
        // harga
        /**
            * daftar_harga.
            *
            * @return void
        */
        public function daftar_harga_cari()
        {
            cek_nput_post('GET');
            $page = $this->input->post('page');
            if (!$page) {
                $offset = 0;
                } else {
                $offset = $page;
            }
            
            $limits = $this->input->post('limits');
            if (!empty($limits)) {
                $limit = $limits;
                } else {
                $limit = $this->perPageBahan;
            }
            $sortBy = $this->input->post('sortBy');
            if (!empty($sortBy)) {
                $conditions['search']['sortBy'] = $sortBy;
            }
            
            $keywords = $this->input->post('keywords');
            if (!empty($keywords)) {
                $conditions['search']['keywords'] = $keywords;
            }
            
            $kategori = $this->input->post('kategori');
            if (!empty($kategori)) {
                $conditions['where'] = [
                '`bahan`.`id_jenis`' => $kategori,
                'bahan.pub' => 1,
                ];
                } else {
                $conditions['where'] = [
                'bahan.pub' => 1,
                ];
            }
            
            // dump($conditions);
            // Get record count
            $conditions['returnType'] = 'count';
            $totalRec = $this->model_data->getBahan($conditions);
            
            // Pagination configuration
            $config['target'] = '#dataBahan';
            $config['base_url'] = base_url('produk/daftar_harga_cari');
            $config['total_rows'] = $totalRec;
            $config['per_page'] = $limit;
            $config['link_func'] = 'search_harga_bahan';
            
            // Initialize pagination library
            $this->ajax_pagination->initialize($config);
            
            // Get records
            $conditions['start'] = $offset;
            $conditions['limit'] = $limit;
            
            if (!empty($kategori)) {
                $conditions['where'] = [
                '`bahan`.`id_jenis`' => $kategori,
                'bahan.pub' => 1,
                ];
                } else {
                $conditions['where'] = [
                'bahan.pub' => 1,
                ];
            }
            unset($conditions['returnType']);
            $data['result'] = $this->model_data->getBahan($conditions);
            
            // $conditions['offset'] = $offset;
            // Load the data list view
            $this->load->view('produk/daftar-harga', $data, false);
        }
        
        // satuan
        /**
            * cariSatuan.
            *
            * @return void
        */
        public function cariSatuan()
        {
            cek_nput_post('GET');
            $page = $this->input->post('page');
            if (!$page) {
                $offset = 0;
                } else {
                $offset = $page;
            }
            
            $limits = $this->input->post('limits');
            if (!empty($limits)) {
                $limit = $limits;
                } else {
                $limit = $this->perPage;
            }
            $sortBy = $this->input->post('sortBy');
            if (!empty($sortBy)) {
                $conditions['search']['sortBy'] = $sortBy;
            }
            
            $keywords = $this->input->post('keywords');
            if (!empty($keywords)) {
                $conditions['search']['keywords'] = $keywords;
            }
            
            // Get record count
            $conditions['returnType'] = 'count';
            $totalRec = $this->model_data->getSatuan($conditions);
            
            // Pagination configuration
            $config['target'] = '#dataSatuan';
            $config['base_url'] = base_url('produk/cariSatuan');
            $config['total_rows'] = $totalRec;
            $config['per_page'] = $limit;
            $config['link_func'] = 'search_Satuan';
            
            // Initialize pagination library
            $this->ajax_pagination->initialize($config);
            
            // Get records
            $conditions['start'] = $offset;
            $conditions['limit'] = $limit;
            
            unset($conditions['returnType']);
            $data['result'] = $this->model_data->getSatuan($conditions);
            
            $conditions['offset'] = $offset;
            // Load the data list view
            $this->load->view('produk/cari-satuan', $data, false);
        }
        
        /**
            * export_produk.
            *
            * @return void
        */
        public function export_produk()
        {
            $productlist = $this->model_app->view_where('produk', ['kunci' => 0])->result();
            $jenis_cetakan = $this->model_app->view_where('jenis_cetakan', ['kunci' => 0])->result();
            $bahan = $this->model_app->view_where('bahan', ['kunci' => 0])->result();
            $satuan = $this->model_app->view_where('satuan', ['pub' => 0])->result();
            $satu_harga = $this->model_app->views('satu_harga')->result();
            $harga_satuan = $this->model_app->views('harga_satuan')->result();
            $harga_member = $this->model_app->views('harga_member')->result();
            $range_harga = $this->model_app->views('range_harga')->result();
            $harga_range_member = $this->model_app->views('harga_range_member')->result();
            
            $spreadsheet = new Spreadsheet();
            
            $spreadsheet->setActiveSheetIndex(0)->setCellValue('A1', 'NO');
            $spreadsheet->setActiveSheetIndex(0)->setCellValue('B1', 'JENIS');
            $spreadsheet->setActiveSheetIndex(0)->setCellValue('C1', 'NAMA PRODUK');
            $spreadsheet->setActiveSheetIndex(0)->setCellValue('D1', 'BARCODE');
            $spreadsheet->setActiveSheetIndex(0)->setCellValue('E1', 'UKURAN DEFAULT');
            $spreadsheet->setActiveSheetIndex(0)->setCellValue('F1', 'JUMLAH DEFAULT');
            $spreadsheet->setActiveSheetIndex(0)->setCellValue('G1', 'LOCK HARGA');
            
            $a = 2;
            foreach ($productlist as $prod) {
                $spreadsheet->setActiveSheetIndex(0)->setCellValue('A'.$a, $prod->id);
                $spreadsheet->setActiveSheetIndex(0)->setCellValue('B'.$a, $prod->id_jenis);
                $spreadsheet->setActiveSheetIndex(0)->setCellValue('C'.$a, $prod->title);
                $spreadsheet->getActiveSheet(0)->setCellValueExplicit('D'.$a, $prod->barcode, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);
                $spreadsheet->setActiveSheetIndex(0)->setCellValue('E'.$a, $prod->ukuran);
                $spreadsheet->setActiveSheetIndex(0)->setCellValue('F'.$a, $prod->jumlah);
                $spreadsheet->setActiveSheetIndex(0)->setCellValue('G'.$a, $prod->lock_harga);
                ++$a;
            }
            // SHEET 0
            $spreadsheet->getActiveSheet()->setTitle('PRODUK');
            
            $spreadsheet->createSheet();
            
            $spreadsheet->setActiveSheetIndex(1)->setCellValue('A1', 'ID');
            $spreadsheet->setActiveSheetIndex(1)->setCellValue('B1', 'NAMA KATEGORI');
            $spreadsheet->setActiveSheetIndex(1)->setCellValue('C1', 'LOCK');
            $spreadsheet->setActiveSheetIndex(1)->setCellValue('D1', 'STATUS');
            $spreadsheet->setActiveSheetIndex(1)->setCellValue('E1', 'ID AKUN');
            
            $b = 2;
            foreach ($jenis_cetakan as $val) {
                $spreadsheet->setActiveSheetIndex(1)->setCellValue('A'.$b, $val->id_jenis);
                $spreadsheet->setActiveSheetIndex(1)->setCellValue('B'.$b, $val->jenis_cetakan);
                $spreadsheet->setActiveSheetIndex(1)->setCellValue('C'.$b, $val->kunci);
                $spreadsheet->setActiveSheetIndex(1)->setCellValue('D'.$b, $val->status);
                $spreadsheet->setActiveSheetIndex(1)->setCellValue('E'.$b, $val->id_akun);
                ++$b;
            }
            // SHEET 1
            $spreadsheet->getActiveSheet()->setTitle('KATEGORI');
            
            $spreadsheet->createSheet();
            $spreadsheet->setActiveSheetIndex(2)->setCellValue('A1', 'ID');
            $spreadsheet->setActiveSheetIndex(2)->setCellValue('B1', 'JENIS');
            $spreadsheet->setActiveSheetIndex(2)->setCellValue('C1', 'NAMA BAHAN');
            $spreadsheet->setActiveSheetIndex(2)->setCellValue('D1', 'HARGA BELI');
            $spreadsheet->setActiveSheetIndex(2)->setCellValue('E1', 'HARGA JUAL');
            $spreadsheet->setActiveSheetIndex(2)->setCellValue('F1', 'ID SATUAN');
            $spreadsheet->setActiveSheetIndex(2)->setCellValue('G1', 'STATUS_STOK');
            $spreadsheet->setActiveSheetIndex(2)->setCellValue('H1', 'LOCK');
            $spreadsheet->setActiveSheetIndex(2)->setCellValue('I1', 'STATUS');
            $spreadsheet->setActiveSheetIndex(2)->setCellValue('J1', 'AKTIF');
            $spreadsheet->setActiveSheetIndex(2)->setCellValue('K1', 'TYPE HARGA');
            
            $c = 2;
            foreach ($bahan as $val) {
                $spreadsheet->setActiveSheetIndex(2)->setCellValue('A'.$c, $val->id);
                $spreadsheet->setActiveSheetIndex(2)->setCellValue('B'.$c, $val->id_jenis);
                $spreadsheet->setActiveSheetIndex(2)->setCellValue('C'.$c, $val->title);
                $spreadsheet->setActiveSheetIndex(2)->setCellValue('D'.$c, $val->harga_modal);
                $spreadsheet->setActiveSheetIndex(2)->setCellValue('E'.$c, $val->harga_jual);
                $spreadsheet->setActiveSheetIndex(2)->setCellValue('F'.$c, $val->id_satuan);
                $spreadsheet->setActiveSheetIndex(2)->setCellValue('G'.$c, $val->status_stok);
                $spreadsheet->setActiveSheetIndex(2)->setCellValue('H'.$c, $val->kunci);
                $spreadsheet->setActiveSheetIndex(2)->setCellValue('I'.$c, $val->status);
                $spreadsheet->setActiveSheetIndex(2)->setCellValue('J'.$c, $val->pub);
                $spreadsheet->setActiveSheetIndex(2)->setCellValue('K'.$c, $val->type_harga);
                ++$c;
            }
            
            // SHEET 2
            $spreadsheet->getActiveSheet()->setTitle('BAHAN');
            
            $spreadsheet->createSheet();
            $spreadsheet->setActiveSheetIndex(3)->setCellValue('A1', 'ID');
            $spreadsheet->setActiveSheetIndex(3)->setCellValue('B1', 'SATUAN');
            $spreadsheet->setActiveSheetIndex(3)->setCellValue('C1', 'NAMA SATUAN');
            $spreadsheet->setActiveSheetIndex(3)->setCellValue('D1', 'JUMLAH SATUAN');
            
            $d = 2;
            foreach ($satuan as $val) {
                $spreadsheet->setActiveSheetIndex(3)->setCellValue('A'.$d, $val->id);
                $spreadsheet->setActiveSheetIndex(3)->setCellValue('B'.$d, $val->satuan);
                $spreadsheet->setActiveSheetIndex(3)->setCellValue('C'.$d, $val->nama_satuan);
                $spreadsheet->setActiveSheetIndex(3)->setCellValue('D'.$d, $val->jumlah);
                ++$d;
            }
            // SHEET 3
            $spreadsheet->getActiveSheet()->setTitle('SATUAN');
            
            $spreadsheet->createSheet();
            $spreadsheet->setActiveSheetIndex(4)->setCellValue('A1', 'ID');
            $spreadsheet->setActiveSheetIndex(4)->setCellValue('B1', 'ID BAHAN');
            $spreadsheet->setActiveSheetIndex(4)->setCellValue('C1', 'ID SATUAN');
            $spreadsheet->setActiveSheetIndex(4)->setCellValue('D1', 'HARGA POKOK');
            $spreadsheet->setActiveSheetIndex(4)->setCellValue('E1', 'PERSENTASE');
            $spreadsheet->setActiveSheetIndex(4)->setCellValue('F1', 'HARGA JUAL');
            
            $e = 2;
            foreach ($satu_harga as $val) {
                $spreadsheet->setActiveSheetIndex(4)->setCellValue('A'.$e, $val->id);
                $spreadsheet->setActiveSheetIndex(4)->setCellValue('B'.$e, $val->id_bahan);
                $spreadsheet->setActiveSheetIndex(4)->setCellValue('C'.$e, $val->id_satuan);
                $spreadsheet->setActiveSheetIndex(4)->setCellValue('D'.$e, $val->harga_pokok);
                $spreadsheet->setActiveSheetIndex(4)->setCellValue('E'.$e, $val->persen);
                $spreadsheet->setActiveSheetIndex(4)->setCellValue('F'.$e, $val->harga_jual);
                ++$e;
            }
            // SHEET 4
            $spreadsheet->getActiveSheet()->setTitle('SATU_HARGA');
            
            $spreadsheet->createSheet();
            $spreadsheet->setActiveSheetIndex(5)->setCellValue('A1', 'ID');
            $spreadsheet->setActiveSheetIndex(5)->setCellValue('B1', 'ID SATUAN');
            $spreadsheet->setActiveSheetIndex(5)->setCellValue('C1', 'ID BAHAN');
            $spreadsheet->setActiveSheetIndex(5)->setCellValue('D1', 'HARGA POKOK');
            $spreadsheet->setActiveSheetIndex(5)->setCellValue('E1', 'HARGA JUAL');
            
            $f = 2;
            foreach ($harga_satuan as $val) {
                $spreadsheet->setActiveSheetIndex(5)->setCellValue('A'.$f, $val->id);
                $spreadsheet->setActiveSheetIndex(5)->setCellValue('B'.$f, $val->id_satuan);
                $spreadsheet->setActiveSheetIndex(5)->setCellValue('C'.$f, $val->id_bahan);
                $spreadsheet->setActiveSheetIndex(5)->setCellValue('D'.$f, $val->harga_pokok);
                $spreadsheet->setActiveSheetIndex(5)->setCellValue('E'.$f, $val->harga_jual);
                ++$f;
            }
            // SHEET 5
            $spreadsheet->getActiveSheet()->setTitle('HARGA_BERDASARKAN_SATUAN');
            
            $spreadsheet->createSheet();
            $spreadsheet->setActiveSheetIndex(6)->setCellValue('A1', 'ID');
            $spreadsheet->setActiveSheetIndex(6)->setCellValue('B1', 'ID SATUAN');
            $spreadsheet->setActiveSheetIndex(6)->setCellValue('C1', 'ID BAHAN');
            $spreadsheet->setActiveSheetIndex(6)->setCellValue('D1', 'ID MEMBER');
            $spreadsheet->setActiveSheetIndex(6)->setCellValue('E1', 'HARGA POKOK');
            $spreadsheet->setActiveSheetIndex(6)->setCellValue('F1', 'HARGA JUAL');
            
            $g = 2;
            foreach ($harga_member as $val) {
                $spreadsheet->setActiveSheetIndex(6)->setCellValue('A'.$g, $val->id);
                $spreadsheet->setActiveSheetIndex(6)->setCellValue('B'.$g, $val->id_satuan);
                $spreadsheet->setActiveSheetIndex(6)->setCellValue('C'.$g, $val->id_bahan);
                $spreadsheet->setActiveSheetIndex(6)->setCellValue('D'.$g, $val->id_member);
                $spreadsheet->setActiveSheetIndex(6)->setCellValue('E'.$g, $val->harga_pokok);
                $spreadsheet->setActiveSheetIndex(6)->setCellValue('F'.$g, $val->harga_jual);
                ++$g;
            }
            // SHEET 6
            $spreadsheet->getActiveSheet()->setTitle('HARGA_BERDASARKAN_LEVEL');
            
            $spreadsheet->createSheet();
            $spreadsheet->setActiveSheetIndex(7)->setCellValue('A1', 'ID');
            $spreadsheet->setActiveSheetIndex(7)->setCellValue('B1', 'ID BAHAN');
            $spreadsheet->setActiveSheetIndex(7)->setCellValue('C1', 'ID SATUAN');
            $spreadsheet->setActiveSheetIndex(7)->setCellValue('D1', 'JML. MIN');
            $spreadsheet->setActiveSheetIndex(7)->setCellValue('E1', 'JML. MAX');
            $spreadsheet->setActiveSheetIndex(7)->setCellValue('F1', 'HARGA POKOK');
            $spreadsheet->setActiveSheetIndex(7)->setCellValue('G1', 'HARGA JUAL');
            
            $h = 2;
            foreach ($range_harga as $val) {
                $spreadsheet->setActiveSheetIndex(7)->setCellValue('A'.$h, $val->id);
                $spreadsheet->setActiveSheetIndex(7)->setCellValue('B'.$h, $val->id_satuan);
                $spreadsheet->setActiveSheetIndex(7)->setCellValue('C'.$h, $val->id_bahan);
                $spreadsheet->setActiveSheetIndex(7)->setCellValue('D'.$h, $val->jumlah_minimal);
                $spreadsheet->setActiveSheetIndex(7)->setCellValue('E'.$h, $val->jumlah_maksimal);
                $spreadsheet->setActiveSheetIndex(7)->setCellValue('F'.$h, $val->harga_pokok);
                $spreadsheet->setActiveSheetIndex(7)->setCellValue('G'.$h, $val->harga_jual);
                ++$h;
            }
            // SHEET 7
            $spreadsheet->getActiveSheet()->setTitle('HARGA_BERDASARKAN_JUMLAH');
            
            $spreadsheet->createSheet();
            $spreadsheet->setActiveSheetIndex(8)->setCellValue('A1', 'ID');
            $spreadsheet->setActiveSheetIndex(8)->setCellValue('B1', 'ID MEMBER');
            $spreadsheet->setActiveSheetIndex(8)->setCellValue('C1', 'ID BAHAN');
            $spreadsheet->setActiveSheetIndex(8)->setCellValue('D1', 'ID SATUAN');
            $spreadsheet->setActiveSheetIndex(8)->setCellValue('E1', 'JML. MIN');
            $spreadsheet->setActiveSheetIndex(8)->setCellValue('F1', 'JML. MAX');
            $spreadsheet->setActiveSheetIndex(8)->setCellValue('G1', 'HARGA POKOK');
            $spreadsheet->setActiveSheetIndex(8)->setCellValue('H1', 'HARGA JUAL');
            
            $i = 2;
            foreach ($harga_range_member as $val) {
                $spreadsheet->setActiveSheetIndex(8)->setCellValue('A'.$i, $val->id);
                $spreadsheet->setActiveSheetIndex(8)->setCellValue('B'.$i, $val->id_member);
                $spreadsheet->setActiveSheetIndex(8)->setCellValue('C'.$i, $val->id_bahan);
                $spreadsheet->setActiveSheetIndex(8)->setCellValue('D'.$i, $val->id_satuan);
                $spreadsheet->setActiveSheetIndex(8)->setCellValue('E'.$i, $val->jumlah_minimal);
                $spreadsheet->setActiveSheetIndex(8)->setCellValue('F'.$i, $val->jumlah_maksimal);
                $spreadsheet->setActiveSheetIndex(8)->setCellValue('G'.$i, $val->harga_pokok);
                $spreadsheet->setActiveSheetIndex(8)->setCellValue('H'.$i, $val->harga_jual);
                ++$i;
            }
            // SHEET 8
            $spreadsheet->getActiveSheet()->setTitle('HARGA_JUMLAH_DAN_LEVEL');
            
            $spreadsheet->setActiveSheetIndex(0);
            
            $filename = 'produk-list';
            
            header('Content-Type: application/vnd.ms-excel');
            header('Content-Disposition: attachment;filename="'.$filename.'.xlsx"');
            header('Cache-Control: max-age=0');
            
            $writer = new Xlsx($spreadsheet);
            $writer->save('php://output');
        }
        
        /**
            * import_data.
            *
            * @return void
        */
        public function import_data()
        {
            $this->load->library('dummy');
            $file_mimes = ['text/x-comma-separated-values', 'text/comma-separated-values', 'application/octet-stream', 'application/vnd.ms-excel', 'application/x-csv', 'text/x-csv', 'text/csv', 'application/csv', 'application/excel', 'application/vnd.msexcel', 'text/plain', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
            
            $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
            
            $spreadsheet = $reader->load($_FILES['filess']['tmp_name']);
            
            $sheetData = $spreadsheet->getActiveSheet()->toArray();
            $sheetJenis = $spreadsheet->getSheet(1)->toArray();
            $sheetBahan = $spreadsheet->getSheet(2)->toArray();
            $sheetsatuan = $spreadsheet->getSheet(3)->toArray();
            $satu_harga = $spreadsheet->getSheet(4)->toArray();
            $harga_satuan = $spreadsheet->getSheet(5)->toArray();
            $harga_member = $spreadsheet->getSheet(6)->toArray();
            $range_harga = $spreadsheet->getSheet(7)->toArray();
            $harga_range_member = $spreadsheet->getSheet(8)->toArray();
            
            $count_nom = [];
            for ($i = 1; $i < count($sheetData); ++$i) {
                $nomor = (int) $sheetData[$i]['0'];
                if (!empty($nomor) and is_numeric($nomor)) {
                    $count_produk[] = $sheetData[$i]['0'];
                    
                    $produk[] = [
                    'id_jenis' => $sheetData[$i]['1'],
                    'barcode' => $sheetData[$i]['3'],
                    'title' => $sheetData[$i]['2'],
                    'harga_beli' => 0,
                    'harga_jual' => 0,
                    'harga_grosir' => 0,
                    'diskon' => 0,
                    'ukuran' => $sheetData[$i]['4'],
                    'jumlah' => $sheetData[$i]['5'],
                    'pub' => 1,
                    'kunci' => 0,
                    'lock_harga' => $sheetData[$i]['6'],
                    ];
                    } else {
                    $empty_produk[] = $sheetData[$i]['0'];
                }
                
                // print_r($nomor);
            }
            
            for ($j = 1; $j < count($sheetJenis); ++$j) {
                $no_jenis = (int) $sheetJenis[$j]['0'];
                if (!empty($no_jenis) and is_numeric($no_jenis)) {
                    $count_jenis[] = $sheetJenis[$j]['0'];
                    
                    $kategori[] = [
                    'jenis_cetakan' => $sheetJenis[$j]['1'],
                    'kunci' => $sheetJenis[$j]['2'],
                    'status' => $sheetJenis[$j]['3'],
                    'id_akun' => $sheetJenis[$j]['4'],
                    'pub' => 'Y',
                    ];
                    } else {
                    $empty_jenis[] = $sheetJenis[$j]['0'];
                }
            }
            // array bahan
            for ($k = 1; $k < count($sheetBahan); ++$k) {
                $no_bahan = (int) $sheetBahan[$k]['0'];
                if (!empty($no_bahan) and is_numeric($no_bahan)) {
                    $count_bahan[] = $sheetBahan[$k]['0'];
                    
                    $bahan[] = [
                    'id_jenis' => $sheetBahan[$k]['1'],
                    'title' => $sheetBahan[$k]['2'],
                    'harga_modal' => $sheetBahan[$k]['3'],
                    'harga_jual' => $sheetBahan[$k]['4'],
                    'harga' => 0,
                    'id_satuan' => $sheetBahan[$k]['5'],
                    'status_stok' => $sheetBahan[$k]['6'],
                    'kunci' => $sheetBahan[$k]['7'],
                    'status' => $sheetBahan[$k]['8'],
                    'pub' => $sheetBahan[$k]['9'],
                    'type_harga' => $sheetBahan[$k]['10'],
                    ];
                    } else {
                    $empty_bahan[] = $sheetBahan[$k]['0'];
                }
            }
            
            // array satuan
            for ($l = 1; $l < count($sheetsatuan); ++$l) {
                $no_satuan = (int) $sheetsatuan[$l]['0'];
                if (!empty($no_satuan) and is_numeric($no_satuan)) {
                    $count_satuan[] = $sheetsatuan[$l]['0'];
                    
                    $satuan[] = [
                    'satuan' => $sheetsatuan[$l]['1'],
                    'nama_satuan' => $sheetsatuan[$l]['2'],
                    'jumlah' => $sheetsatuan[$l]['3'],
                    'pub' => 0,
                    ];
                    } else {
                    $empty_satuan[] = $sheetsatuan[$l]['0'];
                }
            }
            // array satu_harga
            for ($l = 1; $l < count($satu_harga); ++$l) {
                $no_satu_harga = (int) $satu_harga[$l]['0'];
                if (!empty($no_satu_harga) and is_numeric($no_satu_harga)) {
                    $count_satu_harga[] = $satu_harga[$l]['0'];
                    
                    $satuharga[] = [
                    'id_bahan' => $satu_harga[$l]['1'],
                    'id_satuan' => $satu_harga[$l]['2'],
                    'harga_beli' => $satu_harga[$l]['3'],
                    'harga_pokok' => $satu_harga[$l]['3'],
                    'detail_harga_pokok' => '',
                    'persen' => $satu_harga[$l]['4'],
                    'harga_jual' => $satu_harga[$l]['5'],
                    ];
                    } else {
                    $empty_satu_harga[] = $satu_harga[$l]['0'];
                }
            }
            
            // array harga_satuan
            for ($l = 1; $l < count($harga_satuan); ++$l) {
                $no_harga_satuan = (int) $harga_satuan[$l]['0'];
                if (!empty($no_harga_satuan) and is_numeric($no_harga_satuan)) {
                    $count_harga_member[] = $harga_satuan[$l]['0'];
                    
                    $hargasatuan[] = [
                    'id_satuan' => $harga_satuan[$l]['1'],
                    'id_bahan' => $harga_satuan[$l]['2'],
                    'harga_pokok' => $harga_satuan[$l]['3'],
                    'harga_jual' => $harga_satuan[$l]['4'],
                    ];
                    } else {
                    $empty_harga_satuan[] = $harga_satuan[$l]['0'];
                }
            }
            // array harga_member
            for ($l = 1; $l < count($harga_member); ++$l) {
                $no_harga_member = (int) $harga_member[$l]['0'];
                if (!empty($no_harga_member) and is_numeric($no_harga_member)) {
                    $count_harga_member[] = $harga_member[$l]['0'];
                    
                    $hargamember[] = [
                    'id_satuan' => $harga_member[$l]['1'],
                    'id_bahan' => $harga_member[$l]['2'],
                    'id_member' => $harga_member[$l]['3'],
                    'harga_pokok' => $harga_member[$l]['4'],
                    'harga_jual' => $harga_member[$l]['5'],
                    ];
                    } else {
                    $empty_harga_member[] = $harga_member[$l]['0'];
                }
            }
            // array range_harga
            for ($l = 1; $l < count($range_harga); ++$l) {
                $no_harga_member = (int) $range_harga[$l]['0'];
                if (!empty($no_harga_member) and is_numeric($no_harga_member)) {
                    $count_range_harga[] = $range_harga[$l]['0'];
                    
                    $rangeharga[] = [
                    'id_satuan' => $range_harga[$l]['1'],
                    'id_bahan' => $range_harga[$l]['2'],
                    'jumlah_minimal' => $range_harga[$l]['3'],
                    'jumlah_maksimal' => $range_harga[$l]['4'],
                    'harga_pokok' => $range_harga[$l]['5'],
                    'harga_jual' => $range_harga[$l]['6'],
                    ];
                    } else {
                    $empty_range_harga[] = $range_harga[$l]['0'];
                }
            }
            
            // array harga_range_member
            for ($l = 1; $l < count($harga_range_member); ++$l) {
                $no_harga_range_member = (int) $harga_range_member[$l]['0'];
                if (!empty($no_harga_range_member) and is_numeric($no_harga_range_member)) {
                    $count_harga_range_member[] = $harga_range_member[$l]['0'];
                    
                    $hargarangemember[] = [
                    'id_member' => $harga_range_member[$l]['1'],
                    'id_bahan' => $harga_range_member[$l]['2'],
                    'id_satuan' => $harga_range_member[$l]['3'],
                    'jumlah_minimal' => $harga_range_member[$l]['4'],
                    'jumlah_maksimal' => $harga_range_member[$l]['5'],
                    'harga_pokok' => $harga_range_member[$l]['6'],
                    'harga_jual' => $harga_range_member[$l]['7'],
                    ];
                    } else {
                    $empty_harga_range_member[] = $harga_range_member[$l]['0'];
                }
            }
            
            $this->db->truncate('produk');
            $this->db->truncate('jenis_cetakan');
            $this->db->truncate('bahan');
            $this->db->truncate('satuan');
            $this->db->truncate('satu_harga');
            // $this->db->truncate('harga_satuan');
            // $this->db->truncate('harga_member');
            // $this->db->truncate('range_harga');
            // $this->db->truncate('harga_range_member');
            
            if (!empty($produk)) {
                $_produk = $this->dummy->reset_produk();
                $this->db->insert_batch('produk', $_produk);
                $this->db->insert_batch('produk', $produk);
            }
            
            if (!empty($kategori)) {
                $jenis_cetakan = $this->dummy->reset_kategori();
                $this->db->insert_batch('jenis_cetakan', $jenis_cetakan);
                $this->db->insert_batch('jenis_cetakan', $kategori);
            }
            
            if (!empty($bahan)) {
                $_bahan = $this->dummy->reset_bahan();
                $this->db->insert_batch('bahan', $_bahan);
                $this->db->insert_batch('bahan', $bahan);
            }
            
            if (!empty($satuan)) {
                $this->db->insert_batch('satuan', $satuan);
            }
            
            if (!empty($satuharga)) {
                $this->db->insert_batch('satu_harga', $satuharga);
            }
            
            // if (!empty($hargasatuan)) {
            // $this->db->insert_batch('harga_satuan', $hargasatuan);
            // }
            
            // if (!empty($hargamember)) {
            // $this->db->insert_batch('harga_member', $hargamember);
            // }
            
            // if (!empty($rangeharga)) {
            // $this->db->insert_batch('range_harga', $rangeharga);
            // }
            
            $arr = ['status' => 200, 'msg' => 'berhasil input'];
            if (!empty($satuharga)) {
                $res = $this->model_app->import_satuharga($satuharga);
                if ($res) {
                    $arr = ['status' => 200, 'msg' => 'berhasil input'];
                    } else {
                    $arr = ['status' => 404, 'msg' => 'data gagal di input'];
                }
            }
            $this->output->set_output(json_encode($arr));
        }
        
        public function import_data_kategori()
        {
            $this->load->library('dummy');
            $file_mimes = ['text/x-comma-separated-values', 'text/comma-separated-values', 'application/octet-stream', 'application/vnd.ms-excel', 'application/x-csv', 'text/x-csv', 'text/csv', 'application/csv', 'application/excel', 'application/vnd.msexcel', 'text/plain', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
            
            $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
            
            $spreadsheet = $reader->load($_FILES['filess']['tmp_name']);
            
            $sheetJenis = $spreadsheet->getActiveSheet()->toArray();
            
            for ($j = 1; $j < count($sheetJenis); ++$j) {
                $no_jenis = (int) $sheetJenis[$j]['0'];
                if (!empty($no_jenis) and is_numeric($no_jenis)) {
                    $count_jenis[] = $sheetJenis[$j]['0'];
                    
                    $kategori[] = [
                    'jenis_cetakan' => $sheetJenis[$j]['1'],
                    'kunci' => $sheetJenis[$j]['2'],
                    'status' => 0,
                    'id_akun' => 411,
                    ];
                    } else {
                    $empty_jenis[] = $sheetJenis[$j]['0'];
                }
            }
            
            $this->db->truncate('jenis_cetakan');
            
            if (!empty($kategori)) {
                $jenis_cetakan = $this->dummy->reset_kategori();
                $this->db->insert_batch('jenis_cetakan', $jenis_cetakan);
                $this->db->insert_batch('jenis_cetakan', $kategori);
            }
            
            $arr = ['status' => 200, 'msg' => 'berhasil input'];
            if (!empty($hargarangemember)) {
                $res = $this->model_app->import_harga_range_member($hargarangemember);
                if ($res) {
                    $arr = ['status' => 200, 'msg' => 'berhasil input'];
                    } else {
                    $arr = ['status' => 404, 'msg' => 'data gagal di input'];
                }
            }
            $this->output->set_output(json_encode($arr));
        }

        public function export_bahan_excel()
        {
            cek_crud_akses(8);
            $spreadsheet = $this->build_import_bahan_workbook(true);

            $filename = 'export-data-produk-'.date('Ymd-His');
            header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
            header('Content-Disposition: attachment;filename="'.$filename.'.xlsx"');
            header('Cache-Control: max-age=0');

            $writer = new Xlsx($spreadsheet);
            $writer->setPreCalculateFormulas(false);
            $writer->save('php://output');
            exit;
        }

        public function template_import_bahan()
        {
            cek_crud_akses(8);
            $spreadsheet = $this->build_import_bahan_workbook(false);

            $filename = 'template-import-data-produk';
            header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
            header('Content-Disposition: attachment;filename="'.$filename.'.xlsx"');
            header('Cache-Control: max-age=0');

            $writer = new Xlsx($spreadsheet);
            $writer->setPreCalculateFormulas(false);
            $writer->save('php://output');
            exit;
        }

        private function build_import_bahan_workbook($withData = false)
        {
            $this->ensure_bahan_komposisi_schema();
            $this->ensure_bahan_foto_schema();
            $this->ensure_bahan_decimal_schema();
            $this->ensure_satu_harga_decimal_schema();
            $this->ensure_harga_satuan_decimal_schema();
            $this->ensure_harga_tingkat_decimal_schema();

            $produkList = $this->db
                ->select('title')
                ->from('produk')
                ->where('pub', 1)
                ->order_by('title', 'ASC')
                ->get()
                ->result_array();
            $kategoriList = $this->db
                ->select('jenis_cetakan')
                ->from('jenis_cetakan')
                ->where('kunci', 0)
                ->where('pub', 'Y')
                ->order_by('jenis_cetakan', 'ASC')
                ->get()
                ->result_array();
            $satuanList = $this->db
                ->select('satuan')
                ->from('satuan')
                ->where('pub', 0)
                ->order_by('satuan', 'ASC')
                ->get()
                ->result_array();
            $memberList = $this->db
                ->select('title')
                ->from('member')
                ->where('status', 1)
                ->order_by('title', 'ASC')
                ->get()
                ->result_array();

            $produkOptions = $this->import_template_option_values($produkList, 'title');
            $kategoriOptions = $this->import_template_option_values($kategoriList, 'jenis_cetakan');
            $satuanOptions = $this->import_template_option_values($satuanList, 'satuan');
            $memberOptions = $this->import_template_option_values($memberList, 'title');
            $yesNoOptions = ['Y', 'N'];
            $boolOptions = ['1', '0'];
            $hitungUkuranOptions = ['1', '2', '0'];
            $modeHargaOptions = array_values($this->import_bahan_mode_options());
            $isParentFieldAvailable = $this->db->field_exists('parent_bahan_id', 'bahan');

            $mainHeaders = [
                'NAMA BARANG/BAHAN',
                'PRODUK',
                'KATEGORI',
                'HARGA MODAL',
                'HARGA JUAL',
                'SATUAN',
                'MODE HARGA',
                'STOK',
                'LIMIT STOK',
                'HITUNG UKURAN',
                'AKTIF',
                'BARCODE',
                'UKURAN DEFAULT',
                'KOMISI DESAIN',
                'MARGIN FINISHING',
                'VARIAN WARNA',
                'VARIAN UKURAN',
                'HARGA JUAL VARIAN',
                'KOMISI PENJUALAN',
                'TIPE KOMISI PENJUALAN',
                'NILAI KOMISI PENJUALAN',
                'FOTO PRODUK',
            ];

            $spreadsheet = new Spreadsheet();
            $sheet = $spreadsheet->getActiveSheet();
            $sheet->setTitle('IMPORT_BAHAN');
            $sheet->freezePane('A2');
            $sheet->fromArray($mainHeaders, null, 'A1');

            $mainRows = $withData
                ? $this->build_export_import_bahan_main_rows($isParentFieldAvailable)
                : $this->build_template_import_bahan_main_rows($produkOptions, $kategoriOptions, $satuanOptions, $memberOptions);
            if (!empty($mainRows)) {
                $sheet->fromArray($mainRows, null, 'A2');
                foreach ($mainRows as $index => $row) {
                    $barcode = isset($row[11]) ? (string) $row[11] : '';
                    if ($barcode !== '') {
                        $sheet->setCellValueExplicit('L'.($index + 2), $barcode, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);
                    }
                    $foto = isset($row[21]) ? trim((string) $row[21]) : '';
                    if ($foto !== '' && preg_match('#^uploads/produk/[A-Za-z0-9._-]+$#', $foto)) {
                        $sheet->getCell('V'.($index + 2))->getHyperlink()->setUrl(base_url($foto));
                        $sheet->getStyle('V'.($index + 2))->getFont()->getColor()->setRGB('0563C1');
                        $sheet->getStyle('V'.($index + 2))->getFont()->setUnderline(true);
                    }
                }
            }
            $mainInputLimit = max(501, count($mainRows) + 120);

            $referenceSheet = $spreadsheet->createSheet();
            $referenceSheet->setTitle('REFERENSI_DATA');
            $referenceLists = [
                'REF_PRODUK' => ['column' => 'A', 'header' => 'PRODUK', 'values' => $produkOptions],
                'REF_KATEGORI' => ['column' => 'B', 'header' => 'KATEGORI', 'values' => $kategoriOptions],
                'REF_SATUAN' => ['column' => 'C', 'header' => 'SATUAN', 'values' => $satuanOptions],
                'REF_STOK' => ['column' => 'D', 'header' => 'STOK', 'values' => $yesNoOptions],
                'REF_BOOL' => ['column' => 'E', 'header' => 'YA_TIDAK_ANGKA', 'values' => $boolOptions],
                'REF_HITUNG_UKURAN' => ['column' => 'F', 'header' => 'HITUNG_UKURAN', 'values' => $hitungUkuranOptions],
                'REF_MEMBER' => ['column' => 'G', 'header' => 'LEVEL_MEMBER', 'values' => $memberOptions],
                'REF_MODE_HARGA' => ['column' => 'H', 'header' => 'MODE_HARGA', 'values' => $modeHargaOptions],
            ];
            $this->build_import_bahan_reference_sheet($referenceSheet, $referenceLists);
            $referenceRanges = $this->build_import_bahan_dropdown_sources($sheet, [
                'REF_PRODUK' => ['column' => 'AA', 'header' => 'PRODUK', 'values' => $produkOptions],
                'REF_KATEGORI' => ['column' => 'AB', 'header' => 'KATEGORI', 'values' => $kategoriOptions],
                'REF_SATUAN' => ['column' => 'AC', 'header' => 'SATUAN', 'values' => $satuanOptions],
                'REF_STOK' => ['column' => 'AD', 'header' => 'STOK', 'values' => $yesNoOptions],
                'REF_BOOL' => ['column' => 'AE', 'header' => 'YA_TIDAK_ANGKA', 'values' => $boolOptions],
                'REF_HITUNG_UKURAN' => ['column' => 'AF', 'header' => 'HITUNG_UKURAN', 'values' => $hitungUkuranOptions],
                'REF_MEMBER' => ['column' => 'AG', 'header' => 'LEVEL_MEMBER', 'values' => $memberOptions],
                'REF_MODE_HARGA' => ['column' => 'AH', 'header' => 'MODE_HARGA', 'values' => $modeHargaOptions],
            ]);

            $hargaSatuanSheet = $spreadsheet->createSheet();
            $hargaSatuanSheet->setTitle('HARGA_PER_SATUAN');
            $hargaSatuanSheet->freezePane('A2');
            $hargaSatuanSheet->fromArray(['NAMA BARANG/BAHAN', 'SATUAN', 'HARGA POKOK', 'HARGA JUAL'], null, 'A1');
            $hargaSatuanRows = $withData
                ? $this->build_export_import_bahan_harga_satuan_rows($isParentFieldAvailable)
                : $this->build_template_import_bahan_harga_satuan_rows($satuanOptions);
            if (!empty($hargaSatuanRows)) {
                $hargaSatuanSheet->fromArray($hargaSatuanRows, null, 'A2');
            }
            $hargaSatuanRanges = $this->build_import_bahan_dropdown_sources($hargaSatuanSheet, [
                'REF_SATUAN' => ['column' => 'AA', 'header' => 'SATUAN', 'values' => $satuanOptions],
            ]);

            $hargaLevelSheet = $spreadsheet->createSheet();
            $hargaLevelSheet->setTitle('HARGA_PER_LEVEL');
            $hargaLevelSheet->freezePane('A2');
            $hargaLevelSheet->fromArray(['NAMA BARANG/BAHAN', 'SATUAN', 'LEVEL MEMBER', 'HARGA JUAL'], null, 'A1');
            $hargaLevelRows = $withData
                ? $this->build_export_import_bahan_harga_level_rows($isParentFieldAvailable)
                : $this->build_template_import_bahan_harga_level_rows($satuanOptions, $memberOptions);
            if (!empty($hargaLevelRows)) {
                $hargaLevelSheet->fromArray($hargaLevelRows, null, 'A2');
            }
            $hargaLevelRanges = $this->build_import_bahan_dropdown_sources($hargaLevelSheet, [
                'REF_SATUAN' => ['column' => 'AA', 'header' => 'SATUAN', 'values' => $satuanOptions],
                'REF_MEMBER' => ['column' => 'AB', 'header' => 'LEVEL_MEMBER', 'values' => $memberOptions],
            ]);

            $hargaJumlahSheet = $spreadsheet->createSheet();
            $hargaJumlahSheet->setTitle('HARGA_PER_JUMLAH');
            $hargaJumlahSheet->freezePane('A2');
            $hargaJumlahSheet->fromArray(['NAMA BARANG/BAHAN', 'SATUAN', 'JUMLAH MIN', 'JUMLAH MAX', 'HARGA JUAL'], null, 'A1');
            $hargaJumlahRows = $withData
                ? $this->build_export_import_bahan_harga_jumlah_rows($isParentFieldAvailable)
                : $this->build_template_import_bahan_harga_jumlah_rows($satuanOptions);
            if (!empty($hargaJumlahRows)) {
                $hargaJumlahSheet->fromArray($hargaJumlahRows, null, 'A2');
            }
            $hargaJumlahRanges = $this->build_import_bahan_dropdown_sources($hargaJumlahSheet, [
                'REF_SATUAN' => ['column' => 'AA', 'header' => 'SATUAN', 'values' => $satuanOptions],
            ]);

            $hargaJumlahLevelSheet = $spreadsheet->createSheet();
            $hargaJumlahLevelSheet->setTitle('HARGA_JUMLAH_LEVEL');
            $hargaJumlahLevelSheet->freezePane('A2');
            $hargaJumlahLevelSheet->fromArray(['NAMA BARANG/BAHAN', 'SATUAN', 'LEVEL MEMBER', 'JUMLAH MIN', 'JUMLAH MAX', 'HARGA JUAL'], null, 'A1');
            $hargaJumlahLevelRows = $withData
                ? $this->build_export_import_bahan_harga_jumlah_level_rows($isParentFieldAvailable)
                : $this->build_template_import_bahan_harga_jumlah_level_rows($satuanOptions, $memberOptions);
            if (!empty($hargaJumlahLevelRows)) {
                $hargaJumlahLevelSheet->fromArray($hargaJumlahLevelRows, null, 'A2');
            }
            $hargaJumlahLevelRanges = $this->build_import_bahan_dropdown_sources($hargaJumlahLevelSheet, [
                'REF_SATUAN' => ['column' => 'AA', 'header' => 'SATUAN', 'values' => $satuanOptions],
                'REF_MEMBER' => ['column' => 'AB', 'header' => 'LEVEL_MEMBER', 'values' => $memberOptions],
            ]);

            $priceMainStartRow = $withData ? count($mainRows) + 2 : 2;
            $linkedProductCapacity = $withData ? 100 : 120;
            $helperColumns = [
                'PER_SATUAN' => 'AI',
                'PER_LEVEL' => 'AJ',
                'PER_JUMLAH' => 'AK',
                'JUMLAH_DAN_LEVEL' => 'AL',
            ];
            $this->build_import_bahan_mode_index_columns($sheet, $priceMainStartRow, $mainInputLimit, $helperColumns);
            $priceSlotCounts = [
                2 => $this->import_bahan_price_slot_count($hargaSatuanRows, 4),
                3 => $this->import_bahan_price_slot_count($hargaLevelRows, max(4, count($memberOptions))),
                4 => $this->import_bahan_price_slot_count($hargaJumlahRows, 4),
                5 => $this->import_bahan_price_slot_count($hargaJumlahLevelRows, max(6, count($memberOptions) * 2)),
            ];

            $hargaSatuanLinkedStart = max(2, count($hargaSatuanRows) + 2);
            $hargaLevelLinkedStart = max(2, count($hargaLevelRows) + 2);
            $hargaJumlahLinkedStart = max(2, count($hargaJumlahRows) + 2);
            $hargaJumlahLevelLinkedStart = max(2, count($hargaJumlahLevelRows) + 2);

            $hargaSatuanLinkedEnd = $this->add_import_bahan_linked_price_rows(
                $hargaSatuanSheet,
                'PER_SATUAN',
                $priceMainStartRow,
                $mainInputLimit,
                $hargaSatuanLinkedStart,
                $priceSlotCounts[2],
                $linkedProductCapacity,
                $helperColumns['PER_SATUAN']
            );
            $hargaLevelLinkedEnd = $this->add_import_bahan_linked_price_rows(
                $hargaLevelSheet,
                'PER_LEVEL',
                $priceMainStartRow,
                $mainInputLimit,
                $hargaLevelLinkedStart,
                $priceSlotCounts[3],
                $linkedProductCapacity,
                $helperColumns['PER_LEVEL']
            );
            $hargaJumlahLinkedEnd = $this->add_import_bahan_linked_price_rows(
                $hargaJumlahSheet,
                'PER_JUMLAH',
                $priceMainStartRow,
                $mainInputLimit,
                $hargaJumlahLinkedStart,
                $priceSlotCounts[4],
                $linkedProductCapacity,
                $helperColumns['PER_JUMLAH']
            );
            $hargaJumlahLevelLinkedEnd = $this->add_import_bahan_linked_price_rows(
                $hargaJumlahLevelSheet,
                'JUMLAH_DAN_LEVEL',
                $priceMainStartRow,
                $mainInputLimit,
                $hargaJumlahLevelLinkedStart,
                $priceSlotCounts[5],
                $linkedProductCapacity,
                $helperColumns['JUMLAH_DAN_LEVEL']
            );

            $guideSheet = $spreadsheet->createSheet();
            $guideSheet->setTitle('PANDUAN');
            $this->build_import_bahan_guide_sheet($guideSheet);

            $mainSheetMaxRows = $mainInputLimit;
            $hargaSatuanMaxRows = max(2, count($hargaSatuanRows) + 1, $hargaSatuanLinkedEnd);
            $hargaLevelMaxRows = max(2, count($hargaLevelRows) + 1, $hargaLevelLinkedEnd);
            $hargaJumlahMaxRows = max(2, count($hargaJumlahRows) + 1, $hargaJumlahLinkedEnd);
            $hargaJumlahLevelMaxRows = max(2, count($hargaJumlahLevelRows) + 1, $hargaJumlahLevelLinkedEnd);

            $this->style_import_bahan_template($sheet, $mainSheetMaxRows);
            $this->add_import_bahan_comments($sheet);
            $this->add_import_bahan_validations($sheet, $referenceRanges, $mainSheetMaxRows);

            $this->style_import_bahan_price_sheet($hargaSatuanSheet, ['A' => 36, 'B' => 18, 'C' => 18, 'D' => 18], $hargaSatuanMaxRows);
            $this->add_import_bahan_price_comments($hargaSatuanSheet, [
                'A1' => 'Kolom ini otomatis mengikuti sheet IMPORT_BAHAN untuk produk dengan MODE HARGA = PER_SATUAN. Tidak perlu diketik ulang.',
                'B1' => 'Pada area linked, satuan default ikut dari IMPORT_BAHAN. Anda tetap boleh menggantinya bila perlu.',
                'C1' => 'Opsional. Harga pokok per satuan. Kosongkan jika mengikuti harga modal utama.',
                'D1' => 'Wajib. Isi harga jual untuk satuan tersebut.',
            ]);
            $this->add_import_bahan_price_validations($hargaSatuanSheet, $hargaSatuanRanges, 2, $hargaSatuanMaxRows);
            $this->style_import_bahan_linked_price_area($hargaSatuanSheet, $hargaSatuanLinkedStart, $hargaSatuanLinkedEnd);

            $this->style_import_bahan_price_sheet($hargaLevelSheet, ['A' => 36, 'B' => 18, 'C' => 24, 'D' => 18], $hargaLevelMaxRows);
            $this->add_import_bahan_price_comments($hargaLevelSheet, [
                'A1' => 'Kolom ini otomatis mengikuti sheet IMPORT_BAHAN untuk produk dengan MODE HARGA = PER_LEVEL. Satu produk disiapkan beberapa baris agar Anda tinggal isi level dan harga.',
                'B1' => 'Pada area linked, satuan default ikut dari IMPORT_BAHAN. Anda tetap boleh menggantinya bila perlu.',
                'C1' => 'Pilih level member seperti Member Regular, Gold, Silver, dan seterusnya.',
                'D1' => 'Wajib. Isi harga jual untuk level tersebut.',
            ]);
            $this->add_import_bahan_price_validations($hargaLevelSheet, $hargaLevelRanges, 3, $hargaLevelMaxRows);
            $this->style_import_bahan_linked_price_area($hargaLevelSheet, $hargaLevelLinkedStart, $hargaLevelLinkedEnd);

            $this->style_import_bahan_price_sheet($hargaJumlahSheet, ['A' => 36, 'B' => 18, 'C' => 14, 'D' => 14, 'E' => 18], $hargaJumlahMaxRows);
            $this->add_import_bahan_price_comments($hargaJumlahSheet, [
                'A1' => 'Kolom ini otomatis mengikuti sheet IMPORT_BAHAN untuk produk dengan MODE HARGA = PER_JUMLAH. Tidak perlu diketik ulang.',
                'B1' => 'Pada area linked, satuan default ikut dari IMPORT_BAHAN. Anda tetap boleh menggantinya bila perlu.',
                'C1' => 'Isi jumlah minimal.',
                'D1' => 'Isi jumlah maksimal.',
                'E1' => 'Wajib. Isi harga jual untuk rentang jumlah tersebut.',
            ]);
            $this->add_import_bahan_price_validations($hargaJumlahSheet, $hargaJumlahRanges, 4, $hargaJumlahMaxRows);
            $this->style_import_bahan_linked_price_area($hargaJumlahSheet, $hargaJumlahLinkedStart, $hargaJumlahLinkedEnd);

            $this->style_import_bahan_price_sheet($hargaJumlahLevelSheet, ['A' => 36, 'B' => 18, 'C' => 24, 'D' => 14, 'E' => 14, 'F' => 18], $hargaJumlahLevelMaxRows);
            $this->add_import_bahan_price_comments($hargaJumlahLevelSheet, [
                'A1' => 'Kolom ini otomatis mengikuti sheet IMPORT_BAHAN untuk produk dengan MODE HARGA = JUMLAH_DAN_LEVEL. Satu produk disiapkan beberapa baris agar Anda tinggal isi level, jumlah, dan harga.',
                'B1' => 'Pada area linked, satuan default ikut dari IMPORT_BAHAN. Anda tetap boleh menggantinya bila perlu.',
                'C1' => 'Pilih level member.',
                'D1' => 'Isi jumlah minimal.',
                'E1' => 'Isi jumlah maksimal.',
                'F1' => 'Wajib. Isi harga jual untuk kombinasi jumlah dan level.',
            ]);
            $this->add_import_bahan_price_validations($hargaJumlahLevelSheet, $hargaJumlahLevelRanges, 5, $hargaJumlahLevelMaxRows);
            $this->style_import_bahan_linked_price_area($hargaJumlahLevelSheet, $hargaJumlahLevelLinkedStart, $hargaJumlahLevelLinkedEnd);

            $spreadsheet->setActiveSheetIndex(0);

            return $spreadsheet;
        }

        private function import_template_option_values($rows, $field)
        {
            $values = [];
            foreach ($rows as $row) {
                $value = isset($row[$field]) ? trim((string) $row[$field]) : '';
                if ($value !== '' && !in_array($value, $values, true)) {
                    $values[] = $value;
                }
            }

            return $values;
        }

        private function import_bahan_mode_options()
        {
            return [
                'SATU_HARGA',
                'PER_SATUAN',
                'PER_LEVEL',
                'PER_JUMLAH',
                'JUMLAH_DAN_LEVEL',
            ];
        }

        private function import_bahan_mode_label($mode)
        {
            $map = [
                1 => 'SATU_HARGA',
                2 => 'PER_SATUAN',
                3 => 'PER_LEVEL',
                4 => 'PER_JUMLAH',
                5 => 'JUMLAH_DAN_LEVEL',
            ];

            return isset($map[(int) $mode]) ? $map[(int) $mode] : 'SATU_HARGA';
        }

        private function build_template_import_bahan_main_rows($produkOptions, $kategoriOptions, $satuanOptions, $memberOptions)
        {
            $produk = isset($produkOptions[0]) ? $produkOptions[0] : '';
            $kategori = isset($kategoriOptions[0]) ? $kategoriOptions[0] : '';
            $satuan = isset($satuanOptions[0]) ? $satuanOptions[0] : '';
            $hasMember = !empty($memberOptions);
            $rows = [
                ['Cetak Yasin', $produk, $kategori, '15000', '20000', $satuan, 'SATU_HARGA', 'N', '0', '0', '1', '', '-', '0', '0', '', '', ''],
            ];

            if ($satuan !== '') {
                $rows[] = ['Kertas HVS', $produk, $kategori, '500', '700', $satuan, 'PER_SATUAN', 'N', '0', '0', '1', '', '-', '0', '0', '', '', ''];
                $rows[] = ['Cetak Brosur', $produk, $kategori, '2500', '3000', $satuan, 'PER_JUMLAH', 'N', '0', '0', '1', '', '-', '0', '0', '', '', ''];
                if ($hasMember) {
                    $rows[] = ['Fotokopi Warna', $produk, $kategori, '1000', '1500', $satuan, 'PER_LEVEL', 'N', '0', '0', '1', '', '-', '0', '0', '', '', ''];
                    $rows[] = ['Brosur Art Paper', $produk, $kategori, '2500', '3000', $satuan, 'JUMLAH_DAN_LEVEL', 'N', '0', '0', '1', '', '-', '0', '0', '', '', ''];
                }
            }

            return $rows;
        }

        private function build_template_import_bahan_harga_satuan_rows($satuanOptions)
        {
            return [];
        }

        private function build_template_import_bahan_harga_level_rows($satuanOptions, $memberOptions)
        {
            return [];
        }

        private function build_template_import_bahan_harga_jumlah_rows($satuanOptions)
        {
            return [];
        }

        private function build_template_import_bahan_harga_jumlah_level_rows($satuanOptions, $memberOptions)
        {
            return [];
        }

        private function import_bahan_price_slot_count($rows, $minimum = 4)
        {
            $minimum = max(1, (int) $minimum);
            if (empty($rows)) {
                return $minimum;
            }

            $grouped = [];
            foreach ($rows as $row) {
                $title = isset($row[0]) ? trim((string) $row[0]) : '';
                if ($title === '') {
                    continue;
                }

                if (!isset($grouped[$title])) {
                    $grouped[$title] = 0;
                }
                $grouped[$title]++;
            }

            if (empty($grouped)) {
                return $minimum;
            }

            return max($minimum, (int) max($grouped));
        }

        private function build_import_bahan_mode_index_columns($sheet, $mainStartRow, $mainEndRow, $columns)
        {
            if ((int) $mainStartRow > (int) $mainEndRow) {
                return;
            }

            foreach ($columns as $modeLabel => $column) {
                $sheet->setCellValue($column.'1', 'IDX_'.$modeLabel);
                $sheet->getColumnDimension($column)->setVisible(false);
                for ($row = (int) $mainStartRow; $row <= (int) $mainEndRow; $row++) {
                    $sheet->setCellValue(
                        $column.$row,
                        '=IF(AND($A'.$row.'<>"",$G'.$row.'="'.$modeLabel.'"),COUNTIF($G$'.$mainStartRow.':$G'.$row.',"'.$modeLabel.'"),"")'
                    );
                }
            }
        }

        private function add_import_bahan_linked_price_rows($sheet, $modeLabel, $mainStartRow, $mainEndRow, $startRow, $slotCount, $productCapacity = 120, $helperColumn = 'AI')
        {
            $startRow = max(2, (int) $startRow);
            $slotCount = max(1, (int) $slotCount);
            if ((int) $mainStartRow > (int) $mainEndRow) {
                return $startRow - 1;
            }

            $availableProducts = max(0, ((int) $mainEndRow - (int) $mainStartRow) + 1);
            $productCapacity = max(0, min($availableProducts, (int) $productCapacity));
            if ($productCapacity < 1) {
                return $startRow - 1;
            }

            $linkedRows = $productCapacity * $slotCount;
            $endRow = $startRow + $linkedRows - 1;

            $nameFormula = $this->import_bahan_linked_price_formula('A', $mainStartRow, $mainEndRow, $startRow, $slotCount, $helperColumn);
            $satuanFormula = $this->import_bahan_linked_price_formula('F', $mainStartRow, $mainEndRow, $startRow, $slotCount, $helperColumn);

            for ($row = $startRow; $row <= $endRow; $row++) {
                $sheet->setCellValue('A'.$row, $nameFormula);
                $sheet->setCellValue('B'.$row, $satuanFormula);
            }

            return $endRow;
        }

        private function import_bahan_linked_price_formula($sourceColumn, $mainStartRow, $mainEndRow, $startRow, $slotCount, $helperColumn)
        {
            $sourceRange = 'IMPORT_BAHAN!$'.$sourceColumn.'$'.$mainStartRow.':$'.$sourceColumn.'$'.$mainEndRow;
            $helperRange = 'IMPORT_BAHAN!$'.$helperColumn.'$'.$mainStartRow.':$'.$helperColumn.'$'.$mainEndRow;
            $slotIndex = 'INT((ROW()-'.$startRow.')/'.$slotCount.')+1';

            return '=IFERROR(INDEX('.$sourceRange.',MATCH('.$slotIndex.','.$helperRange.',0)),"")';
        }

        private function build_export_import_bahan_main_rows($isParentFieldAvailable)
        {
            $query = $this->db
                ->select('
                    bahan.id,
                    bahan.title,
                    bahan.harga_modal,
                    bahan.harga_jual,
                    bahan.id_satuan,
                    bahan.status_stok,
                    bahan.batas_minimum_stok,
                    bahan.status,
                    bahan.pub,
                    bahan.barcode,
                    bahan.foto,
                    bahan.ukuran_default,
                    bahan.komisi_desain_aktif,
                    bahan.komisi_penjualan_aktif,
                    bahan.komisi_penjualan_tipe,
                    bahan.komisi_penjualan_nilai,
                    bahan.margin_finishing_aktif,
                    bahan.varian_aktif,
                    bahan.type_harga,
                    produk.title AS produk_title,
                    jenis_cetakan.jenis_cetakan,
                    satuan.satuan
                ', false)
                ->from('bahan')
                ->join('produk', 'produk.id = bahan.id_produk', 'left')
                ->join('jenis_cetakan', 'jenis_cetakan.id_jenis = bahan.id_jenis', 'left')
                ->join('satuan', 'satuan.id = bahan.id_satuan', 'left')
                ->where('bahan.kunci', 0);

            if ($isParentFieldAvailable) {
                $query->group_start()
                    ->where('bahan.parent_bahan_id', 0)
                    ->or_where('bahan.parent_bahan_id IS NULL', null, false)
                    ->group_end();
            }

            $items = $query
                ->order_by('bahan.title', 'ASC')
                ->get()
                ->result();

            $rows = [];
            foreach ($items as $item) {
                $baseRow = [
                    (string) $item->title,
                    (string) $item->produk_title,
                    (string) $item->jenis_cetakan,
                    $this->harga_decimal_value($item->harga_modal),
                    $this->harga_decimal_value($item->harga_jual),
                    (string) $item->satuan,
                    $this->import_bahan_mode_label((int) $item->type_harga),
                    (string) $item->status_stok,
                    $this->harga_decimal_value($item->batas_minimum_stok),
                    (string) ((int) $item->status),
                    (string) ((int) $item->pub),
                    (string) $item->barcode,
                    trim((string) $item->ukuran_default) !== '' ? (string) $item->ukuran_default : '-',
                    (string) ((int) $item->komisi_desain_aktif),
                    (string) ((int) $item->margin_finishing_aktif),
                    '',
                    '',
                    '',
                    (string) ((int) $item->komisi_penjualan_aktif),
                    (string) $item->komisi_penjualan_tipe,
                    $this->harga_decimal_value($item->komisi_penjualan_nilai),
                    (string) $item->foto,
                ];

                $variants = [];
                if ((int) $item->varian_aktif === 1 && $isParentFieldAvailable) {
                    $variants = $this->db
                        ->select('varian_warna,varian_ukuran,harga_jual')
                        ->from('bahan')
                        ->where('parent_bahan_id', (int) $item->id)
                        ->where('pub', 1)
                        ->order_by('varian_warna', 'ASC')
                        ->order_by('varian_ukuran', 'ASC')
                        ->get()
                        ->result();
                }

                if (!empty($variants)) {
                    foreach ($variants as $variant) {
                        $variantRow = $baseRow;
                        $variantRow[15] = (string) $variant->varian_warna;
                        $variantRow[16] = (string) $variant->varian_ukuran;
                        $variantRow[17] = $this->harga_decimal_value($variant->harga_jual);
                        $rows[] = $variantRow;
                    }
                    continue;
                }

                $rows[] = $baseRow;
            }

            return $rows;
        }

        private function build_export_import_bahan_harga_satuan_rows($isParentFieldAvailable)
        {
            $query = $this->db
                ->select('bahan.title, satuan.satuan, harga_satuan.harga_pokok, harga_satuan.harga_jual')
                ->from('harga_satuan')
                ->join('bahan', 'bahan.id = harga_satuan.id_bahan', 'inner')
                ->join('satuan', 'satuan.id = harga_satuan.id_satuan', 'left')
                ->where('bahan.kunci', 0)
                ->where('bahan.type_harga', 2);

            if ($isParentFieldAvailable) {
                $query->group_start()
                    ->where('bahan.parent_bahan_id', 0)
                    ->or_where('bahan.parent_bahan_id IS NULL', null, false)
                    ->group_end();
            }

            $rows = [];
            foreach ($query->order_by('bahan.title', 'ASC')->get()->result() as $row) {
                $rows[] = [
                    (string) $row->title,
                    (string) $row->satuan,
                    $this->harga_decimal_value($row->harga_pokok),
                    $this->harga_decimal_value($row->harga_jual),
                ];
            }

            return $rows;
        }

        private function build_export_import_bahan_harga_level_rows($isParentFieldAvailable)
        {
            $query = $this->db
                ->select('bahan.title, satuan.satuan, member.title AS member_title, harga_member.harga_jual')
                ->from('harga_member')
                ->join('bahan', 'bahan.id = harga_member.id_bahan', 'inner')
                ->join('satuan', 'satuan.id = harga_member.id_satuan', 'left')
                ->join('member', 'member.id = harga_member.id_member', 'left')
                ->where('bahan.kunci', 0)
                ->where('bahan.type_harga', 3);

            if ($isParentFieldAvailable) {
                $query->group_start()
                    ->where('bahan.parent_bahan_id', 0)
                    ->or_where('bahan.parent_bahan_id IS NULL', null, false)
                    ->group_end();
            }

            $rows = [];
            foreach ($query->order_by('bahan.title', 'ASC')->get()->result() as $row) {
                $rows[] = [
                    (string) $row->title,
                    (string) $row->satuan,
                    (string) $row->member_title,
                    $this->harga_decimal_value($row->harga_jual),
                ];
            }

            return $rows;
        }

        private function build_export_import_bahan_harga_jumlah_rows($isParentFieldAvailable)
        {
            $query = $this->db
                ->select('bahan.title, satuan.satuan, range_harga.jumlah_minimal, range_harga.jumlah_maksimal, range_harga.harga_jual')
                ->from('range_harga')
                ->join('bahan', 'bahan.id = range_harga.id_bahan', 'inner')
                ->join('satuan', 'satuan.id = range_harga.id_satuan', 'left')
                ->where('bahan.kunci', 0)
                ->where('bahan.type_harga', 4);

            if ($isParentFieldAvailable) {
                $query->group_start()
                    ->where('bahan.parent_bahan_id', 0)
                    ->or_where('bahan.parent_bahan_id IS NULL', null, false)
                    ->group_end();
            }

            $rows = [];
            foreach ($query->order_by('bahan.title', 'ASC')->get()->result() as $row) {
                $rows[] = [
                    (string) $row->title,
                    (string) $row->satuan,
                    (string) $row->jumlah_minimal,
                    (string) $row->jumlah_maksimal,
                    $this->harga_decimal_value($row->harga_jual),
                ];
            }

            return $rows;
        }

        private function build_export_import_bahan_harga_jumlah_level_rows($isParentFieldAvailable)
        {
            $query = $this->db
                ->select('bahan.title, satuan.satuan, member.title AS member_title, harga_range_member.jumlah_minimal, harga_range_member.jumlah_maksimal, harga_range_member.harga_jual')
                ->from('harga_range_member')
                ->join('bahan', 'bahan.id = harga_range_member.id_bahan', 'inner')
                ->join('satuan', 'satuan.id = harga_range_member.id_satuan', 'left')
                ->join('member', 'member.id = harga_range_member.id_member', 'left')
                ->where('bahan.kunci', 0)
                ->where('bahan.type_harga', 5);

            if ($isParentFieldAvailable) {
                $query->group_start()
                    ->where('bahan.parent_bahan_id', 0)
                    ->or_where('bahan.parent_bahan_id IS NULL', null, false)
                    ->group_end();
            }

            $rows = [];
            foreach ($query->order_by('bahan.title', 'ASC')->get()->result() as $row) {
                $rows[] = [
                    (string) $row->title,
                    (string) $row->satuan,
                    (string) $row->member_title,
                    (string) $row->jumlah_minimal,
                    (string) $row->jumlah_maksimal,
                    $this->harga_decimal_value($row->harga_jual),
                ];
            }

            return $rows;
        }

        private function build_import_bahan_reference_sheet($sheet, $lists)
        {
            foreach ($lists as $config) {
                $column = $config['column'];
                $values = empty($config['values']) ? [''] : $config['values'];
                $sheet->setCellValue($column.'1', $config['header']);

                $row = 2;
                foreach ($values as $value) {
                    $sheet->setCellValue($column.$row, $value);
                    $row++;
                }

                $sheet->getColumnDimension($column)->setAutoSize(true);
            }

            $lastColumn = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex(count($lists));
            $sheet->getStyle('A1:'.$lastColumn.'1')->applyFromArray([
                'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']],
                'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '2F75B5']],
                'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
            ]);
            $sheet->freezePane('A2');
        }

        private function build_import_bahan_dropdown_sources($sheet, $lists)
        {
            $ranges = [];

            foreach ($lists as $name => $config) {
                $column = $config['column'];
                $values = empty($config['values']) ? [''] : $config['values'];
                $sheet->setCellValue($column.'1', $config['header']);

                $row = 2;
                foreach ($values as $value) {
                    $sheet->setCellValue($column.$row, $value);
                    $row++;
                }

                $lastRow = max(2, $row - 1);
                $ranges[$name] = '$'.$column.'$2:$'.$column.'$'.$lastRow;
                $sheet->getColumnDimension($column)->setVisible(false);
            }

            return $ranges;
        }

        private function build_import_bahan_guide_sheet($sheet)
        {
            $sheet->setCellValue('A1', 'Panduan Import Data Produk & Harga');
            $sheet->setCellValue('A3', '1. Isi data produk dasar pada sheet IMPORT_BAHAN mulai baris 2.');
            $sheet->setCellValue('A4', '2. Pilih MODE HARGA sesuai kebutuhan: SATU_HARGA, PER_SATUAN, PER_LEVEL, PER_JUMLAH, atau JUMLAH_DAN_LEVEL.');
            $sheet->setCellValue('A5', '3. Setelah MODE HARGA dipilih, nama barang/bahan akan muncul otomatis di sheet harga yang sesuai. Anda tidak perlu mengetik ulang nama barang.');
            $sheet->setCellValue('A6', '4. Pada area linked, satuan default ikut dari IMPORT_BAHAN. Jika perlu, satuan tetap boleh diganti.');
            $sheet->setCellValue('A7', '5. Untuk Per Level dan Jumlah & Level, sistem menyiapkan beberapa baris per produk agar Anda tinggal isi level member, jumlah, dan harga.');
            $sheet->setCellValue('A8', '6. Untuk file export, tambahkan produk baru di bawah data IMPORT_BAHAN yang sudah ada agar nama produk baru ikut muncul otomatis di sheet harga.');
            $sheet->setCellValue('A9', '7. Barcode boleh dikosongkan. Sistem akan membuat barcode otomatis saat import.');
            $sheet->setCellValue('A10', '8. Jika nama barang/bahan atau barcode sudah ada, data akan diupdate.');
            $sheet->setCellValue('A11', '9. Untuk produk varian, ulangi nama barang/bahan yang sama di beberapa baris pada sheet IMPORT_BAHAN, lalu isi VARIAN WARNA, VARIAN UKURAN, dan HARGA JUAL VARIAN.');
            $sheet->setCellValue('A12', '10. Kolom FOTO PRODUK berisi path foto pada server. Saat update, kosongkan kolom ini untuk mempertahankan foto lama. Produk baru dapat diberi foto melalui tombol Edit setelah import.');
            $sheet->setCellValue('A13', '11. Jangan mengubah nama header pada baris 1 di setiap sheet.');

            $guideRows = [
                ['IMPORT_BAHAN', 'Data utama produk: nama, produk, kategori, harga default, satuan default, stok, limit stok, barcode, varian, mode harga, dan path foto produk.'],
                ['HARGA_PER_SATUAN', 'Nama barang dan satuan default terhubung otomatis dari IMPORT_BAHAN untuk produk mode PER_SATUAN. Isi harga pokok dan harga jual sesuai kebutuhan.'],
                ['HARGA_PER_LEVEL', 'Nama barang dan satuan default terhubung otomatis dari IMPORT_BAHAN untuk produk mode PER_LEVEL. Isi level member dan harga jual.'],
                ['HARGA_PER_JUMLAH', 'Nama barang dan satuan default terhubung otomatis dari IMPORT_BAHAN untuk produk mode PER_JUMLAH. Isi rentang jumlah dan harga jual.'],
                ['HARGA_JUMLAH_LEVEL', 'Nama barang dan satuan default terhubung otomatis dari IMPORT_BAHAN untuk produk mode JUMLAH_DAN_LEVEL. Isi level, rentang jumlah, dan harga jual.'],
                ['REFERENSI_DATA', 'Berisi daftar dropdown dari master data: produk, kategori, satuan, level member, dan mode harga.'],
            ];

            $sheet->fromArray(['Sheet', 'Kegunaan'], null, 'A14');
            $sheet->fromArray($guideRows, null, 'A15');

            $sheet->getStyle('A1')->applyFromArray([
                'font' => ['bold' => true, 'size' => 16, 'color' => ['rgb' => '1F4E78']],
            ]);
            $sheet->getStyle('A14:B14')->applyFromArray([
                'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']],
                'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '2F75B5']],
            ]);
            $sheet->getStyle('A14:B20')->applyFromArray([
                'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => 'D9E2F3']]],
                'alignment' => ['vertical' => Alignment::VERTICAL_TOP, 'wrapText' => true],
            ]);
            $sheet->getColumnDimension('A')->setWidth(24);
            $sheet->getColumnDimension('B')->setWidth(95);
            $sheet->freezePane('A14');
        }

        private function style_import_bahan_template($sheet, $maxRows = 501)
        {
            $maxRows = max(2, (int) $maxRows);
            $sheet->getStyle('A1:V1')->applyFromArray([
                'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']],
                'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '305496']],
                'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER, 'wrapText' => true],
            ]);
            $sheet->getStyle('A1:V'.$maxRows)->applyFromArray([
                'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => 'D9E2F3']]],
                'alignment' => ['vertical' => Alignment::VERTICAL_TOP],
            ]);
            $sheet->getStyle('A2:V'.$maxRows)->getAlignment()->setWrapText(true);
            $sheet->getStyle('D2:E'.$maxRows)->getNumberFormat()->setFormatCode('#,##0.####');
            $sheet->getStyle('I2:I'.$maxRows)->getNumberFormat()->setFormatCode('#,##0.##');
            $sheet->getStyle('R2:R'.$maxRows)->getNumberFormat()->setFormatCode('#,##0.####');
            $sheet->getStyle('L2:L'.$maxRows)->getNumberFormat()->setFormatCode('@');
            $sheet->getRowDimension(1)->setRowHeight(32);

            $widths = [
                'A' => 36,
                'B' => 28,
                'C' => 24,
                'D' => 16,
                'E' => 16,
                'F' => 16,
                'G' => 18,
                'H' => 12,
                'I' => 16,
                'J' => 18,
                'K' => 12,
                'L' => 18,
                'M' => 20,
                'N' => 18,
                'O' => 20,
                'P' => 18,
                'Q' => 18,
                'R' => 20,
                'S' => 18,
                'T' => 22,
                'U' => 24,
                'V' => 38,
            ];

            foreach ($widths as $column => $width) {
                $sheet->getColumnDimension($column)->setWidth($width);
            }
        }

        private function style_import_bahan_price_sheet($sheet, $widths, $maxRows = 501)
        {
            $maxRows = max(2, (int) $maxRows);
            $columns = array_keys($widths);
            $lastColumn = end($columns);

            $sheet->getStyle('A1:'.$lastColumn.'1')->applyFromArray([
                'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']],
                'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '548235']],
                'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER, 'wrapText' => true],
            ]);
            $sheet->getStyle('A1:'.$lastColumn.$maxRows)->applyFromArray([
                'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => 'D9E2F3']]],
                'alignment' => ['vertical' => Alignment::VERTICAL_TOP],
            ]);
            $sheet->getStyle('A2:'.$lastColumn.$maxRows)->getAlignment()->setWrapText(true);
            $sheet->getRowDimension(1)->setRowHeight(28);

            foreach ($widths as $column => $width) {
                $sheet->getColumnDimension($column)->setWidth($width);
            }

            foreach ($columns as $column) {
                if ($column !== 'A' && $column !== 'B' && $column !== 'C') {
                    $sheet->getStyle($column.'2:'.$column.$maxRows)->getNumberFormat()->setFormatCode('#,##0.####');
                }
            }
        }

        private function style_import_bahan_linked_price_area($sheet, $startRow, $endRow)
        {
            if ((int) $endRow < (int) $startRow) {
                return;
            }

            $sheet->getStyle('A'.$startRow.':B'.$endRow)->applyFromArray([
                'fill' => [
                    'fillType' => Fill::FILL_SOLID,
                    'startColor' => ['rgb' => 'FFF2CC'],
                ],
            ]);
            $sheet->getStyle('A'.$startRow.':A'.$endRow)->applyFromArray([
                'font' => ['bold' => true, 'color' => ['rgb' => '7F6000']],
            ]);
        }

        private function add_import_bahan_comments($sheet)
        {
            $comments = [
                'A1' => 'Wajib. Isi nama barang/bahan maksimal 50 karakter. Jika nama sudah ada, import akan mengupdate data tersebut.',
                'B1' => 'Wajib. Pilih produk dari dropdown agar sesuai data master produk.',
                'C1' => 'Wajib. Pilih kategori dari dropdown agar sesuai data master kategori.',
                'D1' => 'Wajib. Isi angka harga modal tanpa Rp. Contoh: 35000.',
                'E1' => 'Wajib. Isi angka harga jual default tanpa Rp. Contoh: 40000.',
                'F1' => 'Wajib. Pilih satuan default dari dropdown.',
                'G1' => 'Pilih mode harga yang dipakai produk ini. Setelah dipilih, nama barang akan muncul otomatis pada sheet harga yang sesuai.',
                'H1' => 'Pilih Y jika stok dicatat, N jika tidak.',
                'I1' => 'Isi limit atau batas minimum stok dengan angka 0 atau lebih. Nilai ini digunakan saat STOK = Y; isi 0 jika tidak memakai peringatan limit stok.',
                'J1' => 'Pilih 1 untuk meter persegi, 2 untuk meter lari, atau 0 jika tidak memakai hitung ukuran.',
                'K1' => 'Pilih 1 agar aktif, 0 agar nonaktif.',
                'L1' => 'Opsional. Isi 12 atau 13 digit angka. Kosongkan jika ingin dibuat otomatis.',
                'M1' => 'Opsional. Isi ukuran default, misalnya 100x100, atau - jika tidak dipakai.',
                'N1' => 'Pilih 1 jika komisi desain aktif, 0 jika tidak.',
                'O1' => 'Pilih 1 jika margin finishing aktif, 0 jika tidak.',
                'P1' => 'Opsional. Isi warna varian. Jika diisi bersama ukuran, item akan dibuat sebagai produk parent dengan daftar varian.',
                'Q1' => 'Opsional. Isi ukuran varian, misalnya M, L, XL. Untuk produk varian, ukuran wajib diisi.',
                'R1' => 'Opsional. Harga jual khusus varian. Jika kosong, sistem memakai HARGA JUAL utama.',
                'S1' => 'Pilih 1 jika komisi penjualan aktif untuk produk ini, 0 jika tidak.',
                'T1' => 'Isi persen untuk perhitungan persentase, atau nominal untuk nilai rupiah per item.',
                'U1' => 'Isi nilai komisi. Persentase maksimal 100; nominal diisi tanpa Rp.',
                'V1' => 'Opsional. Isi path foto yang sudah tersedia pada server, misalnya uploads/produk/produk_20260726.jpg. Jika dikosongkan saat update, foto lama tetap dipertahankan. Untuk produk baru, foto dapat diunggah melalui tombol Edit setelah import.',
            ];

            foreach ($comments as $cell => $text) {
                $comment = $sheet->getComment($cell);
                $comment->getText()->createTextRun($text);
                $comment->setWidth('320px');
                $comment->setHeight('120px');
            }
        }

        private function add_import_bahan_price_comments($sheet, $comments)
        {
            foreach ($comments as $cell => $text) {
                $comment = $sheet->getComment($cell);
                $comment->getText()->createTextRun($text);
                $comment->setWidth('320px');
                $comment->setHeight('110px');
            }
        }

        private function add_import_bahan_validations($sheet, $referenceRanges, $maxRows = 501)
        {
            $maxRows = max(2, (int) $maxRows);
            for ($row = 2; $row <= $maxRows; $row++) {
                $this->set_import_list_validation($sheet, 'B'.$row, $referenceRanges['REF_PRODUK'], 'Pilih Produk', 'Pilih produk dari dropdown. Data harus sama dengan master produk.');
                $this->set_import_list_validation($sheet, 'C'.$row, $referenceRanges['REF_KATEGORI'], 'Pilih Kategori', 'Pilih kategori dari dropdown. Data harus sama dengan master kategori.');
                $this->set_import_decimal_validation($sheet, 'D'.$row, 'Harga Modal', 'Isi angka tanpa simbol Rp. Contoh: 35000.');
                $this->set_import_decimal_validation($sheet, 'E'.$row, 'Harga Jual', 'Isi angka tanpa simbol Rp. Contoh: 40000.');
                $this->set_import_list_validation($sheet, 'F'.$row, $referenceRanges['REF_SATUAN'], 'Pilih Satuan', 'Pilih satuan default dari dropdown.');
                $this->set_import_list_validation($sheet, 'G'.$row, $referenceRanges['REF_MODE_HARGA'], 'Mode Harga', 'Pilih mode harga yang dipakai produk.');
                $this->set_import_list_validation($sheet, 'H'.$row, $referenceRanges['REF_STOK'], 'Status Stok', 'Y = stok dicatat, N = stok tidak dicatat.');
                $this->set_import_decimal_validation($sheet, 'I'.$row, 'Limit Stok', 'Isi angka 0 atau lebih. Digunakan jika status stok Y.', 'Limit stok tidak valid', 'Isi limit stok berupa angka 0 atau lebih besar.');
                $this->set_import_list_validation($sheet, 'J'.$row, $referenceRanges['REF_HITUNG_UKURAN'], 'Hitung Ukuran', '1 = meter persegi, 2 = meter lari, 0 = tidak.');
                $this->set_import_list_validation($sheet, 'K'.$row, $referenceRanges['REF_BOOL'], 'Status Aktif', '1 = aktif, 0 = nonaktif.');
                $this->set_import_text_length_validation($sheet, 'L'.$row, 13, 'Barcode', 'Opsional. Maksimal 13 digit angka. Kosongkan agar sistem membuat otomatis.');
                $this->set_import_list_validation($sheet, 'N'.$row, $referenceRanges['REF_BOOL'], 'Komisi Desain', '1 = aktif, 0 = tidak.');
                $this->set_import_list_validation($sheet, 'O'.$row, $referenceRanges['REF_BOOL'], 'Margin Finishing', '1 = aktif, 0 = tidak.');
                $this->set_import_decimal_validation($sheet, 'R'.$row, 'Harga Jual Varian', 'Opsional. Isi angka jika harga varian berbeda dari harga jual utama.');
            }
        }

        private function add_import_bahan_price_validations($sheet, $referenceRanges, $mode, $maxRows = 501)
        {
            $maxRows = max(2, (int) $maxRows);
            for ($row = 2; $row <= $maxRows; $row++) {
                $this->set_import_list_validation($sheet, 'B'.$row, $referenceRanges['REF_SATUAN'], 'Pilih Satuan', 'Pilih satuan dari dropdown.');

                if (in_array((int) $mode, [3, 5], true)) {
                    $this->set_import_list_validation($sheet, 'C'.$row, $referenceRanges['REF_MEMBER'], 'Pilih Level Member', 'Pilih level member dari dropdown.');
                }

                if ((int) $mode === 2) {
                    $this->set_import_decimal_validation($sheet, 'C'.$row, 'Harga Pokok', 'Opsional. Isi angka tanpa simbol Rp.');
                    $this->set_import_decimal_validation($sheet, 'D'.$row, 'Harga Jual', 'Wajib. Isi angka tanpa simbol Rp.');
                } elseif ((int) $mode === 3) {
                    $this->set_import_decimal_validation($sheet, 'D'.$row, 'Harga Jual', 'Wajib. Isi angka tanpa simbol Rp.');
                } elseif ((int) $mode === 4) {
                    $this->set_import_integer_validation($sheet, 'C'.$row, 'Jumlah Minimal', 'Isi angka jumlah minimal.');
                    $this->set_import_integer_validation($sheet, 'D'.$row, 'Jumlah Maksimal', 'Isi angka jumlah maksimal.');
                    $this->set_import_decimal_validation($sheet, 'E'.$row, 'Harga Jual', 'Wajib. Isi angka tanpa simbol Rp.');
                } elseif ((int) $mode === 5) {
                    $this->set_import_integer_validation($sheet, 'D'.$row, 'Jumlah Minimal', 'Isi angka jumlah minimal.');
                    $this->set_import_integer_validation($sheet, 'E'.$row, 'Jumlah Maksimal', 'Isi angka jumlah maksimal.');
                    $this->set_import_decimal_validation($sheet, 'F'.$row, 'Harga Jual', 'Wajib. Isi angka tanpa simbol Rp.');
                }
            }
        }

        private function set_import_list_validation($sheet, $cell, $formula, $title, $prompt)
        {
            $validation = $sheet->getCell($cell)->getDataValidation();
            $validation->setType(DataValidation::TYPE_LIST);
            $validation->setErrorStyle(DataValidation::STYLE_STOP);
            $validation->setAllowBlank(true);
            $validation->setShowDropDown(true);
            $validation->setShowInputMessage(true);
            $validation->setShowErrorMessage(true);
            $validation->setFormula1($formula);
            $validation->setPromptTitle($title);
            $validation->setPrompt($prompt);
            $validation->setErrorTitle('Input tidak sesuai');
            $validation->setError('Pilih nilai dari dropdown agar format import sesuai data master.');
        }

        private function set_import_decimal_validation($sheet, $cell, $title, $prompt, $errorTitle = 'Harga tidak valid', $errorMessage = 'Isi angka 0 atau lebih besar tanpa simbol Rp.')
        {
            $validation = $sheet->getCell($cell)->getDataValidation();
            $validation->setType(DataValidation::TYPE_DECIMAL);
            $validation->setErrorStyle(DataValidation::STYLE_STOP);
            $validation->setOperator(DataValidation::OPERATOR_GREATERTHANOREQUAL);
            $validation->setAllowBlank(true);
            $validation->setShowInputMessage(true);
            $validation->setShowErrorMessage(true);
            $validation->setFormula1('0');
            $validation->setPromptTitle($title);
            $validation->setPrompt($prompt);
            $validation->setErrorTitle($errorTitle);
            $validation->setError($errorMessage);
        }

        private function set_import_integer_validation($sheet, $cell, $title, $prompt)
        {
            $validation = $sheet->getCell($cell)->getDataValidation();
            $validation->setType(DataValidation::TYPE_WHOLE);
            $validation->setErrorStyle(DataValidation::STYLE_STOP);
            $validation->setOperator(DataValidation::OPERATOR_GREATERTHANOREQUAL);
            $validation->setAllowBlank(true);
            $validation->setShowInputMessage(true);
            $validation->setShowErrorMessage(true);
            $validation->setFormula1('0');
            $validation->setPromptTitle($title);
            $validation->setPrompt($prompt);
            $validation->setErrorTitle('Jumlah tidak valid');
            $validation->setError('Isi angka bulat 0 atau lebih besar.');
        }

        private function set_import_text_length_validation($sheet, $cell, $maxLength, $title, $prompt)
        {
            $validation = $sheet->getCell($cell)->getDataValidation();
            $validation->setType(DataValidation::TYPE_TEXTLENGTH);
            $validation->setErrorStyle(DataValidation::STYLE_WARNING);
            $validation->setOperator(DataValidation::OPERATOR_LESSTHANOREQUAL);
            $validation->setAllowBlank(true);
            $validation->setShowInputMessage(true);
            $validation->setShowErrorMessage(true);
            $validation->setFormula1((string) $maxLength);
            $validation->setPromptTitle($title);
            $validation->setPrompt($prompt);
            $validation->setErrorTitle('Barcode terlalu panjang');
            $validation->setError('Barcode maksimal '.$maxLength.' digit. Kosongkan jika ingin otomatis.');
        }

        public function import_bahan_excel()
        {
            cek_nput_post('GET');
            simpan_demo('Simpan');
            cek_crud_akses(9);
            @set_time_limit(120);

            $file = isset($_FILES['file_excel']) ? $_FILES['file_excel'] : [];
            $uploadError = isset($file['error']) ? (int) $file['error'] : UPLOAD_ERR_NO_FILE;
            if ($uploadError !== UPLOAD_ERR_OK) {
                $this->output
                ->set_content_type('application/json')
                ->set_output(json_encode(['status' => 400, 'msg' => $this->import_bahan_upload_error_message($uploadError)]));
                return;
            }

            if (empty($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) {
                $this->output
                ->set_content_type('application/json')
                ->set_output(json_encode(['status' => 400, 'msg' => 'File Excel tidak valid atau gagal diterima server']));
                return;
            }

            $maxFileSize = 10 * 1024 * 1024;
            if (!empty($file['size']) && (int) $file['size'] > $maxFileSize) {
                $this->output
                ->set_content_type('application/json')
                ->set_output(json_encode(['status' => 400, 'msg' => 'Ukuran file Excel maksimal 10 MB']));
                return;
            }

            $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
            if (!in_array($ext, ['xlsx', 'xls', 'csv'])) {
                $this->output
                ->set_content_type('application/json')
                ->set_output(json_encode(['status' => 400, 'msg' => 'Format file harus .xlsx, .xls, atau .csv']));
                return;
            }

            $this->ensure_bahan_komposisi_schema();
            $this->ensure_bahan_decimal_schema();
            $this->ensure_satu_harga_decimal_schema();
            $this->ensure_harga_satuan_decimal_schema();
            $this->ensure_harga_tingkat_decimal_schema();

            try {
                $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile($file['tmp_name']);
                $reader->setReadDataOnly(true);
                $reader->setReadEmptyCells(false);
                $spreadsheet = $reader->load($file['tmp_name']);
            } catch (\Throwable $e) {
                $this->output
                ->set_content_type('application/json')
                ->set_output(json_encode(['status' => 400, 'msg' => 'File Excel tidak bisa dibaca: '.$e->getMessage()]));
                return;
            }

            $mainSheet = $spreadsheet->getSheetByName('IMPORT_BAHAN');
            if (empty($mainSheet)) {
                $mainSheet = $spreadsheet->getActiveSheet();
            }

            $highestInputRow = $this->import_bahan_highest_input_row($mainSheet);
            $rows = $mainSheet->rangeToArray('A1:V'.$highestInputRow, null, true, true, true);
            if (count($rows) < 2) {
                $this->output
                ->set_content_type('application/json')
                ->set_output(json_encode(['status' => 400, 'msg' => 'File Excel belum berisi data produk']));
                return;
            }

            $headerMap = $this->import_bahan_header_map(reset($rows));
            $required = ['title', 'produk', 'kategori', 'harga_modal', 'harga_jual', 'satuan'];
            $missing = [];
            foreach ($required as $key) {
                if (!isset($headerMap[$key])) {
                    $missing[] = $key;
                }
            }

            if (!empty($missing)) {
                $this->output
                ->set_content_type('application/json')
                ->set_output(json_encode([
                    'status' => 400,
                    'msg' => 'Header wajib belum lengkap. Gunakan template Excel dari tombol Template.',
                ]));
                return;
            }

            $priceSheets = [
                2 => $this->import_bahan_sheet_rows($spreadsheet, 'HARGA_PER_SATUAN', 'D', ['C', 'D']),
                3 => $this->import_bahan_sheet_rows($spreadsheet, 'HARGA_PER_LEVEL', 'D', ['C', 'D']),
                4 => $this->import_bahan_sheet_rows($spreadsheet, 'HARGA_PER_JUMLAH', 'E', ['C', 'D', 'E']),
                5 => $this->import_bahan_sheet_rows($spreadsheet, 'HARGA_JUMLAH_LEVEL', 'F', ['C', 'D', 'E', 'F']),
            ];

            $errors = [];
            $inserted = 0;
            $updated = 0;
            $emptyRows = 0;
            $variantGroups = [];
            $variantCount = 0;
            $detailCount = [2 => 0, 3 => 0, 4 => 0, 5 => 0];
            $importedProducts = [];

            $this->db->trans_begin();

            foreach ($rows as $rowNumber => $row) {
                if ((int) $rowNumber === 1) {
                    continue;
                }

                $title = $this->import_bahan_cell($row, $headerMap, 'title');
                $produkInput = $this->import_bahan_cell($row, $headerMap, 'produk');
                $kategoriInput = $this->import_bahan_cell($row, $headerMap, 'kategori');
                $modalInput = $this->import_bahan_cell($row, $headerMap, 'harga_modal');
                $jualInput = $this->import_bahan_cell($row, $headerMap, 'harga_jual');
                $satuanInput = $this->import_bahan_cell($row, $headerMap, 'satuan');
                $modeInput = $this->import_bahan_cell($row, $headerMap, 'mode_harga');
                $modeHarga = $this->import_bahan_mode_value($modeInput, 1);
                $limitStokInput = $this->import_bahan_cell($row, $headerMap, 'batas_minimum_stok');
                $varianWarna = $this->import_bahan_cell($row, $headerMap, 'varian_warna');
                $varianUkuran = $this->import_bahan_cell($row, $headerMap, 'varian_ukuran');
                $varianHargaInput = $this->import_bahan_cell($row, $headerMap, 'varian_harga_jual');
                $fotoInput = $this->import_bahan_cell($row, $headerMap, 'foto');
                $hasVariant = ($varianWarna !== '' || $varianUkuran !== '' || $varianHargaInput !== '');

                if ($title === '' && $produkInput === '' && $kategoriInput === '' && $modalInput === '' && $jualInput === '' && $satuanInput === '' && $modeInput === '' && $limitStokInput === '' && $fotoInput === '' && !$hasVariant) {
                    $emptyRows++;
                    continue;
                }

                if ($ext === 'csv' && $modeHarga !== 1) {
                    $errors[] = 'Baris '.$rowNumber.': format CSV hanya mendukung SATU_HARGA. Gunakan file .xlsx dari tombol Template untuk mode harga lain.';
                    continue;
                }

                if ($limitStokInput !== '' && !preg_match('/^\d+(?:[\.,]\d+)?$/', str_replace(' ', '', $limitStokInput))) {
                    $errors[] = 'Baris '.$rowNumber.': limit stok harus berupa angka 0 atau lebih';
                    continue;
                }

                if ($title === '') {
                    $errors[] = 'Baris '.$rowNumber.': nama barang/bahan kosong';
                    continue;
                }

                if (strlen($title) > 50) {
                    $errors[] = 'Baris '.$rowNumber.': nama barang/bahan maksimal 50 karakter';
                    continue;
                }

                $idProduk = $this->import_find_produk_id($produkInput);
                if (!$idProduk) {
                    $errors[] = 'Baris '.$rowNumber.': produk "'.$produkInput.'" tidak ditemukan';
                    continue;
                }

                $idKategori = $this->import_find_kategori_id($kategoriInput);
                if (!$idKategori) {
                    $errors[] = 'Baris '.$rowNumber.': kategori "'.$kategoriInput.'" tidak ditemukan';
                    continue;
                }

                $idSatuan = $this->import_find_satuan_id($satuanInput);
                if (!$idSatuan) {
                    $errors[] = 'Baris '.$rowNumber.': satuan "'.$satuanInput.'" tidak ditemukan';
                    continue;
                }

                $barcodeInput = $this->import_normalize_barcode($this->import_bahan_cell($row, $headerMap, 'barcode'));
                if ($barcodeInput !== '' && !preg_match('/^\d{13}$/', $barcodeInput)) {
                    $errors[] = 'Baris '.$rowNumber.': barcode harus 12 atau 13 digit angka';
                    continue;
                }

                $existingByTitle = $this->db
                    ->select('id,barcode,title,harga_modal,harga_jual,foto')
                    ->where('UPPER(`title`) = '.$this->db->escape(strtoupper($title)), null, false)
                    ->get('bahan')
                    ->row();
                $hargaModal = $modalInput !== ''
                    ? $this->harga_decimal_value($modalInput)
                    : (!empty($existingByTitle) ? $this->harga_decimal_value($existingByTitle->harga_modal) : 0);
                $hargaJual = $jualInput !== ''
                    ? $this->harga_decimal_value($jualInput)
                    : (!empty($existingByTitle) ? $this->harga_decimal_value($existingByTitle->harga_jual) : 0);
                $barcode = $barcodeInput;
                if ($barcode === '' && !empty($existingByTitle) && !empty($existingByTitle->barcode)) {
                    $barcode = (string) $existingByTitle->barcode;
                } elseif ($barcode === '') {
                    $barcode = $this->unique_bahan_barcode();
                }

                $existingByBarcode = $this->db->select('id,title,harga_modal,harga_jual,foto')->where('barcode', $barcode)->get('bahan')->row();

                $idBahan = 0;
                if (!empty($existingByBarcode)) {
                    $idBahan = (int) $existingByBarcode->id;
                    if ($modalInput === '') {
                        $hargaModal = $this->harga_decimal_value($existingByBarcode->harga_modal);
                    }
                    if ($jualInput === '') {
                        $hargaJual = $this->harga_decimal_value($existingByBarcode->harga_jual);
                    }
                } elseif (!empty($existingByTitle)) {
                    $idBahan = (int) $existingByTitle->id;
                    if (!empty($existingByTitle->barcode) && $existingByTitle->barcode !== $barcode) {
                        $duplicate = $this->db->select('id')->where('barcode', $barcode)->where('id !=', $idBahan)->get('bahan')->row();
                        if (!empty($duplicate)) {
                            $errors[] = 'Baris '.$rowNumber.': barcode sudah dipakai data lain';
                            continue;
                        }
                    }
                }

                $batasMinimumStok = $limitStokInput !== ''
                    ? $this->harga_decimal_value($limitStokInput)
                    : 0;
                if ($limitStokInput === '' && $idBahan > 0) {
                    $existingMinimum = $this->db
                        ->select('batas_minimum_stok')
                        ->where('id', $idBahan)
                        ->get('bahan')
                        ->row();
                    if (!empty($existingMinimum)) {
                        $batasMinimumStok = $this->harga_decimal_value($existingMinimum->batas_minimum_stok);
                    }
                }

                $existingFoto = '';
                if (!empty($existingByBarcode) && !empty($existingByBarcode->foto)) {
                    $existingFoto = (string) $existingByBarcode->foto;
                } elseif (!empty($existingByTitle) && !empty($existingByTitle->foto)) {
                    $existingFoto = (string) $existingByTitle->foto;
                }
                $foto = $this->import_bahan_foto_path($fotoInput, $existingFoto);
                if ($foto === null) {
                    $errors[] = 'Baris '.$rowNumber.': FOTO PRODUK harus berupa path uploads/produk/nama-file.jpg yang tersedia pada server';
                    continue;
                }

				$komisiPenjualanTipe = strtolower($this->import_bahan_cell($row, $headerMap, 'tipe_komisi_penjualan')) === 'nominal' ? 'nominal' : 'persen';
				$komisiPenjualanNilai = max(0, $this->harga_decimal_value($this->import_bahan_cell($row, $headerMap, 'nilai_komisi_penjualan')));
				if ($komisiPenjualanTipe === 'persen') $komisiPenjualanNilai = min(100, $komisiPenjualanNilai);

                $data = [
                    'id_jenis' => $idKategori,
                    'id_produk' => $idProduk,
                    'title' => $title,
                    'harga_modal' => $hargaModal,
                    'harga_jual' => $hargaJual,
                    'harga' => 0,
                    'id_satuan' => $idSatuan,
                    'status_stok' => $this->import_yes_no($this->import_bahan_cell($row, $headerMap, 'stok'), 'N'),
                    'batas_minimum_stok' => $batasMinimumStok,
                    'kunci' => 0,
                    'status' => $this->import_hitung_ukuran_mode($this->import_bahan_cell($row, $headerMap, 'hitung_ukuran'), 0),
                    'pub' => $this->import_bool_int($this->import_bahan_cell($row, $headerMap, 'aktif'), 1),
                    'type_harga' => $modeHarga,
                    'barcode' => $barcode,
                    'foto' => $foto,
                    'ukuran_default' => $this->import_bahan_cell($row, $headerMap, 'ukuran_default') !== '' ? $this->import_bahan_cell($row, $headerMap, 'ukuran_default') : '-',
                    'komisi_desain_aktif' => $this->import_bool_int($this->import_bahan_cell($row, $headerMap, 'komisi_desain'), 0),
                    'komisi_penjualan_aktif' => $this->import_bool_int($this->import_bahan_cell($row, $headerMap, 'komisi_penjualan'), 0),
                    'komisi_penjualan_tipe' => $komisiPenjualanTipe,
                    'komisi_penjualan_nilai' => $komisiPenjualanNilai,
                    'margin_finishing_aktif' => $this->import_bool_int($this->import_bahan_cell($row, $headerMap, 'margin_finishing'), 0),
                    'varian_aktif' => 0,
                ];

                $isVariantContinuation = $idBahan > 0 && $hasVariant && isset($variantGroups[$idBahan]);
                if ($idBahan > 0 && !$isVariantContinuation) {
                    $this->db->where('id', $idBahan)->update('bahan', $data);
                    $updated++;
                } elseif ($idBahan <= 0) {
                    $this->db->insert('bahan', $data);
                    $idBahan = (int) $this->db->insert_id();
                    $inserted++;
                }

                $satuHarga = [
                    'id_satuan' => $idSatuan,
                    'harga_beli' => $hargaModal,
                    'harga_pokok' => $hargaModal,
                    'harga_jual' => $hargaJual,
                ];
                $cekSatuHarga = $this->db->select('id')->where('id_bahan', $idBahan)->get('satu_harga')->row();
                if (!empty($cekSatuHarga) && !$isVariantContinuation) {
                    $this->db->where('id_bahan', $idBahan)->update('satu_harga', $satuHarga);
                } elseif (empty($cekSatuHarga)) {
                    $satuHarga['id_bahan'] = $idBahan;
                    $this->db->insert('satu_harga', $satuHarga);
                }

                if (!$isVariantContinuation) {
                    $importedProducts[$this->import_bahan_title_key($title)] = [
                        'id' => $idBahan,
                        'title' => $title,
                        'mode' => $modeHarga,
                        'default_satuan' => $idSatuan,
                        'harga_modal' => $hargaModal,
                        'harga_jual' => $hargaJual,
                    ];
                }

                if ($hasVariant) {
                    if ($varianUkuran === '') {
                        $errors[] = 'Baris '.$rowNumber.': ukuran varian harus diisi untuk produk varian';
                        continue;
                    }

                    $varianHarga = $varianHargaInput !== '' ? $this->harga_decimal_value($varianHargaInput) : $hargaJual;
                    if (!isset($variantGroups[$idBahan])) {
                        $variantGroups[$idBahan] = [
                            'base_data' => $data,
                            'rows' => [],
                            'keys' => [],
                        ];
                    }
                    $variantKey = strtolower($varianWarna).'|'.strtolower($varianUkuran);
                    if (isset($variantGroups[$idBahan]['keys'][$variantKey])) {
                        $errors[] = 'Baris '.$rowNumber.': kombinasi varian duplikat untuk '.$title;
                        continue;
                    }
                    $variantGroups[$idBahan]['keys'][$variantKey] = true;
                    $variantGroups[$idBahan]['rows'][] = [
                        'warna' => $varianWarna,
                        'ukuran' => $varianUkuran,
                        'harga_jual' => $varianHarga,
                        'label' => trim($varianWarna.($varianWarna !== '' && $varianUkuran !== '' ? ' / ' : '').$varianUkuran),
                        'key' => $variantKey,
                    ];
                    $variantCount++;
                }
            }

            foreach ($variantGroups as $idBahan => $group) {
                $varian = $this->save_bahan_varian($idBahan, 1, $group['rows'], $group['base_data']);
                if ($varian['status'] != 200) {
                    $errors[] = $varian['msg'];
                }
            }

            if ($ext !== 'csv' && empty($errors)) {
                foreach ([2, 3, 4, 5] as $mode) {
                    $parsed = $this->import_bahan_parse_price_sheet($priceSheets[$mode], $mode, $importedProducts);
                    $errors = array_merge($errors, $parsed['errors']);
                    $detailCount[$mode] = $parsed['count'];
                    $priceSheets[$mode] = $parsed['rows'];
                }

                foreach ($importedProducts as $product) {
                    if ((int) $product['mode'] === 1) {
                        continue;
                    }
                    if (empty($detailCount[(int) $product['mode']]) || !$this->import_bahan_product_has_detail($priceSheets[(int) $product['mode']], (int) $product['id'])) {
                        $errors[] = 'Produk "'.$product['title'].'" memakai mode '.$this->import_bahan_mode_label((int) $product['mode']).', tetapi sheet harga pendukungnya belum diisi.';
                    }
                }
            }

            if (empty($errors)) {
                foreach ($importedProducts as $product) {
                    $this->clear_import_bahan_price_details((int) $product['id']);
                }

                if (!empty($priceSheets[2])) {
                    $this->db->insert_batch('harga_satuan', $priceSheets[2]);
                }
                if (!empty($priceSheets[3])) {
                    $this->db->insert_batch('harga_member', $priceSheets[3]);
                }
                if (!empty($priceSheets[4])) {
                    $this->db->insert_batch('range_harga', $priceSheets[4]);
                }
                if (!empty($priceSheets[5])) {
                    $this->db->insert_batch('harga_range_member', $priceSheets[5]);
                }
            }

            if (!empty($errors) || $this->db->trans_status() === false) {
                $this->db->trans_rollback();
                $message = !empty($errors)
                    ? implode('<br>', array_slice($errors, 0, 10))
                    : 'Import gagal karena database menolak data';
                if (count($errors) > 10) {
                    $message .= '<br>Dan '.(count($errors) - 10).' error lainnya.';
                }
                $this->output
                    ->set_content_type('application/json')
                    ->set_output(json_encode(['status' => 400, 'msg' => $message]));
                return;
            }

            $this->db->trans_commit();

            $processed = $inserted + $updated;
            $msg = 'Import selesai. Baru: '.$inserted.', update: '.$updated.', varian: '.$variantCount.', detail harga: '.array_sum($detailCount).', baris kosong dilewati: '.$emptyRows.'.';
            if ($processed === 0) {
                $msg = 'Tidak ada data yang diimport. Pastikan file berisi baris data setelah header.';
            }

            $this->output
                ->set_content_type('application/json')
                ->set_output(json_encode(['status' => 200, 'msg' => $msg]));
        }

        private function import_bahan_upload_error_message($error)
        {
            $messages = [
                UPLOAD_ERR_INI_SIZE => 'Ukuran file melebihi batas upload server',
                UPLOAD_ERR_FORM_SIZE => 'Ukuran file melebihi batas upload form',
                UPLOAD_ERR_PARTIAL => 'File Excel hanya terupload sebagian. Silakan upload ulang',
                UPLOAD_ERR_NO_FILE => 'File Excel belum dipilih',
                UPLOAD_ERR_NO_TMP_DIR => 'Folder sementara upload tidak tersedia',
                UPLOAD_ERR_CANT_WRITE => 'Server gagal menyimpan file upload sementara',
                UPLOAD_ERR_EXTENSION => 'Upload file dihentikan oleh konfigurasi server',
            ];

            return isset($messages[$error]) ? $messages[$error] : 'Upload file Excel gagal';
        }

        private function import_bahan_sheet_rows($spreadsheet, $sheetName, $lastColumn, $meaningfulColumns = [])
        {
            $sheet = $spreadsheet->getSheetByName($sheetName);
            if (empty($sheet)) {
                return [];
            }

            $highestRow = max(1, (int) $sheet->getHighestDataRow());
            $meaningfulColumns = !empty($meaningfulColumns) ? array_values($meaningfulColumns) : range('A', $lastColumn);

            for ($row = $highestRow; $row >= 2; $row--) {
                foreach ($meaningfulColumns as $column) {
                    $value = $sheet->getCell($column.$row)->getValue();
                    if (trim((string) $value) !== '') {
                        return $sheet->rangeToArray('A1:'.$lastColumn.$row, null, true, true, true);
                    }
                }
            }

            if ($highestRow <= 1) {
                return [];
            }

            return $sheet->rangeToArray('A1:'.$lastColumn.'1', null, true, true, true);
        }

        private function import_bahan_header_map($headerRow)
        {
            $aliases = [
                'title' => ['nama_barang_bahan', 'nama_bahan', 'nama_barang', 'barang_bahan', 'nama'],
                'produk' => ['produk', 'nama_produk', 'id_produk'],
                'kategori' => ['kategori', 'nama_kategori', 'jenis', 'jenis_cetakan', 'id_kategori', 'id_jenis'],
                'harga_modal' => ['harga_modal', 'harga_beli', 'modal'],
                'harga_jual' => ['harga_jual', 'jual'],
                'satuan' => ['satuan', 'nama_satuan', 'id_satuan'],
                'stok' => ['stok', 'status_stok'],
                'batas_minimum_stok' => ['limit_stok', 'batas_minimum_stok', 'minimum_stok', 'stok_minimum'],
                'hitung_ukuran' => ['hitung_ukuran', 'ukuran', 'status'],
                'aktif' => ['aktif', 'pub', 'status_aktif'],
                'barcode' => ['barcode', 'kode_barcode'],
                'ukuran_default' => ['ukuran_default', 'default_ukuran'],
                'komisi_desain' => ['komisi_desain', 'komisi_desainer'],
                'komisi_penjualan' => ['komisi_penjualan', 'komisi_sales'],
                'tipe_komisi_penjualan' => ['tipe_komisi_penjualan', 'jenis_komisi_penjualan'],
                'nilai_komisi_penjualan' => ['nilai_komisi_penjualan', 'nominal_komisi_penjualan'],
                'margin_finishing' => ['margin_finishing', 'margin_finishing_aktif'],
                'varian_warna' => ['varian_warna', 'warna', 'warna_varian'],
                'varian_ukuran' => ['varian_ukuran', 'ukuran_varian'],
                'varian_harga_jual' => ['harga_jual_varian', 'varian_harga_jual', 'harga_varian'],
                'mode_harga' => ['mode_harga', 'tipe_harga', 'type_harga'],
                'foto' => ['foto_produk', 'foto', 'gambar_produk', 'gambar', 'photo'],
            ];

            $map = [];
            foreach ($headerRow as $column => $label) {
                $normalized = $this->import_normalize_header($label);
                foreach ($aliases as $field => $names) {
                    if (in_array($normalized, $names) && !isset($map[$field])) {
                        $map[$field] = $column;
                    }
                }
            }

            return $map;
        }

        private function import_bahan_highest_input_row($sheet)
        {
            $highestRow = max(1, (int) $sheet->getHighestDataRow());
            $columns = range('A', 'V');

            for ($row = $highestRow; $row >= 1; $row--) {
                foreach ($columns as $column) {
                    $value = $sheet->getCell($column.$row)->getValue();
                    if (trim((string) $value) !== '') {
                        return $row;
                    }
                }
            }

            return 1;
        }

        private function import_normalize_header($value)
        {
            $value = strtolower(trim((string) $value));
            $value = preg_replace('/[^a-z0-9]+/', '_', $value);
            return trim($value, '_');
        }

        private function import_bahan_cell($row, $map, $field)
        {
            if (!isset($map[$field])) {
                return '';
            }
            $value = isset($row[$map[$field]]) ? $row[$map[$field]] : '';
            return trim((string) $value);
        }

        private function import_bahan_price_header_map($headerRow)
        {
            $aliases = [
                'title' => ['nama_barang_bahan', 'nama_bahan', 'nama_barang', 'barang_bahan', 'nama'],
                'satuan' => ['satuan', 'nama_satuan', 'id_satuan'],
                'harga_pokok' => ['harga_pokok', 'harga_modal', 'harga_beli', 'modal'],
                'harga_jual' => ['harga_jual', 'jual'],
                'level' => ['level', 'member', 'level_member', 'member_level', 'jenis_member'],
                'jumlah_minimal' => ['jumlah_min', 'jumlah_minimal', 'min', 'minimal', 'qty_min', 'jumlah_dari'],
                'jumlah_maksimal' => ['jumlah_max', 'jumlah_maksimal', 'max', 'maksimal', 'qty_max', 'jumlah_sampai'],
            ];

            $map = [];
            foreach ($headerRow as $column => $label) {
                $normalized = $this->import_normalize_header($label);
                foreach ($aliases as $field => $names) {
                    if (in_array($normalized, $names, true) && !isset($map[$field])) {
                        $map[$field] = $column;
                    }
                }
            }

            return $map;
        }

        private function import_bahan_mode_value($value, $default = 1)
        {
            $value = $this->import_normalize_header($value);
            $map = [
                '1' => 1,
                'satu_harga' => 1,
                'satuharga' => 1,
                '2' => 2,
                'per_satuan' => 2,
                'persatuan' => 2,
                '3' => 3,
                'per_level' => 3,
                'perlevel' => 3,
                '4' => 4,
                'per_jumlah' => 4,
                'perjumlah' => 4,
                '5' => 5,
                'jumlah_dan_level' => 5,
                'jumlah_level' => 5,
                'jumlah___level' => 5,
            ];

            return isset($map[$value]) ? $map[$value] : (int) $default;
        }

        private function import_bahan_title_key($value)
        {
            return strtoupper(trim((string) $value));
        }

        private function import_bahan_parse_price_sheet($rows, $mode, $products)
        {
            $result = ['rows' => [], 'errors' => [], 'count' => 0];
            if (count($rows) < 2) {
                return $result;
            }

            $hasData = false;
            foreach ($rows as $rowNumber => $row) {
                if ((int) $rowNumber === 1) {
                    continue;
                }
                foreach ($row as $value) {
                    if (trim((string) $value) !== '') {
                        $hasData = true;
                        break 2;
                    }
                }
            }

            if (!$hasData) {
                return $result;
            }

            $headerMap = $this->import_bahan_price_header_map(reset($rows));
            $requiredByMode = [
                2 => ['title', 'satuan', 'harga_jual'],
                3 => ['title', 'satuan', 'level', 'harga_jual'],
                4 => ['title', 'satuan', 'jumlah_minimal', 'jumlah_maksimal', 'harga_jual'],
                5 => ['title', 'satuan', 'level', 'jumlah_minimal', 'jumlah_maksimal', 'harga_jual'],
            ];
            $sheetLabels = [
                2 => 'HARGA_PER_SATUAN',
                3 => 'HARGA_PER_LEVEL',
                4 => 'HARGA_PER_JUMLAH',
                5 => 'HARGA_JUMLAH_LEVEL',
            ];

            $missing = [];
            foreach ($requiredByMode[(int) $mode] as $field) {
                if (!isset($headerMap[$field])) {
                    $missing[] = $field;
                }
            }
            if (!empty($missing)) {
                $result['errors'][] = 'Sheet '.$sheetLabels[(int) $mode].' belum memakai header template yang benar.';
                return $result;
            }

            $seen = [];
            foreach ($rows as $rowNumber => $row) {
                if ((int) $rowNumber === 1) {
                    continue;
                }

                $title = $this->import_bahan_cell($row, $headerMap, 'title');
                $satuanInput = $this->import_bahan_cell($row, $headerMap, 'satuan');
                $hargaPokokInput = $this->import_bahan_cell($row, $headerMap, 'harga_pokok');
                $hargaJualInput = $this->import_bahan_cell($row, $headerMap, 'harga_jual');
                $levelInput = $this->import_bahan_cell($row, $headerMap, 'level');
                $minInput = $this->import_bahan_cell($row, $headerMap, 'jumlah_minimal');
                $maxInput = $this->import_bahan_cell($row, $headerMap, 'jumlah_maksimal');

                if ($title === '' && $satuanInput === '' && $hargaPokokInput === '' && $hargaJualInput === '' && $levelInput === '' && $minInput === '' && $maxInput === '') {
                    continue;
                }

                if ((int) $mode === 2 && $hargaPokokInput === '' && $hargaJualInput === '') {
                    continue;
                }

                if ((int) $mode === 3 && $levelInput === '' && $hargaJualInput === '') {
                    continue;
                }

                if ((int) $mode === 4 && $minInput === '' && $maxInput === '' && $hargaJualInput === '') {
                    continue;
                }

                if ((int) $mode === 5 && $levelInput === '' && $minInput === '' && $maxInput === '' && $hargaJualInput === '') {
                    continue;
                }

                $productKey = $this->import_bahan_title_key($title);
                if ($title === '' || !isset($products[$productKey])) {
                    $result['errors'][] = 'Sheet '.$sheetLabels[(int) $mode].' baris '.$rowNumber.': nama barang/bahan tidak ditemukan pada sheet IMPORT_BAHAN.';
                    continue;
                }

                $product = $products[$productKey];
                if ((int) $product['mode'] !== (int) $mode) {
                    $result['errors'][] = 'Sheet '.$sheetLabels[(int) $mode].' baris '.$rowNumber.': produk "'.$product['title'].'" memakai mode '.$this->import_bahan_mode_label((int) $product['mode']).'.';
                    continue;
                }

                $idSatuan = $this->import_find_satuan_id($satuanInput);
                if (!$idSatuan) {
                    $result['errors'][] = 'Sheet '.$sheetLabels[(int) $mode].' baris '.$rowNumber.': satuan "'.$satuanInput.'" tidak ditemukan.';
                    continue;
                }

                $hargaJual = $this->harga_decimal_value($hargaJualInput);
                if ($hargaJualInput === '' || (float) $hargaJual < 0) {
                    $result['errors'][] = 'Sheet '.$sheetLabels[(int) $mode].' baris '.$rowNumber.': harga jual wajib diisi.';
                    continue;
                }

                if ((int) $mode === 2) {
                    $uniqueKey = $product['id'].'|'.$idSatuan;
                    if (isset($seen[$uniqueKey])) {
                        $result['errors'][] = 'Sheet '.$sheetLabels[(int) $mode].' baris '.$rowNumber.': kombinasi produk dan satuan duplikat.';
                        continue;
                    }
                    $seen[$uniqueKey] = true;
                    $result['rows'][] = [
                        'id_satuan' => $idSatuan,
                        'id_bahan' => (int) $product['id'],
                        'harga_pokok' => $hargaPokokInput !== '' ? $this->harga_decimal_value($hargaPokokInput) : $product['harga_modal'],
                        'harga_jual' => $hargaJual,
                    ];
                    $result['count']++;
                    continue;
                }

                if ((int) $mode === 3) {
                    $idMember = $this->import_find_member_id($levelInput);
                    if (!$idMember) {
                        $result['errors'][] = 'Sheet '.$sheetLabels[(int) $mode].' baris '.$rowNumber.': level member "'.$levelInput.'" tidak ditemukan.';
                        continue;
                    }
                    $uniqueKey = $product['id'].'|'.$idSatuan.'|'.$idMember;
                    if (isset($seen[$uniqueKey])) {
                        $result['errors'][] = 'Sheet '.$sheetLabels[(int) $mode].' baris '.$rowNumber.': kombinasi produk, satuan, dan level duplikat.';
                        continue;
                    }
                    $seen[$uniqueKey] = true;
                    $result['rows'][] = [
                        'id_satuan' => $idSatuan,
                        'id_bahan' => (int) $product['id'],
                        'id_member' => $idMember,
                        'harga_jual' => $hargaJual,
                    ];
                    $result['count']++;
                    continue;
                }

                $jumlahMinimal = $this->import_quantity_int($minInput);
                $jumlahMaksimal = $this->import_quantity_int($maxInput);
                if ($jumlahMinimal < 0 || $jumlahMaksimal < 0 || $jumlahMaksimal < $jumlahMinimal) {
                    $result['errors'][] = 'Sheet '.$sheetLabels[(int) $mode].' baris '.$rowNumber.': jumlah minimal/maksimal tidak valid.';
                    continue;
                }

                if ((int) $mode === 4) {
                    $uniqueKey = $product['id'].'|'.$idSatuan.'|'.$jumlahMinimal.'|'.$jumlahMaksimal;
                    if (isset($seen[$uniqueKey])) {
                        $result['errors'][] = 'Sheet '.$sheetLabels[(int) $mode].' baris '.$rowNumber.': kombinasi produk, satuan, dan jumlah duplikat.';
                        continue;
                    }
                    $seen[$uniqueKey] = true;
                    $result['rows'][] = [
                        'id_satuan' => $idSatuan,
                        'id_bahan' => (int) $product['id'],
                        'jumlah_minimal' => $jumlahMinimal,
                        'jumlah_maksimal' => $jumlahMaksimal,
                        'harga_jual' => $hargaJual,
                    ];
                    $result['count']++;
                    continue;
                }

                $idMember = $this->import_find_member_id($levelInput);
                if (!$idMember) {
                    $result['errors'][] = 'Sheet '.$sheetLabels[(int) $mode].' baris '.$rowNumber.': level member "'.$levelInput.'" tidak ditemukan.';
                    continue;
                }
                $uniqueKey = $product['id'].'|'.$idSatuan.'|'.$idMember.'|'.$jumlahMinimal.'|'.$jumlahMaksimal;
                if (isset($seen[$uniqueKey])) {
                    $result['errors'][] = 'Sheet '.$sheetLabels[(int) $mode].' baris '.$rowNumber.': kombinasi produk, satuan, level, dan jumlah duplikat.';
                    continue;
                }
                $seen[$uniqueKey] = true;
                $result['rows'][] = [
                    'id_member' => $idMember,
                    'id_bahan' => (int) $product['id'],
                    'id_satuan' => $idSatuan,
                    'jumlah_minimal' => $jumlahMinimal,
                    'jumlah_maksimal' => $jumlahMaksimal,
                    'harga_jual' => $hargaJual,
                ];
                $result['count']++;
            }

            return $result;
        }

        private function import_bahan_product_has_detail($rows, $idBahan)
        {
            foreach ($rows as $row) {
                if (isset($row['id_bahan']) && (int) $row['id_bahan'] === (int) $idBahan) {
                    return true;
                }
            }

            return false;
        }

        private function clear_import_bahan_price_details($idBahan)
        {
            foreach (['harga_satuan', 'harga_member', 'range_harga', 'harga_range_member'] as $table) {
                $this->db->where('id_bahan', (int) $idBahan)->delete($table);
            }
        }

        private function import_find_produk_id($value)
        {
            $value = trim((string) $value);
            if ($value === '') {
                return 0;
            }
            if (ctype_digit($value)) {
                $row = $this->db->select('id')->where('id', (int) $value)->where('pub', 1)->get('produk')->row();
                return !empty($row) ? (int) $row->id : 0;
            }
            $row = $this->db
                ->select('id')
                ->where('UPPER(`title`) = '.$this->db->escape(strtoupper($value)), null, false)
                ->where('pub', 1)
                ->get('produk')
                ->row();
            return !empty($row) ? (int) $row->id : 0;
        }

        private function import_find_kategori_id($value)
        {
            $value = trim((string) $value);
            if ($value === '') {
                return 0;
            }
            if (ctype_digit($value)) {
                $row = $this->db->select('id_jenis')->where('id_jenis', (int) $value)->where('kunci', 0)->where('pub', 'Y')->get('jenis_cetakan')->row();
                return !empty($row) ? (int) $row->id_jenis : 0;
            }
            $row = $this->db
                ->select('id_jenis')
                ->where('UPPER(`jenis_cetakan`) = '.$this->db->escape(strtoupper($value)), null, false)
                ->where('kunci', 0)
                ->where('pub', 'Y')
                ->get('jenis_cetakan')
                ->row();
            return !empty($row) ? (int) $row->id_jenis : 0;
        }

        private function import_find_satuan_id($value)
        {
            $value = trim((string) $value);
            if ($value === '') {
                return 0;
            }
            if (ctype_digit($value)) {
                $row = $this->db->select('id')->where('id', (int) $value)->where('pub', 0)->get('satuan')->row();
                return !empty($row) ? (int) $row->id : 0;
            }
            $row = $this->db
                ->select('id')
                ->group_start()
                ->where('UPPER(`satuan`) = '.$this->db->escape(strtoupper($value)), null, false)
                ->or_where('UPPER(`nama_satuan`) = '.$this->db->escape(strtoupper($value)), null, false)
                ->group_end()
                ->where('pub', 0)
                ->get('satuan')
                ->row();
            return !empty($row) ? (int) $row->id : 0;
        }

        private function import_find_member_id($value)
        {
            $value = trim((string) $value);
            if ($value === '') {
                return 0;
            }
            if (ctype_digit($value)) {
                $row = $this->db->select('id')->where('id', (int) $value)->where('status', 1)->get('member')->row();
                return !empty($row) ? (int) $row->id : 0;
            }
            $row = $this->db
                ->select('id')
                ->where('UPPER(`title`) = '.$this->db->escape(strtoupper($value)), null, false)
                ->where('status', 1)
                ->get('member')
                ->row();
            return !empty($row) ? (int) $row->id : 0;
        }

        private function import_quantity_int($value)
        {
            $value = preg_replace('/[^\d]/', '', (string) $value);
            if ($value === '') {
                return -1;
            }

            return (int) $value;
        }

        private function import_normalize_barcode($value)
        {
            $value = trim((string) $value);
            if ($value === '') {
                return '';
            }
            if (stripos($value, 'E') !== false && is_numeric($value)) {
                $value = sprintf('%.0F', (float) $value);
            }
            $value = preg_replace('/\.0$/', '', $value);
            $value = preg_replace('/\D/', '', $value);
            if (strlen($value) === 12) {
                $value = ean13_check_digit($value);
            }
            return $value;
        }

        private function import_bahan_foto_path($value, $existing = '')
        {
            $value = trim(str_replace('\\', '/', (string) $value));
            $existing = trim(str_replace('\\', '/', (string) $existing));

            if ($value === '') {
                return preg_match('#^uploads/produk/[A-Za-z0-9._-]+$#', $existing) ? $existing : '';
            }

            $basePath = parse_url(base_url(), PHP_URL_PATH);
            $urlPath = parse_url($value, PHP_URL_PATH);
            if (preg_match('#^https?://#i', $value) && is_string($urlPath)) {
                $value = ltrim($urlPath, '/');
                if ($basePath) {
                    $normalizedBase = trim($basePath, '/');
                    if ($normalizedBase !== '' && strpos($value, $normalizedBase.'/') === 0) {
                        $value = substr($value, strlen($normalizedBase) + 1);
                    }
                }
            }

            if (!preg_match('#^uploads/produk/[A-Za-z0-9._-]+\.(?:jpe?g|png|webp)$#i', $value)) {
                return null;
            }

            return is_file(FCPATH.$value) ? $value : null;
        }

        private function unique_bahan_barcode()
        {
            do {
                $barcode = ean13_check_digit(random_string('numeric', 12));
                $exists = $this->db->select('id')->where('barcode', $barcode)->get('bahan')->num_rows() > 0;
            } while ($exists);

            return $barcode;
        }

        private function import_bool_int($value, $default)
        {
            $value = strtolower(trim((string) $value));
            if ($value === '') {
                return (int) $default;
            }
            if (in_array($value, ['1', 'y', 'ya', 'yes', 'true', 'aktif'])) {
                return 1;
            }
            if (in_array($value, ['0', 'n', 'no', 'tidak', 'false', 'nonaktif'])) {
                return 0;
            }
            return (int) $default;
        }

        private function import_hitung_ukuran_mode($value, $default)
        {
            $value = strtolower(trim((string) $value));
            if (in_array($value, ['2', 'meter lari', 'meter_lari', 'm lari'], true)) {
                return 2;
            }
            return $this->import_bool_int($value, $default);
        }

        private function import_yes_no($value, $default)
        {
            return $this->import_bool_int($value, $default === 'Y' ? 1 : 0) === 1 ? 'Y' : 'N';
        }
        
        /**
            * ajax_bahan.
            *
            * @return void
        */
        public function ajax_bahan()
        {
            cek_nput_post('GET');
            // Search term
            $searchTerm = $this->input->post('searchTerm');
            
            // Get users
            $response = $this->model_data->getBahanAjax($searchTerm);
            
            echo json_encode($response);
        }
        
        /**
            * harga_range.
            *
            * @return void
        */
        public function harga_range()
        {
            cek_menu_akses();
            cek_crud_akses(8);
            $data['title'] = 'Harga range | '.info()['title'];
            
            $conditions['returnType'] = 'count';
            $totalRec = $this->model_data->getRange($conditions);
            
            // Pagination configuration
            $config['target'] = '#dataRange';
            $config['base_url'] = base_url('produk/cariRange');
            $config['total_rows'] = $totalRec;
            $config['per_page'] = $this->perPage;
            // Initialize pagination library
            $this->ajax_pagination->initialize($config);
            
            // Get records
            $conditions = [
            'limit' => $this->perPage,
            ];
            $data['bahan'] = $this->model_app->view_where('bahan', ['pub' => 1])->result();
            $data['result'] = $this->model_data->getRange($conditions);
            $this->template->load('main/themes', 'produk/harga_range', $data);
        }
        
        /**
            * cariRange.
            *
            * @return void
        */
        public function cariRange()
        {
            cek_nput_post('GET');
            $page = $this->input->post('page');
            if (!$page) {
                $offset = 0;
                } else {
                $offset = $page;
            }
            
            $limits = $this->input->post('limits');
            if (!empty($limits)) {
                $limit = $limits;
                } else {
                $limit = $this->perPage;
            }
            $sortBy = $this->input->post('sortBy');
            if (!empty($sortBy)) {
                $conditions['search']['sortBy'] = $sortBy;
            }
            
            $keywords = $this->input->post('keywords');
            if (!empty($keywords)) {
                $conditions['search']['keywords'] = $keywords;
            }
            
            $conditions['where'] = [
            'range_harga.pub' => 0,
            ];
            
            // Get record count
            $conditions['returnType'] = 'count';
            $totalRec = $this->model_data->getRange($conditions);
            
            // Pagination configuration
            $config['target'] = '#dataRange';
            $config['base_url'] = base_url('produk/cariRange');
            $config['total_rows'] = $totalRec;
            $config['per_page'] = $limit;
            $config['link_func'] = 'search_Range';
            
            // Initialize pagination library
            $this->ajax_pagination->initialize($config);
            
            // Get records
            $conditions['start'] = $offset;
            $conditions['limit'] = $limit;
            
            unset($conditions['returnType']);
            $data['result'] = $this->model_data->getRange($conditions);
            
            $conditions['offset'] = $offset;
            // Load the data list view
            $this->load->view('produk/cari-harga', $data, false);
        }
        
        /**
            * edit_range.
            *
            * @return void
        */
        public function edit_range()
        {
            cek_nput_post('GET');
            $id = $this->db->escape_str($this->input->post('id'));
            if ($id > 0) {
                $where = ['id' => $id];
                $row = $this->model_app->edit('range_harga', $where)->row_array();
                $data = ['id' => $id, 'bahan' => $row['id_bahan'], 'jml_min' => $row['jumlah_minimal'], 'jml_max' => $row['jumlah_maksimal'], 'harga' => rp($row['harga_jual']), 'aktif' => $row['pub']];
                } else {
                $data = ['id' => 0, 'bahan' => '', 'title' => '', 'jml_min' => 0, 'jml_max' => 0, 'aktif' => ''];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * save_range.
            *
            * @return void
        */
        public function save_range()
        {
            cek_nput_post('GET');
            simpan_demo('Simpan');
            cek_crud_akses(7);
            
            $config = [
            [
            'field' => 'bahan',
            'label' => 'Nama Barang',
            'rules' => 'required',
            'errors' => [
            'required' => '%s. Harus dipilih',
            ],
            ],
            [
            'field' => 'jml_min',
            'label' => 'Jumlah minimal',
            'rules' => 'required',
            'errors' => [
            'required' => '%s. Harus diisi',
            ],
            ],
            [
            'field' => 'jml_max',
            'label' => 'Jumlah maksimal',
            'rules' => 'required',
            'errors' => [
            'required' => '%s. Harus diisi',
            ],
            ],
            [
            'field' => 'harga_jual',
            'label' => 'Harga jual',
            'rules' => 'required',
            'errors' => [
            'required' => '%s. Harus diisi',
            ],
            ],
            [
            'field' => 'aktif',
            'label' => 'Aktif',
            'rules' => 'required',
            'errors' => [
            'required' => '%s. Harus dipilih',
            ],
            ],
            ];
            $this->form_validation->set_rules($config);
            if ($this->form_validation->run() == false) {
                $data = [
                'error' => true,
                'id_bahan_error' => clean(form_error('bahan')),
                'jml_min_error' => clean(form_error('jml_min')),
                'jml_max_error' => clean(form_error('jml_max')),
                'harga_jual_error' => clean(form_error('harga_jual')),
                'aktif_error' => clean(form_error('aktif')),
                ];
                } else {
                $type = $this->db->escape_str($this->input->post('type'));
                $id = $this->db->escape_str($this->input->post('id_range'));
                $_data = [
                'id_bahan' => $this->db->escape_str($this->input->post('bahan')),
                'jumlah_minimal' => $this->db->escape_str($this->input->post('jml_min')),
                'jumlah_maksimal' => $this->db->escape_str($this->input->post('jml_max')),
                'harga_jual' => rp_to_int($this->db->escape_str($this->input->post('harga_jual'))),
                'pub' => $this->db->escape_str($this->input->post('aktif')),
                ];
                
                if ($id == 0 and $type == 'add') {
                    // /data baru
                    $res = $this->model_app->input('range_harga', $_data);
                    if ($res['status'] == 'ok') {
                        $data = ['error' => false, 'status' => 200, 'msg' => 'Data berhasil disimpan'];
                        } else {
                        $data = ['error' => false, 'status' => 400];
                    }
                    } elseif ($type == 'edit') {
                    // /data update
                    $res = $this->model_app->update('range_harga', $_data, ['id' => $id]);
                    if ($res['status'] == 'ok') {
                        $data = ['error' => false, 'status' => 200, 'msg' => 'Data berhasil update'];
                        } else {
                        $data = ['error' => false, 'status' => 400];
                    }
                    } else {
                    $data = ['error' => false, 'status' => 400, 'msg' => 'Data tidak ditemukan'];
                }
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * hapus_range.
            *
            * @return void
        */
        public function hapus_range()
        {
            cek_nput_post('GET');
            simpan_demo('Hapus');
            cek_crud_akses(10);
            $id = $this->db->escape_str($this->input->post('id'));
            $res = $this->model_app->hapus('range_harga', ['id' => $id]);
            if ($res['status'] == 'ok') {
                $data = ['status' => 200, 'msg' => 'Data berhasil dihapus'];
                } else {
                $data = ['status' => 400, 'msg' => 'Data gagal dihapus'];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * load_satuan.
            *
            * @return void
        */
        public function load_satuan()
        {
            cek_nput_post('GET');
            $id = $this->db->escape_str($this->input->post('id'));
            $bahan = null;
            $harga_satuan = [];

            if (!empty($id)) {
                $bahan = $this->model_app->pilih_where('id_satuan,harga_jual', 'bahan', ['id' => $id])->row();
                $query_harga_satuan = $this->model_app->pilih_where('id_satuan,harga_jual', 'harga_satuan', ['id_bahan' => $id]);
                foreach ($query_harga_satuan->result() as $harga) {
                    $harga_satuan[$harga->id_satuan] = $harga->harga_jual;
                }
            }
            
            $result = $this->model_app->view_where('satuan', ['pub' => '0']);
            $data = [];
            foreach ($result->result() as $row) {
                $harga = '';
                if ($bahan && (string) $row->id === (string) $bahan->id_satuan) {
                    $harga = $this->harga_satuan_response($bahan->harga_jual);
                } elseif (array_key_exists($row->id, $harga_satuan)) {
                    $harga = $this->harga_satuan_response($harga_satuan[$row->id]);
                }
                $data[] = ['id' => $row->id, 'name' => $row->satuan, 'harga' => $harga];
            }
            
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }

        private function harga_satuan_response($value)
        {
            $value = (float) $value;
            return rtrim(rtrim(number_format($value, 4, '.', ''), '0'), '.');
        }
        
        /**
            * load_satuan_range.
            *
            * @return void
        */
        public function load_satuan_range()
        {
            cek_nput_post('GET');
            $id = $this->db->escape_str($this->input->post('id'));
            
            $result = $this->model_app->view_where('satuan', ['pub' => '0']);
            $data = [];
            foreach ($result->result() as $row) {
                $data[] = ['id' => $row->id, 'name' => $row->satuan];
            }
            
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * load_member.
            *
            * @return void
        */
        public function load_member()
        {
            cek_nput_post('GET');
            $id = $this->db->escape_str($this->input->post('id'));
            
            $result = $this->model_app->view_where('member', ['status' => 1]);
            $data = [];
            foreach ($result->result() as $row) {
                $data[] = ['id' => $row->id, 'name' => $row->title];
            }
            
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * load_satu_harga.
            *
            * @return void
        */
        public function load_satu_harga()
        {
            cek_nput_post('GET');
            $this->ensure_satu_harga_decimal_schema();
            $id = $this->db->escape_str($this->input->post('id'));
            $data['id_bahan'] = $id;
            $data['bahan'] = $this->model_app->pilih_where('harga,harga_modal,id_satuan', 'bahan', ['id' => $id])->row();
            
            $finishing = '';
            $nama_produk = '-';
            $cari_finishing = $this->model_app->view_where('satu_harga', ['id_bahan' => $id]);
            if ($cari_finishing->num_rows() > 0) {
                $rows = $cari_finishing->row();
                $finishing = $rows->detail_harga_pokok;
            }
            $data['detail_hpp'] = json_decode($finishing);
            
            $data['satuan'] = $this->model_app->view_where('satuan', ['pub' => 0])->result();
            $data['row'] = $this->model_app->view_where('satu_harga', ['id_bahan' => $id])->row();
            
            $this->load->view('produk/load_satu_harga', $data, false);
        }
        
        /**
            * load_level.
            *
            * @return void
        */
        public function load_level()
        {
            cek_nput_post('GET');
            $id = $this->db->escape_str($this->input->post('id'));
            
            $cek = $this->model_app->view_where('harga_member', ['id_bahan' => $id]);
            if ($cek->num_rows() > 0) {
                $row = $cek->row();
                $data = ['id' => $id, 'id_satuan' => $row->id_satuan, 'id_member' => $row->id_member, 'jual' => $row->harga_jual];
                } else {
                $data = ['id' => $id, 'id_satuan' => 0, 'id_member' => 0, 'jual' => 0];
            }
            
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * save_satu_harga.
            *
            * @return void
        */
        public function save_satu_harga()
        {
            cek_nput_post('GET');
            $this->ensure_satu_harga_decimal_schema();
            $id = $this->db->escape_str($this->input->post('id'));
            $satuan = $this->db->escape_str($this->input->post('satuan'));
            $harga_beli = $this->harga_decimal_value($this->input->post('harga_beli'));
            $harga_pokok = $this->harga_decimal_value($this->input->post('harga_pokok'));
            $persen = $this->harga_decimal_value($this->input->post('persen'));
            $harga_jual = $this->harga_decimal_value($this->input->post('harga_jual'));
            $total_hpp = $this->harga_decimal_value($this->input->post('total_hpp'));
            $_data = [
            'id_bahan' => $id,
            'id_satuan' => $satuan,
            'harga_beli' => $harga_beli,
            'harga_pokok' => $harga_pokok,
            'persen' => $persen,
            'harga_jual' => $harga_jual,
            ];
            $cek = $this->model_app->view_where('satu_harga', ['id_bahan' => $id]);
            if ($cek->num_rows() > 0) {
                $res = $this->model_app->update('satu_harga', $_data, ['id_bahan' => $id]);
                $this->model_app->update('bahan', ['harga_modal' => $harga_beli, 'harga_jual' => $harga_jual, 'harga' => $harga_pokok + $total_hpp, 'id_satuan' => $satuan], ['id' => $id]);
                if ($res['status'] == 'ok') {
                    $data = ['error' => false, 'status' => 200, 'msg' => 'Data berhasil update'];
                    } else {
                    $data = ['error' => false, 'status' => 400];
                }
                } else {
                $res = $this->model_app->input('satu_harga', $_data);
                if ($res['status'] == 'ok') {
                    $data = ['error' => false, 'status' => 200, 'msg' => 'Data berhasil disimpan'];
                    } else {
                    $data = ['error' => false, 'status' => 400];
                }
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        /**
            * satuan_harga.
            *
            * @return void
        */
        public function satuan_harga()
        {
            cek_nput_post('GET');
            $this->ensure_harga_satuan_decimal_schema();
            $id = $this->db->escape_str($this->input->post('id'));
            $data['id_bahan'] = $id;
            $data['bahan'] = $this->model_app->pilih_where('harga_modal,harga_jual,id_satuan', 'bahan', ['id' => $id])->row();
            $data['result'] = $this->model_app->view_where('harga_satuan', ['id_bahan' => $id])->result();
            $data['satuan_options'] = $this->model_app->view_where('satuan', ['pub' => 0])->result();
            $this->load->view('produk/load_satuan', $data, false);
        }
        
        /**
            * load_harga_level.
            *
            * @return void
        */
        public function load_harga_level()
        {
            cek_nput_post('GET');
            $this->ensure_harga_tingkat_decimal_schema();
            $id = $this->db->escape_str($this->input->post('id'));
            $data['id_bahan'] = $id;
            $data['bahan'] = $this->model_app->pilih_where('harga_modal,harga_jual,id_satuan', 'bahan', ['id' => $id])->row();
            $data['result'] = $this->model_app->view_where('harga_member', ['id_bahan' => $id])->result();
            $this->load->view('produk/load_level', $data, false);
        }
        
        /**
            * load_range.
            *
            * @return void
        */
        public function load_range()
        {
            cek_nput_post('GET');
            $this->ensure_harga_tingkat_decimal_schema();
            $id = $this->db->escape_str($this->input->post('id'));
            $data['id_bahan'] = $id;
            $data['bahan'] = $this->model_app->pilih_where('harga_modal,harga_jual,id_satuan', 'bahan', ['id' => $id])->row();
            $data['result'] = $this->model_app->view_where('range_harga', ['id_bahan' => $id])->result();
            $this->load->view('produk/load_range', $data, false);
        }
        
        /**
            * load_range_level.
            *
            * @return void
        */
        public function load_range_level()
        {
            cek_nput_post('GET');
            $this->ensure_harga_tingkat_decimal_schema();
            $id = $this->db->escape_str($this->input->post('id'));
            $data['id_bahan'] = $id;
            $data['bahan'] = $this->model_app->pilih_where('harga_modal,harga_jual,id_satuan', 'bahan', ['id' => $id])->row();
            $data['result'] = $this->model_app->view_where('harga_range_member', ['id_bahan' => $id])->result();
            $this->load->view('produk/load_range_level', $data, false);
        }
        
        /**
            * add_harga_range.
            *
            * @return void
        */
        public function add_harga_range()
        {
            cek_nput_post('GET');
            $this->ensure_harga_tingkat_decimal_schema();
            $a = $this->db->escape_str($this->input->post('no'));
            $no = $this->db->escape_str($this->input->post('row'));
            $_data = [
            'id_bahan' => $this->db->escape_str($this->input->post('id')),
            'id_satuan' => $this->db->escape_str($this->input->post('satuan')),
            'jumlah_minimal' => rp_to_int($this->db->escape_str($this->input->post('minimal'))),
            'jumlah_maksimal' => rp_to_int($this->db->escape_str($this->input->post('maksimal'))),
            'harga_jual' => $this->harga_decimal_value($this->db->escape_str($this->input->post('harga'))),
            ];
            
            $res = $this->model_app->input('range_harga', $_data);
            // $result = $this->model_app->views('satuan')->result();
            if ($res['status'] == 'ok') {
                $row = $this->model_app->view_where('range_harga', ['id' => $res['id']])->row();
                $data = [
                'a' => $a,
                'no' => $no,
                'id' => $row->id,
                'satuan' => $row->id_satuan,
                'jumlah_minimal' => $row->jumlah_minimal,
                'jumlah_maksimal' => $row->jumlah_maksimal,
                'harga_jual' => $row->harga_jual,
                ];
                $this->load->view('produk/add_harga_range', $data, false);
            }
        }
        
        /**
            * add_harga_range_level.
            *
            * @return void
        */
        public function add_harga_range_level()
        {
            cek_nput_post('GET');
            $this->ensure_harga_tingkat_decimal_schema();
            $a = $this->db->escape_str($this->input->post('no'));
            $no = $this->db->escape_str($this->input->post('row'));
            
            $_data = [
            'id_member' => $this->db->escape_str($this->input->post('level')),
            'id_bahan' => $this->db->escape_str($this->input->post('id')),
            'id_satuan' => $this->db->escape_str($this->input->post('satuan')),
            'jumlah_minimal' => rp_to_int($this->db->escape_str($this->input->post('minimal'))),
            'jumlah_maksimal' => rp_to_int($this->db->escape_str($this->input->post('maksimal'))),
            'harga_jual' => $this->harga_decimal_value($this->db->escape_str($this->input->post('harga'))),
            ];
            $res = $this->model_app->input('harga_range_member', $_data);
            if ($res['status'] == 'ok') {
                $row = $this->model_app->view_where('harga_range_member', ['id' => $res['id']])->row();
                
                $data =
                [
                'a' => $a,
                'no' => $no,
                'id' => $row->id,
                'satuan' => $row->id_satuan,
                'id_member' => $row->id_member,
                'id_bahan' => $row->id_bahan,
                'jumlah_minimal' => $row->jumlah_minimal,
                'jumlah_maksimal' => $row->jumlah_maksimal,
                'harga_jual' => $row->harga_jual,
                ];
                
                $this->load->view('produk/add_harga_range_level', $data, false);
            }
        }
        
        /**
            * add_harga_level.
            *
            * @return void
        */
        public function add_harga_level()
        {
            cek_nput_post('GET');
            $this->ensure_harga_tingkat_decimal_schema();
            $no = $this->db->escape_str($this->input->post('no'));
            $a = $this->db->escape_str($this->input->post('row'));
            $_data = [
            'id_satuan' => $this->db->escape_str($this->input->post('satuan')),
            'id_bahan' => $this->db->escape_str($this->input->post('id')),
            'id_member' => $this->db->escape_str($this->input->post('level')),
            'harga_jual' => $this->harga_decimal_value($this->db->escape_str($this->input->post('harga'))),
            ];
            $res = $this->model_app->input('harga_member', $_data);
            if ($res['status'] == 'ok') {
                $row = $this->model_app->view_where('harga_member', ['id' => $res['id']])->row();
                
                $data =
                [
                'a' => $a,
                'no' => $no,
                'id' => $row->id,
                'satuan' => $row->id_satuan,
                'id_member' => $row->id_member,
                'harga_jual' => $row->harga_jual,
                ];
                $this->load->view('produk/add_harga_level', $data, false);
            }
        }
        
        /**
            * add_satuan_harga.
            *
            * @return void
        */
        public function add_satuan_harga()
        {
            cek_nput_post('GET');
            $this->ensure_harga_satuan_decimal_schema();
            // dump($_POST);
            $no = xss_filter($this->input->post('no'), 'xss');
            $a = $this->db->escape_str($this->input->post('row'));
            $id_bahan = xss_filter($this->input->post('id'), 'xss');
            $harga_pokok = xss_filter($this->input->post('harga_pokok'), 'xss');
            $harga_jual = xss_filter($this->input->post('harga_jual'), 'xss');
            $harga_pokok = $this->harga_decimal_value($harga_pokok);
            $harga_jual = $this->harga_decimal_value($harga_jual);
            $_data = [
            'id_satuan' => xss_filter($this->input->post('satuan'), 'xss'),
            'id_bahan' => $id_bahan,
            'harga_pokok' => $harga_pokok,
            'harga_jual' => $harga_jual,
            ];
            
            $res = $this->model_app->input('harga_satuan', $_data);
            if ($res['status'] == 'ok') {
                $row = $this->model_app->view_where('harga_satuan', ['id' => $res['id']])->row();
                
                $data =
                [
                'a' => $a,
                'no' => $no,
                'id' => $row->id,
                'satuan' => $row->id_satuan,
                'harga_pokok' => $row->harga_pokok,
                'harga_jual' => $row->harga_jual,
                ];
                
                $this->load->view('produk/add_satuan_harga', $data, false);
            }
        }
        
        /**
            * edit_harga_satuan.
            *
            * @return void
        */
        public function edit_harga_satuan()
        {
            cek_nput_post('GET');
            $this->ensure_harga_satuan_decimal_schema();
            $id = $this->db->escape_str($this->input->post('id'));
            $column = $this->db->escape_str($this->input->post('column'));
            $editval = $this->db->escape_str($this->input->post('editval'));
            if (in_array($column, ['harga_pokok', 'harga_jual'], true)) {
                $editval = $this->harga_decimal_value($editval);
            }
            
            $res = $this->model_app->update('harga_satuan', [$column => $editval], ['id' => $id]);
            
            if ($res['status'] == 'ok') {
                $data = ['error' => false, 'status' => 200, 'msg' => 'Data berhasil update'];
                } else {
                $data = ['error' => true, 'status' => 400];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }

        private function harga_decimal_value($value)
        {
            $value = preg_replace('/\s+/', '', (string) $value);
            $value = preg_replace('/[^0-9\.,]/', '', $value);
            if ($value === '') {
                return '0';
            }

            if (preg_match('/^\d{1,3}([\.,]\d{3})+$/', $value)) {
                $digits = preg_replace('/\D/', '', $value);
                return $digits === '' ? '0' : $digits;
            }

            $lastComma = strrpos($value, ',');
            $lastDot = strrpos($value, '.');
            $decimalPos = -1;

            if ($lastComma !== false && $lastDot !== false) {
                $decimalPos = max($lastComma, $lastDot);
            } else {
                $decimalPos = max($lastComma === false ? -1 : $lastComma, $lastDot === false ? -1 : $lastDot);
                if ($decimalPos !== -1) {
                    $separator = $value[$decimalPos];
                    $firstSeparator = strpos($value, $separator);
                    $decimalLength = strlen($value) - $decimalPos - 1;
                    if ($decimalLength > 4 || ($separator === '.' && $decimalLength === 3 && $firstSeparator === $decimalPos) || ($firstSeparator === $decimalPos && $decimalLength === 0)) {
                        $decimalPos = -1;
                    }
                }
            }

            if ($decimalPos === -1) {
                $digits = preg_replace('/\D/', '', $value);
                return $digits === '' ? '0' : $digits;
            }

            $whole = preg_replace('/\D/', '', substr($value, 0, $decimalPos));
            $decimal = preg_replace('/\D/', '', substr($value, $decimalPos + 1));
            $whole = $whole === '' ? '0' : $whole;

            return $decimal === '' ? $whole : $whole.'.'.$decimal;
        }

        private function harga_decimal_display($value)
        {
            $value = (float) $value;
            $formatted = rtrim(rtrim(number_format($value, 4, '.', ''), '0'), '.');
            if ($formatted === '') {
                return '0';
            }

            $parts = explode('.', $formatted);
            $whole = number_format((float) $parts[0], 0, ',', '.');
            return count($parts) > 1 ? $whole.','.$parts[1] : $whole;
        }

        private function ensure_bahan_decimal_schema()
        {
            if (!$this->db->table_exists('bahan')) {
                return;
            }

            $fields = ['harga_modal', 'harga_jual', 'harga'];
            $columns = $this->db->field_data('bahan');
            foreach ($fields as $field) {
                foreach ($columns as $meta) {
                    if ($meta->name === $field && stripos($meta->type, 'decimal') === false) {
                        $this->db->query("ALTER TABLE `bahan` MODIFY `$field` DECIMAL(15,4) NOT NULL DEFAULT 0");
                        break;
                    }
                }
            }
        }

        private function ensure_satu_harga_decimal_schema()
        {
            if (!$this->db->table_exists('satu_harga')) {
                return;
            }

            $fields = ['harga_beli', 'harga_pokok', 'harga_jual'];
            $columns = $this->db->field_data('satu_harga');
            foreach ($fields as $field) {
                foreach ($columns as $meta) {
                    if ($meta->name === $field && stripos($meta->type, 'decimal') === false) {
                        $null = $field === 'harga_beli' ? 'NULL' : 'NOT NULL';
                        $this->db->query("ALTER TABLE `satu_harga` MODIFY `$field` DECIMAL(15,4) $null DEFAULT 0");
                        break;
                    }
                }
            }
        }

        private function ensure_harga_satuan_decimal_schema()
        {
            if (!$this->db->table_exists('harga_satuan')) {
                return;
            }

            foreach (['harga_pokok', 'harga_jual'] as $field) {
                $column = $this->db->field_data('harga_satuan');
                foreach ($column as $meta) {
                    if ($meta->name === $field && stripos($meta->type, 'decimal') === false) {
                        $null = $field === 'harga_pokok' ? 'NULL' : 'NOT NULL';
                        $this->db->query("ALTER TABLE `harga_satuan` MODIFY `$field` DECIMAL(15,4) $null DEFAULT 0");
                        break;
                    }
                }
            }
        }

        private function ensure_harga_tingkat_decimal_schema()
        {
            foreach (['harga_member', 'range_harga', 'harga_range_member'] as $table) {
                if (!$this->db->table_exists($table)) {
                    continue;
                }

                $columns = $this->db->field_data($table);
                foreach ($columns as $meta) {
                    if ($meta->name === 'harga_jual' && stripos($meta->type, 'decimal') === false) {
                        $this->db->query("ALTER TABLE `$table` MODIFY `harga_jual` DECIMAL(15,4) NOT NULL DEFAULT 0");
                        break;
                    }
                }
            }
        }
        
        /**
            * edit_harga_level.
            *
            * @return void
        */
        public function edit_harga_level()
        {
            cek_nput_post('GET');
            $this->ensure_harga_tingkat_decimal_schema();
            $id = $this->db->escape_str($this->input->post('id'));
            $column = $this->db->escape_str($this->input->post('column'));
            $editval = $this->db->escape_str($this->input->post('editval'));
            if (in_array($column, ['harga_jual'], true)) {
                $editval = $this->harga_decimal_value($editval);
            }
            
            $res = $this->model_app->update('harga_member', [$column => $editval], ['id' => $id]);
            
            if ($res['status'] == 'ok') {
                $data = ['error' => false, 'status' => 200, 'msg' => 'Data berhasil update'];
                } else {
                $data = ['error' => true, 'status' => 400];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        public function edit_harga_range()
        {
            cek_nput_post('GET');
            $this->ensure_harga_tingkat_decimal_schema();
            $id = $this->db->escape_str($this->input->post('id'));
            $column = $this->db->escape_str($this->input->post('column'));
            $editval = $this->db->escape_str($this->input->post('editval'));
            if (in_array($column, ['jumlah_minimal', 'jumlah_maksimal'], true)) {
                $editval = rp_to_int($editval);
            } elseif ($column === 'harga_jual') {
                $editval = $this->harga_decimal_value($editval);
            }
            
            $res = $this->model_app->update('range_harga', [$column => $editval], ['id' => $id]);
            
            if ($res['status'] == 'ok') {
                $data = ['error' => false, 'status' => 200, 'msg' => 'Data berhasil update'];
                } else {
                $data = ['error' => true, 'status' => 400];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        public function edit_harga_range_level()
        {
            cek_nput_post('GET');
            $this->ensure_harga_tingkat_decimal_schema();
            $id = $this->db->escape_str($this->input->post('id'));
            $column = $this->db->escape_str($this->input->post('column'));
            $editval = $this->db->escape_str($this->input->post('editval'));
            if (in_array($column, ['jumlah_minimal', 'jumlah_maksimal'], true)) {
                $editval = rp_to_int($editval);
            } elseif ($column === 'harga_jual') {
                $editval = $this->harga_decimal_value($editval);
            }
            
            $res = $this->model_app->update('harga_range_member', [$column => $editval], ['id' => $id]);
            
            if ($res['status'] == 'ok') {
                $data = ['error' => false, 'status' => 200, 'msg' => 'Data berhasil update'];
                } else {
                $data = ['error' => true, 'status' => 400];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        public function delete_harga_range()
        {
            cek_nput_post('GET');
            simpan_demo('Hapus');
            cek_crud_akses(10);
            $id = $this->db->escape_str($this->input->post('id'));
            $res = $this->model_app->hapus('range_harga', ['id' => $id]);
            if ($res['status'] == 'ok') {
                $data = ['status' => 200, 'msg' => 'Data berhasil dihapus'];
                } else {
                $data = ['status' => 400, 'msg' => 'Data gagal dihapus'];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        public function delete_harga_range_level()
        {
            cek_nput_post('GET');
            simpan_demo('Hapus');
            cek_crud_akses(10);
            $id = $this->db->escape_str($this->input->post('id'));
            $res = $this->model_app->hapus('harga_range_member', ['id' => $id]);
            if ($res['status'] == 'ok') {
                $data = ['status' => 200, 'msg' => 'Data berhasil dihapus'];
                } else {
                $data = ['status' => 400, 'msg' => 'Data gagal dihapus'];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        public function delete_harga_satuan()
        {
            cek_nput_post('GET');
            simpan_demo('Hapus');
            cek_crud_akses(10);
            $id = $this->db->escape_str($this->input->post('id'));
            $res = $this->model_app->hapus('harga_satuan', ['id' => $id]);
            if ($res['status'] == 'ok') {
                $data = ['status' => 200, 'msg' => 'Data berhasil dihapus'];
                } else {
                $data = ['status' => 400, 'msg' => 'Data gagal dihapus'];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        public function delete_harga_level()
        {
            cek_nput_post('GET');
            simpan_demo('Hapus');
            cek_crud_akses(10);
            $id = $this->db->escape_str($this->input->post('id'));
            $res = $this->model_app->hapus('harga_member', ['id' => $id]);
            if ($res['status'] == 'ok') {
                $data = ['status' => 200, 'msg' => 'Data berhasil dihapus'];
                } else {
                $data = ['status' => 400, 'msg' => 'Data gagal dihapus'];
            }
            $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($data));
        }
        
        public function export_barcode()
        {
            $data['title'] = 'Data Produk | '.info()['title'];
            $data['judul'] = 'Data Produk';
            $data['result'] = $this->model_app->view_where('produk', ['kunci' => 0])->result_array();
            
            $this->load->view('produk/export_barcode', $data);
        }
        
        public function export_harga()
        {
            $data['title'] = 'Data Harga Produk | '.info()['title'];
            $data['judul'] = 'Barcode Harga Produk';
            $data['result'] = $this->model_app->view_where('bahan', ['pub' => 1])->result_array();
            
            $this->load->view('produk/export_harga', $data);
        }
        
        public function update_hpp()
        {
            cek_nput_post('GET');
            
            $id_bahan = $this->input->post('id_bahan', true);
            $id_detail = $this->input->post('id_detail', true);
            $title = $this->input->post('title', true);
            $harga = $this->input->post('harga', true);
            $harga = is_array($harga) ? array_map([$this, 'harga_decimal_value'], $harga) : $this->harga_decimal_value($harga);
            
            // dump($harga);
            $this->form_validation->set_rules([
            [
            'field' => 'title[]',
            'label' => 'Title',
            'rules' => 'required|trim|min_length[1]|max_length[9]',
            'errors' => [
            'required' => '%s. Harus di isi',
            'min_length' => '%s minimal 1 digit.',
            'max_length' => '%s minimal 9 digit.',
            ],
            ],
            
            [
            'field' => 'harga[]',
            'label' => 'Harga',
            'rules' => 'required|trim|min_length[1]|max_length[9]',
            'errors' => [
            'required' => '%s. Harus diisi.',
            'min_length' => '%s minimal 1 digit.',
            'max_length' => '%s minimal 9 digit.',
            ],
            ],
            ]);
            // dump($_POST);
            if ($this->form_validation->run() == false) {
                $response['status'] = false;
                $response['msg'] = validation_errors();
                $this->output
                ->set_status_header(200)
                ->set_content_type('application/json', 'utf-8')
                ->set_output(json_encode($response, JSON_HEX_APOS | JSON_HEX_QUOT))
                ->_display();
                exit;
            }
            // dump($_POST);
            $index = 0;
            foreach ($id_detail as $str) {
                $data['data'][] = [
                'id' => $index,
                'id_bahan' => $id_bahan,
                'link_produk' => $id_bahan,
                'title' => $title[$index],
                'harga' => $harga[$index],
                'tanggal' => strtotime(today()),
                ];
                
                ++$index;
            }
            $json_update = json_encode($data);
            // dump($json_update);
            
            if (!empty($json_update)) {
                $_data = ['detail_harga_pokok' => $json_update];
                $where = ['id_bahan' => $id_bahan];
                $res = $this->model_app->update('satu_harga', $_data, $where);
                if ($res['status'] == 'ok') {
                    $data = ['ok' => 'ok', 'msg' => 'ok'];
                    } else {
                    $data = ['ok' => 'err', 'msg' => 'error'];
                }
                echo json_encode($data);
            }
        }
        
        public function hitung_ukuran()
        {
            $id_hitung = $this->input->post('id', true);
            $data['id'] = $id_hitung;
            $this->load->view('produk/load_hitung_ukuran', $data);
        }
        
        public function load_jenis()
        {
            $akun = $this->masterdata->getAccountData();
            // dump($akun);
            foreach ($akun as $row) {
                $data[] = ['id' => $row['id_akun'], 'name' => $row['nama']];
            }
            echo json_encode($data);
        }
        
        public function barcode()
        {
            $barcode = $this->input->post('barcode', true);
            $barcode = ean13_check_digit($barcode);
            $response = ['status' => true, 'barcode' => $barcode];
            echo json_encode($response);
        }
        
        /**
         * Halaman jembatan JSPM (auto print).
         * Dipakai untuk Thermal 58, Thermal 80/85, maupun Dotmatrix (TM-U220).
         */
        public function print_thermal_raw($enc)
        {
            $this->_ensure_one_active_printer();
            $printer_id = (int) $this->input->get('printer_id', true);
            $active_printers = $this->db
                ->select('id,name,shared_name,slug,ukuran_kertas,posisi')
                ->order_by('id', 'DESC')
                ->get_where('printer', ['pub' => 1])
                ->result_array();

            $data['token'] = $enc;
            $data['printer_id'] = $printer_id;
            $data['active_printers'] = $active_printers;
            $data['choose_printer'] = ($printer_id <= 0 && count($active_printers) > 1);
            $this->load->view('produk/print_thermal_raw', $data);
        }

        private function _ensure_one_active_printer()
        {
            if (!$this->db->table_exists('printer')) return;
            $active = $this->db->where('pub', 1)->count_all_results('printer');
            if ((int)$active > 0) return;

            $row = $this->db
                ->like('name', 'L3110')
                ->or_like('shared_name', 'L3110')
                ->order_by('id', 'DESC')
                ->get('printer')
                ->row_array();

            if (!$row) {
                $row = $this->db
                    ->where('slug', 'in')
                    ->order_by('id', 'DESC')
                    ->get('printer')
                    ->row_array();
            }

            if (!$row) {
                $row = $this->db
                    ->order_by('id', 'DESC')
                    ->get('printer')
                    ->row_array();
            }

            if ($row && !empty($row['id'])) {
                $this->model_app->update('printer', ['pub' => 1], ['id' => (int)$row['id']]);
            }
        }

        /**
         * BACKWARD COMPAT: endpoint lama.
         * Sekarang diarahkan ke raw_cmd() yang otomatis pilih profil printer aktif.
         */
        public function thermal_raw_cmd($enc)
        {
            return $this->raw_cmd($enc);
        }

        /**
         * Ambil profil printer default dari tabel `printer`.
         * Dipakai oleh halaman JSPM untuk menampilkan nama yang disarankan.
         */
        public function printer_profile()
        {
            $printer_id = (int) $this->input->get('printer_id', true);
            $row = null;
            if ($printer_id > 0) {
                $row = $this->db->get_where('printer', ['id' => $printer_id, 'pub' => 1])->row_array();
            }
            if (!$row) {
                $row = $this->db->order_by('id', 'DESC')->get_where('printer', ['pub' => 1])->row_array();
            }
            if (!$row) {
                $row = $this->db->order_by('id', 'DESC')->get_where('printer', ['slug' => 'th58'])->row_array();
            }

            $hasAutoCut = $this->db->field_exists('auto_cut', 'printer');
            $hasLogoMode = $this->db->field_exists('logo_mode', 'printer');

            $resp = [
                'ok' => true,
                'id' => $row ? (int)$row['id'] : 0,
                'name' => $row ? $row['name'] : 'Thermal 58mm',
                'shared_name' => $row ? ($row['shared_name'] ?: '') : '',
                'slug' => $row ? ($row['slug'] ?: '') : '',
                'ukuran_kertas' => $row ? ($row['ukuran_kertas'] ?: '') : '',
                'posisi' => $row ? ($row['posisi'] ?: '') : '',
                'auto_cut' => ($row && $hasAutoCut) ? (int)$row['auto_cut'] : 1,
                'logo_mode' => ($row && $hasLogoMode) ? ($row['logo_mode'] ?: 'auto') : 'auto',
            ];

            $this->output
                ->set_content_type('application/json')
                ->set_output(json_encode($resp));
        }

        /**
         * Endpoint RAW utama.
         * Return JSON: { ok, bytes, b64, profile }
         */
        public function raw_cmd($enc)
        {
            $id_invoice = decrypt_url($enc);
            if (!$id_invoice) {
                show_404();
                return;
            }

            // printer aktif (default)
            $printer_id = (int) $this->input->get('printer_id', true);
            $p = null;
            if ($printer_id > 0) {
                $p = $this->db->get_where('printer', ['id' => $printer_id, 'pub' => 1])->row_array();
            }
            if (!$p) {
                $p = $this->db->order_by('id', 'DESC')->get_where('printer', ['pub' => 1])->row_array();
            }
            if (!$p) {
                $p = $this->db->order_by('id', 'DESC')->get_where('printer', ['slug' => 'th58'])->row_array();
            }
            $slug = $p ? ($p['slug'] ?: '') : '';
            $isTmU220 = $this->_is_tmu220_profile($p);

            try {
                // Dotmatrix TM-U220 (80mm) -> pakai builder khusus (logo column)
                if ($isTmU220) {
                    $b64 = $this->_build_raw_dotmatrix80_b64($id_invoice, $p);
                } else {
                    // default: pola thermal 58 (builder lama)
                    $b64 = $this->_build_raw_th58_b64($id_invoice, $p);
                }

                $this->_clear_printed_invoice_reference($id_invoice);

                $rawLen = strlen(base64_decode($b64));
                $this->output
                    ->set_content_type('application/json')
                    ->set_output(json_encode([
                        'ok' => true,
                        'bytes' => $rawLen,
                        'b64' => $b64,
                        'profile' => [
                            'slug' => $isTmU220 ? 'tmu220' : $slug,
                            'name' => $p ? $p['name'] : 'Thermal',
                            'shared_name' => $p ? ($p['shared_name'] ?: '') : ''
                        ]
                    ]));
            } catch (Exception $e) {
                $this->output
                    ->set_status_header(500)
                    ->set_content_type('application/json')
                    ->set_output(json_encode([
                        'ok' => false,
                        'message' => $e->getMessage()
                    ]));
            }
        }

        /**
         * Deteksi profil TM-U220, termasuk data printer lama yang belum punya slug `tmu220`.
         */
        private function _is_tmu220_profile($printerProfile)
        {
            if (!is_array($printerProfile) || empty($printerProfile)) return false;

            $slug = strtolower(trim((string)($printerProfile['slug'] ?? '')));
            if ($slug === 'tmu220') return true;

            $name = strtolower(trim((string)($printerProfile['name'] ?? '')));
            $shared = strtolower(trim((string)($printerProfile['shared_name'] ?? '')));
            $needle = $name . ' ' . $shared;

            return (strpos($needle, 'tm-u220') !== false)
                || (strpos($needle, 'tmu220') !== false)
                || (strpos($needle, 'u220') !== false);
        }

        private function _clear_printed_invoice_reference($id_invoice)
        {
            $id_invoice = (int) $id_invoice;
            if ($id_invoice <= 0) {
                return;
            }

            $this->session->unset_userdata('cart');
            $this->model_app->update(
                'tb_users',
                ['last_invoice' => 0],
                ['id_user' => $this->iduser]
            );
        }

        /**
         * Ambil path logo struk dengan prioritas file yang paling aman untuk receipt.
         */
        private function _resolve_receipt_logo_path()
        {
            $candidates = [FCPATH . 'uploads/logo struk.png'];
            $info = info();

            if (!empty($info['logo_bw'])) {
                $candidates[] = FCPATH . 'uploads/' . ltrim((string)$info['logo_bw'], '/\\');
            }
            if (!empty($info['logo'])) {
                $candidates[] = FCPATH . 'uploads/' . ltrim((string)$info['logo'], '/\\');
            }

            foreach ($candidates as $path) {
                if (!is_string($path) || trim($path) === '') continue;
                $real = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path);
                if (is_file($real) && is_readable($real)) return $real;
            }

            return null;
        }

        /**
         * Preprocess logo agar stabil di TM-U220:
         * - resize maksimum lebar tertentu
         * - grayscale + threshold hitam/putih
         * Return path image hasil proses (atau source jika gagal).
         */
        private function _prepare_tmu220_logo_image($sourcePath, $maxWidth = 300)
        {
            if (empty($sourcePath) || !is_file($sourcePath)) return null;
            if (!function_exists('imagecreatefromstring') || !function_exists('imagepng')) return $sourcePath;

            $blob = @file_get_contents($sourcePath);
            if ($blob === false || $blob === '') return $sourcePath;

            $src = @imagecreatefromstring($blob);
            if (!$src) return $sourcePath;

            $srcW = imagesx($src);
            $srcH = imagesy($src);
            if ($srcW < 1 || $srcH < 1) {
                imagedestroy($src);
                return $sourcePath;
            }

            $maxWidth = max(120, (int)$maxWidth);
            if ($srcW > $maxWidth) {
                $dstW = $maxWidth;
                $dstH = max(1, (int)round(($srcH * $dstW) / $srcW));
            } else {
                $dstW = $srcW;
                $dstH = $srcH;
            }

            $dst = imagecreatetruecolor($dstW, $dstH);
            $white = imagecolorallocate($dst, 255, 255, 255);
            imagefilledrectangle($dst, 0, 0, $dstW, $dstH, $white);
            imagecopyresampled($dst, $src, 0, 0, 0, 0, $dstW, $dstH, $srcW, $srcH);
            imagedestroy($src);

            if (function_exists('imagefilter')) {
                @imagefilter($dst, IMG_FILTER_GRAYSCALE);
                @imagefilter($dst, IMG_FILTER_CONTRAST, -30);
            }

            // Threshold sederhana agar image jadi hitam-putih tegas untuk dotmatrix.
            for ($y = 0; $y < $dstH; $y++) {
                for ($x = 0; $x < $dstW; $x++) {
                    $rgb = imagecolorat($dst, $x, $y);
                    $r = ($rgb >> 16) & 0xFF;
                    $g = ($rgb >> 8) & 0xFF;
                    $b = $rgb & 0xFF;
                    $gray = (int)round(($r * 0.299) + ($g * 0.587) + ($b * 0.114));
                    $bw = ($gray < 165) ? 0 : 255;
                    $color = ($bw << 16) | ($bw << 8) | $bw;
                    imagesetpixel($dst, $x, $y, $color);
                }
            }

            $tmpDir = FCPATH . 'uploads/tmp/';
            if (!is_dir($tmpDir)) @mkdir($tmpDir, 0775, true);
            if (!is_dir($tmpDir) || !is_writable($tmpDir)) $tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR;

            $tmpPath = $tmpDir . 'logo_tmu220_' . md5($sourcePath . microtime(true) . mt_rand()) . '.png';
            $saved = @imagepng($dst, $tmpPath);
            imagedestroy($dst);

            if ($saved && is_file($tmpPath) && filesize($tmpPath) > 0) {
                return $tmpPath;
            }

            return $sourcePath;
        }

/**
 * Build RAW ESC/POS bytes yang SAMA polanya dengan print thermal 58 (th58) versi escpos.
 * Output: base64 string (aman via HTTP/JS).
 */
private function _build_raw_th58_b64($noid, $printerProfile = null)
{
    // pastikan library escpos kamu keload (sesuai project mu)
    $this->load->library('escpos');

    $paperWidth = is_array($printerProfile) ? trim((string)($printerProfile['ukuran_kertas'] ?? '')) : '';
    $charsPerLine = 32;
    if ($paperWidth === '80') {
        $charsPerLine = 42;
    } elseif ($paperWidth === '85') {
        $charsPerLine = 48;
    }
    $itemNameWidth = $charsPerLine;
    $qtyLineWidth = max(20, $charsPerLine - 12);
    $qtyLabel = 'Qty:';
    $qtyLineWidth = max(8, $qtyLineWidth - strlen($qtyLabel));
    if ($charsPerLine >= 48) {
        $priceCols = [16, 3, 10, 19];
    } elseif ($charsPerLine >= 42) {
        $priceCols = [14, 3, 9, 16];
    } else {
        $priceCols = [12, 3, 7, 10];
    }
    $separator = str_repeat("=", $charsPerLine) . "\n";
    $separatorThin = str_repeat("_", $charsPerLine) . "\n";

    // ==== deteksi namespace escpos yang tersedia ====
    $PrinterClass = class_exists('Escpos\\Printer') ? 'Escpos\\Printer' : (class_exists('Mike42\\Escpos\\Printer') ? 'Mike42\\Escpos\\Printer' : null);
    $ImgClass     = class_exists('Escpos\\EscposImage') ? 'Escpos\\EscposImage' : (class_exists('Mike42\\Escpos\\EscposImage') ? 'Mike42\\Escpos\\EscposImage' : null);

    // Connector file: gunakan yang sesuai library terpasang
    $FileConnClass = class_exists('Escpos\\PrintConnectors\\FilePrintConnector')
        ? 'Escpos\\PrintConnectors\\FilePrintConnector'
        : (class_exists('Mike42\\Escpos\\PrintConnectors\\FilePrintConnector')
            ? 'Mike42\\Escpos\\PrintConnectors\\FilePrintConnector'
            : null);

    if (!$PrinterClass || !$ImgClass || !$FileConnClass) {
        throw new Exception("Library ESC/POS tidak lengkap. Pastikan FilePrintConnector/Printer/EscposImage tersedia di escpos library project.");
    }

    // ==== ambil data invoice (minimal yang dibutuhkan untuk cetak) ====
    $row = $this->model_app->view_where('invoice', ['id_invoice' => $noid])->row_array();
    if (!$row) {
        throw new Exception("Invoice tidak ditemukan.");
    }

    // increment cetak (meniru pola th58)
    $jml = (int)$row['cetak'] + 1;
    if ($row['status'] != 'batal') {
        $this->model_app->update(
            'invoice',
            ['cetak' => $jml, 'status' => 'simpan', 'pos' => 'Y', 'oto' => 6],
            ['id_invoice' => $noid]
        );
    }

    // marketing / kasir
    $marketing = [];
    if (!empty($row['id_marketing'])) {
        $marketing = $this->model_app->view_where('tb_users', ['id_user' => $row['id_marketing']])->row_array();
    }
    if (empty($marketing)) {
        $marketing = ['nama_lengkap' => 'Admin'];
    }

    // detail item
    $detail = $this->model_app->produk_cart(['invoice_detail.id_invoice' => $noid]);

    // total bayar
    $totalObj = $this->model_app->total_bayar(['bayar_invoice_detail.id_invoice' => $noid])->row();
    $totalBayar = ($totalObj && isset($totalObj->total)) ? (float)$totalObj->total : 0;

    // ==== siapkan file temporary untuk menampung RAW ====
    $tmpDir = FCPATH . 'uploads/tmp/';
    if (!is_dir($tmpDir)) {
        @mkdir($tmpDir, 0775, true);
    }
    if (!is_dir($tmpDir) || !is_writable($tmpDir)) {
        // fallback ke system temp
        $tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR;
    }

    $tmpFile = $tmpDir . 'raw_th58_' . $noid . '_' . time() . '.bin';

    // ==== buat printer object menulis ke file ====
    $connector = new $FileConnClass($tmpFile);
    $printer   = new $PrinterClass($connector);

    // setting opsional dari tabel printer (kalau kolomnya ada)
    $autoCut = 1;
    if (is_array($printerProfile) && isset($printerProfile['auto_cut'])) {
        $autoCut = (int)$printerProfile['auto_cut'];
    }

    // helper padding (ganti Text::fill supaya gak tergantung class)
    $fill = function($text, $len) {
        $text = (string)$text;
        if (function_exists('mb_strlen')) {
            if (mb_strlen($text, 'UTF-8') > $len) $text = mb_substr($text, 0, $len, 'UTF-8');
        } else {
            if (strlen($text) > $len) $text = substr($text, 0, $len);
        }
        return str_pad($text, $len, ' ', STR_PAD_RIGHT);
    };
    $fillRight = function($text, $len) {
        $text = (string)$text;
        if (function_exists('mb_strlen')) {
            if (mb_strlen($text, 'UTF-8') > $len) $text = mb_substr($text, 0, $len, 'UTF-8');
        } else {
            if (strlen($text) > $len) $text = substr($text, 0, $len);
        }
        return str_pad($text, $len, ' ', STR_PAD_LEFT);
    };
    $strLen = function($text) {
        $text = (string) $text;
        return function_exists('mb_strlen') ? mb_strlen($text, 'UTF-8') : strlen($text);
    };
    $rataKiriKanan = function($kiri, $kanan, $lebar = 32) use ($strLen) {
        $kiri  = trim((string)$kiri);
        $kanan = trim((string)$kanan);
        $spasi = $lebar - $strLen($kiri) - $strLen($kanan);
        if ($spasi < 1) $spasi = 1;
        return $kiri . str_repeat(" ", $spasi) . $kanan . "\n";
    };

    try {
        // ======= LOGO (SAMA SEPERTI TH58) =======
        try {
            $logoPath = FCPATH . 'uploads/logo struk.png';
            if (file_exists($logoPath)) {
                $logo = call_user_func([$ImgClass, 'load'], $logoPath, false);
                $printer->setJustification($PrinterClass::JUSTIFY_CENTER);
                $printer->bitImage($logo);
                $printer->setJustification($PrinterClass::JUSTIFY_LEFT);
            }
        } catch (Exception $e) {
            // kalau gagal logo, jangan stop cetak
            $printer->text("Gagal load logo: " . $e->getMessage() . "\n");
        }

        // garis
        $printer->initialize();
        $printer->text($separator);

        // ======= HEADER TOKO =======
        $printer->initialize();
        $printer->setJustification($PrinterClass::JUSTIFY_CENTER);
        $printer->text($this->info['title'] . "\n");

        $printer->initialize();
        $printer->setJustification($PrinterClass::JUSTIFY_CENTER);
        $printer->text(strip_word_html(base64_decode($this->info['deskripsi'])) . "\n");

        $printer->initialize();
        $printer->setJustification($PrinterClass::JUSTIFY_CENTER);
        $printer->text($this->info['phone'] . "\n");
        $printer->text("\n");

        // ======= NOMOR TRANSAKSI GEDE =======
        $printer->initialize();
        $printer->setJustification($PrinterClass::JUSTIFY_CENTER);
        $printer->selectPrintMode($PrinterClass::MODE_DOUBLE_HEIGHT);
        $printer->selectPrintMode($PrinterClass::MODE_DOUBLE_HEIGHT);
        $printer->selectPrintMode($PrinterClass::MODE_DOUBLE_WIDTH);
        $printer->setEmphasis(true);
        $printer->text('' . $row['id_transaksi'] . "\n");
        $printer->setEmphasis(true);
        $printer->text("\n");

        // pelanggan
        $konsumen = $this->model_app->view_where('konsumen', ['id' => $row['id_konsumen']])->row_array();
        $nama_pelanggan = $konsumen ? $konsumen['nama'] : 'Umum';

        $idkonsumen = $row['id_konsumen'];
        $cek = cekKonsumen($idkonsumen);
        $panggilan = isset($cek['panggilan']) ? $cek['panggilan'] : '';

        // data transaksi
        $printer->initialize();
        $printer->setJustification($PrinterClass::JUSTIFY_LEFT);
        $printer->text('Kasir     : ' . $marketing['nama_lengkap'] . "\n");
        $printer->text('Tanggal   : ' . tgl_indo($row['tgl_trx']) . "\n");
        $printer->text('Jam       : ' . invoice_order_time($row) . "\n");
        $printer->text('Pelanggan : ' . $panggilan . ' ' . $nama_pelanggan . "\n");

        $printer->initialize();
        $printer->text($separator);

        // ======= ITEM =======
        $totalb   = 0;
        $subtotal = 0;

        $itemCount = is_array($detail) ? count($detail) : 0;
        $idx = 0;

        if ($itemCount > 0) {
            foreach ($detail as $val) {
                $idx++;

                $diskonItem = $val['jumlah'] * $val['harga'] * $val['diskon'] / 100;
                $totalb = $val['jumlah'] * $val['harga'] - $diskonItem;
                $subtotal += $totalb;

                // finishing/catatan (SAMA seperti snippet kamu)
                $finishingData = [];
                if (isset($val['detail']) && $val['detail'] !== '') {
                    $decoded = json_decode($val['detail']);
                    if (is_object($decoded) && isset($decoded->data) && is_array($decoded->data)) {
                        $finishingData = $decoded->data;
                    }
                }
                $hasNotes = !empty($finishingData);
                $ket = trim($val['keterangan']);

                $printer->text($fill($val['nbahan'], $itemNameWidth) . "\n");
                $printer->text($fill('Ukuran:' . $val['ukuran'], $qtyLineWidth) . $qtyLabel . $val['jumlah'] . "\n");

                if ($ket !== '') {
                    $printer->text('Ket:' . $ket . "\n");
                }

                if ($hasNotes) {
                    $printer->text("Catatan:\n");
                    foreach ($finishingData as $fn) {
                        $t = isset($fn->title) ? trim($fn->title) : '';
                        $i = isset($fn->isi) ? trim($fn->isi) : '';
                        $lineTxt = trim($t . ($i !== '' ? ' ' . $i : ''));
                        if ($lineTxt === '') continue;
                        $printer->text($lineTxt . "\n");
                    }
                }

                $printer->text(
                    $fill('  ' . $val['jumlah'] . ' Pcs', $priceCols[0]) .
                    $fill('x', $priceCols[1]) .
                    $fill(number_format($val['harga'], 0, ',', '.'), $priceCols[2]) .
                    $fillRight(number_format($totalb, 0, ',', '.'), $priceCols[3]) . "\n"
                );

                if ($idx < $itemCount) {
                    $printer->text($separatorThin);
                }
            }
        }

        // ======= TOTAL, BAYAR, SISA/KEMBALIAN =======
        $pajak = ((float)$subtotal * (float)$row['pajak']) / 100;
        $sisa = $pajak + $subtotal - $totalBayar;

        $printer->text($separator);
        $printer->setEmphasis(true);
        $printer->text($rataKiriKanan('TOTAL', rp($subtotal), $charsPerLine));
        $printer->setEmphasis(false);
        $printer->selectPrintMode(); // reset

        $select = 'pajak, total_bayar AS total, jumlah_bayar, kembalian';
        $where  = ['id_invoice' => $noid];
        $tdetail = $this->model_app->cek_total('invoice', $select, $where);

        if ((float)$sisa == 0.0) {
            $kembalian = ($tdetail && isset($tdetail->kembalian)) ? $tdetail->kembalian : 0;
            $printer->text($rataKiriKanan('BAYAR', rp($totalBayar), $charsPerLine));
            $printer->text($rataKiriKanan('KEMBALIAN', rp($kembalian), $charsPerLine));
        } else {
            $printer->text($rataKiriKanan('BAYAR', rp($totalBayar), $charsPerLine));
            $printer->text($rataKiriKanan('SISA', rp($sisa), $charsPerLine));
        }

        $printer->text($separator);

		// ======= QRIS / QR TRACKING =======
        try {
			$invoiceQr = invoice_qr_context($noid, info()['kode_qris']);
            if (!empty($invoiceQr['content'])) {
                $printer->setJustification($PrinterClass::JUSTIFY_CENTER);
				if ($invoiceQr['is_tracking']) {
					$printer->text("Scan QR Code untuk\nTracking Pesanan\n");
				}
                // Kirim QR langsung sebagai perintah ESC/POS. Cara ini tidak
                // bergantung pada GD/PNG sehingga aman untuk proses RAW thermal.
                $qrSize = $charsPerLine >= 48 ? 7 : ($charsPerLine >= 42 ? 6 : 5);
                $printer->qrCode(
                    (string)$invoiceQr['content'],
                    $PrinterClass::QR_ECLEVEL_L,
                    $qrSize,
                    $PrinterClass::QR_MODEL_2
                );
                $printer->setJustification($PrinterClass::JUSTIFY_LEFT);
            }
        } catch (Exception $e) {
            $printer->text("Gagal mencetak QRIS: " . $e->getMessage() . "\n");
        }

        // ======= FOOTER =======
        $printer->initialize();
        $printer->text($separator . "\n");

        $printer->initialize();
        $printer->setJustification($PrinterClass::JUSTIFY_CENTER);
        $printer->text("Terima kasih\n");
        $printer->text("Barang Yang Sudah Dibeli\n");
        $printer->text("Tidak Dapat Di Batalkan !\n");
        $printer->text("Barang tidak di ambil lebih dari 1 bulan\n");
        $printer->text("BUKAN TANGGUNG JAWAB KAMI!!!\n");

        $printer->feed(3);

        // Auto cut (jika printer mendukung)
        if ($autoCut === 1 && method_exists($printer, 'cut')) {
            try { $printer->cut(); } catch (Exception $e) {}
        }
        $printer->close();

        // ambil bytes & encode base64
        $raw = @file_get_contents($tmpFile);
        @unlink($tmpFile);

        if ($raw === false || $raw === '') {
            throw new Exception("RAW kosong. Cek permission folder tmp atau proses build.");
        }

        return base64_encode($raw);

    } catch (Exception $e) {
        // pastikan file temp dibersihkan
        @unlink($tmpFile);
        try { $printer->close(); } catch (Exception $x) {}
        throw $e;
    }
}

/**
 * Build RAW ESC/POS untuk Dotmatrix Epson TM-U220 (kertas 80mm).
 * Fokus: output rapi + logo aman (mode column format) + auto cut (jika ada cutter).
 * Output: base64 string.
 */
private function _build_raw_dotmatrix80_b64($noid, $printerProfile = null)
{
    $this->load->library('escpos');

    $PrinterClass = class_exists('Escpos\\Printer') ? 'Escpos\\Printer' : (class_exists('Mike42\\Escpos\\Printer') ? 'Mike42\\Escpos\\Printer' : null);
    $ImgClass     = class_exists('Escpos\\EscposImage') ? 'Escpos\\EscposImage' : (class_exists('Mike42\\Escpos\\EscposImage') ? 'Mike42\\Escpos\\EscposImage' : null);
    $FileConnClass = class_exists('Escpos\\PrintConnectors\\FilePrintConnector')
        ? 'Escpos\\PrintConnectors\\FilePrintConnector'
        : (class_exists('Mike42\\Escpos\\PrintConnectors\\FilePrintConnector')
            ? 'Mike42\\Escpos\\PrintConnectors\\FilePrintConnector'
            : null);

    if (!$PrinterClass || !$ImgClass || !$FileConnClass) {
        throw new Exception("Library ESC/POS tidak lengkap. Pastikan FilePrintConnector/Printer/EscposImage tersedia di escpos library project.");
    }

    $row = $this->model_app->view_where('invoice', ['id_invoice' => $noid])->row_array();
    if (!$row) throw new Exception('Invoice tidak ditemukan.');

    // increment cetak (meniru pola th58)
    $jml = (int)$row['cetak'] + 1;
    if ($row['status'] != 'batal') {
        $this->model_app->update(
            'invoice',
            ['cetak' => $jml, 'status' => 'simpan', 'pos' => 'Y', 'oto' => 6],
            ['id_invoice' => $noid]
        );
    }

    $marketing = [];
    if (!empty($row['id_marketing'])) {
        $marketing = $this->model_app->view_where('tb_users', ['id_user' => $row['id_marketing']])->row_array();
    }
    if (empty($marketing)) $marketing = ['nama_lengkap' => 'Admin'];

    $detail = $this->model_app->produk_cart(['invoice_detail.id_invoice' => $noid]);
    $totalObj = $this->model_app->total_bayar(['bayar_invoice_detail.id_invoice' => $noid])->row();
    $totalBayar = ($totalObj && isset($totalObj->total)) ? (float)$totalObj->total : 0;

    $tmpDir = FCPATH . 'uploads/tmp/';
    if (!is_dir($tmpDir)) @mkdir($tmpDir, 0775, true);
    if (!is_dir($tmpDir) || !is_writable($tmpDir)) $tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR;
    $tmpFile = $tmpDir . 'raw_tmu220_' . $noid . '_' . time() . '.bin';

    $connector = new $FileConnClass($tmpFile);
    $printer   = new $PrinterClass($connector);

    try {
        // ====== setting dari tabel printer (jika ada) ======
        $logoMode = 'column';
        $autoCut  = 1;
        if (is_array($printerProfile)) {
            if (!empty($printerProfile['logo_mode'])) $logoMode = $printerProfile['logo_mode'];
            if (isset($printerProfile['auto_cut'])) $autoCut = (int)$printerProfile['auto_cut'];
        }
        // TM-U220 lebih stabil dengan mode column; raster sering menyebabkan logo rusak.
        if ($logoMode !== 'column') $logoMode = 'column';

        // ====== helper formatting ======
        $W = 40; // lebar karakter aman TM-U220 mode receipt
        $toPrintable = function ($text) {
            $text = strip_tags((string)$text);
            $text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
            $text = preg_replace('/\s+/u', ' ', $text);
            $text = trim($text);
            if ($text === '') return '';

            if (function_exists('iconv')) {
                $ascii = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $text);
                if ($ascii !== false && $ascii !== '') {
                    $text = $ascii;
                }
            }
            $text = preg_replace('/[^\x20-\x7E]/', '', $text);
            return trim($text);
        };
        $fit = function($text, $len, $padType = STR_PAD_RIGHT) use ($toPrintable) {
            $txt = $toPrintable($text);
            if (strlen($txt) > $len) $txt = substr($txt, 0, $len);
            return str_pad($txt, $len, ' ', $padType);
        };
        $line = function($left, $right = '') use ($W, $fit, $toPrintable) {
            $right = $toPrintable($right);
            if ($right === '') return $fit($left, $W);
            if (strlen($right) >= $W) return substr($right, 0, $W);
            $maxLeft = $W - strlen($right);
            return $fit($left, $maxLeft) . $right;
        };
        $wrap = function($text, $prefix = '') use ($W, $toPrintable) {
            $plain = $toPrintable($text);
            $prefix = $toPrintable($prefix);
            if ($plain === '') return [];

            $result = [];
            $firstWidth = $W - strlen($prefix);
            if ($firstWidth < 8) {
                $firstWidth = $W;
                $prefix = '';
            }

            while ($plain !== '') {
                $width = empty($result) ? $firstWidth : $W;
                if (strlen($plain) <= $width) {
                    $result[] = $plain;
                    break;
                }

                $slice = substr($plain, 0, $width);
                $cutPos = strrpos($slice, ' ');
                if ($cutPos === false || $cutPos < (int)($width / 2)) {
                    $cutPos = $width;
                }

                $result[] = rtrim(substr($plain, 0, $cutPos));
                $plain = ltrim(substr($plain, $cutPos));
            }

            if ($prefix !== '' && !empty($result)) {
                $result[0] = $prefix . $result[0];
            }

            return $result;
        };
        $fmtAmount = function($n) {
            return number_format((float)$n, 0, ',', '.');
        };
        $hr = str_repeat('-', $W) . "\n";

        // ====== HEADER ======
        $printer->initialize();
        $printer->setJustification($PrinterClass::JUSTIFY_CENTER);

        // Logo TM-U220: pilih file paling aman lalu preprocess dulu agar tidak rusak.
        $logoSourcePath = $this->_resolve_receipt_logo_path();
        $logoPrintPath = null;
        if (!empty($logoSourcePath)) {
            // Perkecil logo agar tidak terpotong di area cetak TM-U220.
            $logoPrintPath = $this->_prepare_tmu220_logo_image($logoSourcePath, 180);
            if (empty($logoPrintPath)) $logoPrintPath = $logoSourcePath;

            try {
                $img = $ImgClass::load($logoPrintPath, false);
                if (method_exists($printer, 'bitImageColumnFormat')) {
                    // Kompatibilitas TM-U220: pakai mode ESC * m=0 (8-dot single density).
                    // Default IMG_DEFAULT (m=33) sering bikin output logo jadi karakter acak.
                    $imgModeCompat = 3; // IMG_DOUBLE_WIDTH | IMG_DOUBLE_HEIGHT -> densityCode 0
                    if (defined($PrinterClass . '::IMG_DOUBLE_WIDTH') && defined($PrinterClass . '::IMG_DOUBLE_HEIGHT')) {
                        $imgModeCompat = constant($PrinterClass . '::IMG_DOUBLE_WIDTH') | constant($PrinterClass . '::IMG_DOUBLE_HEIGHT');
                    }
                    $printer->bitImageColumnFormat($img, $imgModeCompat);
                } elseif (method_exists($printer, 'bitImage')) {
                    $printer->bitImage($img);
                }
                $printer->feed(1);
            } catch (Exception $e) {
                // kalau logo gagal, lanjut cetak teks
            }
        }
        if (!empty($logoPrintPath) && $logoPrintPath !== $logoSourcePath && is_file($logoPrintPath)) {
            @unlink($logoPrintPath);
        }

        $info = info();
        $storeTitle = isset($this->info['title']) ? $this->info['title'] : ($info['title'] ?? 'TOKO');
        $storeAddress = isset($this->info['deskripsi']) ? strip_word_html(base64_decode($this->info['deskripsi'])) : ($info['alamat'] ?? '');
        $storePhone = isset($this->info['phone']) ? $this->info['phone'] : (!empty($info['telp']) ? $info['telp'] : '');

        $printer->setEmphasis(true);
        $printer->text($toPrintable($storeTitle) . "\n");
        $printer->setEmphasis(false);
        if (!empty($storeAddress)) $printer->text($toPrintable($storeAddress) . "\n");
        if (!empty($storePhone)) $printer->text("Telp: " . $toPrintable($storePhone) . "\n");

        $printer->text($hr);

        $konsumen = $this->model_app->view_where('konsumen', ['id' => $row['id_konsumen']])->row_array();
        $namaPelanggan = $konsumen ? $konsumen['nama'] : 'Umum';
        $cek = cekKonsumen($row['id_konsumen']);
        $panggilan = isset($cek['panggilan']) ? trim((string)$cek['panggilan']) : '';

        $printer->setJustification($PrinterClass::JUSTIFY_LEFT);
        $printer->text($line('No. Invoice', ': ' . $row['id_transaksi']) . "\n");
        $printer->text($line('Tanggal', ': ' . tgl_indo($row['tgl_trx'])) . "\n");
        $printer->text($line('Jam', ': ' . invoice_order_time($row)) . "\n");
        $printer->text($line('Kasir', ': ' . ($marketing['nama_lengkap'] ?? 'Admin')) . "\n");
        $printer->text($line('Pelanggan', ': ' . trim($panggilan . ' ' . $namaPelanggan)) . "\n");
        $printer->text($hr);

        // ====== ITEM LIST ======
        $printer->text($line('Item', 'Total') . "\n");
        $printer->text($hr);

        $subtotal = 0;
        foreach ($detail as $d) {
            $qty = (int)($d['jumlah'] ?? 1);
            $harga = (float)($d['harga'] ?? 0);
            $disc = (float)($d['diskon'] ?? 0);
            $sub = ($qty * $harga);
            if ($disc > 0) $sub = $sub - ($sub * ($disc / 100));
            $subtotal += $sub;

            $nama = !empty($d['nbahan']) ? $d['nbahan'] : (!empty($d['title']) ? $d['title'] : 'Item');
            $discText = rtrim(rtrim(number_format($disc, 2, '.', ''), '0'), '.');
            $hargaLine = "  {$qty} x " . $fmtAmount($harga) . ($disc > 0 ? " (-{$discText}%)" : '');

            $printer->text($line($nama, $fmtAmount($sub)) . "\n");
            $printer->text($line($hargaLine) . "\n");

            if (!empty($d['ukuran'])) {
                $printer->text($line('  Ukuran: ' . $d['ukuran']) . "\n");
            }

            if (!empty($d['keterangan'])) {
                $ketLines = $wrap($d['keterangan'], '  Ket: ');
                foreach ($ketLines as $ketLine) {
                    $printer->text($line($ketLine) . "\n");
                }
            }

            $finishingData = [];
            if (!empty($d['detail'])) {
                $decoded = json_decode($d['detail']);
                if (is_object($decoded) && isset($decoded->data) && is_array($decoded->data)) {
                    $finishingData = $decoded->data;
                }
            }
            if (!empty($finishingData)) {
                $printer->text($line('  Catatan:') . "\n");
                foreach ($finishingData as $fn) {
                    $t = isset($fn->title) ? trim((string)$fn->title) : '';
                    $i = isset($fn->isi) ? trim((string)$fn->isi) : '';
                    $noteLine = trim($t . ($i !== '' ? ' ' . $i : ''));
                    if ($noteLine === '') continue;
                    $noteLines = $wrap($noteLine, '    - ');
                    foreach ($noteLines as $nline) {
                        $printer->text($line($nline) . "\n");
                    }
                }
            }

            $printer->text($hr);
        }

        // ====== TOTAL ======
        $printer->setEmphasis(true);
        $printer->text($line('TOTAL', $fmtAmount($subtotal)) . "\n");
        $printer->setEmphasis(false);
        $printer->text($line('BAYAR', $fmtAmount($totalBayar)) . "\n");
        $selisih = $totalBayar - $subtotal;
        if ($selisih >= 0) {
            $printer->text($line('KEMBALI', $fmtAmount($selisih)) . "\n");
        } else {
            $printer->text($line('SISA', $fmtAmount(abs($selisih))) . "\n");
        }

        $printer->text($hr);

        // ====== FOOTER ======
        $printer->setJustification($PrinterClass::JUSTIFY_CENTER);
        $printer->text("Terima kasih\n");
        $printer->text("Barang Yang Sudah Dibeli\n");
        $printer->text("Tidak Dapat Di Batalkan !\n");
        $printer->text("Barang tidak di ambil lebih dari 1 bulan\n");
        $printer->text("BUKAN TANGGUNG JAWAB KAMI!!!\n");
        $printer->feed(2);

        // Auto cut
        if ($autoCut === 1 && method_exists($printer, 'cut')) {
            try { $printer->cut(); } catch (Exception $e) {}
        }

        $printer->close();

        $raw = @file_get_contents($tmpFile);
        @unlink($tmpFile);
        if ($raw === false || $raw === '') throw new Exception('RAW kosong. Cek permission folder tmp atau proses build.');
        return base64_encode($raw);

    } catch (Exception $e) {
        @unlink($tmpFile);
        try { $printer->close(); } catch (Exception $x) {}
        throw $e;
    }
}

        
    }                                                            
