load->database();$this->load->library(array('CompanyContext','MultiCurrencyService','AuditService'));} public function index() { $companyId=(int)$this->companycontext->id(); $d=array('active_menu'=>'companies'); $d['active_company']=$this->db->get_where('companies',array('id'=>$companyId))->row(); $d['companies']=$this->db->order_by('name')->get('companies')->result(); $d['currencies']=$this->db->order_by('code')->get('currencies')->result(); $d['active_currencies']=$this->db->where('is_active',1)->order_by('code')->get('currencies')->result(); $d['bank_accounts']=$this->db->table_exists('company_bank_accounts')?$this->db->select('b.*,a.kode_akun,a.nama_akun')->from('company_bank_accounts b')->join('accounts a','a.id=b.gl_account_id AND a.company_id=b.company_id','left')->where('b.company_id',$companyId)->order_by('b.is_primary','DESC')->order_by('b.id')->get()->result():array(); $d['bank_gl_accounts']=$this->db->where(array('company_id'=>$companyId,'tipe'=>'asset','is_active'=>1,'allow_posting'=>1))->order_by('kode_akun')->get('accounts')->result(); $d['rates']=$this->db->select('r.*,c.name company_name')->from('exchange_rates r')->join('companies c','c.id=r.company_id')->where('r.company_id',$companyId)->order_by('rate_date','DESC')->limit(100)->get()->result(); $this->load->view('partials/header',$d);$this->load->view('companies/index',$d);$this->load->view('partials/footer',$d); } public function switch_company($id){$this->companycontext->assertAccess($id);$this->session->set_userdata('company_id',(int)$id);redirect('companies');} public function update() { $this->postOnly();$id=(int)$this->companycontext->id();$company=$this->db->get_where('companies',array('id'=>$id))->row();if(!$company)show_404(); try{ $name=trim((string)$this->input->post('name',true));$currency=strtoupper(trim((string)$this->input->post('base_currency',true)));if($name==='')throw new RuntimeException('Nama perusahaan wajib diisi.');if(!$this->db->get_where('currencies',array('code'=>$currency,'is_active'=>1))->row())throw new RuntimeException('Mata uang dasar tidak valid.'); $logo=$this->uploadLogo((string)($company->logo_path??'')); $this->db->where('id',$id)->update('companies',array('name'=>$name,'legal_name'=>trim((string)$this->input->post('legal_name',true)),'tax_number'=>trim((string)$this->input->post('tax_number',true)),'address'=>trim((string)$this->input->post('address',true)),'phone'=>trim((string)$this->input->post('phone',true)),'email'=>trim((string)$this->input->post('email',true)),'logo_path'=>$logo,'base_currency'=>$currency)); $this->session->set_flashdata('success','Company Setting berhasil diperbarui.'); }catch(Throwable$e){$this->session->set_flashdata('error',$e->getMessage());} redirect('companies'); } public function save() { $this->postOnly();try{$code=strtoupper(trim((string)$this->input->post('code',true)));$name=trim((string)$this->input->post('name',true));$currency=strtoupper(trim((string)$this->input->post('base_currency',true)));if($code===''||$name==='')throw new RuntimeException('Kode dan nama perusahaan wajib diisi.');if($this->db->get_where('companies',array('company_code'=>$code))->row())throw new RuntimeException('Kode perusahaan sudah digunakan.');$this->db->insert('companies',array('company_code'=>$code,'name'=>$name,'legal_name'=>trim((string)$this->input->post('legal_name',true)),'tax_number'=>trim((string)$this->input->post('tax_number',true)),'address'=>trim((string)$this->input->post('address',true)),'phone'=>trim((string)$this->input->post('phone',true)),'email'=>trim((string)$this->input->post('email',true)),'base_currency'=>$currency?:'IDR','is_active'=>1,'created_at'=>date('Y-m-d H:i:s')));$id=(int)$this->db->insert_id();$this->db->insert('user_companies',array('user_id'=>$this->session->userdata('user_id'),'company_id'=>$id,'is_default'=>0,'can_consolidate'=>1));$this->session->set_flashdata('success','Perusahaan baru berhasil ditambahkan.');}catch(Throwable$e){$this->session->set_flashdata('error',$e->getMessage());}redirect('companies'); } public function save_bank() { $this->postOnly();$company=(int)$this->companycontext->id();$transactionStarted=false; try{ $id=(int)$this->input->post('id');$type=(string)$this->input->post('account_type',true);$opened=(string)$this->input->post('opened_at',true);$maturity=(string)$this->input->post('maturity_date',true);$interest=max(0,round((float)$this->input->post('interest_rate'),4));$gl=(int)$this->input->post('gl_account_id'); if(!in_array($type,array('savings','deposit'),true))throw new RuntimeException('Jenis rekening tidak valid.'); if(!$this->validDate($opened))throw new RuntimeException('Tanggal pembukaan rekening wajib diisi dengan benar.'); if($type==='deposit'&&!$this->validDate($maturity))throw new RuntimeException('Tanggal jatuh tempo wajib untuk rekening Deposito.'); if($type==='savings')$maturity=null; if($maturity!==null&&$maturity<$opened)throw new RuntimeException('Tanggal jatuh tempo tidak boleh sebelum tanggal pembukaan.'); $coa=$this->db->get_where('accounts',array('id'=>$gl,'company_id'=>$company,'tipe'=>'asset','is_active'=>1,'allow_posting'=>1))->row();if(!$coa)throw new RuntimeException('Akun COA bank tidak valid atau bukan akun Aktiva aktif.'); $row=array('company_id'=>$company,'bank_name'=>trim((string)$this->input->post('bank_name',true)),'account_number'=>trim((string)$this->input->post('account_number',true)),'account_holder'=>trim((string)$this->input->post('account_holder',true)),'product_name'=>trim((string)$this->input->post('product_name',true)),'account_type'=>$type,'interest_rate'=>$interest,'opened_at'=>$opened,'maturity_date'=>$maturity,'currency'=>strtoupper(trim((string)$this->input->post('currency',true))),'gl_account_id'=>$gl,'is_primary'=>$this->input->post('is_primary')?1:0,'show_on_invoice'=>$this->input->post('show_on_invoice')?1:0,'updated_at'=>date('Y-m-d H:i:s')); if($row['bank_name']===''||$row['account_number']===''||$row['account_holder']===''||$row['product_name']==='')throw new RuntimeException('Nama bank, nomor rekening, pemilik, dan produk bank wajib diisi.'); if(!$this->db->get_where('currencies',array('code'=>$row['currency'],'is_active'=>1))->row())throw new RuntimeException('Mata uang rekening tidak valid.'); $this->db->where(array('company_id'=>$company,'account_number'=>$row['account_number']));if($id)$this->db->where('id !=',$id);if($this->db->count_all_results('company_bank_accounts'))throw new RuntimeException('Nomor rekening sudah terdaftar pada perusahaan ini.'); $before=null;$this->db->trans_begin();$transactionStarted=true; if($row['is_primary'])$this->db->where('company_id',$company)->update('company_bank_accounts',array('is_primary'=>0,'updated_at'=>date('Y-m-d H:i:s'))); if($id){$before=$this->db->get_where('company_bank_accounts',array('id'=>$id,'company_id'=>$company))->row();if(!$before)throw new RuntimeException('Rekening perusahaan tidak ditemukan.');$this->db->where(array('id'=>$id,'company_id'=>$company))->update('company_bank_accounts',$row);$bankId=$id;}else{$row['is_active']=1;$row['created_at']=date('Y-m-d H:i:s');$this->db->insert('company_bank_accounts',$row);$bankId=(int)$this->db->insert_id();} $after=$this->db->get_where('company_bank_accounts',array('id'=>$bankId,'company_id'=>$company))->row();$this->auditservice->record('company','company_bank_account',$bankId,$id?'update':'create',$before,$after,(int)$this->session->userdata('user_id'));if(!$this->db->trans_status())throw new RuntimeException('Rekening perusahaan gagal disimpan.');$this->db->trans_commit();$transactionStarted=false;$this->session->set_flashdata('success','Rekening perusahaan berhasil disimpan.'); }catch(Throwable$e){if($transactionStarted)$this->db->trans_rollback();$this->session->set_flashdata('error',$e->getMessage());} redirect('companies#companyBanks'); } public function toggle_bank($id) { $this->postOnly();$company=(int)$this->companycontext->id();$this->db->trans_begin(); try{$bank=$this->db->query('SELECT * FROM company_bank_accounts WHERE id=? AND company_id=? FOR UPDATE',array((int)$id,$company))->row();if(!$bank)throw new RuntimeException('Rekening perusahaan tidak ditemukan.');$update=array('is_active'=>$bank->is_active?0:1,'is_primary'=>0,'updated_at'=>date('Y-m-d H:i:s'));$this->db->where(array('id'=>$bank->id,'company_id'=>$company))->update('company_bank_accounts',$update);$after=$this->db->get_where('company_bank_accounts',array('id'=>$bank->id,'company_id'=>$company))->row();$this->auditservice->record('company','company_bank_account',$bank->id,'change_status',$bank,$after,(int)$this->session->userdata('user_id'));if(!$this->db->trans_status())throw new RuntimeException('Status rekening perusahaan gagal diperbarui.');$this->db->trans_commit();$this->session->set_flashdata('success','Status rekening perusahaan diperbarui.');}catch(Throwable$e){$this->db->trans_rollback();$this->session->set_flashdata('error',$e->getMessage());}redirect('companies#companyBanks'); } public function bank_export() { $company=(int)$this->companycontext->id();$this->db->select('b.bank_name,b.account_number,b.account_holder,b.product_name,b.account_type,b.interest_rate,b.opened_at,b.maturity_date,b.currency,b.is_active,a.kode_akun,a.nama_akun')->from('company_bank_accounts b')->join('accounts a','a.id=b.gl_account_id AND a.company_id=b.company_id','left')->where('b.company_id',$company);$bank=trim((string)$this->input->get('bank',true));$type=trim((string)$this->input->get('type',true));$status=$this->input->get('status');$from=trim((string)$this->input->get('maturity_from',true));$to=trim((string)$this->input->get('maturity_to',true));if($bank!=='')$this->db->like('b.bank_name',$bank);if(in_array($type,array('savings','deposit'),true))$this->db->where('b.account_type',$type);if($status==='0'||$status==='1')$this->db->where('b.is_active',(int)$status);if($this->validDate($from))$this->db->where('b.maturity_date >=',$from);if($this->validDate($to))$this->db->where('b.maturity_date <=',$to);$rows=$this->db->order_by('b.bank_name')->order_by('b.account_number')->get()->result_array();$this->output->set_content_type('text/csv')->set_header('Content-Disposition: attachment; filename="rekening-perusahaan-'.date('Ymd').'.csv"');$out=fopen('php://output','w');fputcsv($out,array('Nama Bank','Nomor Rekening','Pemilik','Produk','Jenis','Bunga (%)','Tanggal Pembukaan','Jatuh Tempo','Mata Uang','Status','Kode COA','Nama COA'));foreach($rows as$row){$row['account_type']=$row['account_type']==='deposit'?'Deposito':'Tabungan';$row['is_active']=$row['is_active']?'Aktif':'Nonaktif';fputcsv($out,array_values($row));}fclose($out); } public function save_currency() { $this->postOnly();try{$code=strtoupper(trim((string)$this->input->post('code',true)));$name=trim((string)$this->input->post('name',true));$symbol=trim((string)$this->input->post('symbol',true));$decimals=max(0,min(6,(int)$this->input->post('decimal_places')));if(!preg_match('/^[A-Z]{3}$/',$code)||$name==='')throw new RuntimeException('Kode mata uang harus tiga huruf dan nama wajib diisi.');$row=array('name'=>$name,'symbol'=>$symbol,'decimal_places'=>$decimals,'is_active'=>$this->input->post('is_active')?1:0);if($this->db->get_where('currencies',array('code'=>$code))->row())$this->db->where('code',$code)->update('currencies',$row);else{$row['code']=$code;$this->db->insert('currencies',$row);}$this->session->set_flashdata('success','Pengaturan mata uang berhasil disimpan.');}catch(Throwable$e){$this->session->set_flashdata('error',$e->getMessage());}redirect('companies'); } public function rate() { $this->postOnly();try{$rate=(float)$this->input->post('rate');$from=strtoupper(trim((string)$this->input->post('from_currency',true)));$to=strtoupper(trim((string)$this->input->post('to_currency',true)));if($rate<=0||$from===''||$to===''||$from===$to)throw new RuntimeException('Pasangan mata uang dan nilai kurs tidak valid.');$this->db->insert('exchange_rates',array('company_id'=>$this->companycontext->id(),'rate_date'=>$this->input->post('date'),'from_currency'=>$from,'to_currency'=>$to,'rate'=>$rate,'source'=>$this->input->post('source',true),'created_by'=>$this->session->userdata('user_id'),'created_at'=>date('Y-m-d H:i:s')));$this->session->set_flashdata('success','Kurs berhasil disimpan.');}catch(Throwable$e){$this->session->set_flashdata('error',$e->getMessage());}redirect('companies'); } public function revalue(){try{$this->multicurrencyservice->revalue($this->input->post('date'),$this->session->userdata('user_id'));$this->session->set_flashdata('success','Revaluasi kurs berhasil diposting.');}catch(Throwable$e){$this->session->set_flashdata('error',$e->getMessage());}redirect('companies');} private function uploadLogo($current) { if(empty($_FILES['logo'])||$_FILES['logo']['error']===UPLOAD_ERR_NO_FILE)return$current;if($_FILES['logo']['error']!==UPLOAD_ERR_OK)throw new RuntimeException('Logo perusahaan gagal diunggah.');if((int)$_FILES['logo']['size']>2*1024*1024)throw new RuntimeException('Ukuran logo maksimal 2 MB.');$mime=(new finfo(FILEINFO_MIME_TYPE))->file($_FILES['logo']['tmp_name']);$allowed=array('image/jpeg'=>'jpg','image/png'=>'png','image/webp'=>'webp');if(!isset($allowed[$mime]))throw new RuntimeException('Logo hanya menerima JPG, PNG, atau WEBP.');$dir=FCPATH.'uploads/accounting/company/';if(!is_dir($dir)&&!mkdir($dir,0755,true))throw new RuntimeException('Folder logo perusahaan tidak dapat dibuat.');$relative='uploads/accounting/company/'.bin2hex(random_bytes(16)).'.'.$allowed[$mime];if(!move_uploaded_file($_FILES['logo']['tmp_name'],FCPATH.$relative))throw new RuntimeException('Logo perusahaan tidak dapat disimpan.');return$relative; } private function postOnly(){if(strtoupper($this->input->method())!=='POST')show_error('Metode tidak diizinkan.',405);} private function validDate($value){$date=DateTime::createFromFormat('Y-m-d',(string)$value);return$date&&$date->format('Y-m-d')===$value;} }