Files
2026-09-12 01:45:03 +07:00

74 lines
8.8 KiB
PHP

<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Receivables extends MY_Controller
{
public function __construct(){parent::__construct();$this->load->library(array('ReceivableService','ReceivableAdjustmentsService','SalesService'));}
private function uid(){return(int)$this->session->userdata('user_id');}
private function roleId(){$id=(int)$this->session->userdata('role_id');if(!$id){$u=$this->db->select('role_id')->get_where('users',array('id'=>$this->uid()))->row();$id=$u?(int)$u->role_id:0;}return$id;}
private function company(){return(int)$this->companycontext->id();}
public function index()
{
$focusId=(int)$this->input->get('invoice_id');$focus=null;
if($focusId)$focus=$this->db->select('i.id,i.customer_id,i.no_invoice,i.sisa_piutang,c.nama customer_name')->from('invoices i')->join('customers c','c.id=i.customer_id')->where(array('i.id'=>$focusId,'i.company_id'=>$this->company(),'i.workflow_status'=>'posted'))->where('i.sisa_piutang >',0)->get()->row();
$data=array('active_menu'=>'receivables','aging'=>$this->receivableservice->aging($this->input->get('as_of')?:date('Y-m-d')),'focus_invoice'=>$focus,'can_post'=>is_master_admin_user()||check_permission('invoices','can_post'),'summary'=>$this->salesservice->dashboard());
$this->load->view('partials/header',$data);$this->load->view('receivables/index',$data);$this->load->view('partials/footer',$data);
}
public function lookup_customers()
{
$q=trim((string)$this->input->get('q'));$page=max(1,(int)$this->input->get('page'));$limit=20;
$this->db->select('id,nama text,nama')->from('customers')->where(array('company_id'=>$this->company()));if($q!=='')$this->db->like('nama',$q);$rows=$this->db->order_by('nama')->limit($limit+1,($page-1)*$limit)->get()->result();$more=count($rows)>$limit;if($more)array_pop($rows);
return json_response(true,'Customer ditemukan.',array('results'=>$rows,'pagination'=>array('more'=>$more)));
}
public function lookup_cash_accounts()
{
$q=trim((string)$this->input->get('q'));$page=max(1,(int)$this->input->get('page'));$limit=20;
$this->db->select("a.id,CONCAT(a.kode_akun,' - ',a.nama_akun,' (',IF(a.tipe='asset','Aktiva','Passiva'),')') text",false)->from('accounts a')->where(array('a.company_id'=>$this->company(),'a.is_active'=>1,'a.allow_posting'=>1))->where_in('a.tipe',array('asset','liability'));if(!is_master_admin_user())$this->db->where('a.is_hidden',0);if($q!=='')$this->db->group_start()->like('a.kode_akun',$q)->or_like('a.nama_akun',$q)->group_end();$rows=$this->db->order_by('a.tipe')->order_by('a.kode_akun')->limit($limit+1,($page-1)*$limit)->get()->result();$more=count($rows)>$limit;if($more)array_pop($rows);
return json_response(true,'Akun pelunasan Aktiva/Passiva ditemukan.',array('results'=>$rows,'pagination'=>array('more'=>$more)));
}
public function open_invoices($customerId)
{
$rows=$this->db->select('id,no_invoice,tanggal,jatuh_tempo,total,total_bayar,sisa_piutang,invoice_type')->where(array('company_id'=>$this->company(),'customer_id'=>(int)$customerId,'workflow_status'=>'posted'))->where('sisa_piutang >',0)->order_by('jatuh_tempo')->order_by('id')->get('invoices')->result();
return json_response(true,'Invoice terbuka ditemukan.',array('invoices'=>$rows));
}
public function payment_history($customerId)
{
$rows=$this->db->select("p.id,p.payment_no,p.tanggal,p.jumlah,p.reference_no,p.created_at,u.nama creator_name,GROUP_CONCAT(CONCAT(i.no_invoice,' · Rp ',FORMAT(pa.amount,2,'id_ID')) ORDER BY i.id SEPARATOR '||') allocations",false)->from('payments p')->join('payment_allocations pa','pa.payment_id=p.id','left')->join('invoices i','i.id=pa.invoice_id','left')->join('users u','u.id=p.created_by','left')->where(array('p.company_id'=>$this->company(),'p.customer_id'=>(int)$customerId,'p.status'=>'posted'))->group_by('p.id')->order_by('p.id','DESC')->limit(50)->get()->result();
return json_response(true,'Riwayat pembayaran ditemukan.',array('payments'=>$rows));
}
public function submit_invoice($id){return$this->action(fn()=>$this->salesservice->submitInvoice($id,$this->uid()),'Invoice diajukan untuk approval.');}
public function approve_invoice($id){return$this->action(fn()=>$this->salesservice->approveAndPostInvoice($id,$this->uid(),$this->roleId()),'Invoice disetujui dan otomatis diposting.');}
public function post_invoice($id){return json_response(false,'Posting terpisah dinonaktifkan. Approval terakhir otomatis memposting invoice.',array('redirect'=>base_url('invoices/detail/'.(int)$id)),410);}
public function payment()
{
$saved=array();
try{
$saved=$this->paymentUploads();$sources=json_decode((string)$this->input->post('sources'),true);$allocations=json_decode((string)$this->input->post('allocations_json'),true);
if(!is_array($sources))$sources=array(array('account_id'=>$this->input->post('cash_account_id'),'amount'=>$this->input->post('amount')));
if(!is_array($allocations)){$allocations=array();foreach((array)$this->input->post('allocations')as$id=>$amount)$allocations[(int)$id]=$amount;}
$result=$this->salesservice->recordPayment($this->input->post('customer_id'),$this->input->post('date'),$sources,$allocations,(bool)$this->input->post('allow_advance'),$this->uid(),$this->input->post('reference_no',true),$this->input->post('notes',true),$saved);
return json_response(true,'Pembayaran berhasil diposting.',array('result'=>$result));
}catch(Throwable$e){foreach($saved as$f)if(is_file(FCPATH.$f['stored_name']))@unlink(FCPATH.$f['stored_name']);return business_exception_response($e);}
}
public function reverse_invoice($id){$this->requireAdmin();return$this->action(fn()=>$this->receivableservice->reverseInvoice($id,$this->input->post('date'),$this->input->post('reason',true),$this->uid()),'Invoice berhasil direversal.');}
public function writeoff($id){$this->requireAdmin();return$this->action(fn()=>$this->receivableservice->postWriteoff($id,$this->input->post('date'),$this->input->post('amount'),$this->input->post('reason',true),$this->uid()),'Write-off berhasil diposting.');}
public function credit_note($id){$this->requireAdmin();return$this->action(fn()=>$this->receivableadjustmentsservice->creditNote($id,$this->input->post('date'),$this->input->post('amount'),$this->input->post('reason',true),$this->uid()),'Credit note berhasil diposting.');}
public function refund($advanceId){$this->requireAdmin();return$this->action(fn()=>$this->receivableadjustmentsservice->refund($advanceId,$this->input->post('date'),$this->input->post('amount'),$this->input->post('bank_account_id'),$this->input->post('reason',true),$this->uid()),'Refund berhasil diposting.');}
public function statement($customerId){$from=$this->input->get('from')?:date('Y-01-01');$to=$this->input->get('to')?:date('Y-m-d');$data=$this->receivableservice->statement($customerId,$from,$to);$data+=array('active_menu'=>'receivables','from'=>$from,'to'=>$to);$this->load->view('partials/header',$data);$this->load->view('receivables/statement',$data);$this->load->view('partials/footer',$data);}
public function receipt($paymentId){redirect('sales/receipt_pdf/'.(int)$paymentId);}
private function action($fn,$message){if(strtoupper($this->input->method())!=='POST')return json_response(false,'Metode tidak diizinkan.',array(),405);try{return json_response(true,$message,array('result'=>$fn()));}catch(Throwable$e){return business_exception_response($e);}}
private function paymentUploads()
{
$f=$_FILES['attachments']??null;if(!$f||empty($f['name']))throw new BusinessException('Bukti pembayaran gambar atau PDF wajib diunggah.');$names=is_array($f['name'])?$f['name']:array($f['name']);if(count($names)>10)throw new BusinessException('Maksimal 10 file.');$dir=FCPATH.'uploads/accounting/sales/';if(!is_dir($dir)&&!mkdir($dir,0755,true))throw new BusinessException('Folder lampiran tidak tersedia.');$allowed=array('image/jpeg'=>'jpg','image/png'=>'png','image/webp'=>'webp','image/gif'=>'gif','application/pdf'=>'pdf');$fi=new finfo(FILEINFO_MIME_TYPE);$out=array();foreach($names as$i=>$name){$tmp=is_array($f['tmp_name'])?$f['tmp_name'][$i]:$f['tmp_name'];$size=is_array($f['size'])?$f['size'][$i]:$f['size'];$err=is_array($f['error'])?$f['error'][$i]:$f['error'];$mime=$err===UPLOAD_ERR_OK?$fi->file($tmp):'';if(!isset($allowed[$mime])||$size>5*1024*1024)throw new BusinessException('Bukti hanya menerima JPG, PNG, WEBP, GIF, atau PDF maksimal 5 MB.');$stored=bin2hex(random_bytes(16)).'.'.$allowed[$mime];if(!move_uploaded_file($tmp,$dir.$stored))throw new BusinessException('Bukti tidak dapat disimpan.');$out[]=array('original_name'=>basename($name),'stored_name'=>'uploads/accounting/sales/'.$stored,'mime_type'=>$mime,'file_size'=>$size,'sha256'=>hash_file('sha256',$dir.$stored));}return$out;
}
}