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

150 lines
6.5 KiB
PHP

<?php
defined('BASEPATH') OR exit('No direct script access allowed');
require_once APPPATH . 'exceptions/BusinessException.php';
class FiscalPeriodService
{
private $CI;
public function __construct()
{
$this->CI =& get_instance();
$this->CI->load->model('FiscalPeriodModel', 'fiscalPeriodModel');
$this->CI->load->library('TransactionService');
$this->CI->load->library('AuditService');
}
public function periodForDate($date)
{
$parsed = DateTime::createFromFormat('Y-m-d', (string) $date);
if (!$parsed || $parsed->format('Y-m-d') !== $date) {
throw new BusinessException('Tanggal periode accounting tidak valid.');
}
$period = $this->CI->fiscalPeriodModel->findByDate($date);
if (!$period) {
throw new BusinessException('Periode accounting untuk tanggal ' . $date . ' belum tersedia.');
}
return $period;
}
public function assertOpen($date)
{
$period = $this->periodForDate($date);
if ($period->status !== 'open') {
throw new BusinessException('Periode ' . $period->name . ' berstatus ' . $period->status . ' dan tidak menerima transaksi.');
}
return $period;
}
public function changeStatus($periodId, $targetStatus, $userId, $reason = '')
{
$allowed = array('open', 'soft_closed', 'closed');
if (!in_array($targetStatus, $allowed, true)) {
throw new BusinessException('Status periode tidak valid.');
}
return $this->CI->transactionservice->run(function () use ($periodId, $targetStatus, $userId, $reason) {
$period = $this->CI->db->query('SELECT * FROM accounting_periods WHERE id = ? FOR UPDATE', array((int) $periodId))->row();
if (!$period) {
throw new BusinessException('Periode accounting tidak ditemukan.', array(), 404);
}
if ($period->status === $targetStatus) {
return $period;
}
$transitions = array(
'open' => array('soft_closed', 'closed'),
'soft_closed' => array('open', 'closed'),
'closed' => array('open')
);
if (!in_array($targetStatus, $transitions[$period->status], true)) {
throw new BusinessException('Perubahan status periode tidak diizinkan.');
}
if ($targetStatus === 'open' && $period->status !== 'open' && trim($reason) === '') {
throw new BusinessException('Alasan membuka kembali periode wajib diisi.');
}
if (in_array($targetStatus, array('soft_closed', 'closed'), true)) {
$pending = $this->CI->db
->where('tanggal >=', $period->start_date)
->where('tanggal <=', $period->end_date)
->where_in('status', array('draft', 'submitted', 'approved'))
->count_all_results('journals');
if ($pending > 0) {
throw new BusinessException('Periode masih mempunyai ' . $pending . ' jurnal yang belum diposting atau ditolak.');
}
}
$update = array('status' => $targetStatus, 'updated_at' => date('Y-m-d H:i:s'));
if ($targetStatus === 'closed') {
$update['closed_at'] = date('Y-m-d H:i:s');
$update['closed_by'] = (int) $userId;
} elseif ($targetStatus === 'open') {
$update['reopened_at'] = date('Y-m-d H:i:s');
$update['reopened_by'] = (int) $userId;
}
$this->CI->db->where('id', $period->id)->update('accounting_periods', $update);
$this->CI->db->insert('accounting_period_logs', array(
'accounting_period_id' => $period->id,
'from_status' => $period->status,
'to_status' => $targetStatus,
'reason' => trim($reason) ?: null,
'changed_by' => (int) $userId,
'changed_at' => date('Y-m-d H:i:s')
));
$notClosed = $this->CI->db
->where('fiscal_year_id', $period->fiscal_year_id)
->where('status !=', 'closed')
->count_all_results('accounting_periods');
$this->CI->db->where('id', $period->fiscal_year_id)->update('fiscal_years', array(
'status' => $notClosed === 0 ? 'closed' : 'open'
));
$after=$this->CI->fiscalPeriodModel->find($period->id);
$this->CI->auditservice->record('accounting_period','accounting_period',$period->id,'change_status',$period,$after,$userId);
return $after;
});
}
public function createCalendarYear($year, $userId)
{
$year = (int) $year;
if ($year < 2000 || $year > 2200) {
throw new BusinessException('Tahun buku tidak valid.');
}
return $this->CI->transactionservice->run(function () use ($year, $userId) {
$start = $year . '-01-01';
$end = $year . '-12-31';
$overlap = $this->CI->db
->where('start_date <=', $end)
->where('end_date >=', $start)
->count_all_results('fiscal_years');
if ($overlap > 0) {
throw new BusinessException('Tahun buku tersebut sudah tersedia atau bertumpang tindih.');
}
$this->CI->db->insert('fiscal_years', array(
'name' => 'Tahun Buku ' . $year,
'start_date' => $start,
'end_date' => $end,
'status' => 'open',
'created_at' => date('Y-m-d H:i:s'),
'created_by' => (int) $userId
));
$fiscalId = (int) $this->CI->db->insert_id();
for ($month = 1; $month <= 12; $month++) {
$periodStart = sprintf('%04d-%02d-01', $year, $month);
$this->CI->db->insert('accounting_periods', array(
'fiscal_year_id' => $fiscalId,
'period_number' => $month,
'name' => date('F Y', strtotime($periodStart)),
'start_date' => $periodStart,
'end_date' => date('Y-m-t', strtotime($periodStart)),
'status' => 'open'
));
}
$this->CI->auditservice->record('fiscal_year','fiscal_year',$fiscalId,'create',null,array('year'=>$year),$userId);
return $fiscalId;
});
}
}