add confirm/refuse in ticket mail

This commit is contained in:
dbdbd9 2025-06-11 16:44:11 +07:00
parent 8474bd97a1
commit 7217a83e33
5 changed files with 397 additions and 21 deletions

View File

@ -364,8 +364,10 @@ class TicketController extends Controller
$admins = Admin::where('permission', 'like', '%admin%')->get();
foreach ($admins as $key => $value) {
$data = array(
"ticket_id" => $ticket->id,
"email_template" => "email.notification_tickets",
"email" => $user->email,
"admin_email" => $value->email,
"name" => $user->name,
"date" => optional($dataMasterStartPeriod)->c_name . " (" . $formattedStartDate . ") - " . optional($dataMasterEndPeriod)->c_name . " (" . $formattedEndDate . ")",
"type" => optional($dataMasterType)->c_name,
@ -984,6 +986,297 @@ class TicketController extends Controller
return response()->json(['message' => "failed", 'status' => false]);
}
// Handle Logic same as HandleTicket, but need email to identify admin and redirect to Management page
public function handleTicketEmail(Request $request)
{
$rules = [
'ticket_id' => 'required',
'action' => 'required',
'admin_email' => 'required' // Need Admin Email
];
// Validate the request
$request->validate($rules);
$ticket_id = $request->input('ticket_id');
$admin_note = $request->input('admin_note');
$admin_email = $request->input('admin_email');
$action = $request->input('action'); // 'confirm' or 'refuse'
$admin = Admin::where('email', $admin_email)->first(); // Get admin by email not token
$ticket = Ticket::find($ticket_id);
if (!$ticket || $ticket->status !== "WAITING") {
// No ticket found or already confirmed or refused
return redirect()->to(config('app.client_url') . '/tickets-management');
}
// Send notification email to users
$startDate = $ticket->start_date; //Start day
$startPeriod = $ticket->start_period; //The session begins
$endDate = $ticket->end_date; //End date
$endPeriod = $ticket->end_period; //Session ends
$type = $ticket->type;
$dataMasterStartPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $startPeriod);
$dataMasterEndPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $endPeriod);
$dataMasterType = CategoryController::getListMasterByCodeAndType("REASON", $type);
$dataMasterTypeNotes = CategoryController::getListMasterByType("REASON_NOTES");
$onleave = null;
$leaveWithoutPay = null;
if ($dataMasterTypeNotes) {
// get nghỉ phép, nghỉ không phép
$onleave = optional($dataMasterTypeNotes->where('c_code', 'ONLEAVE')->first())->c_code;
$leaveWithoutPay = optional($dataMasterTypeNotes->where('c_code', 'LEAVE_WITHOUT_PAY')->first())->c_code;
}
$formattedStartDate = Carbon::createFromFormat('Y-m-d', $startDate)->format('d/m/Y');
$formattedEndDate = Carbon::createFromFormat('Y-m-d', $endDate)->format('d/m/Y');
$user = Admin::find($ticket->user_id);
if ($onleave == null || $leaveWithoutPay == null) {
// Data reason notes not found
return redirect()->to(config('app.client_url') . '/tickets-management');
}
if ($action == "confirm") {
if ($ticket->type == "ONLEAVE") {
$dataListPeriod = $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period);
$balanceCheckResult = $this->checkLeaveBalance($user, $dataListPeriod);
// dd($balanceCheckResult,$dataListPeriod);
if ($balanceCheckResult['success'] == false) {
if ($balanceCheckResult['months_info']) {
foreach ($balanceCheckResult['months_info'] as $monthInfo) {
// Lọc các ngày thuộc đúng tháng/năm này
$daysInMonth = array_filter($dataListPeriod, function ($item) use ($monthInfo) {
$date = \Carbon\Carbon::parse($item['date']);
return $date->year == $monthInfo['year'] && $date->month == $monthInfo['month'];
});
$daysWillUse = $monthInfo['days_will_use'] ?? 0;
$daysWillUseWithoutPay = $monthInfo['days_will_use_without_pay'] ?? 0;
// dd($daysWillUse,$daysWillUseWithoutPay,$daysInMonth);
foreach ($daysInMonth as $item) {
list($year, $month, $day) = explode('-', $item['date']);
$period = $item['period'];
$value = ($period === 'ALL') ? 1.0 : 0.5;
if ($period === 'ALL' && $daysWillUse == 0.5) {
// Chỉ còn 0.5 phép, chia thành 2 bản ghi: 1 phép, 1 không phép
// Ưu tiên phép cho buổi sáng (S), không phép cho buổi chiều (C)
Notes::create([
'n_user_id' => $ticket->user_id,
'n_day' => $day,
'n_month' => $month,
'n_year' => $year,
'n_time_type' => 'S',
'n_reason' => $onleave,
'n_note' => $ticket->reason
]);
Notes::create([
'n_user_id' => $ticket->user_id,
'n_day' => $day,
'n_month' => $month,
'n_year' => $year,
'n_time_type' => 'C',
'n_reason' => $leaveWithoutPay,
'n_note' => $ticket->reason
]);
$daysWillUse = 0;
$daysWillUseWithoutPay -= 0.5;
} elseif ($daysWillUse > 0) {
// Dùng ngày phép trước
$use = min($daysWillUse, $value);
Notes::create([
'n_user_id' => $ticket->user_id,
'n_day' => $day,
'n_month' => $month,
'n_year' => $year,
'n_time_type' => $period,
'n_reason' => $onleave,
'n_note' => $ticket->reason
]);
$daysWillUse -= $use;
} elseif ($daysWillUseWithoutPay > 0) {
// Hết phép, chuyển sang không phép
$use = min($daysWillUseWithoutPay, $value);
Notes::create([
'n_user_id' => $ticket->user_id,
'n_day' => $day,
'n_month' => $month,
'n_year' => $year,
'n_time_type' => $period,
'n_reason' => $leaveWithoutPay,
'n_note' => $ticket->reason
]);
$daysWillUseWithoutPay -= $use;
}
// Nếu cả hai đều hết thì thôi, không tạo nữa
}
}
}
} else {
//Đủ phép
foreach ($dataListPeriod as $result) {
list($year, $month, $day) = explode('-', $result['date']);
Notes::create([
'n_user_id' => $ticket->user_id,
'n_day' => $day,
'n_month' => $month,
'n_year' => $year,
'n_time_type' => $result['period'],
'n_reason' => $onleave, // có phép
'n_note' => $ticket->reason
]);
}
}
$yearCheck = Carbon::parse($endDate)->year;
// Check giá trị ld_day_total của bảng leave_days thuộc user id đó với giá trị của list item note trong bảng notes của user id đó
$leaveDaysInfo = LeaveDays::where('ld_user_id', $ticket->user_id)
->where('ld_year', $yearCheck)
->first();
if ($leaveDaysInfo) {
// Tính tổng số ngày nghỉ có phép đã sử dụng trong năm
$totalUsedLeaveDays = Notes::join('categories', function ($join) {
$join->on('notes.n_time_type', '=', 'categories.c_code')
->where('categories.c_type', 'TIME_TYPE');
})
->where('n_user_id', $ticket->user_id)
->where('n_year', $yearCheck)
->where('n_reason', 'ONLEAVE')
->sum('categories.c_value');
// Tính tổng số ngày phép được cấp
$totalAllocatedDays = $leaveDaysInfo->ld_day_total +
$leaveDaysInfo->ld_additional_day +
$leaveDaysInfo->ld_special_leave_day;
// Tính số ngày vượt quá và làm tròn lên
$excessDays = $totalUsedLeaveDays - $totalAllocatedDays;
$roundedExcessDays = ceil($excessDays); // Làm tròn lên số nguyên gần nhất
// Kiểm tra nếu số ngày đã sử dụng vượt quá số ngày được cấp
if ($roundedExcessDays > 0) {
Log::warning("User ID: {$ticket->user_id} has used more leave days ({$totalUsedLeaveDays}) than allocated ({$totalAllocatedDays})");
// Cập nhật cột ld_day_total với số ngày đã làm tròn
if ($roundedExcessDays > 0) {
$leaveDaysInfo->ld_day_total += $roundedExcessDays;
$leaveDaysInfo->save();
Log::info("Updated leave days for User ID: {$ticket->user_id}. Added {$roundedExcessDays} days (rounded from {$excessDays})");
}
}
}
} else if ($ticket->type == "WFH") {
$dataListPeriod = $this->getAllPeriod($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period);
foreach ($dataListPeriod as $result) {
list($year, $month, $day) = explode('-', $result['date']);
Notes::create([
'n_user_id' => $ticket->user_id,
'n_day' => $day,
'n_month' => $month,
'n_year' => $year,
'n_time_type' => $result['period'],
'n_reason' => $ticket->type,
'n_note' => $ticket->reason
]);
//WFH - start tracking
$type = $result['period'];
$date = Carbon::create($year, $month, $day)->setTimezone(env('TIME_ZONE'));
//Default: ALL
$start = $date->copy()->setTime(7, 30, 0);
$end = $date->copy()->setTime(17, 0, 0);
if ($type == 'S') {
$end = $date->copy()->setTime(11, 30, 0);
} else if ($type == 'C') {
$start = $date->copy()->setTime(11, 30, 0);
}
Tracking::insert([
[
'name' => $user->name,
'user_id' => $user->id,
'status' => 'check in',
'time_string' => $start->format('Y-m-d H:i:s'),
'created_at' => $start->setTimezone('UTC')
],
[
'name' => $user->name,
'user_id' => $user->id,
'status' => 'check out',
'time_string' => $end->format('Y-m-d H:i:s'),
'created_at' => $end->setTimezone('UTC')
]
]);
//WFH - end tracking
}
}
$ticket['updated_by'] = $admin->name;
$ticket['admin_note'] = $admin_note;
$ticket['status'] = 'CONFIRMED';
$ticket->save();
$this->createOrUpdateRecordForCurrentMonth($month, $year);
// Send notification email to users
$data = array(
"email_template" => "email.notification_tickets_user",
"user_name" => $user->name,
"email" => $user->email,
"name" => $admin->name, //name admin duyệt
"date" => $dataMasterStartPeriod->c_name . " (" . $formattedStartDate . ") - " . $dataMasterEndPeriod->c_name . " (" . $formattedEndDate . ")",
"type" => $dataMasterType->c_name,
"note" => $ticket->reason,
"admin_note" => $admin_note,
"link" => "/tickets", //link đến page admin
"status" => "confirmed",
"subject" => "[Ticket response] Ticket From " . $admin->name
);
Mail::to($user->email)->send(new TicketMail($data));
// Confirm Success
return redirect()->to(config('app.client_url') . '/tickets-management');
}
if ($action == "refuse") {
$ticket['updated_by'] = $admin->name;
$ticket['admin_note'] = $admin_note;
$ticket['status'] = 'REFUSED';
$ticket->save();
$data = array(
"email_template" => "email.notification_tickets_user",
"user_name" => $user->name,
"email" => $user->email,
"name" => $admin->name, //name admin duyệt
"date" => $dataMasterStartPeriod->c_name . " (" . $formattedStartDate . ") - " . $dataMasterEndPeriod->c_name . " (" . $formattedEndDate . ")",
"type" => $dataMasterType->c_name,
"note" => $ticket->reason,
"admin_note" => $admin_note,
"link" => "/tickets", //link đến page admin
"status" => "refused",
"subject" => "[Ticket response] Ticket From " . $admin->name
);
Mail::to($user->email)->send(new TicketMail($data));
// Refuse Success
return redirect()->to(config('app.client_url') . '/tickets-management');
}
// Failed
return redirect()->to(config('app.client_url') . '/tickets-management');
}
private function getAllPeriodNew($startDate, $startPeriod, $endDate, $endPeriod)
{
// Đảm bảo $startDate và $endDate là đối tượng Carbon

View File

@ -44,6 +44,7 @@ Route::middleware('api')
Route::post('login', [AdminController::class, 'login']);
Route::post('reset-password', [AdminController::class, 'resetPassword']);
Route::get('forgot-password', [AdminController::class, 'forgotPassword']);
Route::get('/email-handle-ticket', [TicketController::class, 'handleTicketEmail'])->name('email.ticket.handle');
});
// NOTE after login

View File

@ -59,6 +59,8 @@ return [
'asset_url' => env('ASSET_URL'),
'client_url' => env('ADMIN_URL', 'http://localhost'),
/*
|--------------------------------------------------------------------------
| Application Timezone

View File

@ -9,14 +9,14 @@ class RenameLdDayToLdDayTotalInLeaveDaysTable extends Migration
public function up()
{
Schema::table('leave_days', function (Blueprint $table) {
$table->renameColumn('ld_day_total', 'ld_day_total');
$table->renameColumn('ld_day', 'ld_day_total');
});
}
public function down()
{
Schema::table('leave_days', function (Blueprint $table) {
$table->renameColumn('ld_day_total', 'ld_day_total');
$table->renameColumn('ld_day_total', 'ld_day');
});
}
}

View File

@ -98,29 +98,109 @@
</div>
</td>
</tr>
<tr>
<td>
<p style="margin:0 0 16px;padding:5px;margin: 5px;text-align: center;">
<a href='{{ config('app.url') . $data['link'] }}'
style="
color: #fff;
border-radius: 10px;
background-color: rgba(68,115,196);
background-image: linear-gradient(to top left,rgba(0,0,0,.2),rgba(0,0,0,.2) 30%,rgba(0,0,0,0));
text-decoration: none;
display: inline-block;
font-weight: 600;
font-size: 16px;
line-height: 150%;
text-align: center;
margin: 0;
padding: 10px 12px;
">
Check now</a>
</p>
<p
style="
margin: 0 0 16px;
padding: 5px;
margin: 5px;
text-align: center;
"
>
<a
href="{{ config('app.client_url') . $data['link'] }}"
style="
color: #fff;
border-radius: 10px;
background-color: rgba(68, 115, 196);
background-image: linear-gradient(
to top left,
rgba(0, 0, 0, 0.2),
rgba(0, 0, 0, 0.2) 30%,
rgba(0, 0, 0, 0)
);
text-decoration: none;
display: inline-block;
font-weight: 600;
font-size: 16px;
line-height: 150%;
text-align: center;
margin: 0;
padding: 10px 12px;
"
>
Check now</a
>
</p>
<p style="text-align: center">
Or you can quick
<span style="font-weight: bold">Confirm</span> or
<span style="font-weight: bold">Refuse</span> here:
</p>
<div
style="
display: flex;
justify-content: center;
gap: 10px;
margin-top: 10px;
"
>
<a
href="{{ route('email.ticket.handle', ['ticket_id' => $data['ticket_id'], 'action' => 'confirm', 'admin_email' => $data['admin_email']]) }}"
style="
color: #fff;
border-radius: 10px;
background-color: #12b886;
background-image: linear-gradient(
to top left,
rgba(0, 0, 0, 0.2),
rgba(0, 0, 0, 0.2) 30%,
rgba(0, 0, 0, 0)
);
text-decoration: none;
display: inline-block;
font-weight: 600;
font-size: 16px;
line-height: 150%;
text-align: center;
margin: 0;
padding: 10px 12px;
"
>
Confirm</a
>
<a
href="{{ route('email.ticket.handle', ['ticket_id' => $data['ticket_id'], 'action' => 'refuse', 'admin_email' => $data['admin_email']]) }}"
style="
color: #fff;
border-radius: 10px;
background-color: #f03e3e;
background-image: linear-gradient(
to top left,
rgba(0, 0, 0, 0.2),
rgba(0, 0, 0, 0.2) 30%,
rgba(0, 0, 0, 0)
);
text-decoration: none;
display: inline-block;
font-weight: 600;
font-size: 16px;
line-height: 150%;
text-align: center;
margin: 0;
padding: 10px 12px;
"
>
Refuse</a
>
</div>
</td>
</tr>
</table>
</td>
</tr>