Files
2026-09-11 16:03:00 +07:00

171 lines
9.2 KiB
PHP

<?php
defined('BASEPATH') OR exit('No direct script access allowed');
require_once APPPATH . 'exceptions/BusinessException.php';
class JournalWorkflowService
{
private $CI;
public function __construct()
{
$this->CI =& get_instance();
$this->CI->load->model('JournalModel', 'workflowJournalModel');
$this->CI->load->library('TransactionService');
$this->CI->load->library('FiscalPeriodService');
$this->CI->load->library('NumberingService');
$this->CI->load->library('PostingService');
$this->CI->load->library('ApprovalService');
$this->CI->load->library('AuditService');
$this->CI->config->load('accounting', true);
}
public function submit($journalId, $userId)
{
return $this->CI->transactionservice->run(function () use ($journalId, $userId) {
$journal = $this->lockedJournal($journalId, 'draft');
if ((int)$journal->created_by !== (int)$userId && $this->CI->session->userdata('role') !== 'Admin') throw new BusinessException('Anda hanya dapat mengajukan jurnal yang Anda buat.');
$this->CI->fiscalperiodservice->assertOpen($journal->tanggal);
$lines=$this->CI->workflowJournalModel->lines($journal->id); $amount=0;
foreach($lines as $line) $amount+=(float)$line['debit'];
$this->CI->approvalservice->create('journal','journal',$journal->id,$amount,$userId,array('journal'=>$journal,'lines'=>$lines));
$this->CI->workflowJournalModel->updateStatus($journal->id,'submitted',array('submitted_at'=>date('Y-m-d H:i:s'),'submitted_by'=>(int)$userId));
$this->CI->workflowJournalModel->addStatusHistory($journal->id,'draft','submitted',$userId);
$after=$this->CI->workflowJournalModel->find($journal->id);
$this->CI->auditservice->record('journal','journal',$journal->id,'submit',$journal,$after,$userId);
return $after;
});
}
public function approve($journalId, $userId)
{
return $this->CI->transactionservice->run(function () use ($journalId, $userId) {
$journal = $this->lockedJournal($journalId, 'submitted');
$request=$this->CI->approvalservice->pendingForEntity('journal',$journal->id);
if(!$request) throw new BusinessException('Permintaan approval jurnal tidak ditemukan.');
$roleId=$this->roleId($userId);
$approval=$this->CI->approvalservice->act($request->id,'approved',$userId,$roleId);
if($approval->status!=='approved') return $journal;
$this->CI->fiscalperiodservice->assertOpen($journal->tanggal);
$now=date('Y-m-d H:i:s');
$this->CI->workflowJournalModel->updateStatus($journal->id, 'posted', array(
'approved_at'=>$now,'approved_by'=>(int)$userId,'posted_at'=>$now,'posted_by'=>(int)$userId
));
$this->CI->workflowJournalModel->addStatusHistory($journal->id, 'submitted', 'approved', $userId);
$this->CI->workflowJournalModel->addStatusHistory($journal->id, 'approved', 'posted', $userId, 'Otomatis diposting setelah approval final.');
$after=$this->CI->workflowJournalModel->find($journal->id);
$this->CI->auditservice->record('journal','journal',$journal->id,'approve_and_post',$journal,$after,$userId);
return $after;
});
}
public function post($journalId, $userId)
{
return $this->CI->transactionservice->run(function () use ($journalId, $userId) {
$journal = $this->lockedJournal($journalId, 'approved');
$this->CI->fiscalperiodservice->assertOpen($journal->tanggal);
$this->CI->workflowJournalModel->updateStatus($journal->id, 'posted', array(
'posted_at' => date('Y-m-d H:i:s'), 'posted_by' => (int) $userId
));
$this->CI->workflowJournalModel->addStatusHistory($journal->id, 'approved', 'posted', $userId);
$after=$this->CI->workflowJournalModel->find($journal->id);
$this->CI->auditservice->record('journal','journal',$journal->id,'post',$journal,$after,$userId);
return $after;
});
}
public function reject($journalId, $userId, $reason)
{
$reason = trim((string) $reason);
if ($reason === '') {
throw new BusinessException('Alasan penolakan wajib diisi.');
}
return $this->CI->transactionservice->run(function () use ($journalId, $userId, $reason) {
$journal = $this->CI->workflowJournalModel->findForUpdate($journalId);
if (!$journal || !in_array($journal->status, array('submitted', 'approved'), true)) {
throw new BusinessException('Jurnal tidak berada pada status yang dapat ditolak.');
}
if($journal->status==='submitted') {
$request=$this->CI->approvalservice->pendingForEntity('journal',$journal->id);
if($request) $this->CI->approvalservice->act($request->id,'rejected',$userId,$this->roleId($userId),$reason);
}
$this->CI->workflowJournalModel->updateStatus($journal->id, 'rejected', array(
'rejected_at' => date('Y-m-d H:i:s'), 'rejected_by' => (int) $userId, 'rejection_reason' => $reason
));
$this->CI->workflowJournalModel->addStatusHistory($journal->id, $journal->status, 'rejected', $userId, $reason);
$after=$this->CI->workflowJournalModel->find($journal->id);
$this->CI->auditservice->record('journal','journal',$journal->id,'reject',$journal,$after,$userId);
return $after;
});
}
public function reverse($journalId, $date, $userId, $reason)
{
$reason = trim((string) $reason);
if ($reason === '') {
throw new BusinessException('Alasan reversal wajib diisi.');
}
return $this->CI->transactionservice->run(function () use ($journalId, $date, $userId, $reason) {
$journal = $this->lockedJournal($journalId, 'posted');
if ((int) $journal->is_reversed === 1) {
throw new BusinessException('Jurnal sudah pernah direversal.');
}
$this->CI->fiscalperiodservice->assertOpen($date);
$lines = array();
foreach ($this->CI->workflowJournalModel->lines($journal->id) as $line) {
$lines[] = array('account_id' => $line['account_id'], 'debit' => $line['kredit'], 'kredit' => $line['debit']);
}
$number = $this->CI->numberingservice->next('reversal', $date);
$reversalId = $this->CI->postingservice->post(array(
'tanggal' => $date,
'no_ref' => $number,
'keterangan' => 'Reversal ' . $journal->no_ref . ': ' . $reason,
'ref_type' => 'journal_reversal',
'ref_id' => $journal->id,
'reversal_of' => $journal->id,
'reversal_reason' => $reason,
'created_by' => (int) $userId
), $lines, false, true);
$this->CI->workflowJournalModel->updateStatus($journal->id, 'reversed', array(
'is_reversed' => 1, 'reversal_reason' => $reason, 'updated_by' => (int) $userId
));
$this->CI->workflowJournalModel->addStatusHistory($journal->id, 'posted', 'reversed', $userId, $reason);
$this->CI->auditservice->record('journal','journal',$journal->id,'reverse',$journal,$this->CI->workflowJournalModel->find($journal->id),$userId);
return array('journal' => $this->CI->workflowJournalModel->find($journal->id), 'reversal_id' => $reversalId, 'reversal_no' => $number);
});
}
private function transition($journalId, $from, $to, $userId, array $extra)
{
return $this->CI->transactionservice->run(function () use ($journalId, $from, $to, $userId, $extra) {
$journal = $this->lockedJournal($journalId, $from);
if ($from === 'draft' && (int) $journal->created_by !== (int) $userId && $this->CI->session->userdata('role') !== 'Admin') {
throw new BusinessException('Anda hanya dapat mengajukan jurnal yang Anda buat.');
}
$this->CI->fiscalperiodservice->assertOpen($journal->tanggal);
$this->CI->workflowJournalModel->updateStatus($journal->id, $to, $extra);
$this->CI->workflowJournalModel->addStatusHistory($journal->id, $from, $to, $userId);
return $this->CI->workflowJournalModel->find($journal->id);
});
}
private function lockedJournal($journalId, $expectedStatus)
{
$journal = $this->CI->workflowJournalModel->findForUpdate($journalId);
if (!$journal) {
throw new BusinessException('Jurnal tidak ditemukan.', array(), 404);
}
if ($journal->status !== $expectedStatus) {
throw new BusinessException('Jurnal harus berstatus ' . $expectedStatus . ' untuk tindakan ini.');
}
return $journal;
}
private function roleId($userId)
{
$roleId=(int)$this->CI->session->userdata('role_id');
if(!$roleId) { $user=$this->CI->db->select('role_id')->get_where('users',array('id'=>(int)$userId))->row(); $roleId=$user?(int)$user->role_id:0; }
return $roleId;
}
}