167 lines
8.5 KiB
PHP
167 lines
8.5 KiB
PHP
<?php
|
|
defined('BASEPATH') OR exit('No direct script access allowed');
|
|
|
|
require_once APPPATH . 'exceptions/BusinessException.php';
|
|
|
|
class JournalService
|
|
{
|
|
private $CI;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->CI =& get_instance();
|
|
$this->CI->load->model('JournalModel', 'journalModel');
|
|
$this->CI->load->model('AccountModel', 'accountModel');
|
|
$this->CI->load->library('TransactionService');
|
|
$this->CI->load->library('FiscalPeriodService');
|
|
$this->CI->load->library('JournalDescriptionService');
|
|
$this->CI->load->library('AuditService');
|
|
if ($this->CI->db->table_exists('companies')) $this->CI->load->library('CompanyContext');
|
|
}
|
|
|
|
public function create(array $header, array $lines, $manageTransaction = true)
|
|
{
|
|
$normalized = $this->validateAndNormalize($header, $lines);
|
|
$operation = function () use ($normalized) {
|
|
$journalId = $this->CI->journalModel->createHeader($normalized['header']);
|
|
$details = array();
|
|
foreach ($normalized['lines'] as $line) {
|
|
$line['journal_id'] = $journalId;
|
|
$details[] = $line;
|
|
}
|
|
$this->CI->journalModel->addLines($details);
|
|
if ($this->CI->db->table_exists('journal_status_histories')) {
|
|
$this->CI->journalModel->addStatusHistory(
|
|
$journalId,
|
|
null,
|
|
$normalized['header']['status'],
|
|
$normalized['header']['created_by'],
|
|
'Jurnal dibuat'
|
|
);
|
|
}
|
|
return $journalId;
|
|
};
|
|
|
|
return $manageTransaction
|
|
? $this->CI->transactionservice->run($operation)
|
|
: $operation();
|
|
}
|
|
|
|
public function totals(array $lines)
|
|
{
|
|
$debit = 0.0;
|
|
$credit = 0.0;
|
|
foreach ($lines as $line) {
|
|
$debit += isset($line['debit']) ? (float) $line['debit'] : 0;
|
|
$credit += isset($line['kredit']) ? (float) $line['kredit'] : 0;
|
|
}
|
|
return array('debit' => round($debit, 2), 'kredit' => round($credit, 2));
|
|
}
|
|
|
|
public function reviseRejected($journalId, array $header, array $lines, $userId)
|
|
{
|
|
return $this->CI->transactionservice->run(function () use ($journalId, $header, $lines, $userId) {
|
|
$journal = $this->CI->db->query(
|
|
'SELECT * FROM journals WHERE id=? AND company_id=? FOR UPDATE',
|
|
array((int)$journalId, (int)$this->CI->companycontext->id())
|
|
)->row();
|
|
if (!$journal) throw new BusinessException('Jurnal tidak ditemukan.');
|
|
if ($journal->status !== 'rejected') throw new BusinessException('Hanya jurnal yang ditolak yang dapat dikoreksi.');
|
|
if (!in_array((string)$journal->ref_type, array('', 'manual_journal'), true)) throw new BusinessException('Jurnal otomatis tidak dapat diedit melalui Jurnal Umum.');
|
|
if ((int)$journal->created_by !== (int)$userId && !(function_exists('is_master_admin_user') && is_master_admin_user()) && $this->CI->session->userdata('role') !== 'Admin') throw new BusinessException('Hanya pembuat jurnal atau Admin yang dapat melakukan koreksi.');
|
|
|
|
$header['no_ref'] = $journal->no_ref;
|
|
$header['ref_type'] = $journal->ref_type ?: 'manual_journal';
|
|
$header['ref_id'] = $journal->ref_id;
|
|
$header['created_by'] = $journal->created_by;
|
|
$normalized = $this->validateAndNormalize($header, $lines, (int)$journal->id);
|
|
$before = array('journal'=>$journal, 'lines'=>$this->CI->journalModel->lines($journal->id));
|
|
$update = array(
|
|
'tanggal'=>$normalized['header']['tanggal'],
|
|
'keterangan'=>$normalized['header']['keterangan'],
|
|
'accounting_period_id'=>$normalized['header']['accounting_period_id'],
|
|
'status'=>'draft',
|
|
'submitted_at'=>null,
|
|
'submitted_by'=>null,
|
|
'approved_at'=>null,
|
|
'approved_by'=>null,
|
|
'updated_by'=>(int)$userId,
|
|
'updated_at'=>date('Y-m-d H:i:s'),
|
|
);
|
|
$this->CI->db->where('id', $journal->id)->update('journals', $update);
|
|
$this->CI->db->where('journal_id', $journal->id)->delete('journal_details');
|
|
$details=array();foreach($normalized['lines']as$line){$line['journal_id']=$journal->id;$details[]=$line;}
|
|
$this->CI->journalModel->addLines($details);
|
|
$notes='Jurnal dikoreksi dan dikembalikan menjadi Draft.';
|
|
if(trim((string)$journal->rejection_reason)!=='')$notes.=' Alasan penolakan sebelumnya: '.$journal->rejection_reason;
|
|
$this->CI->journalModel->addStatusHistory($journal->id,'rejected','draft',$userId,$notes);
|
|
$after=array('journal'=>$this->CI->journalModel->find($journal->id),'lines'=>$this->CI->journalModel->lines($journal->id));
|
|
$this->CI->auditservice->record('journal','journal',$journal->id,'revise_rejected',$before,$after,$userId);
|
|
return $after['journal'];
|
|
});
|
|
}
|
|
|
|
private function validateAndNormalize(array $header, array $lines, $excludeJournalId = 0)
|
|
{
|
|
foreach (array('tanggal', 'no_ref', 'keterangan', 'created_by') as $required) {
|
|
if (!isset($header[$required]) || trim((string) $header[$required]) === '') {
|
|
throw new BusinessException('Data header jurnal "' . $required . '" wajib diisi.');
|
|
}
|
|
}
|
|
|
|
$date = DateTime::createFromFormat('Y-m-d', $header['tanggal']);
|
|
if (!$date || $date->format('Y-m-d') !== $header['tanggal']) {
|
|
throw new BusinessException('Tanggal jurnal tidak valid.');
|
|
}
|
|
if ($this->CI->journalModel->referenceExists($header['no_ref'], $excludeJournalId)) {
|
|
throw new BusinessException('Nomor referensi jurnal sudah digunakan.');
|
|
}
|
|
|
|
$period = $this->CI->fiscalperiodservice->assertOpen($header['tanggal']);
|
|
if (count($lines) < 2) {
|
|
throw new BusinessException('Jurnal minimal mempunyai dua baris transaksi.');
|
|
}
|
|
|
|
$normalized = array();
|
|
$accountIds = array();
|
|
foreach ($lines as $index => $line) {
|
|
$accountId = isset($line['account_id']) ? (int) $line['account_id'] : 0;
|
|
$debit = round(isset($line['debit']) ? (float) $line['debit'] : 0, 2);
|
|
$credit = round(isset($line['kredit']) ? (float) $line['kredit'] : 0, 2);
|
|
|
|
if ($accountId <= 0 || $debit < 0 || $credit < 0 || ($debit > 0 && $credit > 0) || ($debit <= 0 && $credit <= 0)) {
|
|
throw new BusinessException('Baris jurnal ke-' . ($index + 1) . ' tidak valid.');
|
|
}
|
|
|
|
$detail=array('account_id'=>$accountId,'debit'=>$debit,'kredit'=>$credit);
|
|
foreach(array('currency_amount','currency_code','exchange_rate')as$currencyField)if(isset($line[$currencyField])&&$line[$currencyField]!=='')$detail[$currencyField]=$line[$currencyField];
|
|
if($this->CI->db->field_exists('company_id','journal_details'))$detail['company_id']=$this->CI->companycontext->id();
|
|
$normalized[]=$detail;
|
|
$accountIds[] = $accountId;
|
|
}
|
|
|
|
$existingIds = $this->CI->accountModel->existingIds($accountIds);
|
|
$missingIds = array_diff(array_unique($accountIds), $existingIds);
|
|
if (!empty($missingIds)) {
|
|
throw new BusinessException('Terdapat akun jurnal yang tidak ditemukan: ' . implode(', ', $missingIds));
|
|
}
|
|
|
|
$totals = $this->totals($normalized);
|
|
if ($totals['debit'] <= 0 || abs($totals['debit'] - $totals['kredit']) > 0.009) {
|
|
throw new BusinessException('Total debit dan kredit jurnal harus seimbang.');
|
|
}
|
|
|
|
$header['keterangan'] = $this->CI->journaldescriptionservice->build($header, $normalized);
|
|
|
|
$header['status'] = isset($header['status']) ? $header['status'] : 'draft';
|
|
$header['accounting_period_id'] = (int) $period->id;
|
|
$allowedHeader = array_intersect_key($header, array_flip(array(
|
|
'tanggal', 'no_ref', 'keterangan', 'ref_type', 'ref_id', 'created_by',
|
|
'status', 'accounting_period_id', 'posted_at', 'posted_by', 'reversal_of', 'reversal_reason','company_id','transaction_currency','exchange_rate','base_amount'
|
|
)));
|
|
if($this->CI->db->field_exists('company_id','journals'))$allowedHeader['company_id']=$this->CI->companycontext->id();
|
|
|
|
return array('header' => $allowedHeader, 'lines' => $normalized, 'totals' => $totals);
|
|
}
|
|
}
|