2025-01-09 14:17:28 +07:00
< ? php
namespace App\Helpers ;
2026-06-01 15:01:03 +07:00
use App\Models\Kategori ;
2025-01-09 14:17:28 +07:00
use App\Models\BudgetControl ;
use App\Models\FormUpCountry ;
use App\Models\FormMeetingSeminar ;
use App\Models\FormOthers ;
use App\Models\FormEntertaimentPresentation ;
use App\Models\FormVehicleRunningCost ;
2025-01-11 13:31:44 +07:00
use App\Models\UserHasCabang ;
2025-05-25 15:14:23 +07:00
use Illuminate\Support\Carbon ;
2026-06-01 15:01:03 +07:00
use Illuminate\Support\Facades\DB ;
use Illuminate\Support\Facades\Schema ;
2025-01-09 14:17:28 +07:00
class BudgetHelper
{
2026-06-01 15:01:03 +07:00
/**
* Cache memori sementara untuk fungsi jurnal agar loading halaman riwayat tidak berat.
*/
private static $jurnalCache = [];
/**
* Daftar status yang akan memotong (menge-hold) sisa budget secara real-time.
*/
const HOLD_STATUSES = [
'Approved 1' ,
'Approved 2' ,
'Waiting Approval' ,
'Validated' ,
'Approved' ,
'Closed' ,
'Completed'
];
public static function getBulkAvailableBudget ( $budgetControls )
{
if ( $budgetControls -> isEmpty ()) return [];
$cabangIds = $budgetControls -> pluck ( 'cabang_id' ) -> unique () -> toArray ();
// 1. Mapping User per Cabang
$branchUsers = UserHasCabang :: whereIn ( 'cabang_id' , $cabangIds )
-> get ()
-> groupBy ( 'cabang_id' )
-> map ( fn ( $item ) => $item -> pluck ( 'user_id' ) -> toArray ());
$allUserIds = $branchUsers -> flatten () -> unique () -> toArray ();
$resultsMap = [];
if ( empty ( $allUserIds )) return [];
$formModels = [
'tanggal' => [
FormUpCountry :: class ,
FormVehicleRunningCost :: class ,
FormEntertaimentPresentation :: class ,
FormOthers :: class
],
'created_at' => [
FormMeetingSeminar :: class
]
];
// 2. Tarik data pengeluaran sekaligus dari semua tabel form
foreach ( $formModels as $dateColumn => $models ) {
foreach ( $models as $model ) {
$query = $model :: whereIn ( 'user_id' , $allUserIds )
-> whereIn ( 'status' , self :: HOLD_STATUSES )
-> select (
'user_id' ,
DB :: raw ( " MONTH( $dateColumn ) as month " ),
DB :: raw ( " YEAR( $dateColumn ) as year " ),
DB :: raw ( " SUM(approved_total) as total " )
)
-> groupBy ( 'user_id' , 'month' , 'year' )
-> get ();
// 3. Masukkan ke Map berdasarkan Cabang_Bulan_Tahun
foreach ( $query as $row ) {
foreach ( $branchUsers as $cabangId => $userIds ) {
if ( in_array ( $row -> user_id , $userIds )) {
$monthName = strtolower ( date ( 'F' , mktime ( 0 , 0 , 0 , $row -> month , 10 )));
$key = " { $cabangId } _ { $monthName } _ { $row -> year } " ;
$resultsMap [ $key ] = ( $resultsMap [ $key ] ? ? 0 ) + $row -> total ;
}
}
}
}
}
return $resultsMap ;
}
/**
* Menghitung sisa budget (Pagu - Total Hold/Terpakai).
* Tetap menggunakan kolom tanggal/created_at karena pengajuan yang baru
* dibuat (Approved 1) belum memiliki final_approved_at.
*/
2025-05-25 15:14:23 +07:00
public static function getAvailableBudget ( $cabang_id , $periode_month , $periode_year )
2026-06-01 15:01:03 +07:00
{
$periode_month = strtolower ( $periode_month );
$periode_year = ( int ) $periode_year ;
$totalPagu = BudgetControl :: where ( 'cabang_id' , $cabang_id )
-> where ( 'periode_month' , $periode_month )
-> where ( 'periode_year' , $periode_year )
-> sum ( 'amount' );
if ( $totalPagu <= 0 ) return 0 ;
$userIds = UserHasCabang :: where ( 'cabang_id' , $cabang_id ) -> pluck ( 'user_id' ) -> toArray ();
if ( empty ( $userIds )) return $totalPagu ;
$usedBudget = 0 ;
$monthNumeric = date ( 'm' , strtotime ( $periode_month ));
$formModels = [
FormUpCountry :: class ,
FormVehicleRunningCost :: class ,
FormEntertaimentPresentation :: class ,
FormMeetingSeminar :: class ,
FormOthers :: class
];
foreach ( $formModels as $model ) {
$query = $model :: whereIn ( 'user_id' , $userIds )
-> whereIn ( 'status' , self :: HOLD_STATUSES );
if ( $model === FormMeetingSeminar :: class ) {
$query -> whereMonth ( 'created_at' , $monthNumeric )
-> whereYear ( 'created_at' , $periode_year );
} else {
$query -> whereMonth ( 'tanggal' , $monthNumeric )
-> whereYear ( 'tanggal' , $periode_year );
}
$usedBudget += $query -> sum ( 'approved_total' );
}
return $totalPagu - $usedBudget ;
}
/**
* Memvalidasi apakah tanggal expense yang diinput user berada dalam
* rentang periode aktif (Tanggal 11 s/d 10).
*/
public static function checkExpenseInputWindow ( $tanggal_expense )
{
$inputDate = \Carbon\Carbon :: parse ( $tanggal_expense ) -> startOfDay ();
$today = \Carbon\Carbon :: now ();
$startDay = 11 ;
$endDay = 10 ;
// Menentukan jendela periode yang sedang aktif hari ini
if ( $today -> day >= $startDay ) {
// Jika hari ini tgl 11 atau lebih: Jendela aktif adalah Tgl 11 bulan ini s/d Tgl 10 bulan depan
$periodStart = \Carbon\Carbon :: create ( $today -> year , $today -> month , $startDay ) -> startOfDay ();
$periodEnd = \Carbon\Carbon :: create ( $today -> year , $today -> month , $endDay ) -> addMonth () -> endOfDay ();
} else {
// Jika hari ini tgl 1-10: Jendela aktif adalah Tgl 11 bulan lalu s/d Tgl 10 bulan ini
$periodStart = \Carbon\Carbon :: create ( $today -> year , $today -> month , $startDay ) -> subMonth () -> startOfDay ();
$periodEnd = \Carbon\Carbon :: create ( $today -> year , $today -> month , $endDay ) -> endOfDay ();
}
// Jika tanggal form yang diinput user berada di luar jendela aktif
if ( $inputDate -> lt ( $periodStart ) || $inputDate -> gt ( $periodEnd )) {
return [
'is_valid' => false ,
'message' => " Gagal! Batas input expense untuk periode aktif saat ini adalah { $periodStart -> format ( 'd M Y' ) } s/d { $periodEnd -> format ( 'd M Y' ) } . Tanggal { $inputDate -> format ( 'd M Y' ) } sudah ditutup atau belum dibuka. "
];
}
return [ 'is_valid' => true ];
}
/**
* Mengambil nominal per tipe form.
*/
public static function getNominalByFormType ( $userIds , $type , $type_id , $month , $year )
{
$monthNumeric = date ( 'm' , strtotime ( $month ));
$used = 0 ;
if ( $type == 'Up Country' ) {
$used = FormUpCountry :: whereIn ( 'user_id' , $userIds )
-> whereMonth ( 'tanggal' , $monthNumeric ) -> whereYear ( 'tanggal' , $year )
-> whereIn ( 'status' , self :: HOLD_STATUSES ) -> sum ( 'approved_total' );
} elseif ( $type == 'Vehicle Running Cost' ) {
$used = FormVehicleRunningCost :: whereIn ( 'user_id' , $userIds )
-> whereMonth ( 'tanggal' , $monthNumeric ) -> whereYear ( 'tanggal' , $year )
-> whereIn ( 'status' , self :: HOLD_STATUSES ) -> sum ( 'approved_total' );
} elseif ( $type == 'Entertainment' ) {
$used = FormEntertaimentPresentation :: whereIn ( 'user_id' , $userIds )
-> whereMonth ( 'tanggal' , $monthNumeric ) -> whereYear ( 'tanggal' , $year )
-> whereIn ( 'status' , self :: HOLD_STATUSES ) -> sum ( 'approved_total' );
} elseif ( $type == 'Meeting / Seminar' ) {
$used = FormMeetingSeminar :: whereIn ( 'user_id' , $userIds )
-> whereMonth ( 'created_at' , $monthNumeric ) -> whereYear ( 'created_at' , $year )
-> whereIn ( 'status' , self :: HOLD_STATUSES ) -> sum ( 'approved_total' );
} else {
$used = FormOthers :: whereIn ( 'user_id' , $userIds )
-> where ( 'kategori_id' , $type_id )
-> whereMonth ( 'tanggal' , $monthNumeric ) -> whereYear ( 'tanggal' , $year )
-> whereIn ( 'status' , self :: HOLD_STATUSES ) -> sum ( 'approved_total' );
}
return $used ;
}
/**
* Mengambil tanggal closing.
*/
public static function getClosingDate ( $cabang_id )
{
return BudgetControl :: where ( 'cabang_id' , $cabang_id )
-> orderBy ( 'created_at' , 'desc' )
-> value ( 'closing_at' );
}
/**
* KALKULASI JURNAL AKUNTANSI
* Berlaku seragam menggunakan kolom final_approved_at untuk semua form
* Cut-off Date: Tgl 11 s/d Tgl 13 Bulan Berikutnya
*/
/**
* KALKULASI JURNAL AKUNTANSI (Log & Report)
* Sinkron dengan 12 Kategori Utama & Akun 800xxx
*/
// public static function getKalkulasiJurnal($cabang_id, $periode_month, $periode_year)
// {
// $periode_month = strtolower($periode_month);
// $periode_year = (int) $periode_year;
// $cacheKey = "{$cabang_id}_{$periode_month}_{$periode_year}";
// if (isset(self::$jurnalCache[$cacheKey])) return self::$jurnalCache[$cacheKey];
// $userIds = UserHasCabang::where('cabang_id', $cabang_id)->pluck('user_id')->toArray();
// $totalDebet = 0;
// if (!empty($userIds)) {
// // Ambil Range Tanggal (Contoh: 11 Maret - 13 April)
// $startDay = env('STARTING_DATE', 11);
// $closeDay = env('CLOSING_DATE', 13);
// $monthNumeric = date('m', strtotime($periode_month));
// $startDate = Carbon::create($periode_year, $monthNumeric, $startDay)->startOfDay();
// $endDate = Carbon::create($periode_year, $monthNumeric, $closeDay)->addMonth()->endOfDay();
// // Mapping Model dengan pengamanan Typo
// $formModels = [
// 'Up Country' => FormUpCountry::class,
// 'Vehicle Running Cost' => FormVehicleRunningCost::class,
// 'Entertainment' => FormEntertaimentPresentation::class,
// 'Entertaiment' => FormEntertaimentPresentation::class,
// 'Presentation' => FormEntertaimentPresentation::class,
// 'Meeting / Seminar' => FormMeetingSeminar::class,
// ];
// // Ambil kategori yang punya account_number dan tidak dikecualikan
// $kategoris = \App\Models\Kategori::whereNotIn('name', ['Cash in Bank BCA', 'Donasi/Gift', 'PPn Masukan', 'Sponsorship'])
// ->whereNotNull('account_number')
// ->where('account_number', '!=', '')
// ->get();
// foreach ($kategoris as $kat) {
// $katName = trim($kat->name);
// if (isset($formModels[$katName])) {
// $model = $formModels[$katName];
// $query = $model::whereIn('user_id', $userIds)
// ->whereIn('status', ['Closed', 'Final Approved'])
// ->whereBetween('final_approved_at', [$startDate, $endDate]);
// // PENTING: Filter berdasarkan kategori_id agar data tidak tertukar di tabel yang sama
// if (\Illuminate\Support\Facades\Schema::hasColumn((new $model)->getTable(), 'kategori_id')) {
// $query->where('kategori_id', $kat->id);
// }
// $totalDebet += $query->sum('approved_total');
// } else {
// // Masuk ke Form Others
// $totalDebet += FormOthers::whereIn('user_id', $userIds)
// ->where('kategori_id', $kat->id)
// ->whereIn('status', ['Closed', 'Final Approved'])
// ->whereBetween('final_approved_at', [$startDate, $endDate])
// ->sum('approved_total');
// }
// }
// }
// self::$jurnalCache[$cacheKey] = $totalDebet;
// return $totalDebet;
// }
public static function getBulkKalkulasiJurnal ( array $lookups )
{
if ( empty ( $lookups )) return [];
$resultsMap = [];
$startDay = 11 ; // Batas awal periode (Contoh: tgl 11)
$closeDay = 13 ; // Batas akhir approval (Contoh: tgl 13 bulan depan)
foreach ( $lookups as $item ) {
$cabId = $item [ 'cabang_id' ];
$monthNumeric = date ( 'm' , strtotime ( $item [ 'month' ]));
$year = ( int ) $item [ 'year' ];
$key = $cabId . '_' . strtolower ( $item [ 'month' ]) . '_' . $year ;
// Penentuan Rentang Tanggal Cut-off Akuntansi
$startDate = \Illuminate\Support\Carbon :: create ( $year , $monthNumeric , $startDay ) -> startOfDay ();
$endDate = $startDate -> copy () -> addMonth () -> day ( $closeDay ) -> endOfDay ();
$userIds = \App\Models\UserHasCabang :: where ( 'cabang_id' , $cabId ) -> pluck ( 'user_id' ) -> toArray ();
if ( empty ( $userIds )) {
$resultsMap [ $key ] = 0 ;
continue ;
}
// Menghitung Total Pengeluaran Riil (Status: Closed / Final Approved)
$tables = [ 'form_up_country' , 'form_vehicle_running_cost' , 'form_entertainment_presentation' , 'form_meeting_seminar' , 'form_others' ];
$totalExpense = 0 ;
foreach ( $tables as $table ) {
$totalExpense += \Illuminate\Support\Facades\DB :: table ( $table )
-> whereIn ( 'user_id' , $userIds )
-> whereIn ( 'status' , [ 'Closed' , 'Final Approved' ])
-> whereBetween ( 'final_approved_at' , [ $startDate , $endDate ])
-> sum ( 'approved_total' );
}
$resultsMap [ $key ] = $totalExpense ;
}
return $resultsMap ;
2025-01-09 14:17:28 +07:00
}
2026-06-01 15:01:03 +07:00
}