2024-12-12 18:18:26 +07:00
< ? php
namespace App\Http\Controllers\Forms ;
use Illuminate\Http\Request ;
use App\Http\Controllers\Controller ;
use App\Models\FormUpCountry ;
use App\Models\Rayon ;
2024-12-16 17:01:55 +07:00
use Illuminate\Support\Facades\DB ;
use Illuminate\Support\Facades\Storage ;
use App\Models\Region ;
use App\Models\Cabang ;
use App\Models\UserHasCabang ;
use App\Models\Kategori ;
2024-12-18 12:56:16 +07:00
use Illuminate\Support\Str ;
2024-12-24 15:12:24 +07:00
use Illuminate\Support\Facades\Mail ;
use App\Mail\ExpenseApproved ;
2025-01-16 15:56:22 +07:00
use App\Mail\ExpenseApproved2 ;
2024-12-24 15:12:24 +07:00
use App\Mail\ExpenseRejected ;
2024-12-26 13:49:00 +07:00
use App\Mail\FinalApprove ;
2025-01-09 13:22:56 +07:00
use App\Mail\ExpenseCreated ;
2024-12-25 13:37:49 +07:00
use App\Helpers\WhatsappHelper ;
2025-01-01 17:14:46 +07:00
use App\Helpers\AuditTrailHelper ;
2025-01-02 18:21:03 +07:00
use App\Helpers\NextCloudHelper ;
2025-01-02 15:19:43 +07:00
use App\Rules\FileType ;
2025-01-09 13:22:56 +07:00
use App\Models\Admin ;
2025-10-13 14:55:46 +07:00
use App\Models\AttachmentForm ;
2025-01-09 14:17:28 +07:00
use App\Helpers\BudgetHelper ;
2025-01-17 16:17:53 +07:00
use App\Helpers\NotificationHelper ;
2025-10-13 00:14:28 +07:00
use App\Helpers\OutstandingHelper ;
2025-01-23 17:46:38 +07:00
use Illuminate\Support\Facades\Log ;
2025-03-05 13:44:58 +07:00
use App\Services\BrevoService ;
2025-10-13 14:55:46 +07:00
use App\Services\AttachmentService ;
use App\Enums\AttachmentTableName ;
2025-05-05 19:40:38 +07:00
use Carbon\Carbon ;
2025-10-12 18:29:07 +07:00
use Illuminate\Container\Attributes\Auth ;
2024-12-12 18:18:26 +07:00
class FormUpCountryController extends Controller
{
2025-10-13 14:55:46 +07:00
protected AttachmentService $attachmentService ;
2025-10-23 17:21:46 +07:00
/**
* Attachment categories (key => label) for Up Country forms.
*
* @var array<string,string>
*/
2025-10-13 14:55:46 +07:00
protected array $attachmentCategories = [
2025-10-23 17:21:46 +07:00
'bukti_allowance' => 'Bukti Allowance' ,
'bukti_transport_dalkot' => 'Bukti Transport Dalam Kota' ,
'bukti_transport_ankot' => 'Bukti Transport Antar Kota' ,
'bukti_hotel' => 'Bukti Hotel' ,
'bukti_total' => 'Bukti Total' ,
'bukti_lainnya' => 'Bukti Lainnya' ,
2025-10-13 14:55:46 +07:00
];
public function __construct ( AttachmentService $attachmentService )
{
$this -> attachmentService = $attachmentService ;
view () -> share ( 'upCountryAttachmentCategories' , $this -> attachmentCategories );
2025-10-23 17:21:46 +07:00
view () -> share ( 'upCountryAttachmentCategoryKeys' , array_keys ( $this -> attachmentCategories ));
2025-10-13 14:55:46 +07:00
}
2024-12-12 18:18:26 +07:00
public function index ()
{
2024-12-16 17:01:55 +07:00
$this -> checkAuthorization ( auth () -> user (), [ 'forms.country.view' ]);
2024-12-23 10:10:09 +07:00
$role = auth () -> user () -> getRoleNames ()[ 0 ];
2025-05-05 19:40:38 +07:00
$startDay = ( int ) env ( 'STARTING_DATE' , 11 );
2026-06-01 15:01:03 +07:00
$closingDay = ( int ) env ( 'CLOSING_DATE' , 13 );
2025-05-05 19:40:38 +07:00
2025-06-18 19:15:53 +07:00
// Current time reference
2025-05-05 19:40:38 +07:00
$now = Carbon :: now ();
2025-06-18 19:15:53 +07:00
// Determine the period
2025-05-05 19:40:38 +07:00
if ( $now -> day >= $startDay ) {
2025-06-19 19:39:20 +07:00
$currentPeriodStartDate = Carbon :: create ( $now -> year , $now -> month , $startDay ) -> startOfDay ();
$currentPeriodClosingDate = Carbon :: create ( $now -> copy () -> addMonth () -> year , $now -> copy () -> addMonth () -> month , $closingDay ) -> endOfDay ();
2025-05-05 19:40:38 +07:00
} else {
2025-06-19 19:39:20 +07:00
$currentPeriodStartDate = Carbon :: create ( $now -> copy () -> subMonth () -> year , $now -> copy () -> subMonth () -> month , $startDay ) -> startOfDay ();
$currentPeriodClosingDate = Carbon :: create ( $now -> year , $now -> month , $closingDay ) -> endOfDay ();
2025-05-05 19:40:38 +07:00
}
2025-01-18 15:01:28 +07:00
2025-06-18 19:15:53 +07:00
// Calculate the actual start date for data retrieval (1 month before currentPeriodStartDate)
2025-06-26 19:47:57 +07:00
$dataRetrievalStartDate = $currentPeriodStartDate -> copy () -> subMonth () -> subDay () -> startOfDay ();
2025-06-18 19:15:53 +07:00
$forms = FormUpCountry :: whereBetween ( 'tanggal' , [ $dataRetrievalStartDate , $currentPeriodClosingDate ])
-> orderBy ( 'tanggal' , 'desc' )
-> with ([ 'rayon' , 'user' ])
-> get ();
2025-01-12 13:34:35 +07:00
2025-01-11 13:31:44 +07:00
$availableBudget = null ;
2025-10-13 00:14:28 +07:00
$userRegionData = auth () -> user () -> getMyCabangAndRegionId ();
$regionContextId = $userRegionData [ 'region' ] !== '-' ? $userRegionData [ 'region' ] : null ;
$cabangContextId = $userRegionData [ 'cabang' ] !== '-' ? $userRegionData [ 'cabang' ] : null ;
2025-06-18 19:15:53 +07:00
$periodeMonth = strtolower ( $currentPeriodStartDate -> format ( 'F' ));
$periodeYear = ( int ) $currentPeriodStartDate -> format ( 'Y' );
if ( $role == 'Admin Region' || $role == 'Marketing Operational Manager Region' ) {
2025-10-13 00:14:28 +07:00
if ( $regionContextId ) {
$users = UserHasCabang :: whereHas ( 'cabang' , function ( $query ) use ( $regionContextId ) {
$query -> where ( 'region_id' , $regionContextId );
2025-06-18 19:15:53 +07:00
}) -> get ();
$userIds = $users -> pluck ( 'user_id' ) -> toArray ();
$forms = $forms -> whereIn ( 'user_id' , $userIds );
}
} else if ( $role == 'Area Manager Cabang' ) {
2025-10-13 00:14:28 +07:00
if ( $cabangContextId ) {
$users = UserHasCabang :: where ( 'cabang_id' , $cabangContextId ) -> get ();
2025-06-18 19:15:53 +07:00
$userIds = $users -> pluck ( 'user_id' ) -> toArray ();
$forms = $forms -> whereIn ( 'user_id' , $userIds );
2025-10-13 00:14:28 +07:00
$availableBudget = BudgetHelper :: getAvailableBudget ( $cabangContextId , $periodeMonth , $periodeYear );
2025-06-18 19:15:53 +07:00
}
} else if ( $role == 'Medical Representatif' ) {
$forms = $forms -> where ( 'user_id' , auth () -> user () -> id );
$cabang_id = UserHasCabang :: where ( 'user_id' , auth () -> user () -> id ) -> first () -> cabang_id ;
2025-05-25 15:14:23 +07:00
$availableBudget = BudgetHelper :: getAvailableBudget ( $cabang_id , $periodeMonth , $periodeYear );
2025-06-18 19:15:53 +07:00
} else {
$forms = $forms ;
2024-12-23 10:10:09 +07:00
}
2025-10-13 00:14:28 +07:00
$outstandingForms = OutstandingHelper :: filterForms (
$forms ,
$role ,
$currentPeriodStartDate ,
$currentPeriodClosingDate ,
[
'region_id' => $regionContextId ,
'cabang_id' => $cabangContextId ,
]
);
2025-10-12 20:47:43 +07:00
2024-12-26 13:28:06 +07:00
session () -> put ( 'redirect_url' , route ( 'forms.up-country' ));
2025-06-18 19:15:53 +07:00
return view ( 'backend.pages.forms.upcountry.index' , [
'forms' => $forms ,
2025-10-12 20:47:43 +07:00
'availableBudget' => $availableBudget ,
'outstandingCount' => $outstandingForms -> count (),
'outstandingTotal' => $outstandingForms -> sum ( 'total' ),
2025-10-13 00:14:28 +07:00
'outstandingIds' => $outstandingForms -> pluck ( 'id' ) -> toArray (),
2025-06-18 19:15:53 +07:00
]);
}
2024-12-12 18:18:26 +07:00
2025-01-02 17:59:33 +07:00
public function detail ( $id )
{
$this -> checkAuthorization ( auth () -> user (), [ 'forms.country.view' ]);
$form = FormUpCountry :: with ([ 'rayon' , 'user' ]) -> findOrFail ( $id );
2025-01-03 21:37:58 +07:00
$form -> bukti_allowance = $form -> bukti_allowance ? NextCloudHelper :: getFileUrl ( $form -> bukti_allowance ) : null ;
$form -> bukti_transport_dalkot = $form -> bukti_transport_dalkot ? NextCloudHelper :: getFileUrl ( $form -> bukti_transport_dalkot ) : null ;
$form -> bukti_transport_ankot = $form -> bukti_transport_ankot ? NextCloudHelper :: getFileUrl ( $form -> bukti_transport_ankot ) : null ;
$form -> bukti_hotel = $form -> bukti_hotel ? NextCloudHelper :: getFileUrl ( $form -> bukti_hotel ) : null ;
2025-06-18 19:15:53 +07:00
$form -> uc_plan = $form -> uc_plan ? NextCloudHelper :: getFileUrl ( $form -> uc_plan ) : null ;
2025-01-03 21:37:58 +07:00
2025-01-02 17:59:33 +07:00
return response () -> json ( $form );
}
2025-01-04 13:53:53 +07:00
public function view ( $id )
{
$this -> checkAuthorization ( auth () -> user (), [ 'forms.country.view' ]);
2025-10-13 14:55:46 +07:00
$form = FormUpCountry :: with ([ 'rayon' , 'user' , 'attachments' ]) -> findOrFail ( $id );
2025-01-04 13:53:53 +07:00
$form -> bukti_allowance = $form -> bukti_allowance ? NextCloudHelper :: getFileUrl ( $form -> bukti_allowance ) : null ;
$form -> bukti_transport_dalkot = $form -> bukti_transport_dalkot ? NextCloudHelper :: getFileUrl ( $form -> bukti_transport_dalkot ) : null ;
$form -> bukti_transport_ankot = $form -> bukti_transport_ankot ? NextCloudHelper :: getFileUrl ( $form -> bukti_transport_ankot ) : null ;
$form -> bukti_hotel = $form -> bukti_hotel ? NextCloudHelper :: getFileUrl ( $form -> bukti_hotel ) : null ;
2025-06-18 19:15:53 +07:00
$form -> uc_plan = $form -> uc_plan ? NextCloudHelper :: getFileUrl ( $form -> uc_plan ) : null ;
2025-01-04 13:53:53 +07:00
2025-10-13 14:55:46 +07:00
$attachments = $form -> attachments -> map ( function ( $attachment ) {
$filePath = $attachment -> file_path ;
$extension = strtolower ( pathinfo ( $filePath , PATHINFO_EXTENSION ));
return [
'id' => $attachment -> id ,
'file_category' => $attachment -> file_category ,
'file_path' => $filePath ,
'download_url' => $filePath ? NextCloudHelper :: getFileUrl ( $filePath ) : null ,
'preview_url' => $filePath ? NextCloudHelper :: getPreviewUrl ( $filePath ) : null ,
'preview_type' => in_array ( $extension , [ 'jpg' , 'jpeg' , 'png' ]) ? 'image' : 'pdf' ,
'filename' => $filePath ? basename ( $filePath ) : null ,
'extension' => $extension ,
];
}) -> values ();
2025-01-04 13:53:53 +07:00
return view ( 'backend.pages.forms.upcountry.view' , [
'form' => $form ,
2025-01-09 14:17:28 +07:00
'rayons' => Rayon :: all (),
2025-10-13 14:55:46 +07:00
'attachments' => $attachments ,
2025-01-04 13:53:53 +07:00
]);
}
2026-06-01 15:01:03 +07:00
public function create ()
{
2024-12-16 17:01:55 +07:00
$this -> checkAuthorization ( auth () -> user (), [ 'forms.country.create' ]);
2025-10-23 17:21:46 +07:00
$rayons = $this -> getAccessibleRayonsForCurrentUser ();
2024-12-16 17:01:55 +07:00
2026-06-01 15:01:03 +07:00
return view ( 'backend.pages.forms.upcountry.create' , [
'rayons' => $rayons ,
2025-10-23 17:21:46 +07:00
'attachmentCategories' => array_keys ( $this -> attachmentCategories ),
'attachmentCategoryLabels' => $this -> attachmentCategories ,
2026-06-01 15:01:03 +07:00
]);
}
2024-12-12 18:18:26 +07:00
2026-06-01 15:01:03 +07:00
public function store ( Request $request )
{
2024-12-16 17:01:55 +07:00
$this -> checkAuthorization ( auth () -> user (), [ 'forms.country.create' ]);
$request -> validate ([
'rayon_id' => 'required|exists:rayon,id' ,
'tanggal' => 'required' ,
'tujuan' => 'required' ,
'jarak' => 'required' ,
2025-01-03 21:37:58 +07:00
'allowance' => 'nullable|numeric' ,
'transport_dalkot' => 'nullable|numeric' ,
'transport_ankot' => 'nullable|numeric' ,
'hotel' => 'nullable|numeric' ,
2025-10-13 14:55:46 +07:00
'attachments' => 'nullable|array' ,
2025-10-23 17:21:46 +07:00
'attachments.*.file_category' => 'required|string|in:' . implode ( ',' , array_keys ( $this -> attachmentCategories )),
2025-10-13 14:55:46 +07:00
'attachments.*.file_path' => [ 'nullable' , 'file' , 'mimes:jpg,jpeg,png,pdf' , 'max:5120' ],
2025-06-18 19:15:53 +07:00
'uc_plan' => 'nullable|file|max:51200' ,
2024-12-16 17:01:55 +07:00
]);
2025-05-05 19:40:38 +07:00
$tanggal = Carbon :: parse ( $request -> tanggal );
2026-06-01 15:01:03 +07:00
// SINKRONISASI JENDELA INPUT ATURAN BISNIS: 11 s/d 10 Bulan Berikutnya
2025-05-05 19:40:38 +07:00
$startDay = ( int ) env ( 'STARTING_DATE' , 11 );
2026-06-01 15:01:03 +07:00
$inputMaxDay = ( int ) env ( 'INPUT_MAX_DATE' , 10 );
2025-05-05 19:40:38 +07:00
$now = Carbon :: now ();
if ( $now -> day >= $startDay ) {
2026-06-01 15:01:03 +07:00
$startDate = Carbon :: create ( $now -> year , $now -> month , $startDay ) -> startOfDay ();
$maxInputDate = Carbon :: create ( $now -> copy () -> addMonth () -> year , $now -> copy () -> addMonth () -> month , $inputMaxDay ) -> endOfDay ();
2025-05-05 19:40:38 +07:00
} else {
2026-06-01 15:01:03 +07:00
$startDate = Carbon :: create ( $now -> copy () -> subMonth () -> year , $now -> copy () -> subMonth () -> month , $startDay ) -> startOfDay ();
$maxInputDate = Carbon :: create ( $now -> year , $now -> month , $inputMaxDay ) -> endOfDay ();
2025-05-05 19:40:38 +07:00
}
2025-01-18 15:01:28 +07:00
2026-06-01 15:01:03 +07:00
// Blokade Sistem: Tolak input jika berada di luar siklus pengisian aktif
if ( $now -> lt ( $startDate ) || $now -> gt ( $maxInputDate )) {
session () -> flash ( 'error' , " Gagal, pengajuan saat ini sudah ditutup. Periode pengisian aktif hanya diizinkan dari tanggal { $startDay } sampai tanggal { $inputMaxDay } berikutnya. " );
return redirect () -> back () -> withInput ();
2025-01-18 15:01:28 +07:00
}
2025-05-25 15:14:23 +07:00
$cabang = UserHasCabang :: with ( 'cabang' ) -> where ( 'user_id' , auth () -> user () -> id ) -> first () ? -> cabang ;
if ( ! $cabang ) {
session () -> flash ( 'error' , 'Cabang tidak ditemukan.' );
return redirect () -> back ();
}
2024-12-16 17:01:55 +07:00
2025-05-25 15:14:23 +07:00
$region = Region :: find ( $cabang -> region_id );
2026-06-01 15:01:03 +07:00
// Optimasi pencarian kategori menggunakan LIKE fleksibel untuk menjamin ID ditemukan
$kategori = Kategori :: where ( 'name' , 'LIKE' , '%Up Country%' ) -> first ();
2024-12-16 17:01:55 +07:00
2025-05-25 15:14:23 +07:00
// Hitung periode budget dari tanggal input
if ( $tanggal -> day >= $startDay ) {
$periodeDate = Carbon :: create ( $tanggal -> year , $tanggal -> month , $startDay );
} else {
$periodeDate = Carbon :: create ( $tanggal -> copy () -> subMonth () -> year , $tanggal -> copy () -> subMonth () -> month , $startDay );
}
2024-12-18 12:56:16 +07:00
2025-05-25 15:14:23 +07:00
$periodeMonth = strtolower ( $periodeDate -> format ( 'F' ));
$periodeYear = ( int ) $periodeDate -> format ( 'Y' );
2024-12-18 12:56:16 +07:00
2025-10-13 14:55:46 +07:00
$data_nominal = [
'allowance' => $request -> allowance ? ? 0 ,
'transport_dalkot' => $request -> transport_dalkot ? ? 0 ,
'transport_ankot' => $request -> transport_ankot ? ? 0 ,
'hotel' => $request -> hotel ? ? 0 ,
];
2024-12-16 17:01:55 +07:00
2025-10-13 14:55:46 +07:00
$totalNominal = array_sum ( $data_nominal );
2025-01-03 21:37:58 +07:00
2025-10-13 14:55:46 +07:00
// Validasi budget berdasarkan periode
$availableBudget = BudgetHelper :: getAvailableBudget ( $cabang -> id , $periodeMonth , $periodeYear );
if ( $totalNominal > $availableBudget ) {
session () -> flash ( 'error' , 'Alokasi budget bulanan cabang tidak mencukupi,silahkan ajukan pada periode expense berikutnya' );
return redirect () -> back ();
2025-01-03 21:37:58 +07:00
}
2024-12-18 12:56:16 +07:00
2025-10-13 14:55:46 +07:00
$folderPath = '/expense/' . $region -> code . '/' . $cabang -> code . '/upcountry' ;
2024-12-18 12:56:16 +07:00
2025-10-13 14:55:46 +07:00
$ucPlanPath = null ;
2024-12-16 17:01:55 +07:00
2025-06-18 19:15:53 +07:00
if ( $request -> hasFile ( 'uc_plan' )) {
$ucPlan = $request -> file ( 'uc_plan' );
$filename = Str :: uuid () . '.' . $ucPlan -> extension ();
2025-10-13 14:55:46 +07:00
$ucPlanPath = $folderPath . '/' . $filename ;
2025-06-18 19:15:53 +07:00
NextCloudHelper :: uploadFile ( $folderPath , $ucPlan -> getContent (), $filename );
}
2025-10-13 14:55:46 +07:00
$attachmentsPayload = $this -> buildAttachmentPayload ( $request , $folderPath );
2024-12-12 18:18:26 +07:00
2025-10-13 14:55:46 +07:00
// Generate expense number
DB :: beginTransaction ();
try {
$sequence_number = FormUpCountry :: withTrashed ()
-> where ( 'expense_number' , 'like' , $region -> code . '-' . $cabang -> code . '-UPC-' . date ( 'ym' ) . '%' )
-> count () + 1 ;
$formatted_sequence_number = str_pad ( $sequence_number , 4 , '0' , STR_PAD_LEFT );
$expense_number = $region -> code . '-' . $cabang -> code . '-UPC-' . date ( 'ym' ) . $formatted_sequence_number ;
$form = FormUpCountry :: create ([
'expense_number' => $expense_number ,
'user_id' => auth () -> user () -> id ,
2026-06-01 15:01:03 +07:00
'cabang_id' => $cabang -> id , // <--- REPARASI: Memasukkan Cabang ID milik user pengaju secara instan
'kategori_id' => $kategori -> id ? ? null , // SUNTIK KATEGORI_ID OTOMATIS BERHASIL
2025-10-13 14:55:46 +07:00
'rayon_id' => $request -> rayon_id ,
'tanggal' => $request -> tanggal ,
'tujuan' => $request -> tujuan ,
'jarak' => $request -> jarak ,
'allowance' => $data_nominal [ 'allowance' ],
'transport_dalkot' => $data_nominal [ 'transport_dalkot' ],
'transport_ankot' => $data_nominal [ 'transport_ankot' ],
'hotel' => $data_nominal [ 'hotel' ],
'total' => $totalNominal ,
'bukti_allowance' => null ,
'bukti_transport_dalkot' => null ,
'bukti_transport_ankot' => null ,
'bukti_hotel' => null ,
'uc_plan' => $ucPlanPath ,
2026-06-01 15:01:03 +07:00
'account_number' => $kategori -> account_number ? ? null ,
2025-10-13 14:55:46 +07:00
'status' => 'On Progress' ,
]);
if ( ! empty ( $attachmentsPayload )) {
$this -> attachmentService -> addMultipleAttachments (
$form -> id ,
2026-06-01 15:01:03 +07:00
$dynamicTable = AttachmentTableName :: FORM_UP_COUNTRY ,
2025-10-13 14:55:46 +07:00
$attachmentsPayload
);
}
2025-01-03 21:37:58 +07:00
2025-10-13 14:55:46 +07:00
DB :: commit ();
} catch ( \Throwable $th ) {
DB :: rollBack ();
Log :: error ( 'Failed to store Form Up Country' , [
'message' => $th -> getMessage (),
'trace' => $th -> getTraceAsString (),
]);
2025-05-25 15:14:23 +07:00
2025-10-13 14:55:46 +07:00
session () -> flash ( 'error' , 'Terjadi kesalahan saat menyimpan data, silakan coba lagi.' );
return redirect () -> back () -> withInput ();
2025-01-09 14:17:28 +07:00
}
2025-01-16 16:42:06 +07:00
// Send notification to MIS and Admin Region and Area Manager Cabang
2025-03-03 20:13:30 +07:00
$roles = [ 'Marketing Information System' , 'Admin Region' , 'Area Manager Cabang' ];
2025-01-16 16:42:06 +07:00
$recipients = [ $form -> user -> email , auth () -> user () -> email ];
$phoneNumbers = [ $form -> user -> phone , auth () -> user () -> phone ];
2025-01-17 16:17:53 +07:00
$receiver_ids = [];
2025-01-09 13:22:56 +07:00
$name = auth () -> user () -> name ;
$tanggal = $form -> tanggal ;
$total = $form -> total ;
// Collect all recipients (emails and phone numbers) for the specified roles
2025-03-04 12:24:36 +07:00
$cabang = UserHasCabang :: where ( 'user_id' , $form -> user_id ) -> first ();
$cabang_id = $cabang ? $cabang -> cabang_id : null ;
$cabangData = $cabang_id ? Cabang :: where ( 'id' , $cabang_id ) -> first () : null ;
$region_id = $cabangData ? $cabangData -> region -> id : null ;
2025-01-09 13:22:56 +07:00
foreach ( $roles as $role ) {
2025-03-04 12:24:36 +07:00
$users = Admin :: getUserByRoleName ( $role ) -> filter ( function ( $user ) use ( $role , $cabang_id , $region_id ) {
2025-01-16 16:42:06 +07:00
if ( $role === 'Marketing Information System' ) {
2026-06-01 15:01:03 +07:00
return true ;
2025-03-04 12:24:36 +07:00
}
if ( ! $user -> cabang || ! $user -> cabang -> cabang ) {
2026-06-01 15:01:03 +07:00
return false ;
2025-01-16 16:42:06 +07:00
}
2025-03-04 12:24:36 +07:00
if ( $role === 'Admin Region' || $role === 'Marketing Operational Manager Region' ) {
return $user -> cabang -> cabang -> region -> id === $region_id ;
}
return $user -> cabang -> cabang -> id === $cabang_id ;
2025-01-16 16:42:06 +07:00
});
2025-01-09 13:22:56 +07:00
foreach ( $users as $user ) {
$recipients [] = $user -> email ;
$phoneNumbers [] = $user -> phone ;
2025-01-17 16:17:53 +07:00
$receiver_ids [] = $user -> id ;
2025-01-09 13:22:56 +07:00
}
}
// Remove duplicate email addresses, if any
$recipients = array_unique ( $recipients );
2025-01-16 16:42:06 +07:00
$phoneNumbers = array_unique ( $phoneNumbers );
2025-01-17 16:17:53 +07:00
$receiver_ids = array_unique ( $receiver_ids );
2025-01-09 13:22:56 +07:00
// Generate URL and send WhatsApp notification
$url = route ( 'forms.up-country.view' , $form -> id );
2025-01-16 15:56:22 +07:00
WhatsappHelper :: newExpense ( $phoneNumbers , $expense_number , $url , $tanggal , $total , $name );
2025-01-09 13:22:56 +07:00
// Send bulk email
2025-03-05 13:44:58 +07:00
$brevoService = app ( BrevoService :: class );
2025-01-23 17:46:38 +07:00
foreach ( $recipients as $recipient ) {
try {
2025-03-05 13:44:58 +07:00
$mail = new ExpenseCreated (
2025-01-23 17:46:38 +07:00
$name ,
$expense_number ,
$tanggal ,
$total ,
$url
2025-03-05 13:44:58 +07:00
);
$response = $brevoService -> expenseCreated ( $recipient , $name , $mail );
if ( isset ( $response [ 'success' ]) && $response [ 'success' ] === false ) {
Log :: warning ( " Email to { $recipient } failed: " . ( $response [ 'message' ] ? ? 'Unknown error' ));
}
2025-01-23 17:46:38 +07:00
} catch ( \Exception $e ) {
Log :: error ( " Failed to send email to { $recipient } : " . $e -> getMessage ());
continue ;
}
}
2025-01-09 13:22:56 +07:00
2025-01-23 17:46:38 +07:00
NotificationHelper :: newExpense ( 'up-country' , $form -> id , $form -> user_id , $expense_number , $receiver_ids );
2025-01-17 16:17:53 +07:00
2025-01-09 13:22:56 +07:00
AuditTrailHelper :: AddAuditTrail ( 'Insert' , 'Insert New Expense at Form Up Country (' . $expense_number . ')' );
2024-12-16 17:01:55 +07:00
session () -> flash ( 'success' , 'Form has been created.' );
2024-12-23 10:42:27 +07:00
return redirect () -> back ();
2026-06-01 15:01:03 +07:00
}
2024-12-12 18:18:26 +07:00
2026-06-01 15:01:03 +07:00
public function edit ( $id )
{
2024-12-16 17:01:55 +07:00
$this -> checkAuthorization ( auth () -> user (), [ 'forms.country.edit' ]);
2025-01-07 17:23:42 +07:00
$role = auth () -> user () -> getRoleNames ()[ 0 ];
2024-12-16 17:01:55 +07:00
2025-10-23 17:21:46 +07:00
$rayonData = $this -> getAccessibleRayonsForCurrentUser ();
2025-10-12 18:29:07 +07:00
2025-10-23 17:21:46 +07:00
$form = FormUpCountry :: with ([ 'rayon.cabang' , 'attachments' ]) -> findOrfail ( $id );
2025-01-16 15:56:22 +07:00
if (( $form -> status != 'On Progress' && $form -> status != 'Rejected' ) && $role == 'Medical Representatif' ) {
session () -> flash ( 'error' , 'You cannot edit this form because it has been approved or closed.' );
2024-12-24 11:23:27 +07:00
return redirect () -> back ();
}
2025-10-13 14:55:46 +07:00
2025-10-23 17:21:46 +07:00
if ( $form -> rayon && ! $rayonData -> contains ( 'id' , $form -> rayon_id )) {
$rayonData -> push ( $form -> rayon );
$rayonData = $rayonData -> sortBy ( 'name' ) -> values ();
}
2025-10-13 14:55:46 +07:00
$existingAttachments = $form -> attachments -> map ( function ( $attachment ) {
$filePath = $attachment -> file_path ;
$extension = strtolower ( pathinfo ( $filePath , PATHINFO_EXTENSION ));
return [
'id' => $attachment -> id ,
'file_category' => $attachment -> file_category ,
'file_path' => $filePath ,
'download_url' => $filePath ? NextCloudHelper :: getFileUrl ( $filePath ) : null ,
'preview_url' => $filePath ? NextCloudHelper :: getPreviewUrl ( $filePath ) : null ,
'preview_type' => in_array ( $extension , [ 'jpg' , 'jpeg' , 'png' ]) ? 'image' : 'pdf' ,
'filename' => $filePath ? basename ( $filePath ) : null ,
'extension' => $extension ,
];
}) -> values ();
2026-06-01 15:01:03 +07:00
return view ( 'backend.pages.forms.upcountry.edit' , [
'rayons' => $rayonData ,
2025-10-13 14:55:46 +07:00
'form' => $form ,
'attachments' => $existingAttachments ,
2025-10-23 17:21:46 +07:00
'attachmentCategories' => array_keys ( $this -> attachmentCategories ),
'attachmentCategoryLabels' => $this -> attachmentCategories ,
2026-06-01 15:01:03 +07:00
]);
}
2024-12-12 18:18:26 +07:00
2025-10-23 17:21:46 +07:00
protected function getAccessibleRayonsForCurrentUser ()
{
$user = auth () -> user ();
$role = $user -> getRoleNames () -> first ();
$rayonQuery = Rayon :: with ( 'cabang' ) -> orderBy ( 'name' );
if ( in_array ( $role , [ 'Superadmin' , 'Marketing Information System' ], true )) {
return $rayonQuery -> get ();
}
$userRegionData = $user -> getMyCabangAndRegionId ();
$regionContextId = $userRegionData [ 'region' ] !== '-' ? $userRegionData [ 'region' ] : null ;
$cabangContextId = $userRegionData [ 'cabang' ] !== '-' ? $userRegionData [ 'cabang' ] : null ;
if ( $role === 'Admin Region' && $regionContextId ) {
return $rayonQuery
-> whereHas ( 'cabang' , function ( $query ) use ( $regionContextId ) {
$query -> where ( 'region_id' , $regionContextId );
})
-> get ();
}
if ( $cabangContextId ) {
return $rayonQuery
-> where ( 'cabang_id' , $cabangContextId )
-> get ();
}
return collect ();
}
2025-10-13 14:55:46 +07:00
public function update ( Request $request , $id )
2026-06-01 15:01:03 +07:00
{
2024-12-16 17:01:55 +07:00
$this -> checkAuthorization ( auth () -> user (), [ 'forms.country.edit' ]);
2025-01-07 17:23:42 +07:00
$role = auth () -> user () -> getRoleNames ()[ 0 ];
2024-12-16 17:01:55 +07:00
$request -> validate ([
'rayon_id' => 'required|exists:rayon,id' ,
'tanggal' => 'required' ,
'tujuan' => 'required' ,
'jarak' => 'required' ,
2025-01-03 21:37:58 +07:00
'allowance' => 'nullable|numeric' ,
'transport_dalkot' => 'nullable|numeric' ,
'transport_ankot' => 'nullable|numeric' ,
'hotel' => 'nullable|numeric' ,
2025-10-13 14:55:46 +07:00
'attachments' => 'nullable|array' ,
2025-10-23 17:21:46 +07:00
'attachments.*.file_category' => 'required|string|in:' . implode ( ',' , array_keys ( $this -> attachmentCategories )),
2025-10-13 14:55:46 +07:00
'attachments.*.file_path' => [ 'nullable' , 'file' , 'mimes:jpg,jpeg,png,pdf' , 'max:5120' ],
2025-06-18 19:15:53 +07:00
'uc_plan' => 'nullable|file|max:51200' ,
2024-12-16 17:01:55 +07:00
]);
2025-05-25 15:14:23 +07:00
$form = FormUpCountry :: findOrFail ( $id );
if (( $form -> status !== 'On Progress' && $form -> status !== 'Rejected' ) && $role === 'Medical Representatif' ) {
2025-01-16 15:56:22 +07:00
session () -> flash ( 'error' , 'You cannot edit this form because it has been approved or closed.' );
2024-12-24 11:23:27 +07:00
return redirect () -> back ();
}
2025-05-25 15:14:23 +07:00
$cabang = UserHasCabang :: with ( 'cabang' ) -> where ( 'user_id' , $form -> user_id ) -> first () ? -> cabang ;
if ( ! $cabang ) {
session () -> flash ( 'error' , 'Cabang tidak ditemukan.' );
return redirect () -> back ();
}
2026-06-01 15:01:03 +07:00
// PEMBATASAN INPUT MENGGUNAKAN STARTING_DATE & INPUT_MAX_DATE
$startDay = ( int ) env ( 'STARTING_DATE' , 11 );
$inputMaxDay = ( int ) env ( 'INPUT_MAX_DATE' , 10 );
$now = Carbon :: now ();
if ( $now -> day >= $startDay ) {
$startDate = Carbon :: create ( $now -> year , $now -> month , $startDay ) -> startOfDay ();
$maxInputDate = Carbon :: create ( $now -> copy () -> addMonth () -> year , $now -> copy () -> addMonth () -> month , $inputMaxDay ) -> endOfDay ();
} else {
$startDate = Carbon :: create ( $now -> copy () -> subMonth () -> year , $now -> copy () -> subMonth () -> month , $startDay ) -> startOfDay ();
$maxInputDate = Carbon :: create ( $now -> year , $now -> month , $inputMaxDay ) -> endOfDay ();
}
if ( $now -> lt ( $startDate ) || $now -> gt ( $maxInputDate )) {
session () -> flash ( 'error' , " Gagal, pengajuan saat ini sudah ditutup. Periode pengisian aktif hanya diizinkan dari tanggal { $startDay } sampai tanggal { $inputMaxDay } berikutnya. " );
return redirect () -> back () -> withInput ();
}
2025-05-25 15:14:23 +07:00
$region = Region :: find ( $cabang -> region_id );
2026-06-01 15:01:03 +07:00
// Optimasi pencarian kategori menggunakan LIKE fleksibel untuk menjamin ID ditemukan
$kategori = Kategori :: where ( 'name' , 'LIKE' , '%Up Country%' ) -> first ();
2024-12-16 17:01:55 +07:00
2025-05-25 15:14:23 +07:00
// Hitung periode dari tanggal input
$tanggal = Carbon :: parse ( $request -> tanggal );
if ( $tanggal -> day >= $startDay ) {
$periodeDate = Carbon :: create ( $tanggal -> year , $tanggal -> month , $startDay );
} else {
$periodeDate = Carbon :: create ( $tanggal -> copy () -> subMonth () -> year , $tanggal -> copy () -> subMonth () -> month , $startDay );
}
$periodeMonth = strtolower ( $periodeDate -> format ( 'F' ));
$periodeYear = ( int ) $periodeDate -> format ( 'Y' );
2024-12-19 10:51:27 +07:00
$folderPath = '/expense/' . $region -> code . '/' . $cabang -> code . '/upcountry' ;
2024-12-16 17:01:55 +07:00
2025-10-13 14:55:46 +07:00
$ucPlanPath = null ;
2025-01-03 21:37:58 +07:00
2025-06-18 19:15:53 +07:00
if ( $request -> hasFile ( 'uc_plan' )) {
$file = $request -> file ( 'uc_plan' );
$filename = Str :: uuid () . '.' . $file -> extension ();
2025-10-13 14:55:46 +07:00
$ucPlanPath = $folderPath . '/' . $filename ;
2025-06-18 19:15:53 +07:00
NextCloudHelper :: uploadFile ( $folderPath , $file -> getContent (), $filename );
}
2025-10-13 14:55:46 +07:00
$attachmentsPayload = $this -> buildAttachmentPayload ( $request , $folderPath );
2025-05-25 15:14:23 +07:00
// Hitung total nominal
2025-01-03 21:37:58 +07:00
$data_nominal = [
'allowance' => $request -> allowance ? ? 0 ,
'transport_dalkot' => $request -> transport_dalkot ? ? 0 ,
'transport_ankot' => $request -> transport_ankot ? ? 0 ,
'hotel' => $request -> hotel ? ? 0 ,
];
2025-05-25 15:14:23 +07:00
$totalNominal = array_sum ( $data_nominal );
2024-12-16 17:01:55 +07:00
2025-05-25 15:14:23 +07:00
// Validasi budget dengan periode
$availableBudget = BudgetHelper :: getAvailableBudget ( $cabang -> id , $periodeMonth , $periodeYear );
2026-06-01 15:01:03 +07:00
if ( $totalNominal > $availableBudget ) {
2025-01-09 14:17:28 +07:00
session () -> flash ( 'error' , 'The total nominal exceeds the available budget.' );
return redirect () -> back ();
}
2024-12-16 17:01:55 +07:00
// Save the form data
2025-10-13 14:55:46 +07:00
DB :: beginTransaction ();
try {
$form -> update ([
'rayon_id' => $request -> rayon_id ,
2026-06-01 15:01:03 +07:00
'cabang_id' => $cabang -> id , // <--- REPARASI: Memastikan Cabang ID tersimpan akurat saat update data
'kategori_id' => $kategori -> id ? ? null , // RE-SUNTIK KATEGORI_ID SAAT UPDATE
2025-10-13 14:55:46 +07:00
'tanggal' => $request -> tanggal ,
'tujuan' => $request -> tujuan ,
'jarak' => $request -> jarak ,
'allowance' => $data_nominal [ 'allowance' ],
'transport_dalkot' => $data_nominal [ 'transport_dalkot' ],
'transport_ankot' => $data_nominal [ 'transport_ankot' ],
'hotel' => $data_nominal [ 'hotel' ],
'total' => $totalNominal ,
'uc_plan' => $ucPlanPath ? ? $form -> uc_plan ,
2026-06-01 15:01:03 +07:00
'account_number' => $kategori -> account_number ? ? $form -> account_number ,
2025-10-13 14:55:46 +07:00
]);
if ( ! empty ( $attachmentsPayload )) {
$this -> attachmentService -> addMultipleAttachments (
$form -> id ,
AttachmentTableName :: FORM_UP_COUNTRY ,
$attachmentsPayload
);
}
DB :: commit ();
} catch ( \Throwable $th ) {
DB :: rollBack ();
Log :: error ( 'Failed to update Form Up Country' , [
'message' => $th -> getMessage (),
'trace' => $th -> getTraceAsString (),
]);
session () -> flash ( 'error' , 'Terjadi kesalahan saat memperbarui data, silakan coba lagi.' );
return redirect () -> back () -> withInput ();
}
2024-12-12 18:18:26 +07:00
2025-01-09 13:22:56 +07:00
AuditTrailHelper :: AddAuditTrail ( 'Update' , 'Update Record at Form Up Country (' . $form -> expense_number . ')' );
2024-12-16 17:01:55 +07:00
session () -> flash ( 'success' , 'Form has been updated.' );
2024-12-23 10:42:27 +07:00
return redirect () -> back ();
2026-06-01 15:01:03 +07:00
}
2024-12-12 18:18:26 +07:00
2025-10-13 14:55:46 +07:00
public function deleteAttachment ( Request $request , $formId , $attachmentId )
{
$this -> checkAuthorization ( auth () -> user (), [ 'forms.country.edit' ]);
$form = FormUpCountry :: findOrFail ( $formId );
$role = auth () -> user () -> getRoleNames ()[ 0 ];
if (( $form -> status !== 'On Progress' && $form -> status !== 'Rejected' ) && $role === 'Medical Representatif' ) {
return response () -> json ([ 'message' => 'You cannot modify attachments once the form is approved or closed.' ], 403 );
}
$attachment = AttachmentForm :: where ( 'id' , $attachmentId )
-> where ( 'form_id' , $form -> id )
-> where ( 'table_name' , AttachmentTableName :: FORM_UP_COUNTRY )
-> first ();
if ( ! $attachment ) {
return response () -> json ([ 'message' => 'Attachment not found.' ], 404 );
}
$this -> attachmentService -> deleteAttachment ( $attachment -> id );
AuditTrailHelper :: AddAuditTrail ( 'Delete Attachment' , 'Delete Attachment at Form Up Country (' . $form -> expense_number . ')' );
return response () -> json ([ 'message' => 'Attachment deleted successfully.' ]);
}
protected function buildAttachmentPayload ( Request $request , string $folderPath ) : array
{
$payload = [];
$attachmentInputs = $request -> input ( 'attachments' , []);
$attachmentFiles = $request -> file ( 'attachments' , []);
foreach ( $attachmentFiles as $index => $fileGroup ) {
$uploadedFile = $fileGroup [ 'file_path' ] ? ? null ;
if ( ! $uploadedFile ) {
continue ;
}
$category = $attachmentInputs [ $index ][ 'file_category' ] ? ? null ;
if ( ! $category ) {
continue ;
}
$filename = Str :: uuid () . '.' . $uploadedFile -> extension ();
$filePath = $folderPath . '/' . $filename ;
NextCloudHelper :: uploadFile ( $folderPath , $uploadedFile -> getContent (), $filename );
$payload [] = [
'file_category' => $category ,
'file_path' => $filePath ,
];
}
return $payload ;
}
2025-01-23 17:46:38 +07:00
public function approve ( Request $request , $id )
2024-12-23 10:10:09 +07:00
{
$this -> checkAuthorization ( auth () -> user (), [ 'approval.approve' ]);
$form = FormUpCountry :: findOrfail ( $id );
2025-01-23 17:46:38 +07:00
$approved_data = [
'allowance' => $request -> allowance ? $form -> allowance : 0 ,
'transport_dalkot' => $request -> transport_dalkot ? $form -> transport_dalkot : 0 ,
'transport_ankot' => $request -> transport_ankot ? $form -> transport_ankot : 0 ,
'hotel' => $request -> hotel ? $form -> hotel : 0 ,
];
2024-12-23 10:10:09 +07:00
$form -> update ([
'approved_at' => now (),
'approved_by' => auth () -> user () -> id ,
2025-01-16 15:56:22 +07:00
'status' => 'Approved 1' ,
2025-01-23 17:46:38 +07:00
'approved_allowance' => $approved_data [ 'allowance' ],
'approved_transport_dalkot' => $approved_data [ 'transport_dalkot' ],
'approved_transport_ankot' => $approved_data [ 'transport_ankot' ],
'approved_hotel' => $approved_data [ 'hotel' ],
'approved_total' => array_sum ( $approved_data ),
2024-12-23 10:10:09 +07:00
]);
2024-12-24 15:12:24 +07:00
$name = $form -> user -> name ;
$expense_number = $form -> expense_number ;
$tanggal = $form -> tanggal ;
2025-01-16 15:56:22 +07:00
$total = $form -> total ;
2024-12-24 15:12:24 +07:00
// Collect all recipients
2025-03-03 20:13:30 +07:00
$roles = [ 'Marketing Information System' , 'Admin Region' , 'Area Manager Cabang' , 'Marketing Operational Manager Region' ];
2024-12-26 13:40:17 +07:00
$recipients = [ $form -> user -> email , auth () -> user () -> email ];
$phoneNumbers = [ $form -> user -> phone , auth () -> user () -> phone ];
2025-01-17 16:17:53 +07:00
$receiver_ids = [];
2024-12-24 15:12:24 +07:00
2025-03-04 12:24:36 +07:00
// Collect all recipients (emails and phone numbers) for the specified roles
$cabang = UserHasCabang :: where ( 'user_id' , $form -> user_id ) -> first ();
$cabang_id = $cabang ? $cabang -> cabang_id : null ;
$cabangData = $cabang_id ? Cabang :: where ( 'id' , $cabang_id ) -> first () : null ;
$region_id = $cabangData ? $cabangData -> region -> id : null ;
2025-01-16 16:42:06 +07:00
foreach ( $roles as $role ) {
2025-03-04 12:24:36 +07:00
$users = Admin :: getUserByRoleName ( $role ) -> filter ( function ( $user ) use ( $role , $cabang_id , $region_id ) {
2025-01-16 16:42:06 +07:00
if ( $role === 'Marketing Information System' ) {
2026-06-01 15:01:03 +07:00
return true ;
2025-03-04 12:24:36 +07:00
}
if ( ! $user -> cabang || ! $user -> cabang -> cabang ) {
2026-06-01 15:01:03 +07:00
return false ;
2025-01-16 16:42:06 +07:00
}
2024-12-24 15:12:24 +07:00
2025-03-04 12:24:36 +07:00
if ( $role === 'Admin Region' || $role === 'Marketing Operational Manager Region' ) {
return $user -> cabang -> cabang -> region -> id === $region_id ;
}
return $user -> cabang -> cabang -> id === $cabang_id ;
2025-01-16 16:42:06 +07:00
});
foreach ( $users as $user ) {
$recipients [] = $user -> email ;
$phoneNumbers [] = $user -> phone ;
2025-01-17 16:17:53 +07:00
$receiver_ids [] = $user -> id ;
2025-01-16 16:42:06 +07:00
}
2024-12-24 15:12:24 +07:00
}
2024-12-25 13:37:49 +07:00
// Remove duplicate data, if any
2024-12-24 15:12:24 +07:00
$recipients = array_unique ( $recipients );
2024-12-25 13:37:49 +07:00
$phoneNumbers = array_unique ( $phoneNumbers );
2025-01-17 16:17:53 +07:00
$receiver_ids = array_unique ( $receiver_ids );
2024-12-24 15:12:24 +07:00
2024-12-25 13:37:49 +07:00
// send whatsapp message
2025-01-04 14:09:28 +07:00
$url = route ( 'forms.up-country.view' , $form -> id );
2025-01-16 15:56:22 +07:00
WhatsappHelper :: approveExpense ( $phoneNumbers , $expense_number , $url , $tanggal , $total , $name );
2024-12-25 13:37:49 +07:00
2025-03-05 13:44:58 +07:00
// Send bulk email
$brevoService = app ( BrevoService :: class );
2025-01-23 17:46:38 +07:00
foreach ( $recipients as $recipient ) {
try {
2025-03-05 13:44:58 +07:00
$mail = new ExpenseApproved (
2025-01-23 17:46:38 +07:00
$name ,
$expense_number ,
$tanggal ,
$total ,
$url
2025-03-05 13:44:58 +07:00
);
$response = $brevoService -> expenseApproved ( $recipient , $name , $mail );
if ( isset ( $response [ 'success' ]) && $response [ 'success' ] === false ) {
Log :: warning ( " Email to { $recipient } failed: " . ( $response [ 'message' ] ? ? 'Unknown error' ));
}
2025-01-23 17:46:38 +07:00
} catch ( \Exception $e ) {
Log :: error ( " Failed to send email to { $recipient } : " . $e -> getMessage ());
continue ;
}
}
2025-01-09 13:22:56 +07:00
2025-01-23 17:46:38 +07:00
NotificationHelper :: approveExpense ( 'up-country' , $form -> id , $form -> user_id , $expense_number , $receiver_ids );
2025-01-17 16:17:53 +07:00
2025-01-09 13:22:56 +07:00
AuditTrailHelper :: AddAuditTrail ( 'Approve' , 'Approve Expense at Form Up Country (' . $form -> expense_number . ')' );
2024-12-23 10:10:09 +07:00
session () -> flash ( 'success' , 'Form has been approved.' );
2024-12-23 10:42:27 +07:00
return redirect () -> back ();
2024-12-23 10:10:09 +07:00
}
2025-01-16 15:56:22 +07:00
public function approve2 ( $id )
2024-12-23 10:10:09 +07:00
{
2025-01-16 15:56:22 +07:00
$this -> checkAuthorization ( auth () -> user (), [ 'approval2.approve' ]);
2024-12-23 10:10:09 +07:00
$form = FormUpCountry :: findOrfail ( $id );
$form -> update ([
2025-01-16 15:56:22 +07:00
'approved2_at' => now (),
'approved2_by' => auth () -> user () -> id ,
'status' => 'Approved 2' ,
2024-12-23 10:10:09 +07:00
]);
2024-12-24 15:12:24 +07:00
$name = $form -> user -> name ;
$expense_number = $form -> expense_number ;
$tanggal = $form -> tanggal ;
$total = $form -> total ;
2025-03-03 20:13:30 +07:00
$roles = [ 'Marketing Information System' , 'Area Manager Cabang' , 'Marketing Operational Manager Region' , 'Head of Sales Marketing' ];
2024-12-26 13:40:17 +07:00
$recipients = [ $form -> user -> email , auth () -> user () -> email ];
$phoneNumbers = [ $form -> user -> phone , auth () -> user () -> phone ];
2025-01-17 16:17:53 +07:00
$receiver_ids = [];
2024-12-24 15:12:24 +07:00
2025-03-04 12:24:36 +07:00
// Collect all recipients (emails and phone numbers) for the specified roles
$cabang = UserHasCabang :: where ( 'user_id' , $form -> user_id ) -> first ();
$cabang_id = $cabang ? $cabang -> cabang_id : null ;
$cabangData = $cabang_id ? Cabang :: where ( 'id' , $cabang_id ) -> first () : null ;
$region_id = $cabangData ? $cabangData -> region -> id : null ;
2024-12-24 15:12:24 +07:00
2025-01-16 16:42:06 +07:00
foreach ( $roles as $role ) {
2025-03-04 12:24:36 +07:00
$users = Admin :: getUserByRoleName ( $role ) -> filter ( function ( $user ) use ( $role , $cabang_id , $region_id ) {
if ( $role === 'Marketing Information System' || $role === 'Head of Sales Marketing' ) {
2026-06-01 15:01:03 +07:00
return true ;
2025-03-04 12:24:36 +07:00
}
if ( ! $user -> cabang || ! $user -> cabang -> cabang ) {
2026-06-01 15:01:03 +07:00
return false ;
2025-03-04 12:24:36 +07:00
}
if ( $role === 'Admin Region' || $role === 'Marketing Operational Manager Region' ) {
return $user -> cabang -> cabang -> region -> id === $region_id ;
2025-01-16 16:42:06 +07:00
}
2025-03-04 12:24:36 +07:00
return $user -> cabang -> cabang -> id === $cabang_id ;
2025-01-16 16:42:06 +07:00
});
foreach ( $users as $user ) {
$recipients [] = $user -> email ;
$phoneNumbers [] = $user -> phone ;
2025-01-17 16:17:53 +07:00
$receiver_ids [] = $user -> id ;
2025-01-16 16:42:06 +07:00
}
2024-12-24 15:12:24 +07:00
}
2025-01-16 15:56:22 +07:00
// Remove duplicate data, if any
2024-12-24 15:12:24 +07:00
$recipients = array_unique ( $recipients );
2024-12-25 13:37:49 +07:00
$phoneNumbers = array_unique ( $phoneNumbers );
2024-12-24 15:12:24 +07:00
2024-12-25 13:37:49 +07:00
// send whatsapp message
2025-01-04 14:09:28 +07:00
$url = route ( 'forms.up-country.view' , $form -> id );
2025-01-16 15:56:22 +07:00
WhatsappHelper :: approve2Expense ( $phoneNumbers , $expense_number , $url , $tanggal , $total , $name );
2024-12-25 13:37:49 +07:00
2025-03-05 13:44:58 +07:00
// Send bulk email
$brevoService = app ( BrevoService :: class );
2025-01-23 17:46:38 +07:00
foreach ( $recipients as $recipient ) {
try {
2025-03-05 13:44:58 +07:00
$mail = new ExpenseApproved2 (
2025-01-23 17:46:38 +07:00
$name ,
$expense_number ,
$tanggal ,
$total ,
$url
2025-03-05 13:44:58 +07:00
);
$response = $brevoService -> expenseApproved2 ( $recipient , $name , $mail );
if ( isset ( $response [ 'success' ]) && $response [ 'success' ] === false ) {
Log :: warning ( " Email to { $recipient } failed: " . ( $response [ 'message' ] ? ? 'Unknown error' ));
}
2025-01-23 17:46:38 +07:00
} catch ( \Exception $e ) {
Log :: error ( " Failed to send email to { $recipient } : " . $e -> getMessage ());
continue ;
}
}
2025-01-09 13:22:56 +07:00
2025-01-23 17:46:38 +07:00
NotificationHelper :: approve2Expense ( 'up-country' , $form -> id , $form -> user_id , $expense_number , $receiver_ids );
2025-01-17 16:17:53 +07:00
2025-01-16 15:56:22 +07:00
AuditTrailHelper :: AddAuditTrail ( 'Approve' , 'Approve Expense at Form Up Country (' . $form -> expense_number . ')' );
session () -> flash ( 'success' , 'Form has been approved.' );
2024-12-23 10:42:27 +07:00
return redirect () -> back ();
2024-12-23 10:10:09 +07:00
}
2026-06-01 15:01:03 +07:00
public function finalApprove ( Request $request , $id ) // <--- MENAMBAHKAN OBJECT REQUEST
2024-12-23 10:10:09 +07:00
{
$this -> checkAuthorization ( auth () -> user (), [ 'final_approval.approve' ]);
2025-05-25 15:14:23 +07:00
$form = FormUpCountry :: findOrFail ( $id );
2024-12-23 10:10:09 +07:00
2025-05-25 15:14:23 +07:00
if ( $form -> approved_at === null || $form -> approved2_at === null ) {
2025-01-16 15:56:22 +07:00
session () -> flash ( 'error' , 'Form must be approved by both approvers before final approval.' );
return redirect () -> back ();
}
2026-06-01 15:01:03 +07:00
// ATURAN BISNIS: Batas Akhir Approval adalah tanggal CLOSING_DATE (Bulan Depan tgl 13)
$closingDay = ( int ) env ( 'CLOSING_DATE' , 13 );
$startDay = ( int ) env ( 'STARTING_DATE' , 11 );
$now = Carbon :: now ();
$tanggalForm = Carbon :: parse ( $form -> tanggal );
// Hitung batas maksimal closing sesuai siklus transaksi form tersebut
if ( $tanggalForm -> day >= $startDay ) {
$maxClosingDate = Carbon :: create ( $tanggalForm -> year , $tanggalForm -> month , $closingDay ) -> addMonth () -> endOfDay ();
} else {
$maxClosingDate = Carbon :: create ( $tanggalForm -> year , $tanggalForm -> month , $closingDay ) -> endOfDay ();
}
if ( $now -> gt ( $maxClosingDate )) {
session () -> flash ( 'error' , " Gagal, Batas waktu maksimal approval (Closing Date) untuk pengajuan ini telah berakhir pada { $maxClosingDate -> format ( 'd M Y H:i' ) } WIB. " );
return redirect () -> back ();
}
2025-05-25 15:14:23 +07:00
$cabang_id = UserHasCabang :: where ( 'user_id' , $form -> user_id ) -> first () ? -> cabang_id ;
if ( ! $cabang_id ) {
session () -> flash ( 'error' , 'Data cabang tidak ditemukan.' );
return redirect () -> back ();
}
2026-06-01 15:01:03 +07:00
if ( $tanggalForm -> day >= $startDay ) {
$periodeDate = Carbon :: create ( $tanggalForm -> year , $tanggalForm -> month , $startDay );
2025-05-25 15:14:23 +07:00
} else {
2026-06-01 15:01:03 +07:00
$periodeDate = Carbon :: create ( $tanggalForm -> copy () -> subMonth () -> year , $tanggalForm -> copy () -> subMonth () -> month , $startDay );
2025-05-25 15:14:23 +07:00
}
$periodeMonth = strtolower ( $periodeDate -> format ( 'F' ));
$periodeYear = ( int ) $periodeDate -> format ( 'Y' );
2026-06-01 15:01:03 +07:00
// MENGAMBIL NILAI AKUMULASI CHECKLIST DARI JAVASCRIPT JS (Fallback ke total awal jika kosong)
$approvedTotal = $request -> input ( 'approved_total' , $form -> total );
2025-05-25 15:14:23 +07:00
// Ambil sisa budget sesuai periode
$availableBudget = BudgetHelper :: getAvailableBudget ( $cabang_id , $periodeMonth , $periodeYear );
2026-06-01 15:01:03 +07:00
if ( $approvedTotal > $availableBudget ) { // <--- DISINKRONKAN DENGAN NILAI BARU HASIL CHECKLIST
2025-01-23 17:46:38 +07:00
session () -> flash ( 'error' , 'Budget cabang untuk periode ini tidak mencukupi, silahkan ajukan pada periode expense berikutnya' );
2024-12-23 10:42:27 +07:00
return redirect () -> back ();
2024-12-23 10:10:09 +07:00
}
2026-06-01 15:01:03 +07:00
// UPDATE DATABASE SECARA AKURAT BERDASARKAN HASIL PILIHAN CHECKLIST FINANCE
2024-12-23 10:10:09 +07:00
$form -> update ([
2026-06-01 15:01:03 +07:00
'approved_total' => $approvedTotal , // <--- SUNTIK NOMINAL BARU HASIL CHECKLIST KE DATABASE
2024-12-23 10:10:09 +07:00
'final_approved_at' => now (),
'final_approved_by' => auth () -> user () -> id ,
2026-06-01 15:01:03 +07:00
'status' => 'Closed' ,
2024-12-23 10:10:09 +07:00
]);
2024-12-26 13:49:00 +07:00
$name = $form -> user -> name ;
$expense_number = $form -> expense_number ;
$tanggal = $form -> tanggal ;
2026-06-01 15:01:03 +07:00
$total = $form -> approved_total ; // <--- OTOMATIS MEMAKAI DATA UPDATE TERBARU
2024-12-26 13:49:00 +07:00
2025-01-16 16:42:06 +07:00
$roles = [ 'Head of Sales Marketing' , 'Marketing Operational Manager Region' ];
2024-12-26 13:49:00 +07:00
$recipients = [ $form -> user -> email , auth () -> user () -> email ];
$phoneNumbers = [ $form -> user -> phone , auth () -> user () -> phone ];
2025-03-04 12:24:36 +07:00
$cabang = UserHasCabang :: where ( 'user_id' , $form -> user_id ) -> first ();
$cabang_id = $cabang ? $cabang -> cabang_id : null ;
$cabangData = $cabang_id ? Cabang :: where ( 'id' , $cabang_id ) -> first () : null ;
$region_id = $cabangData ? $cabangData -> region -> id : null ;
2025-01-16 16:42:06 +07:00
foreach ( $roles as $role ) {
2025-03-04 12:24:36 +07:00
$users = Admin :: getUserByRoleName ( $role ) -> filter ( function ( $user ) use ( $role , $cabang_id , $region_id ) {
if ( $role === 'Marketing Information System' || $role === 'Head of Sales Marketing' ) {
2026-06-01 15:01:03 +07:00
return true ;
2025-03-04 12:24:36 +07:00
}
if ( ! $user -> cabang || ! $user -> cabang -> cabang ) {
2026-06-01 15:01:03 +07:00
return false ;
2025-03-04 12:24:36 +07:00
}
if ( $role === 'Admin Region' || $role === 'Marketing Operational Manager Region' ) {
return $user -> cabang -> cabang -> region -> id === $region_id ;
2025-01-16 16:42:06 +07:00
}
2024-12-26 13:49:00 +07:00
2025-03-04 12:24:36 +07:00
return $user -> cabang -> cabang -> id === $cabang_id ;
2025-01-16 16:42:06 +07:00
});
foreach ( $users as $user ) {
$recipients [] = $user -> email ;
$phoneNumbers [] = $user -> phone ;
}
2024-12-26 13:49:00 +07:00
}
// Remove duplicate email addresses, if any
$recipients = array_unique ( $recipients );
$phoneNumbers = array_unique ( $phoneNumbers );
// send whatsapp message
2025-01-04 14:09:28 +07:00
$url = route ( 'forms.up-country.view' , $form -> id );
2025-01-16 15:56:22 +07:00
WhatsappHelper :: finalApprove ( $phoneNumbers , $expense_number , $url , $tanggal , $total , $name );
2024-12-26 13:49:00 +07:00
2025-01-09 13:22:56 +07:00
// Send bulk email
2025-03-05 13:44:58 +07:00
$brevoService = app ( BrevoService :: class );
2025-01-23 17:46:38 +07:00
foreach ( $recipients as $recipient ) {
try {
2025-03-05 13:44:58 +07:00
$mail = new FinalApprove (
2025-01-23 17:46:38 +07:00
$name ,
$expense_number ,
$tanggal ,
$total ,
$url
2025-03-05 13:44:58 +07:00
);
$response = $brevoService -> expenseFinalApproved ( $recipient , $name , $mail );
if ( isset ( $response [ 'success' ]) && $response [ 'success' ] === false ) {
Log :: warning ( " Email to { $recipient } failed: " . ( $response [ 'message' ] ? ? 'Unknown error' ));
}
2025-01-23 17:46:38 +07:00
} catch ( \Exception $e ) {
Log :: error ( " Failed to send email to { $recipient } : " . $e -> getMessage ());
continue ;
}
}
2025-01-09 13:22:56 +07:00
AuditTrailHelper :: AddAuditTrail ( 'Final Approve' , 'Final Approve Expense at Form Up Country (' . $form -> expense_number . ')' );
2024-12-23 10:10:09 +07:00
session () -> flash ( 'success' , 'Form has been final approved.' );
2024-12-23 10:42:27 +07:00
return redirect () -> back ();
2024-12-23 10:10:09 +07:00
}
2025-10-12 19:14:17 +07:00
public function reject ( Request $request , $id )
2025-01-16 15:56:22 +07:00
{
2026-06-01 15:01:03 +07:00
if ( ! auth () -> user () -> hasAnyPermission ([ 'approval.reject' , 'approval2.reject' , 'final_approval.approve' ])) {
abort ( 403 , 'Akses Ditolak: Anda tidak memiliki izin untuk melakukan Reject.' );
}
2025-01-16 15:56:22 +07:00
2025-10-12 19:14:17 +07:00
$request -> validate ([
2026-06-01 15:01:03 +07:00
'remarks' => 'required|string|max:500' ,
2025-10-12 19:14:17 +07:00
]);
2025-01-16 15:56:22 +07:00
$form = FormUpCountry :: findOrfail ( $id );
2026-06-01 15:01:03 +07:00
2025-01-16 15:56:22 +07:00
$form -> update ([
2026-06-01 15:01:03 +07:00
'status' => 'Rejected' ,
'remarks' => $request -> remarks ,
'rejected_by' => auth () -> id (), // SUNTIK DATA AUDIT TRAIL
'rejected_at' => now (), // SUNTIK DATA AUDIT TRAIL
2025-01-16 15:56:22 +07:00
]);
$heads = $form -> user -> getCabangAndRegionHead ();
$name = $form -> user -> name ;
$expense_number = $form -> expense_number ;
$tanggal = $form -> tanggal ;
$total = $form -> total ;
$recipients = [ $form -> user -> email , auth () -> user () -> email ];
$phoneNumbers = [ $form -> user -> phone , auth () -> user () -> phone ];
if ( $heads [ 'cabang_head' ]) {
2026-06-01 15:01:03 +07:00
$recipients [] = $heads [ 'cabang_head' ][ 'email' ];
$phoneNumbers [] = $heads [ 'cabang_head' ][ 'phone' ];
2025-01-16 15:56:22 +07:00
}
if ( $heads [ 'region_head' ]) {
2026-06-01 15:01:03 +07:00
$recipients [] = $heads [ 'region_head' ][ 'email' ];
$phoneNumbers [] = $heads [ 'region_head' ][ 'phone' ];
2025-01-16 15:56:22 +07:00
}
$recipients = array_unique ( $recipients );
$phoneNumbers = array_unique ( $phoneNumbers );
2026-06-01 15:01:03 +07:00
$url = route ( 'forms.up-country.view' , $form -> id );
2025-10-26 14:14:06 +07:00
WhatsappHelper :: rejectExpense ( $phoneNumbers , $expense_number , $url , $tanggal , $total , $name , $request -> remarks );
2025-01-16 15:56:22 +07:00
2025-03-05 13:44:58 +07:00
$brevoService = app ( BrevoService :: class );
2025-01-23 17:46:38 +07:00
foreach ( $recipients as $recipient ) {
2026-06-01 15:01:03 +07:00
try {
$mail = new ExpenseRejected ( $name , $expense_number , $tanggal , $total , $url , $request -> remarks );
$brevoService -> expenseRejected ( $recipient , $name , $mail );
} catch ( \Exception $e ) {
Log :: error ( " Failed to send email to { $recipient } : " . $e -> getMessage ());
continue ;
2025-03-05 13:44:58 +07:00
}
2025-01-23 17:46:38 +07:00
}
2025-01-16 15:56:22 +07:00
2026-06-01 15:01:03 +07:00
AuditTrailHelper :: AddAuditTrail ( 'Reject' , 'Reject Expense at Form Up Country (' . $form -> expense_number . ') oleh ' . auth () -> user () -> name );
2025-01-16 15:56:22 +07:00
session () -> flash ( 'success' , 'Form has been rejected.' );
return redirect () -> back ();
}
2024-12-23 10:10:09 +07:00
public function open ( $id )
{
2024-12-26 17:28:16 +07:00
$this -> checkAuthorization ( auth () -> user (), [ 'final_approval.open' ]);
2024-12-23 10:10:09 +07:00
$form = FormUpCountry :: findOrfail ( $id );
$form -> update ([
'final_approved_at' => null ,
'final_approved_by' => null ,
2025-01-16 15:56:22 +07:00
'status' => 'Approved 2'
2024-12-23 10:10:09 +07:00
]);
2025-01-09 13:22:56 +07:00
AuditTrailHelper :: AddAuditTrail ( 'Open' , 'Open Expense at Form Up Country (' . $form -> expense_number . ')' );
2024-12-23 10:10:09 +07:00
session () -> flash ( 'success' , 'Form has been opened.' );
2024-12-23 10:42:27 +07:00
return redirect () -> back ();
2024-12-23 10:10:09 +07:00
}
2026-06-01 15:01:03 +07:00
public function destroy ( $id )
{
2024-12-16 17:01:55 +07:00
$this -> checkAuthorization ( auth () -> user (), [ 'forms.country.delete' ]);
2025-01-10 17:10:45 +07:00
$role = auth () -> user () -> getRoleNames ()[ 0 ];
2024-12-16 17:01:55 +07:00
2026-06-01 15:01:03 +07:00
$form = FormUpCountry :: findOrfail ( $id );
2025-01-07 17:23:42 +07:00
if ( $form -> status != 'On Progress' && $role == 'Medical Representatif' ) {
session () -> flash ( 'error' , 'You cannot edit this form because it has been approved or rejected.' );
return redirect () -> back ();
}
2025-01-09 13:22:56 +07:00
AuditTrailHelper :: AddAuditTrail ( 'Delete' , 'Delete Record at Form Up Country (' . $form -> expense_number . ')' );
2025-01-01 17:14:46 +07:00
2026-06-01 15:01:03 +07:00
$form -> delete ();
2024-12-12 18:18:26 +07:00
2026-06-01 15:01:03 +07:00
session () -> flash ( 'success' , 'Form has been deleted.' );
return redirect () -> back ();
}
}