Update besar apk finance
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
class AccountModel extends CI_Model
|
||||
{
|
||||
public function existingIds(array $ids)
|
||||
{
|
||||
if (empty($ids)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$rows = $this->db
|
||||
->select('id')
|
||||
->where_in('id', array_values(array_unique($ids)))
|
||||
->where('is_active', 1)
|
||||
->where('allow_posting', 1)
|
||||
->get('accounts')
|
||||
->result_array();
|
||||
return array_map('intval', array_column($rows, 'id'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
class FiscalPeriodModel extends CI_Model
|
||||
{
|
||||
public function find($id)
|
||||
{
|
||||
return $this->db->get_where('accounting_periods', array('id' => (int) $id))->row();
|
||||
}
|
||||
|
||||
public function findByDate($date)
|
||||
{
|
||||
return $this->db
|
||||
->where('start_date <=', $date)
|
||||
->where('end_date >=', $date)
|
||||
->limit(1)
|
||||
->get('accounting_periods')
|
||||
->row();
|
||||
}
|
||||
|
||||
public function allWithFiscalYear()
|
||||
{
|
||||
return $this->db
|
||||
->select('accounting_periods.*, fiscal_years.name fiscal_year_name')
|
||||
->from('accounting_periods')
|
||||
->join('fiscal_years', 'fiscal_years.id = accounting_periods.fiscal_year_id')
|
||||
->order_by('accounting_periods.start_date', 'DESC')
|
||||
->get()
|
||||
->result();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
class FixedAssetModel extends CI_Model
|
||||
{
|
||||
public function dashboard($companyId)
|
||||
{
|
||||
$row = $this->db->query("SELECT
|
||||
COUNT(*) total_assets,
|
||||
SUM(workflow_status='capitalized' AND lifecycle_status IN('active','fully_depreciated')) active_assets,
|
||||
COALESCE(SUM(IF(workflow_status='capitalized' AND lifecycle_status NOT IN('disposed','lost','damaged'),nilai_perolehan,0)),0) acquisition_cost,
|
||||
COALESCE(SUM(IF(workflow_status='capitalized' AND lifecycle_status NOT IN('disposed','lost','damaged'),akumulasi_penyusutan,0)),0) accumulated_depreciation,
|
||||
COALESCE(SUM(IF(workflow_status='capitalized' AND lifecycle_status NOT IN('disposed','lost','damaged'),nilai_buku,0)),0) book_value,
|
||||
SUM(lifecycle_status='fully_depreciated') fully_depreciated,
|
||||
SUM(workflow_status IN('submitted','approved')) pending_workflow
|
||||
FROM assets WHERE company_id=?", array((int) $companyId))->row();
|
||||
$row->maintenance_due = (int) $this->db->where('company_id', (int) $companyId)->where('status', 'posted')->where('next_due_date IS NOT NULL', null, false)->where('next_due_date <=', date('Y-m-d', strtotime('+30 days')))->count_all_results('asset_maintenance');
|
||||
$row->open_opnames = (int) $this->db->where('company_id', (int) $companyId)->where_in('status', array('draft','submitted','approved'))->count_all_results('asset_opnames');
|
||||
$row->depreciation_due = (int) $this->db->where('company_id', (int) $companyId)->where('workflow_status', 'capitalized')->where('lifecycle_status', 'active')->where('nilai_buku > nilai_residu', null, false)->count_all_results('assets');
|
||||
return $row;
|
||||
}
|
||||
|
||||
private function registerQuery($companyId, array $filters, $search = '')
|
||||
{
|
||||
$this->db->select('a.id,a.document_no,a.kode_asset,a.nama_asset,a.tanggal_perolehan,a.capitalization_date,a.source_type,a.workflow_status,a.lifecycle_status,a.nilai_perolehan,a.akumulasi_penyusutan,a.nilai_buku,a.nilai_residu,a.masa_manfaat,a.qty,a.qr_code,c.name category_name,l.nama location_name,e.full_name responsible_name,i.kode_detail,b.barcode,b.serial_number');
|
||||
$this->db->from('assets a');
|
||||
$this->db->join('asset_categories c', 'c.id=a.category_id', 'left');
|
||||
$this->db->join('lokasi_asset l', 'l.id=a.lokasi_asset_id', 'left');
|
||||
$this->db->join('k_employees e', 'e.id=a.responsible_employee_id', 'left');
|
||||
$this->db->join('items i', 'i.id=a.item_id', 'left');
|
||||
$this->db->join('item_barcodes b', 'b.id=CAST(a.barcode_id AS UNSIGNED)', 'left', false);
|
||||
$this->db->where('a.company_id', (int) $companyId);
|
||||
foreach (array('category_id','lokasi_asset_id','source_type','workflow_status','lifecycle_status','responsible_employee_id') as $field) {
|
||||
if (!empty($filters[$field])) $this->db->where('a.'.$field, $filters[$field]);
|
||||
}
|
||||
if (!empty($filters['date_from'])) $this->db->where('a.tanggal_perolehan >=', $filters['date_from']);
|
||||
if (!empty($filters['date_to'])) $this->db->where('a.tanggal_perolehan <=', $filters['date_to']);
|
||||
if ($search !== '') {
|
||||
$this->db->group_start()->like('a.document_no', $search)->or_like('a.kode_asset', $search)->or_like('a.nama_asset', $search)->or_like('c.name', $search)->or_like('l.nama', $search)->or_like('e.full_name', $search)->or_like('b.barcode', $search)->or_like('b.serial_number', $search)->group_end();
|
||||
}
|
||||
}
|
||||
|
||||
public function registerPage($companyId, array $filters, $start, $length, $search)
|
||||
{
|
||||
$this->registerQuery($companyId, $filters, $search);
|
||||
$filtered = (int) $this->db->count_all_results('', false);
|
||||
$rows = $this->db->order_by('a.id', 'DESC')->limit((int) $length, (int) $start)->get()->result();
|
||||
$total = (int) $this->db->where('company_id', (int) $companyId)->count_all_results('assets');
|
||||
return array('rows'=>$rows,'total'=>$total,'filtered'=>$filtered);
|
||||
}
|
||||
|
||||
public function detail($id, $companyId)
|
||||
{
|
||||
$asset = $this->db->select('a.*,c.name category_name,c.code category_code,l.nama location_name,l.code location_code,e.full_name responsible_name,i.kode_detail,i.nama_barang,w.nama warehouse_name,bin.code bin_code,b.barcode,b.serial_number,uc.nama creator_name,us.nama submitter_name,ua.nama approver_name,up.nama capitalizer_name,j.no_ref source_journal_no,aj.no_ref acquisition_journal_no')
|
||||
->from('assets a')->join('asset_categories c','c.id=a.category_id','left')->join('lokasi_asset l','l.id=a.lokasi_asset_id','left')->join('k_employees e','e.id=a.responsible_employee_id','left')->join('items i','i.id=a.item_id','left')->join('warehouses w','w.id=a.warehouse_id','left')->join('warehouse_bins bin','bin.id=a.bin_id','left')->join('item_barcodes b','b.id=CAST(a.barcode_id AS UNSIGNED)','left',false)->join('users uc','uc.id=a.created_by','left')->join('users us','us.id=a.submitted_by','left')->join('users ua','ua.id=a.approved_by','left')->join('users up','up.id=a.capitalized_by','left')->join('journals j','j.id=a.source_journal_id','left')->join('journals aj','aj.id=a.acquisition_journal_id','left')->where(array('a.id'=>(int)$id,'a.company_id'=>(int)$companyId))->get()->row();
|
||||
if (!$asset) return null;
|
||||
$asset->events = $this->db->select('ev.*,u.nama user_name,fl.nama from_location,tl.nama to_location,j.no_ref journal_no')->from('asset_events ev')->join('users u','u.id=ev.created_by','left')->join('lokasi_asset fl','fl.id=ev.from_location_id','left')->join('lokasi_asset tl','tl.id=ev.to_location_id','left')->join('journals j','j.id=ev.journal_id','left')->where(array('ev.asset_id'=>$asset->id,'ev.company_id'=>(int)$companyId))->order_by('ev.event_date','DESC')->order_by('ev.id','DESC')->get()->result();
|
||||
$asset->schedule = $this->db->where(array('asset_id'=>$asset->id,'company_id'=>(int)$companyId))->order_by('period','DESC')->get('asset_depreciation_schedule')->result();
|
||||
$asset->maintenance = $this->db->where(array('asset_id'=>$asset->id,'company_id'=>(int)$companyId))->order_by('maintenance_date','DESC')->get('asset_maintenance')->result();
|
||||
$asset->workflow = $this->db->select('h.*,u.nama user_name')->from('asset_workflow_history h')->join('users u','u.id=h.user_id','left')->where(array('h.asset_id'=>$asset->id,'h.company_id'=>(int)$companyId))->order_by('h.id')->get()->result();
|
||||
$asset->responsibilities = $this->db->select('h.*,f.full_name from_name,t.full_name to_name,u.nama creator_name')->from('asset_responsibility_history h')->join('k_employees f','f.id=h.from_employee_id','left')->join('k_employees t','t.id=h.to_employee_id','left')->join('users u','u.id=h.created_by','left')->where(array('h.asset_id'=>$asset->id,'h.company_id'=>(int)$companyId))->order_by('h.effective_date','DESC')->get()->result();
|
||||
$asset->attachments = $this->db->where(array('entity_type'=>'asset','entity_id'=>$asset->id,'company_id'=>(int)$companyId))->order_by('id')->get('transaction_attachments')->result();
|
||||
return $asset;
|
||||
}
|
||||
|
||||
public function categories($companyId, $activeOnly = false)
|
||||
{
|
||||
$this->db->where('company_id', (int) $companyId);
|
||||
if ($activeOnly) $this->db->where('is_active', 1);
|
||||
return $this->db->order_by('name')->get('asset_categories')->result();
|
||||
}
|
||||
|
||||
public function locations($companyId, $activeOnly = false)
|
||||
{
|
||||
$this->db->where('company_id', (int) $companyId);
|
||||
if ($activeOnly) $this->db->where('is_active', 1);
|
||||
return $this->db->order_by('nama')->get('lokasi_asset')->result();
|
||||
}
|
||||
|
||||
public function availableInventory($companyId, $search, $page = 1, $limit = 20)
|
||||
{
|
||||
$offset = (max(1, (int) $page)-1) * (int) $limit;
|
||||
$params = array((int) $companyId);
|
||||
$where = '';
|
||||
if (trim($search) !== '') {
|
||||
$where = ' AND (i.kode_detail LIKE ? OR i.nama_barang LIKE ? OR b.barcode LIKE ? OR b.serial_number LIKE ?)';
|
||||
$needle = '%'.trim($search).'%';
|
||||
array_push($params, $needle, $needle, $needle, $needle);
|
||||
}
|
||||
$params[] = (int) $limit + 1;
|
||||
$params[] = $offset;
|
||||
return $this->db->query("SELECT b.id barcode_id,b.item_id,b.barcode,b.serial_number,b.warehouse_id,b.bin_id,b.batch_id,b.qty_sisa,b.reserved_qty,b.return_reserved_qty,i.kode_detail,i.nama_barang,i.harga_beli,k.tracking_type,k.need_serial_number,w.nama warehouse_name,wb.code bin_code,bt.batch_no,
|
||||
GREATEST(b.qty_sisa-b.reserved_qty-b.return_reserved_qty,0) available_qty,
|
||||
COALESCE((SELECT SUM(IF(il.direction='in',il.value,-il.value))/NULLIF(SUM(IF(il.direction='in',il.qty,-il.qty)),0) FROM inventory_ledger il WHERE il.item_id=b.item_id AND il.warehouse_id=b.warehouse_id),i.harga_beli,0) ledger_unit_cost
|
||||
FROM item_barcodes b JOIN items i ON i.id=b.item_id LEFT JOIN kode_barang k ON k.id=i.kode_id LEFT JOIN warehouses w ON w.id=b.warehouse_id LEFT JOIN warehouse_bins wb ON wb.id=b.bin_id LEFT JOIN inventory_batches bt ON bt.id=b.batch_id
|
||||
WHERE i.company_id=? AND i.status='active' AND b.status='available' AND b.warehouse_id IS NOT NULL AND b.qty_sisa-b.reserved_qty-b.return_reserved_qty>0 {$where}
|
||||
ORDER BY i.nama_barang,b.barcode LIMIT ? OFFSET ?", $params)->result();
|
||||
}
|
||||
|
||||
public function employees($companyId, $search, $page = 1, $limit = 20)
|
||||
{
|
||||
$this->db->select('id,employee_code,full_name')->where(array('company_id'=>(int)$companyId,'is_active'=>1));
|
||||
if (trim($search) !== '') $this->db->group_start()->like('employee_code',$search)->or_like('full_name',$search)->group_end();
|
||||
return $this->db->order_by('full_name')->limit((int)$limit+1,(max(1,(int)$page)-1)*(int)$limit)->get('k_employees')->result();
|
||||
}
|
||||
|
||||
public function accounts($companyId)
|
||||
{
|
||||
return $this->db->select('id,kode_akun,nama_akun,tipe,sub_tipe')
|
||||
->where(array('company_id'=>(int)$companyId,'is_active'=>1,'allow_posting'=>1))
|
||||
->order_by('kode_akun')->get('accounts')->result();
|
||||
}
|
||||
|
||||
public function opnames($companyId)
|
||||
{
|
||||
return $this->db->select('o.*,l.nama location_name,u.nama creator_name,a.nama approver_name')->from('asset_opnames o')->join('lokasi_asset l','l.id=o.location_id','left')->join('users u','u.id=o.created_by','left')->join('users a','a.id=o.approved_by','left')->where('o.company_id',(int)$companyId)->order_by('o.id','DESC')->limit(200)->get()->result();
|
||||
}
|
||||
|
||||
public function opnameDetail($id, $companyId)
|
||||
{
|
||||
$row=$this->db->select('o.*,l.nama location_name,u.nama creator_name,s.nama submitter_name,a.nama approver_name,p.nama poster_name')->from('asset_opnames o')->join('lokasi_asset l','l.id=o.location_id','left')->join('users u','u.id=o.created_by','left')->join('users s','s.id=o.submitted_by','left')->join('users a','a.id=o.approved_by','left')->join('users p','p.id=o.posted_by','left')->where(array('o.id'=>(int)$id,'o.company_id'=>(int)$companyId))->get()->row();
|
||||
if(!$row)return null;
|
||||
$row->lines=$this->db->select('ol.*,a.kode_asset,a.nama_asset,el.nama expected_location,al.nama actual_location')->from('asset_opname_lines ol')->join('assets a','a.id=ol.asset_id')->join('lokasi_asset el','el.id=ol.expected_location_id','left')->join('lokasi_asset al','al.id=ol.actual_location_id','left')->where(array('ol.asset_opname_id'=>$row->id,'ol.company_id'=>(int)$companyId))->order_by('a.kode_asset')->get()->result();
|
||||
$row->attachments=$this->db->where(array('entity_type'=>'asset_opname','entity_id'=>$row->id,'company_id'=>(int)$companyId))->order_by('id')->get('transaction_attachments')->result();
|
||||
return$row;
|
||||
}
|
||||
|
||||
public function reconciliation($companyId, $asOf)
|
||||
{
|
||||
$costRows=$this->db->query("SELECT a.asset_account_id account_id,ac.kode_akun,ac.nama_akun,SUM(a.nilai_perolehan) subledger,COALESCE((SELECT SUM(jd.debit-jd.kredit) FROM journal_details jd JOIN journals j ON j.id=jd.journal_id WHERE j.company_id=? AND j.status='posted' AND j.tanggal<=? AND jd.account_id=a.asset_account_id),0) gl_value FROM assets a LEFT JOIN accounts ac ON ac.id=a.asset_account_id WHERE a.company_id=? AND a.workflow_status='capitalized' AND a.lifecycle_status NOT IN('disposed','lost','damaged') AND a.capitalization_date<=? GROUP BY a.asset_account_id,ac.kode_akun,ac.nama_akun",array((int)$companyId,$asOf,(int)$companyId,$asOf))->result();
|
||||
$accRows=$this->db->query("SELECT a.accumulated_depreciation_account_id account_id,ac.kode_akun,ac.nama_akun,SUM(a.akumulasi_penyusutan) subledger,COALESCE((SELECT SUM(jd.kredit-jd.debit) FROM journal_details jd JOIN journals j ON j.id=jd.journal_id WHERE j.company_id=? AND j.status='posted' AND j.tanggal<=? AND jd.account_id=a.accumulated_depreciation_account_id),0) gl_value FROM assets a LEFT JOIN accounts ac ON ac.id=a.accumulated_depreciation_account_id WHERE a.company_id=? AND a.workflow_status='capitalized' AND a.lifecycle_status NOT IN('disposed','lost','damaged') AND a.capitalization_date<=? GROUP BY a.accumulated_depreciation_account_id,ac.kode_akun,ac.nama_akun",array((int)$companyId,$asOf,(int)$companyId,$asOf))->result();
|
||||
$costSub=$costGl=$accSub=$accGl=0.0;foreach($costRows as$r){$costSub+=(float)$r->subledger;$costGl+=(float)$r->gl_value;$r->difference=(float)$r->subledger-(float)$r->gl_value;}foreach($accRows as$r){$accSub+=(float)$r->subledger;$accGl+=(float)$r->gl_value;$r->difference=(float)$r->subledger-(float)$r->gl_value;}
|
||||
return array('as_of'=>$asOf,'cost_subledger'=>$costSub,'cost_gl'=>$costGl,'cost_difference'=>$costSub-$costGl,'accumulated_subledger'=>$accSub,'accumulated_gl'=>$accGl,'accumulated_difference'=>$accSub-$accGl,'book_value'=>$costSub-$accSub,'status'=>abs($costSub-$costGl)<.01&&abs($accSub-$accGl)<.01?'matched':'variance','cost_details'=>$costRows,'accumulated_details'=>$accRows);
|
||||
}
|
||||
|
||||
public function report($companyId, $type, array $filters)
|
||||
{
|
||||
if ($type === 'depreciation') return $this->db->select('s.period,a.kode_asset,a.nama_asset,s.opening_book_value,s.depreciation_amount,s.closing_book_value,s.status,j.no_ref journal_no')->from('asset_depreciation_schedule s')->join('assets a','a.id=s.asset_id')->join('journals j','j.id=s.journal_id','left')->where('s.company_id',(int)$companyId)->where('s.due_date >=',$filters['date_from'])->where('s.due_date <=',$filters['date_to'])->order_by('s.period','DESC')->order_by('a.kode_asset')->get()->result();
|
||||
if ($type === 'maintenance') return $this->db->select('m.maintenance_no,m.maintenance_date,m.next_due_date,a.kode_asset,a.nama_asset,m.vendor,m.description,m.cost,m.status')->from('asset_maintenance m')->join('assets a','a.id=m.asset_id')->where('m.company_id',(int)$companyId)->where('m.maintenance_date >=',$filters['date_from'])->where('m.maintenance_date <=',$filters['date_to'])->order_by('m.maintenance_date','DESC')->get()->result();
|
||||
if ($type === 'events') return $this->db->select('e.event_no,e.event_date,e.event_type,a.kode_asset,a.nama_asset,e.amount,e.book_value_before,e.book_value_after,e.notes,j.no_ref journal_no')->from('asset_events e')->join('assets a','a.id=e.asset_id')->join('journals j','j.id=e.journal_id','left')->where('e.company_id',(int)$companyId)->where('e.event_date >=',$filters['date_from'])->where('e.event_date <=',$filters['date_to'])->order_by('e.event_date','DESC')->order_by('e.id','DESC')->get()->result();
|
||||
$page=$this->registerPage($companyId,$filters,0,10000,'');return$page['rows'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
class InventoryModel extends CI_Model
|
||||
{
|
||||
public function calculatedStock($itemId)
|
||||
{
|
||||
$row=$this->db->query("SELECT COALESCE(SUM(IF(direction='in',qty,-qty)),0) stok FROM inventory_ledger WHERE item_id=?",array((int)$itemId))->row();
|
||||
return $row?(float)$row->stok:0.0;
|
||||
}
|
||||
|
||||
public function synchronizeStock($itemId)
|
||||
{
|
||||
$stock=$this->calculatedStock($itemId);
|
||||
if(!$this->db->where('id',(int)$itemId)->update('items',array('stok'=>$stock)))throw new RuntimeException('Gagal menyinkronkan cache stok barang.');
|
||||
return $stock;
|
||||
}
|
||||
|
||||
public function synchronizeCompany($companyId)
|
||||
{
|
||||
$items=$this->db->select('id')->where('company_id',(int)$companyId)->get('items')->result();
|
||||
foreach($items as$item)$this->synchronizeStock($item->id);
|
||||
return count($items);
|
||||
}
|
||||
|
||||
public function dashboard($companyId)
|
||||
{
|
||||
$companyId=(int)$companyId;
|
||||
$row=$this->db->query("SELECT COUNT(CASE WHEN i.status='active' THEN 1 END) active_items,COUNT(CASE WHEN i.status='draft' OR EXISTS(SELECT 1 FROM item_barcodes ib WHERE ib.item_id=i.id AND ib.status='pending') THEN 1 END) pending_items,COALESCE(SUM(CASE WHEN x.qty>0 THEN x.value ELSE 0 END),0) inventory_value,COUNT(CASE WHEN x.qty<=i.reorder_point THEN 1 END) reorder_items FROM items i LEFT JOIN(SELECT item_id,SUM(IF(direction='in',qty,-qty)) qty,SUM(IF(direction='in',value,-value)) value FROM inventory_ledger GROUP BY item_id)x ON x.item_id=i.id WHERE i.company_id=?",array($companyId))->row();
|
||||
$row->open_documents=(int)$this->db->where('company_id',$companyId)->where_in('status',array('draft','submitted','approved'))->count_all_results('stock_documents');
|
||||
$row->open_reservations=(int)$this->db->where('company_id',$companyId)->where_in('status',array('active','partially_released'))->count_all_results('stock_reservations');
|
||||
$row->expiring_batches=(int)$this->db->where('company_id',$companyId)->where('is_active',1)->where('expiry_date IS NOT NULL',null,false)->where('expiry_date <=',date('Y-m-d',strtotime('+30 days')))->count_all_results('inventory_batches');
|
||||
return$row;
|
||||
}
|
||||
|
||||
public function recentDocuments($companyId,$limit=8)
|
||||
{
|
||||
return$this->db->select('d.*,wf.nama from_warehouse,wt.nama to_warehouse,u.nama creator_name')->from('stock_documents d')->join('warehouses wf','wf.id=d.from_warehouse_id','left')->join('warehouses wt','wt.id=d.to_warehouse_id','left')->join('users u','u.id=d.created_by','left')->where('d.company_id',(int)$companyId)->order_by('d.id','DESC')->limit((int)$limit)->get()->result();
|
||||
}
|
||||
|
||||
public function actionItems($companyId,$limit=8)
|
||||
{
|
||||
return$this->db->query("SELECT i.id,i.kode_detail,i.nama_barang,i.harga_beli,i.harga_jual,COUNT(DISTINCT CASE WHEN ib.status='pending' THEN ib.id END) pending_barcodes,COUNT(DISTINCT CASE WHEN ib.status='pending' AND (kb.need_serial_number<>'true' OR NULLIF(ib.serial_number,'') IS NOT NULL) THEN ib.id END) ready_barcodes FROM items i LEFT JOIN kode_barang kb ON kb.id=i.kode_id LEFT JOIN item_barcodes ib ON ib.item_id=i.id WHERE i.company_id=? AND EXISTS(SELECT 1 FROM goods_receipt_lines grl WHERE grl.item_id=i.id) AND(i.status='draft' OR ib.status='pending') GROUP BY i.id,i.kode_detail,i.nama_barang,i.harga_beli,i.harga_jual ORDER BY i.id DESC LIMIT ".(int)$limit,array((int)$companyId))->result();
|
||||
}
|
||||
|
||||
public function documentDetail($id,$companyId)
|
||||
{
|
||||
$d=$this->db->select('d.*,c.name company_name,c.company_code,wf.nama from_warehouse,wt.nama to_warehouse,uc.nama creator_name,us.nama submitter_name,ua.nama approver_name,up.nama poster_name,ur.nama rejecter_name')->from('stock_documents d')->join('companies c','c.id=d.company_id','left')->join('warehouses wf','wf.id=d.from_warehouse_id','left')->join('warehouses wt','wt.id=d.to_warehouse_id','left')->join('users uc','uc.id=d.created_by','left')->join('users us','us.id=d.submitted_by','left')->join('users ua','ua.id=d.approved_by','left')->join('users up','up.id=d.posted_by','left')->join('users ur','ur.id=d.rejected_by','left')->where(array('d.id'=>(int)$id,'d.company_id'=>(int)$companyId))->get()->row();
|
||||
if(!$d)return null;
|
||||
$d->lines=$this->db->select('l.*,i.kode_detail,i.nama_barang,k.unit,k.tracking_type,k.need_serial_number,b.barcode,b.serial_number,bt.batch_no,fb.code from_bin,tb.code to_bin')->from('stock_document_lines l')->join('items i','i.id=l.item_id')->join('kode_barang k','k.id=i.kode_id','left')->join('item_barcodes b','b.id=l.barcode_id','left')->join('inventory_batches bt','bt.id=l.batch_id','left')->join('warehouse_bins fb','fb.id=l.from_bin_id','left')->join('warehouse_bins tb','tb.id=l.to_bin_id','left')->where('l.stock_document_id',$d->id)->order_by('l.line_no')->order_by('l.id')->get()->result();
|
||||
$d->history=$this->db->select('h.*,u.nama user_name')->from('stock_document_status_history h')->join('users u','u.id=h.user_id','left')->where('h.stock_document_id',$d->id)->order_by('h.id')->get()->result();
|
||||
$d->attachments=$this->db->where(array('entity_type'=>'stock_document','entity_id'=>$d->id))->order_by('id')->get('transaction_attachments')->result();
|
||||
return$d;
|
||||
}
|
||||
|
||||
public function valuation($companyId,array$filters=array())
|
||||
{
|
||||
$params=array((int)$companyId);$where='';
|
||||
if(!empty($filters['warehouse_id'])){$where.=' AND l.warehouse_id=?';$params[]=(int)$filters['warehouse_id'];}
|
||||
if(!empty($filters['item_id'])){$where.=' AND l.item_id=?';$params[]=(int)$filters['item_id'];}
|
||||
return$this->db->query("SELECT i.id,i.kode_detail,i.nama_barang,l.warehouse_id,w.nama warehouse_name,i.costing_method,i.minimum_stock,i.reorder_point,i.last_movement_at,SUM(IF(l.direction='in',l.qty,-l.qty)) qty,SUM(IF(l.direction='in',l.value,-l.value)) inventory_value FROM inventory_ledger l JOIN items i ON i.id=l.item_id JOIN warehouses w ON w.id=l.warehouse_id WHERE i.company_id=? {$where} GROUP BY i.id,i.kode_detail,i.nama_barang,l.warehouse_id,w.nama,i.costing_method,i.minimum_stock,i.reorder_point,i.last_movement_at ORDER BY i.nama_barang,w.nama",$params)->result();
|
||||
}
|
||||
|
||||
public function reconciliation($companyId,$asOf)
|
||||
{
|
||||
$companyId=(int)$companyId;
|
||||
$ledger=(float)$this->db->query("SELECT COALESCE(SUM(IF(l.direction='in',l.value,-l.value)),0) total FROM inventory_ledger l JOIN items i ON i.id=l.item_id WHERE i.company_id=? AND l.movement_date<=?",array($companyId,$asOf))->row()->total;
|
||||
$stockLog=(float)$this->db->query("SELECT COALESCE(SUM(IF(s.tipe='masuk',s.qty*COALESCE(s.unit_cost,0),-s.qty*COALESCE(s.unit_cost,0))),0) total FROM stock_logs s JOIN items i ON i.id=s.item_id WHERE i.company_id=? AND DATE(s.created_at)<=?",array($companyId,$asOf))->row()->total;
|
||||
$mapping=$this->db->where(array('mapping_key'=>'inventory','is_active'=>1))->get('system_account_mappings')->row();$gl=0;
|
||||
if($mapping)$gl=(float)$this->db->query("SELECT COALESCE(SUM(d.debit-d.kredit),0) total FROM journals j JOIN journal_details d ON d.journal_id=j.id WHERE d.account_id=? AND j.company_id=? AND j.tanggal<=? AND j.status IN('posted','reversed')",array($mapping->account_id,$companyId,$asOf))->row()->total;
|
||||
$cache=(int)$this->db->query("SELECT COUNT(*) total FROM items i LEFT JOIN(SELECT item_id,SUM(IF(direction='in',qty,-qty)) qty FROM inventory_ledger WHERE movement_date<=? GROUP BY item_id)l ON l.item_id=i.id WHERE i.company_id=? AND ABS(i.stok-COALESCE(l.qty,0))>.0001",array($asOf,$companyId))->row()->total;
|
||||
$barcode=(int)$this->db->query("SELECT COUNT(*) total FROM(SELECT l.item_id,l.warehouse_id,l.qty-COALESCE(b.qty,0) variance FROM(SELECT il.item_id,il.warehouse_id,SUM(IF(il.direction='in',il.qty,-il.qty)) qty FROM inventory_ledger il JOIN items i ON i.id=il.item_id WHERE i.company_id=? AND il.movement_date<=? GROUP BY il.item_id,il.warehouse_id)l LEFT JOIN(SELECT item_id,warehouse_id,SUM(qty_sisa) qty FROM item_barcodes GROUP BY item_id,warehouse_id)b ON b.item_id=l.item_id AND b.warehouse_id=l.warehouse_id HAVING ABS(variance)>.0001)x",array($companyId,$asOf))->row()->total;
|
||||
$details=array();
|
||||
$details['ledger_vs_stock_log']=$this->db->query("SELECT i.id item_id,i.kode_detail,i.nama_barang,w.nama warehouse_name,COALESCE(l.qty,0) ledger_qty,COALESCE(s.qty,0) stock_log_qty,COALESCE(l.inventory_value,0) ledger_value,COALESCE(s.stock_log_value,0) stock_log_value,COALESCE(l.qty,0)-COALESCE(s.qty,0) qty_difference,COALESCE(l.inventory_value,0)-COALESCE(s.stock_log_value,0) value_difference FROM(SELECT item_id,warehouse_id FROM inventory_ledger WHERE movement_date<=? UNION SELECT item_id,COALESCE(warehouse_id,0) FROM stock_logs WHERE DATE(created_at)<=?)p JOIN items i ON i.id=p.item_id LEFT JOIN warehouses w ON w.id=p.warehouse_id LEFT JOIN(SELECT item_id,warehouse_id,SUM(IF(direction='in',qty,-qty)) qty,SUM(IF(direction='in',value,-value)) inventory_value FROM inventory_ledger WHERE movement_date<=? GROUP BY item_id,warehouse_id)l ON l.item_id=p.item_id AND l.warehouse_id=p.warehouse_id LEFT JOIN(SELECT item_id,COALESCE(warehouse_id,0) warehouse_id,SUM(IF(tipe='masuk',qty,-qty)) qty,SUM(IF(tipe='masuk',qty*COALESCE(unit_cost,0),-qty*COALESCE(unit_cost,0))) stock_log_value FROM stock_logs WHERE DATE(created_at)<=? GROUP BY item_id,COALESCE(warehouse_id,0))s ON s.item_id=p.item_id AND s.warehouse_id=p.warehouse_id WHERE i.company_id=? HAVING ABS(qty_difference)>.0001 OR ABS(value_difference)>.01 ORDER BY ABS(value_difference) DESC,ABS(qty_difference) DESC LIMIT 50",array($asOf,$asOf,$asOf,$asOf,$companyId))->result();
|
||||
$details['barcode']=$this->db->query("SELECT i.id item_id,i.kode_detail,i.nama_barang,w.nama warehouse_name,l.qty ledger_qty,COALESCE(b.qty,0) barcode_qty,l.qty-COALESCE(b.qty,0) difference FROM(SELECT il.item_id,il.warehouse_id,SUM(IF(il.direction='in',il.qty,-il.qty)) qty FROM inventory_ledger il WHERE il.movement_date<=? GROUP BY il.item_id,il.warehouse_id)l JOIN items i ON i.id=l.item_id LEFT JOIN warehouses w ON w.id=l.warehouse_id LEFT JOIN(SELECT item_id,warehouse_id,SUM(qty_sisa) qty FROM item_barcodes GROUP BY item_id,warehouse_id)b ON b.item_id=l.item_id AND b.warehouse_id=l.warehouse_id WHERE i.company_id=? HAVING ABS(difference)>.0001 ORDER BY ABS(difference) DESC LIMIT 50",array($asOf,$companyId))->result();
|
||||
$details['cache']=$this->db->query("SELECT i.id item_id,i.kode_detail,i.nama_barang,i.stok cache_qty,COALESCE(l.qty,0) ledger_qty,i.stok-COALESCE(l.qty,0) difference FROM items i LEFT JOIN(SELECT item_id,SUM(IF(direction='in',qty,-qty)) qty FROM inventory_ledger WHERE movement_date<=? GROUP BY item_id)l ON l.item_id=i.id WHERE i.company_id=? HAVING ABS(difference)>.0001 ORDER BY ABS(difference) DESC LIMIT 50",array($asOf,$companyId))->result();
|
||||
return array('ledger_value'=>$ledger,'stock_log_value'=>$stockLog,'gl_value'=>$gl,'difference'=>$ledger-$gl,'cache_variance_count'=>$cache,'barcode_variance_count'=>$barcode,'status'=>abs($ledger-$gl)<.01&&abs($ledger-$stockLog)<.01&&$cache===0&&$barcode===0?'matched':'variance','details'=>$details);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
class InvoiceModel extends CI_Model
|
||||
{
|
||||
public function find($id)
|
||||
{
|
||||
return $this->db->get_where('invoices', array('id' => (int) $id))->row();
|
||||
}
|
||||
|
||||
public function createDraft(array $data)
|
||||
{
|
||||
$data['status'] = 'draft';
|
||||
if (!$this->db->insert('invoices', $data)) {
|
||||
throw new RuntimeException('Gagal membuat draft invoice.');
|
||||
}
|
||||
return (int) $this->db->insert_id();
|
||||
}
|
||||
|
||||
public function updateById($id, array $data)
|
||||
{
|
||||
return $this->db->where('id', (int) $id)->update('invoices', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
defined('BASEPATH') OR exit('No direct script access allowed');
|
||||
|
||||
class JournalModel extends CI_Model
|
||||
{
|
||||
public function createHeader(array $data)
|
||||
{
|
||||
if (!$this->db->insert('journals', $data)) {
|
||||
throw new RuntimeException('Gagal membuat header jurnal.');
|
||||
}
|
||||
|
||||
return (int) $this->db->insert_id();
|
||||
}
|
||||
|
||||
public function addLines(array $lines)
|
||||
{
|
||||
if (empty($lines) || !$this->db->insert_batch('journal_details', $lines)) {
|
||||
throw new RuntimeException('Gagal membuat detail jurnal.');
|
||||
}
|
||||
}
|
||||
|
||||
public function referenceExists($reference)
|
||||
{
|
||||
return $this->db->where('no_ref', $reference)->count_all_results('journals') > 0;
|
||||
}
|
||||
|
||||
public function sourceExists($type, $id)
|
||||
{
|
||||
return $this->db
|
||||
->where('ref_type', $type)
|
||||
->where('ref_id', $id)
|
||||
->count_all_results('journals') > 0;
|
||||
}
|
||||
|
||||
public function find($id)
|
||||
{
|
||||
return $this->db->get_where('journals', array('id' => (int) $id))->row();
|
||||
}
|
||||
|
||||
public function findForUpdate($id)
|
||||
{
|
||||
return $this->db->query('SELECT * FROM journals WHERE id = ? FOR UPDATE', array((int) $id))->row();
|
||||
}
|
||||
|
||||
public function lines($journalId)
|
||||
{
|
||||
return $this->db->where('journal_id', (int) $journalId)->order_by('id', 'ASC')->get('journal_details')->result_array();
|
||||
}
|
||||
|
||||
public function updateStatus($journalId, $status, array $extra = array())
|
||||
{
|
||||
return $this->db->where('id', (int) $journalId)->update('journals', array_merge(array(
|
||||
'status' => $status,
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
), $extra));
|
||||
}
|
||||
|
||||
public function addStatusHistory($journalId, $from, $to, $userId, $notes = null)
|
||||
{
|
||||
return $this->db->insert('journal_status_histories', array(
|
||||
'journal_id' => (int) $journalId,
|
||||
'from_status' => $from,
|
||||
'to_status' => $to,
|
||||
'notes' => $notes,
|
||||
'changed_by' => (int) $userId,
|
||||
'changed_at' => date('Y-m-d H:i:s')
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,12 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
private function companyId()
|
||||
{
|
||||
$companyId = (int) $this->session->userdata('company_id');
|
||||
return $companyId > 0 ? $companyId : 1;
|
||||
}
|
||||
|
||||
public function get_teknisi_list()
|
||||
{
|
||||
return $this->db
|
||||
@@ -15,8 +21,9 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
->from('k_employees e')
|
||||
->join('k_positions p', 'p.id = e.position_id', 'left')
|
||||
->join('k_departments d', 'd.id = e.department_id', 'left')
|
||||
->where('e.company_id', $this->companyId())
|
||||
->where('e.is_active', 1)
|
||||
->where('(e.resign_date IS NULL OR e.resign_date = \'0000-00-00\')')
|
||||
->where_in('LOWER(p.position_name)', ['teknisi', 'noc'])
|
||||
->order_by('e.full_name', 'ASC')
|
||||
->get()
|
||||
->result();
|
||||
@@ -42,6 +49,8 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
->join('items i', 'i.id = it.item_id', 'left')
|
||||
->join('k_employees ke', 'ke.id = it.user_id', 'left');
|
||||
|
||||
$this->db->where('i.company_id', $this->companyId());
|
||||
|
||||
if ($user_id) {
|
||||
$this->db->where('it.user_id', $user_id);
|
||||
}
|
||||
@@ -65,6 +74,7 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
->from('item_barcodes ib')
|
||||
->join('items i', 'i.id = ib.item_id', 'left')
|
||||
->where('ib.barcode', $barcode)
|
||||
->where('i.company_id', $this->companyId())
|
||||
->where('ib.qty_sisa >', 0)
|
||||
->get()
|
||||
->row();
|
||||
@@ -75,6 +85,7 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
->from('item_barcodes ib')
|
||||
->join('items i', 'i.id = ib.item_id', 'left')
|
||||
->where('ib.serial_number', $barcode)
|
||||
->where('i.company_id', $this->companyId())
|
||||
->where('ib.qty_sisa >', 0)
|
||||
->get()
|
||||
->row();
|
||||
@@ -89,8 +100,10 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
->select('it.user_id, ke.full_name, ke.employee_code')
|
||||
->from('item_technician it')
|
||||
->join('k_employees ke', 'ke.id = it.user_id', 'left')
|
||||
->join('items i', 'i.id = it.item_id', 'inner')
|
||||
->where('it.barcode', $barcode)
|
||||
->where('it.status', 'active')
|
||||
->where('i.company_id', $this->companyId())
|
||||
->get()
|
||||
->row();
|
||||
}
|
||||
@@ -106,31 +119,36 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
$barcode = trim($barcode);
|
||||
if ($barcode == '') continue;
|
||||
|
||||
$item = $this->db
|
||||
->select('it.*, ib.id as barcode_id, tec.full_name as teknisi_name')
|
||||
->from('item_technician it')
|
||||
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
|
||||
->join('k_employees tec', 'tec.id = it.user_id', 'left')
|
||||
->where('it.barcode', $barcode)
|
||||
->where('it.status', 'active')
|
||||
->get()
|
||||
->row();
|
||||
$item = $this->db->query(
|
||||
"SELECT it.*, ib.id AS barcode_id, ib.warehouse_id, ib.qty_sisa, tec.full_name AS teknisi_name
|
||||
FROM item_technician it
|
||||
JOIN item_barcodes ib ON ib.barcode = it.barcode
|
||||
JOIN items i ON i.id = it.item_id AND i.company_id = ?
|
||||
LEFT JOIN k_employees tec ON tec.id = it.user_id
|
||||
WHERE it.barcode = ? AND it.status = 'active'
|
||||
FOR UPDATE",
|
||||
[$this->companyId(), $barcode]
|
||||
)->row();
|
||||
|
||||
if (!$item) {
|
||||
$errors[] = "Barcode $barcode tidak ditemukan atau sudah dikembalikan";
|
||||
continue;
|
||||
}
|
||||
|
||||
$assigned=(float)($item->assigned_qty?:$item->qty_sisa);$carried=(float)$item->carried_qty;$installed=(float)$item->installed_qty;
|
||||
if($carried>0&&$installed>0){$errors[]="Barcode $barcode memiliki status sebagian dibawa dan sebagian dipasang; gunakan Detail Teknisi untuk menentukan jumlah dan asal pengembalian";continue;}
|
||||
$source=$installed>0?'installed':'carried';$qty=$assigned;
|
||||
|
||||
$this->db->insert('item_movements', [
|
||||
'item_id' => $item->item_id,
|
||||
'barcode_id' => $item->barcode_id,
|
||||
'qty' => 1,
|
||||
'from_type' => 'technician',
|
||||
'from_id' => $item->user_id,
|
||||
'qty' => $qty,
|
||||
'from_type' => $source === 'installed' ? 'customer' : 'technician',
|
||||
'from_id' => $source === 'installed' ? $item->customer_id : $item->user_id,
|
||||
'to_type' => 'warehouse',
|
||||
'to_id' => null,
|
||||
'to_id' => $item->warehouse_id,
|
||||
'movement_type' => 'transfer',
|
||||
'notes' => 'Pengembalian dari: ' . $item->teknisi_name . ' ke gudang',
|
||||
'notes' => (($item->usage_status??'carried') === 'installed' ? 'Pengembalian dari customer melalui teknisi: ' : 'Pengembalian dari teknisi: ') . $item->teknisi_name . ' ke gudang',
|
||||
'created_at' => $now
|
||||
]);
|
||||
|
||||
@@ -139,9 +157,14 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
->where('status', 'active')
|
||||
->update('item_technician', [
|
||||
'status' => 'inactive',
|
||||
'assigned_qty' => 0,
|
||||
'carried_qty' => 0,
|
||||
'installed_qty' => 0,
|
||||
'deleted_at' => $now
|
||||
]);
|
||||
|
||||
$this->db->insert('technician_item_status_logs',array('company_id'=>$this->companyId(),'user_id'=>$item->user_id,'item_id'=>$item->item_id,'barcode_id'=>$item->barcode_id,'barcode'=>$item->barcode,'from_status'=>$source,'to_status'=>'returned','qty'=>$qty,'customer_id'=>$source==='installed'?$item->customer_id:null,'event_date'=>date('Y-m-d'),'notes'=>'Pengembalian batch ke gudang','created_by'=>(int)$this->session->userdata('user_id'),'created_at'=>$now));
|
||||
|
||||
if ($item->barcode_id) {
|
||||
$this->db
|
||||
->where('id', $item->barcode_id)
|
||||
@@ -171,8 +194,10 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
return $this->db
|
||||
->select("DATE(it.created_at) as tanggal")
|
||||
->from('item_technician it')
|
||||
->join('items i', 'i.id = it.item_id', 'inner')
|
||||
->where('it.user_id', $user_id)
|
||||
->where('it.status', 'active')
|
||||
->where('i.company_id', $this->companyId())
|
||||
->group_by("DATE(it.created_at)")
|
||||
->order_by("DATE(it.created_at)", "DESC")
|
||||
->get()
|
||||
@@ -189,22 +214,34 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
->select('id, full_name, employee_code')
|
||||
->from('k_employees')
|
||||
->where('id', $user_id)
|
||||
->where('company_id', $this->companyId())
|
||||
->get()
|
||||
->row();
|
||||
|
||||
if (!$technician) {
|
||||
return ['status' => false, 'message' => 'Teknisi tidak ditemukan pada perusahaan aktif.'];
|
||||
}
|
||||
|
||||
$this->db->trans_start();
|
||||
|
||||
foreach ($barcodes as $barcode) {
|
||||
$barcode = trim($barcode);
|
||||
if ($barcode == '') continue;
|
||||
|
||||
$barcode_row = $this->db
|
||||
->select('ib.id as barcode_id, ib.item_id, ib.barcode')
|
||||
->from('item_barcodes ib')
|
||||
->where('ib.barcode', $barcode)
|
||||
->where_not_in('ib.status', ['damaged', 'lost', 'returned_supplier', 'sold_out'])
|
||||
->get()
|
||||
->row();
|
||||
$barcode_row = $this->db->query(
|
||||
"SELECT ib.id AS barcode_id, ib.item_id, ib.barcode, ib.warehouse_id,ib.qty_sisa,
|
||||
COALESCE(k.tracking_type,'QTY') tracking_type
|
||||
FROM item_barcodes ib
|
||||
JOIN items i ON i.id = ib.item_id AND i.company_id = ?
|
||||
LEFT JOIN kode_barang k ON k.id=i.kode_id
|
||||
WHERE ib.barcode = ?
|
||||
AND ib.status = 'available'
|
||||
AND ib.qty_sisa > 0
|
||||
AND COALESCE(ib.reserved_qty, 0) = 0
|
||||
AND COALESCE(ib.return_reserved_qty, 0) = 0
|
||||
FOR UPDATE",
|
||||
[$this->companyId(), $barcode]
|
||||
)->row();
|
||||
|
||||
if (!$barcode_row) {
|
||||
$errors[] = "Barcode $barcode tidak valid atau tidak tersedia";
|
||||
@@ -228,9 +265,14 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
|
||||
$this->db->insert('item_technician', [
|
||||
'user_id' => $user_id,
|
||||
'company_id' => $this->companyId(),
|
||||
'barcode' => $barcode_row->barcode,
|
||||
'item_id' => $barcode_row->item_id,
|
||||
'status' => 'active',
|
||||
'usage_status' => 'carried',
|
||||
'assigned_qty' => (float)$barcode_row->qty_sisa,
|
||||
'carried_qty' => (float)$barcode_row->qty_sisa,
|
||||
'installed_qty' => 0,
|
||||
'created_at' => $now
|
||||
]);
|
||||
|
||||
@@ -242,7 +284,7 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
->row();
|
||||
|
||||
$from_type = 'warehouse';
|
||||
$from_id = null;
|
||||
$from_id = $barcode_row->warehouse_id;
|
||||
if ($last_movement) {
|
||||
$from_type = $last_movement->to_type;
|
||||
$from_id = $last_movement->to_id;
|
||||
@@ -251,7 +293,7 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
$this->db->insert('item_movements', [
|
||||
'item_id' => $barcode_row->item_id,
|
||||
'barcode_id' => $barcode_row->barcode_id,
|
||||
'qty' => 1,
|
||||
'qty' => (float)$barcode_row->qty_sisa,
|
||||
'from_type' => $from_type,
|
||||
'from_id' => $from_id,
|
||||
'to_type' => 'technician',
|
||||
@@ -307,6 +349,7 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
->from('k_employees e')
|
||||
->join('k_positions p', 'p.id = e.position_id', 'left')
|
||||
->join('k_departments d', 'd.id = e.department_id', 'left')
|
||||
->where('e.company_id', $this->companyId())
|
||||
->where('(e.resign_date IS NULL OR e.resign_date = \'0000-00-00\')')
|
||||
->where_in('LOWER(p.position_name)', ['teknisi', 'noc']);
|
||||
|
||||
@@ -336,6 +379,7 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
$this->db
|
||||
->from('k_employees e')
|
||||
->join('k_positions p', 'p.id = e.position_id', 'left')
|
||||
->where('e.company_id', $this->companyId())
|
||||
->where('(e.resign_date IS NULL OR e.resign_date = \'0000-00-00\')')
|
||||
->where_in('LOWER(p.position_name)', ['teknisi', 'noc']);
|
||||
|
||||
@@ -348,6 +392,7 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
->from('k_employees e')
|
||||
->join('k_positions p', 'p.id = e.position_id', 'left')
|
||||
->join('k_departments d', 'd.id = e.department_id', 'left')
|
||||
->where('e.company_id', $this->companyId())
|
||||
->where('(e.resign_date IS NULL OR e.resign_date = \'0000-00-00\')')
|
||||
->where_in('LOWER(p.position_name)', ['teknisi', 'noc']);
|
||||
|
||||
@@ -370,6 +415,7 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
->join('k_positions p', 'p.id = e.position_id', 'left')
|
||||
->join('k_departments d', 'd.id = e.department_id', 'left')
|
||||
->where('e.id', $user_id)
|
||||
->where('e.company_id', $this->companyId())
|
||||
->get()
|
||||
->row();
|
||||
}
|
||||
@@ -387,17 +433,32 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
$this->db
|
||||
->select("
|
||||
it.barcode,
|
||||
it.user_id,
|
||||
i.nama_barang,
|
||||
ib.serial_number,
|
||||
ib.qty_sisa,
|
||||
COALESCE(k.tracking_type, 'QTY') tracking_type,
|
||||
COALESCE(NULLIF(it.assigned_qty,0),ib.qty_sisa) assigned_qty,
|
||||
COALESCE(it.carried_qty,CASE WHEN COALESCE(it.usage_status,'carried')='carried' THEN ib.qty_sisa ELSE 0 END) carried_qty,
|
||||
COALESCE(it.installed_qty,CASE WHEN it.usage_status='installed' THEN ib.qty_sisa ELSE 0 END) installed_qty,
|
||||
it.status,
|
||||
COALESCE(it.usage_status, 'carried') usage_status,
|
||||
it.usage_date,
|
||||
it.customer_id,
|
||||
c.nama customer_name,
|
||||
ib.id barcode_id,
|
||||
COALESCE((SELECT MAX(l.attachment_id) FROM technician_item_status_logs l WHERE l.company_id=it.company_id AND l.barcode_id=ib.id),
|
||||
(SELECT MAX(ta.id) FROM transaction_attachments ta WHERE ta.entity_type='technician_installation' AND ta.entity_id=ib.id)) proof_id,
|
||||
DATE_FORMAT(it.created_at, '%d/%m/%Y') as tanggal
|
||||
")
|
||||
->from('item_technician it')
|
||||
->join('items i', 'i.id = it.item_id', 'left')
|
||||
->join('kode_barang k', 'k.id = i.kode_id', 'left')
|
||||
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
|
||||
->join('customers c', 'c.id = it.customer_id', 'left')
|
||||
->where('it.user_id', $user_id)
|
||||
->where('it.status', 'active');
|
||||
->where('it.status', 'active')
|
||||
->where('i.company_id', $this->companyId());
|
||||
|
||||
if (!empty($search)) {
|
||||
$this->db->group_start()
|
||||
@@ -424,8 +485,10 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
{
|
||||
$this->db
|
||||
->from('item_technician it')
|
||||
->join('items i', 'i.id = it.item_id', 'inner')
|
||||
->where('it.user_id', $user_id)
|
||||
->where('it.status', 'active');
|
||||
->where('it.status', 'active')
|
||||
->where('i.company_id', $this->companyId());
|
||||
|
||||
return $this->db->count_all_results();
|
||||
}
|
||||
@@ -437,7 +500,8 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
->join('items i', 'i.id = it.item_id', 'left')
|
||||
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
|
||||
->where('it.user_id', $user_id)
|
||||
->where('it.status', 'active');
|
||||
->where('it.status', 'active')
|
||||
->where('i.company_id', $this->companyId());
|
||||
|
||||
if (!empty($search)) {
|
||||
$this->db->group_start()
|
||||
@@ -469,9 +533,10 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
->join('item_barcodes ib', 'ib.id = im.barcode_id', 'left')
|
||||
->join('k_employees from_ke', 'from_ke.id = im.from_id', 'left')
|
||||
->join('k_employees to_ke', 'to_ke.id = im.to_id', 'left')
|
||||
->where('im.movement_type', 'installation')
|
||||
->where_in('im.movement_type', ['installation', 'transfer'])
|
||||
->where('im.from_type', 'technician')
|
||||
->where('im.to_type', 'technician')
|
||||
->where('i.company_id', $this->companyId())
|
||||
->order_by('im.created_at', 'DESC')
|
||||
->limit(200)
|
||||
->get()
|
||||
@@ -496,6 +561,7 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
->join('items i', 'i.id = it.item_id', 'left')
|
||||
->join('k_employees ke', 'ke.id = it.user_id', 'left')
|
||||
->where('it.barcode', $barcode)
|
||||
->where('i.company_id', $this->companyId())
|
||||
->get()
|
||||
->row();
|
||||
}
|
||||
@@ -515,7 +581,8 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
->join('items i', 'i.id = it.item_id', 'left')
|
||||
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
|
||||
->where('it.user_id', $user_id)
|
||||
->where('it.status', 'active');
|
||||
->where('it.status', 'active')
|
||||
->where('i.company_id', $this->companyId());
|
||||
|
||||
if ($tanggal AND $tanggal != 'all') {
|
||||
$this->db->where('DATE(it.created_at)', $tanggal);
|
||||
@@ -534,23 +601,108 @@ class PeralatanTeknisi_model extends CI_Model {
|
||||
|
||||
public function get_log_barang()
|
||||
{
|
||||
return $this->db
|
||||
return $this->get_log_barang_filtered(array());
|
||||
}
|
||||
|
||||
public function get_log_barang_filtered(array $filters)
|
||||
{
|
||||
$this->db
|
||||
->select("
|
||||
it.barcode,
|
||||
it.item_id,
|
||||
it.user_id,
|
||||
it.status,
|
||||
COALESCE(it.usage_status, 'carried') usage_status,
|
||||
COALESCE(it.assigned_qty,0) assigned_qty,
|
||||
COALESCE(it.carried_qty,0) carried_qty,
|
||||
COALESCE(it.installed_qty,0) installed_qty,
|
||||
it.usage_date,
|
||||
it.customer_id,
|
||||
it.usage_notes,
|
||||
it.created_at,
|
||||
it.deleted_at,
|
||||
ke.full_name as teknisi_name,
|
||||
ke.employee_code,
|
||||
c.nama customer_name,
|
||||
i.kode_detail as item_code,
|
||||
i.nama_barang
|
||||
i.nama_barang,
|
||||
ib.serial_number,
|
||||
ib.qty_sisa,
|
||||
(SELECT COUNT(*) FROM transaction_attachments ta WHERE ta.entity_type='technician_installation' AND ta.entity_id=ib.id) attachment_count
|
||||
")
|
||||
->from('item_technician it')
|
||||
->join('k_employees ke', 'ke.id = it.user_id', 'left')
|
||||
->join('items i', 'i.id = it.item_id', 'left')
|
||||
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
|
||||
->join('customers c', 'c.id = it.customer_id', 'left')
|
||||
->where('i.company_id', $this->companyId());
|
||||
if(!empty($filters['user_id']))$this->db->where('it.user_id',(int)$filters['user_id']);
|
||||
if(!empty($filters['customer_id']))$this->db->where('it.customer_id',(int)$filters['customer_id']);
|
||||
$status=trim((string)($filters['status']??''));
|
||||
if($status==='returned'){
|
||||
$this->db->where('it.status','inactive');
|
||||
}elseif($status==='installed'){
|
||||
$this->db->where('it.status','active')->where('it.installed_qty >',0);
|
||||
}elseif($status==='carried'){
|
||||
$this->db->where('it.status','active')->where('it.carried_qty >',0);
|
||||
}
|
||||
$from=trim((string)($filters['date_from']??''));$to=trim((string)($filters['date_to']??''));
|
||||
if($from!==''||$to!==''){
|
||||
$from=$from!==''?$from:'1900-01-01';$to=$to!==''?$to:'2999-12-31';
|
||||
$this->db->group_start()->where("DATE(it.created_at) BETWEEN ".$this->db->escape($from).' AND '.$this->db->escape($to),null,false)->or_where("it.usage_date BETWEEN ".$this->db->escape($from).' AND '.$this->db->escape($to),null,false)->or_where("DATE(it.deleted_at) BETWEEN ".$this->db->escape($from).' AND '.$this->db->escape($to),null,false)->group_end();
|
||||
}
|
||||
return $this->db
|
||||
->order_by('it.created_at', 'DESC')
|
||||
->get()
|
||||
->result();
|
||||
}
|
||||
|
||||
public function change_item_status($barcode,$userId,array $data,$actorId)
|
||||
{
|
||||
$barcode=trim((string)$barcode);$company=$this->companyId();$now=date('Y-m-d H:i:s');
|
||||
$this->db->trans_begin();
|
||||
try{
|
||||
$key=trim((string)($data['idempotency_key']??''));
|
||||
if($key!==''){$duplicate=$this->db->where(array('company_id'=>$company,'idempotency_key'=>$key))->get('technician_item_status_logs')->row();if($duplicate){$this->db->trans_commit();return array('duplicate'=>true,'log_id'=>(int)$duplicate->id,'qty'=>(float)$duplicate->qty,'status'=>$duplicate->to_status);}}
|
||||
$row=$this->db->query("SELECT it.*,ib.id barcode_id,ib.qty_sisa,ib.warehouse_id,i.nama_barang,e.full_name technician_name,COALESCE(k.tracking_type,'QTY') tracking_type FROM item_technician it JOIN item_barcodes ib ON ib.barcode=it.barcode JOIN items i ON i.id=it.item_id JOIN k_employees e ON e.id=it.user_id LEFT JOIN kode_barang k ON k.id=i.kode_id WHERE it.company_id=? AND it.user_id=? AND it.barcode=? AND it.status='active' FOR UPDATE",array($company,(int)$userId,$barcode))->row();
|
||||
if(!$row)throw new RuntimeException('Barang aktif pada teknisi tidak ditemukan.');
|
||||
|
||||
$target=(string)$data['status'];$tracking=strtoupper((string)$row->tracking_type)==='UNIT'?'UNIT':'QTY';
|
||||
$assigned=(float)$row->assigned_qty;$carried=(float)$row->carried_qty;$installed=(float)$row->installed_qty;
|
||||
if($assigned<=0){$assigned=(float)$row->qty_sisa;if(($row->usage_status??'carried')==='installed')$installed=$assigned;else$carried=$assigned;}
|
||||
$qty=$tracking==='UNIT'?1:(float)($data['qty']??0);
|
||||
if($qty<=0)throw new RuntimeException('Jumlah yang diubah wajib lebih dari 0.');
|
||||
|
||||
$source='';$customerId=null;$customerName='-';
|
||||
if($target==='installed'){$source='carried';$available=$carried;$customerId=(int)($data['customer_id']??0);if($customerId<1)throw new RuntimeException('Customer wajib dipilih untuk status Dipasang.');$customer=$this->db->select('id,nama')->where(array('id'=>$customerId,'company_id'=>$company))->get('customers')->row();if(!$customer)throw new RuntimeException('Customer tidak ditemukan pada perusahaan aktif.');$customerName=$customer->nama;if($installed>0&&!empty($row->customer_id)&&(int)$row->customer_id!==$customerId)throw new RuntimeException('Sebagian barcode ini sudah dipasang pada customer lain. Kembalikan dahulu ke teknisi sebelum memilih customer berbeda.');}
|
||||
elseif($target==='carried'){$source='installed';$available=$installed;$customerId=(int)$row->customer_id;if($customerId<1)throw new RuntimeException('Customer asal pemasangan tidak ditemukan.');$customer=$this->db->select('nama')->where(array('id'=>$customerId,'company_id'=>$company))->get('customers')->row();$customerName=$customer?$customer->nama:'-';}
|
||||
elseif($target==='warehouse'){$source=(string)($data['source_status']??'');if(!in_array($source,array('carried','installed'),true)){if($carried>0&&$installed<=0)$source='carried';elseif($installed>0&&$carried<=0)$source='installed';else throw new RuntimeException('Pilih asal barang yang akan dikembalikan ke gudang.');}$available=$source==='installed'?$installed:$carried;if($source==='installed'){$customerId=(int)$row->customer_id;$customer=$customerId?$this->db->select('nama')->where(array('id'=>$customerId,'company_id'=>$company))->get('customers')->row():null;$customerName=$customer?$customer->nama:'-';}}
|
||||
else throw new RuntimeException('Status tujuan tidak valid.');
|
||||
if($qty>$available+0.00001)throw new RuntimeException('Jumlah melebihi saldo status asal ('.number_format($available,4,',','.').').');
|
||||
|
||||
if($source==='carried')$carried-=$qty;else$installed-=$qty;
|
||||
if($target==='installed')$installed+=$qty;elseif($target==='carried')$carried+=$qty;else$assigned-=$qty;
|
||||
$carried=max(0,$carried);$installed=max(0,$installed);$assigned=max(0,$assigned);
|
||||
$usage=$installed>0&&$carried>0?'mixed':($installed>0?'installed':'carried');$inactive=$assigned<=0.00001;
|
||||
$update=array('assigned_qty'=>$assigned,'carried_qty'=>$carried,'installed_qty'=>$installed,'usage_status'=>$usage,'usage_date'=>$installed>0?($target==='installed'?$data['date']:$row->usage_date):null,'customer_id'=>$installed>0?$customerId:null,'usage_notes'=>$data['notes'],'updated_by'=>(int)$actorId,'updated_at'=>$now);
|
||||
if($inactive){$update['status']='inactive';$update['deleted_at']=$now;}
|
||||
$this->db->where(array('company_id'=>$company,'user_id'=>(int)$userId,'barcode'=>$barcode,'status'=>'active'))->update('item_technician',$update);
|
||||
|
||||
$labels=array('carried'=>'Dibawa Teknisi','installed'=>'Dipasang di Customer','warehouse'=>'Dikembalikan ke Gudang');
|
||||
$notes='[AUTO] Perubahan status peralatan | Barang '.$row->nama_barang.' | Barcode '.$row->barcode.' | Teknisi '.$row->technician_name.' | Dari '.$labels[$source].' ke '.$labels[$target].' | Qty '.number_format($qty,4,',','.').' | Tanggal '.$data['date'].' | Customer '.$customerName.' | Keterangan '.$data['notes'];
|
||||
$movement=array('item_id'=>$row->item_id,'barcode_id'=>$row->barcode_id,'qty'=>$qty,'from_type'=>$source==='installed'?'customer':'technician','from_id'=>$source==='installed'?$customerId:$row->user_id,'to_type'=>$target==='installed'?'customer':($target==='carried'?'technician':'warehouse'),'to_id'=>$target==='installed'?$customerId:($target==='carried'?$row->user_id:$row->warehouse_id),'movement_type'=>$target==='installed'?'installation':($target==='carried'?'uninstallation':'transfer'),'notes'=>mb_substr($notes,0,250),'created_at'=>$now);
|
||||
$this->db->insert('item_movements',$movement);
|
||||
$this->db->insert('technician_item_status_logs',array('company_id'=>$company,'user_id'=>(int)$userId,'item_id'=>$row->item_id,'barcode_id'=>$row->barcode_id,'barcode'=>$row->barcode,'from_status'=>$source,'to_status'=>$target==='warehouse'?'returned':$target,'qty'=>$qty,'customer_id'=>$customerId?:null,'event_date'=>$data['date'],'notes'=>$data['notes'],'idempotency_key'=>$key?:null,'created_by'=>(int)$actorId,'created_at'=>$now));
|
||||
$logId=(int)$this->db->insert_id();
|
||||
$this->db->where('id',$row->barcode_id)->update('item_barcodes',array('status'=>$inactive?'available':'installed'));
|
||||
if($this->db->trans_status()===false)throw new RuntimeException('Perubahan status barang gagal disimpan.');
|
||||
$this->db->trans_commit();
|
||||
return array('log_id'=>$logId,'barcode_id'=>(int)$row->barcode_id,'status'=>$target,'qty'=>$qty,'customer_name'=>$customerName,'technician_name'=>$row->technician_name,'remaining_qty'=>$assigned);
|
||||
}catch(Throwable$e){$this->db->trans_rollback();throw$e;}
|
||||
}
|
||||
|
||||
public function get_status_history($userId)
|
||||
{
|
||||
return $this->db->select('l.*,i.kode_detail,i.nama_barang,c.nama customer_name,u.nama actor_name')->from('technician_item_status_logs l')->join('items i','i.id=l.item_id','left')->join('customers c','c.id=l.customer_id','left')->join('users u','u.id=l.created_by','left')->where(array('l.company_id'=>$this->companyId(),'l.user_id'=>(int)$userId))->order_by('l.event_date','DESC')->order_by('l.id','DESC')->get()->result();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user