122 lines
16 KiB
PHP
122 lines
16 KiB
PHP
<?php
|
|
defined('BASEPATH') OR exit('No direct script access allowed');
|
|
|
|
/** Halaman/API Invoice. Seluruh mutasi bisnis dipusatkan di SalesService. */
|
|
class Invoices extends MY_Controller
|
|
{
|
|
public function __construct(){parent::__construct();$this->load->library('SalesService');}
|
|
private function company(){return(int)$this->companycontext->id();}
|
|
private function uid(){return(int)$this->session->userdata('user_id');}
|
|
private function roleId(){return(int)$this->session->userdata('role_id');}
|
|
private function allowed($action){return is_master_admin_user()||check_permission('invoices',$action);}
|
|
private function permissions(){return array('view'=>$this->allowed('can_view'),'create'=>$this->allowed('can_create'),'update'=>$this->allowed('can_update'),'delete'=>$this->allowed('can_delete'),'submit'=>$this->allowed('can_submit')||$this->allowed('can_create'),'approve'=>$this->allowed('can_approve'),'post'=>$this->allowed('can_post'),'export'=>$this->allowed('can_export'));}
|
|
|
|
public function index(){$this->page('invoices/index',array('active_menu'=>'sales_invoice','summary'=>$this->salesservice->dashboard(),'permissions'=>$this->permissions()));}
|
|
public function draft(){redirect('invoices?workflow_status=draft');}
|
|
public function paid(){redirect('invoices?payment_status=paid');}
|
|
|
|
public function create()
|
|
{
|
|
$this->requirePermission('invoices','can_create');
|
|
$this->page('invoices/editor',array('active_menu'=>'sales_invoice','mode'=>'create','invoice'=>null,'permissions'=>$this->permissions(),'source_order'=>(int)$this->input->get('source_order')));
|
|
}
|
|
public function edit($id)
|
|
{
|
|
$this->requirePermission('invoices','can_update');
|
|
try{$invoice=$this->salesservice->invoiceForEditor($id);$this->guardView($invoice);if($invoice->workflow_status!=='draft')redirect('invoices/detail/'.$invoice->id);$this->page('invoices/editor',array('active_menu'=>'sales_invoice','mode'=>'edit','invoice'=>$invoice,'permissions'=>$this->permissions(),'source_order'=>0));}catch(Throwable$e){show_error($e->getMessage(),404,'Invoice tidak ditemukan');}
|
|
}
|
|
public function detail($id)
|
|
{
|
|
try{$invoice=$this->salesservice->invoiceForEditor($id);$this->guardView($invoice);$this->page('invoices/editor',array('active_menu'=>'sales_invoice','mode'=>'detail','invoice'=>$invoice,'permissions'=>$this->permissions(),'source_order'=>0));}catch(Throwable$e){show_error($e->getMessage(),404,'Invoice tidak ditemukan');}
|
|
}
|
|
|
|
public function data()
|
|
{
|
|
$draw=(int)$this->input->get_post('draw');$start=max(0,(int)$this->input->get_post('start'));$length=max(10,min(100,(int)($this->input->get_post('length')?:25)));$search=$this->input->get_post('search');$term=is_array($search)?trim((string)($search['value']??'')):trim((string)$search);
|
|
$this->invoiceQuery(false,'');$recordsTotal=(int)$this->db->count_all_results();$this->invoiceQuery(true,$term);$recordsFiltered=(int)$this->db->count_all_results();$this->invoiceQuery(true,$term);
|
|
$rows=$this->db->select("i.id,i.no_invoice,i.tanggal,i.jatuh_tempo,i.recognition_policy,i.total,i.total_bayar,i.sisa_piutang,i.status,i.workflow_status,i.delivery_status,i.created_by,c.nama customer_name,u.nama creator_name,(SELECT dx.line_type FROM invoice_details dx WHERE dx.invoice_id=i.id AND dx.company_id=i.company_id ORDER BY dx.id LIMIT 1) invoice_purpose,(SELECT COUNT(*) FROM invoice_details dx WHERE dx.invoice_id=i.id AND dx.company_id=i.company_id AND dx.line_type='inventory') inventory_line_count,(SELECT COUNT(*) FROM invoice_details dx WHERE dx.invoice_id=i.id AND dx.company_id=i.company_id AND dx.line_type='inventory' AND dx.delivery_line_id IS NULL) pending_delivery_count",false)->order_by('i.id','DESC')->limit($length,$start)->get()->result();
|
|
return json_response(true,'Daftar invoice ditemukan.',array('draw'=>$draw,'recordsTotal'=>$recordsTotal,'recordsFiltered'=>$recordsFiltered,'data'=>$rows));
|
|
}
|
|
private function invoiceQuery($filters,$term)
|
|
{
|
|
$this->db->from('invoices i')->join('customers c','c.id=i.customer_id')->join('users u','u.id=i.created_by','left')->where('i.company_id',$this->company())->where('i.deleted_at IS NULL',null,false);
|
|
if(!is_master_admin_user())$this->db->group_start()->where('i.workflow_status !=','draft')->or_where('i.created_by',$this->uid())->group_end();
|
|
if(!$filters)return;$workflow=trim((string)$this->input->get_post('workflow_status'));$payment=trim((string)$this->input->get_post('payment_status'));$dueStatus=trim((string)$this->input->get_post('due_status'));$from=trim((string)$this->input->get_post('date_from'));$to=trim((string)$this->input->get_post('date_to'));
|
|
if($workflow!=='')$this->db->where('i.workflow_status',$workflow);if($payment!=='')$this->db->where('i.status',$payment);if($dueStatus==='overdue')$this->db->where('i.jatuh_tempo <',date('Y-m-d'))->where('i.sisa_piutang >',0);elseif($dueStatus==='not_due')$this->db->where('i.jatuh_tempo >=',date('Y-m-d'))->where('i.sisa_piutang >',0);if($from!=='')$this->db->where('i.tanggal >=',$from);if($to!=='')$this->db->where('i.tanggal <=',$to);if($term!=='')$this->db->group_start()->like('i.no_invoice',$term)->or_like('c.nama',$term)->group_end();
|
|
}
|
|
|
|
public function lines($id)
|
|
{
|
|
try{$invoice=$this->salesservice->invoiceForEditor($id);$this->guardView($invoice);$search=$this->input->get_post('search');$term=is_array($search)?($search['value']??''):$search;$result=$this->salesservice->invoiceLinesPage($id,$this->input->get_post('start'),$this->input->get_post('length')?:25,$term);return json_response(true,'Item invoice ditemukan.',array('draw'=>(int)$this->input->get_post('draw'),'recordsTotal'=>$result['recordsTotal'],'recordsFiltered'=>$result['recordsFiltered'],'data'=>$result['rows'],'summary'=>$result['summary'],'invoice'=>$result['invoice']));}catch(Throwable$e){return business_exception_response($e);}
|
|
}
|
|
|
|
public function lookup_customers(){return$this->selectLookup('customers','nama',array('nama','alamat'),'Customer ditemukan.');}
|
|
public function lookup_accounts()
|
|
{
|
|
$q=trim((string)$this->input->get('q'));$scope=trim((string)$this->input->get('scope'));$types=array('revenue');$message='Akun pendapatan ditemukan.';if($scope==='asset'){$types=array('asset');$message='Akun Aktiva ditemukan.';}elseif($scope==='liability'){$types=array('liability');$message='Akun Passiva ditemukan.';}elseif($scope==='settlement'){$types=array('asset','liability');$message='Akun pelunasan ditemukan.';}
|
|
$page=max(1,(int)$this->input->get('page'));$limit=20;$this->db->select("id,CONCAT(kode_akun,' - ',nama_akun) text,tipe",false)->from('accounts')->where('company_id',$this->company())->where_in('tipe',$types)->where(array('is_active'=>1,'allow_posting'=>1));if(!is_master_admin_user())$this->db->where('is_hidden',0);if($q!=='')$this->db->group_start()->like('kode_akun',$q)->or_like('nama_akun',$q)->group_end();$rows=$this->db->order_by('kode_akun')->limit($limit+1,($page-1)*$limit)->get()->result();$more=count($rows)>$limit;if($more)array_pop($rows);return json_response(true,$message,array('results'=>$rows,'pagination'=>array('more'=>$more)));
|
|
}
|
|
public function lookup_items()
|
|
{
|
|
$q=trim((string)$this->input->get('q'));$page=max(1,(int)$this->input->get('page'));$limit=20;$this->db->select("i.id,CONCAT(i.kode_detail,' - ',i.nama_barang) text,i.kode_detail,i.nama_barang,i.harga_jual,i.stok,k.tracking_type,k.unit",false)->from('items i')->join('kode_barang k','k.id=i.kode_id','left')->where(array('i.company_id'=>$this->company(),'i.status'=>'active','i.stok >'=>'0'));if($q!=='')$this->db->group_start()->like('i.kode_detail',$q)->or_like('i.nama_barang',$q)->group_end();$rows=$this->db->order_by('i.nama_barang')->limit($limit+1,($page-1)*$limit)->get()->result();$more=count($rows)>$limit;if($more)array_pop($rows);return json_response(true,'Barang ditemukan.',array('results'=>$rows,'pagination'=>array('more'=>$more)));
|
|
}
|
|
public function lookup_warehouses(){return$this->selectLookup('warehouses','nama',array('nama'),'Gudang ditemukan.');}
|
|
public function lookup_barcodes()
|
|
{
|
|
$q=trim((string)$this->input->get('q'));$item=(int)$this->input->get('item_id');$warehouse=(int)$this->input->get('warehouse_id');$exact=(int)$this->input->get('exact')===1;$page=max(1,(int)$this->input->get('page'));$limit=20;
|
|
if(!$exact&&(!$item||!$warehouse))return json_response(true,'Pilih gudang dan barang terlebih dahulu.',array('results'=>array(),'pagination'=>array('more'=>false)));
|
|
$this->db->select("b.id,CONCAT(b.barcode,IF(NULLIF(b.serial_number,'') IS NULL,'',CONCAT(' / ',b.serial_number)),' - tersedia ',ROUND(b.qty_sisa-b.reserved_qty-COALESCE(b.return_reserved_qty,0),4)) text,b.barcode,b.serial_number,b.item_id,b.warehouse_id,(b.qty_sisa-b.reserved_qty-COALESCE(b.return_reserved_qty,0)) available,i.kode_detail,i.nama_barang,i.harga_jual,k.tracking_type,k.unit,w.nama warehouse_name",false)->from('item_barcodes b')->join('items i','i.id=b.item_id')->join('kode_barang k','k.id=i.kode_id','left')->join('warehouses w','w.id=b.warehouse_id')->where(array('i.company_id'=>$this->company(),'b.status'=>'available'))->where('(b.qty_sisa-b.reserved_qty-COALESCE(b.return_reserved_qty,0)) >',0,false);if($item)$this->db->where('b.item_id',$item);if($warehouse)$this->db->where('b.warehouse_id',$warehouse);if($q!==''){$this->db->group_start();if($exact)$this->db->where('b.barcode',$q)->or_where('b.serial_number',$q);else$this->db->like('b.barcode',$q)->or_like('b.serial_number',$q);$this->db->group_end();}$rows=$this->db->order_by('b.id')->limit($limit+1,($page-1)*$limit)->get()->result();$more=count($rows)>$limit;if($more)array_pop($rows);return json_response(true,'Barcode ditemukan.',array('results'=>$rows,'pagination'=>array('more'=>$more)));
|
|
}
|
|
private function selectLookup($table,$label,array$fields,$message)
|
|
{
|
|
$q=trim((string)$this->input->get('q'));$page=max(1,(int)$this->input->get('page'));$limit=20;$this->db->select('id,'.$label.' text,'.implode(',',$fields))->from($table)->where('company_id',$this->company());if($this->db->field_exists('is_active',$table))$this->db->where('is_active',1);if($q!=='')$this->db->like($label,$q);$rows=$this->db->order_by($label)->limit($limit+1,($page-1)*$limit)->get()->result();$more=count($rows)>$limit;if($more)array_pop($rows);return json_response(true,$message,array('results'=>$rows,'pagination'=>array('more'=>$more)));
|
|
}
|
|
|
|
public function create_header(){return$this->mutation('can_create',function(){return$this->salesservice->createInvoiceHeader($this->input->post(),$this->uid());},'Header invoice berhasil disimpan sebagai Draft.');}
|
|
public function update_header($id){return$this->mutation('can_update',function()use($id){return$this->salesservice->updateInvoiceHeader($id,$this->input->post(),$this->uid());},'Aturan invoice berhasil diperbarui.');}
|
|
public function add_line($id){return$this->mutation('can_update',function()use($id){$row=$this->input->post();$bars=json_decode((string)$this->input->post('barcodes'),true);$row['barcodes']=is_array($bars)?$bars:array();return$this->salesservice->appendInvoiceLine($id,$row,$this->uid());},'Item berhasil ditambahkan ke invoice.');}
|
|
public function add_lines($id)
|
|
{
|
|
return $this->mutation('can_update', function() use ($id) {
|
|
$rows=json_decode((string)$this->input->post('rows'),true);
|
|
if(!is_array($rows)||!$rows)throw new BusinessException('Tambahkan minimal satu barang, jasa, atau tabungan.');
|
|
if(count($rows)>100)throw new BusinessException('Maksimal 100 item dalam satu kali penambahan.');
|
|
|
|
$serviceDate=trim((string)$this->input->post('service_date'));
|
|
$batchKey=trim((string)$this->input->post('idempotency_key'));
|
|
foreach($rows as $index=>&$row){
|
|
if(!is_array($row))throw new BusinessException('Format item ke-'.($index+1).' tidak valid.');
|
|
$row['service_date']=$serviceDate;
|
|
$row['specification']='';
|
|
$row['barcodes']=isset($row['barcodes'])&&is_array($row['barcodes'])?$row['barcodes']:array();
|
|
$row['idempotency_key']=$batchKey!==''?$batchKey.'-'.($index+1):'';
|
|
}
|
|
unset($row);
|
|
|
|
return $this->salesservice->appendInvoiceLinesBatch(
|
|
$id,
|
|
$rows,
|
|
$this->input->post('batch_discount'),
|
|
$this->uid()
|
|
);
|
|
}, 'Semua item berhasil ditambahkan ke invoice.');
|
|
}
|
|
public function remove_line($invoiceId,$lineId){return$this->mutation('can_update',function()use($invoiceId,$lineId){return$this->salesservice->removeInvoiceLine($invoiceId,$lineId,$this->uid());},'Item berhasil dihapus, reservasi dilepas, dan jurnal awal dibalik.');}
|
|
public function update_line($invoiceId,$lineId){return$this->mutation('can_update',function()use($invoiceId,$lineId){return$this->salesservice->updateNonInventoryLine($invoiceId,$lineId,$this->input->post(),$this->uid());},'Item nonbarang berhasil diperbarui.');}
|
|
public function generate_delivery($invoiceId){return$this->mutation('can_update',function()use($invoiceId){return$this->salesservice->generateDelivery($invoiceId,$this->uid());},'Surat Jalan berhasil dibuat sebagai Draft.');}
|
|
public function submit($id){return$this->mutation('can_submit',function()use($id){return$this->salesservice->submitInvoice($id,$this->uid());},'Invoice berhasil diajukan.');}
|
|
public function approve($id){return$this->mutation('can_approve',function()use($id){return$this->salesservice->approveAndPostInvoice($id,$this->uid(),$this->roleId());},'Invoice disetujui dan otomatis diposting.');}
|
|
public function reject($id){return$this->mutation('can_approve',function()use($id){return$this->salesservice->rejectInvoice($id,$this->input->post('reason',true),$this->uid(),$this->roleId());},'Invoice ditolak dan dikembalikan ke Draft.');}
|
|
public function delete_draft($id){return$this->mutation('can_delete',function()use($id){return$this->salesservice->deleteDraftInvoice($id,$this->uid(),is_master_admin_user());},'Draft invoice dibatalkan dan seluruh reservasi dilepas.');}
|
|
public function pdf($id)
|
|
{
|
|
$this->requirePermission('invoices','can_export');$this->guardView($this->salesservice->invoiceForEditor($id));$data=$this->salesservice->invoiceDetail($id);$data['pdf_company']=$this->db->get_where('companies',array('id'=>$this->company()))->row();require_once FCPATH.'vendor/autoload.php';$html=$this->load->view('sales/invoice_pdf',$data,true);$pdf=new Dompdf\Dompdf();$pdf->loadHtml($html);$pdf->setPaper('A4','portrait');$pdf->render();$pdf->stream('Invoice-'.$data['invoice']->no_invoice.'.pdf',array('Attachment'=>false));
|
|
}
|
|
private function mutation($permission,callable$callback,$message)
|
|
{
|
|
if(strtoupper($this->input->method())!=='POST')return json_response(false,'Metode tidak diizinkan.',array(),405);if(!$this->allowed($permission)&&!($permission==='can_submit'&&$this->allowed('can_create')))return json_response(false,'Anda tidak memiliki izin untuk tindakan ini.',array(),403);try{return json_response(true,$message,array('result'=>$callback()));}catch(Throwable$e){return business_exception_response($e);}
|
|
}
|
|
private function guardView($invoice){if($invoice->workflow_status==='draft'&&!is_master_admin_user()&&(int)$invoice->created_by!==$this->uid())throw new BusinessException('Draft invoice hanya dapat dilihat oleh pembuatnya.');}
|
|
private function page($view,array$data){$this->load->view('partials/header',$data);$this->load->view($view,$data);$this->load->view('partials/footer',$data);}
|
|
}
|