initial commit
This commit is contained in:
18
.editorconfig
Normal file
18
.editorconfig
Normal file
@@ -0,0 +1,18 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[docker-compose.yml]
|
||||
indent_size = 4
|
||||
32
.htaccess
Normal file
32
.htaccess
Normal file
@@ -0,0 +1,32 @@
|
||||
<IfModule mod_rewrite.c>
|
||||
<IfModule mod_negotiation.c>
|
||||
Options -MultiViews -Indexes
|
||||
</IfModule>
|
||||
|
||||
<Files .env>
|
||||
Order Allow,Deny
|
||||
Deny from all
|
||||
</Files>
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
# Handle Authorization Header
|
||||
RewriteCond %{HTTP:Authorization} .
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
|
||||
# Redirect Trailing Slashes If Not A Folder...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_URI} (.+)/$
|
||||
RewriteRule ^ %1 [L,R=301]
|
||||
|
||||
# Handle Front Controller...
|
||||
RewriteCond %{REQUEST_URI} !(\.css|\.js|\.png|\.jpg|\.jpeg|\.gif|\.pdf|robots\.txt|\.ico|\.woff|\.woff2|.ttf|\.svg)$ [NC]
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ index.php [L]
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_URI} !^/public/
|
||||
RewriteRule ^(css|assets|market_assets|images|landing|uploads|storage|installer|js|vendor|build|screenshots)/(.*)$ public/$1/$2 [L,NC]
|
||||
</IfModule>
|
||||
3
.prettierignore
Normal file
3
.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
resources/js/components/ui/*
|
||||
resources/js/ziggy.js
|
||||
resources/views/mail/*
|
||||
18
.prettierrc
Normal file
18
.prettierrc
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"singleAttributePerLine": false,
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"printWidth": 150,
|
||||
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"],
|
||||
"tailwindFunctions": ["clsx", "cn"],
|
||||
"tabWidth": 4,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "**/*.yml",
|
||||
"options": {
|
||||
"tabWidth": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
45
app/Console/Commands/AssignDefaultPlanToUsers.php
Normal file
45
app/Console/Commands/AssignDefaultPlanToUsers.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Plan;
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class AssignDefaultPlanToUsers extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'users:assign-default-plan';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Assign default plan to company users without a plan';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$defaultPlan = Plan::getDefaultPlan();
|
||||
|
||||
if (!$defaultPlan) {
|
||||
$this->error(__('No default plan found. Please create a default plan first.'));
|
||||
return 1;
|
||||
}
|
||||
|
||||
$count = User::where('type', 'company')
|
||||
->whereNull('plan_id')
|
||||
->update(['plan_id' => $defaultPlan->id, 'plan_is_active' => 1]);
|
||||
|
||||
$this->info("Successfully assigned default plan to {$count} users.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
27
app/Console/Kernel.php
Normal file
27
app/Console/Kernel.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
/**
|
||||
* Define the application's command schedule.
|
||||
*/
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
// $schedule->command('inspire')->hourly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the commands for the application.
|
||||
*/
|
||||
protected function commands(): void
|
||||
{
|
||||
$this->load(__DIR__.'/Commands');
|
||||
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
}
|
||||
16
app/Events/UserCreated.php
Normal file
16
app/Events/UserCreated.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class UserCreated
|
||||
{
|
||||
use Dispatchable, SerializesModels;
|
||||
|
||||
public function __construct(public User $user, public string $plainPassword = '')
|
||||
{
|
||||
}
|
||||
}
|
||||
38
app/Helpers/AssetHelper.php
Normal file
38
app/Helpers/AssetHelper.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
class AssetHelper
|
||||
{
|
||||
/**
|
||||
* Generate the correct asset URL regardless of installation environment
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public static function asset($path)
|
||||
{
|
||||
// Get the current URL from the request
|
||||
$currentUrl = url('/');
|
||||
|
||||
// For Vite assets, use the correct manifest path
|
||||
if (str_starts_with($path, 'build/')) {
|
||||
return self::viteAsset($path);
|
||||
}
|
||||
|
||||
// For other assets, use the standard asset helper
|
||||
return asset($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the correct Vite asset URL
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public static function viteAsset($path)
|
||||
{
|
||||
// Use Vite's asset helper but ensure it uses relative paths
|
||||
return vite(str_replace('build/', '', $path));
|
||||
}
|
||||
}
|
||||
2027
app/Helpers/helper.php
Normal file
2027
app/Helpers/helper.php
Normal file
File diff suppressed because it is too large
Load Diff
211
app/Http/Controllers/AamarpayPaymentController.php
Normal file
211
app/Http/Controllers/AamarpayPaymentController.php
Normal file
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Plan;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AamarpayPaymentController extends Controller
|
||||
{
|
||||
public function processPayment(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request, [
|
||||
'pay_status' => 'required|string',
|
||||
'mer_txnid' => 'required|string',
|
||||
]);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!isset($settings['payment_settings']['aamarpay_store_id'])) {
|
||||
return back()->withErrors(['error' => __('Aamarpay not configured')]);
|
||||
}
|
||||
|
||||
if ($validated['pay_status'] === 'Successful') {
|
||||
processPaymentSuccess([
|
||||
'user_id' => auth()->id(),
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => $validated['billing_cycle'],
|
||||
'payment_method' => 'aamarpay',
|
||||
'coupon_code' => $validated['coupon_code'] ?? null,
|
||||
'payment_id' => $validated['mer_txnid'],
|
||||
]);
|
||||
|
||||
return back()->with('success', __('Payment successful and plan activated'));
|
||||
}
|
||||
|
||||
return back()->withErrors(['error' => __('Payment failed or cancelled')]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return handlePaymentError($e, 'aamarpay');
|
||||
}
|
||||
}
|
||||
|
||||
public function createPayment(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$pricing = calculatePlanPricing($plan, $validated['coupon_code'] ?? null);
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!isset($settings['payment_settings']['aamarpay_store_id']) || !isset($settings['payment_settings']['aamarpay_signature'])) {
|
||||
return response()->json(['error' => __('Aamarpay not configured')], 400);
|
||||
}
|
||||
|
||||
$user = auth()->user();
|
||||
$orderID = strtoupper(str_replace('.', '', uniqid('', true)));
|
||||
$currency = $settings['payment_settings']['currency'] ?? 'BDT';
|
||||
$url = 'https://sandbox.aamarpay.com/request.php';
|
||||
|
||||
// Use proper test store_id for sandbox
|
||||
$storeId = $settings['payment_settings']['aamarpay_store_id'];
|
||||
if ($storeId === 'aamarpaytest') {
|
||||
$storeId = 'aamarpaytest'; // This might need to be changed to actual test store ID
|
||||
}
|
||||
|
||||
$fields = [
|
||||
'store_id' => $storeId,
|
||||
'amount' => $pricing['final_price'],
|
||||
'payment_type' => '',
|
||||
'currency' => $currency,
|
||||
'tran_id' => $orderID,
|
||||
'cus_name' => $user->name ?? 'Customer',
|
||||
'cus_email' => $user->email,
|
||||
'cus_add1' => '',
|
||||
'cus_add2' => '',
|
||||
'cus_city' => '',
|
||||
'cus_state' => '',
|
||||
'cus_postcode' => '',
|
||||
'cus_country' => '',
|
||||
'cus_phone' => '1234567890',
|
||||
'success_url' => route('aamarpay.success', [
|
||||
'response' => 'success',
|
||||
'coupon' => $validated['coupon_code'] ?? '',
|
||||
'plan_id' => $plan->id,
|
||||
'price' => $pricing['final_price'],
|
||||
'order_id' => $orderID,
|
||||
'user_id' => $user->id,
|
||||
'billing_cycle' => $validated['billing_cycle']
|
||||
]),
|
||||
'fail_url' => route('aamarpay.success', [
|
||||
'response' => 'failure',
|
||||
'coupon' => $validated['coupon_code'] ?? '',
|
||||
'plan_id' => $plan->id,
|
||||
'price' => $pricing['final_price'],
|
||||
'order_id' => $orderID
|
||||
]),
|
||||
'cancel_url' => route('aamarpay.success', ['response' => 'cancel']),
|
||||
'signature_key' => $settings['payment_settings']['aamarpay_signature'],
|
||||
'desc' => 'Plan: ' . $plan->name,
|
||||
];
|
||||
|
||||
$fields_string = http_build_query($fields);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
$response = curl_exec($ch);
|
||||
$url_forward = str_replace('"', '', stripslashes($response));
|
||||
curl_close($ch);
|
||||
|
||||
if ($url_forward) {
|
||||
return $this->redirectToMerchant($url_forward);
|
||||
}
|
||||
|
||||
return response()->json(['error' => __('Payment creation failed')], 500);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Payment creation failed')], 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function redirectToMerchant($url)
|
||||
{
|
||||
$token = csrf_token();
|
||||
$redirectUrl = 'https://sandbox.aamarpay.com/' . $url;
|
||||
|
||||
return response(view('aamarpay-redirect', compact('redirectUrl', 'token')));
|
||||
}
|
||||
|
||||
public function success(Request $request)
|
||||
{
|
||||
try {
|
||||
$response = $request->input('response');
|
||||
$planId = $request->input('plan_id');
|
||||
$userId = $request->input('user_id');
|
||||
$coupon = $request->input('coupon');
|
||||
$billingCycle = $request->input('billing_cycle', 'monthly');
|
||||
$orderId = $request->input('order_id');
|
||||
|
||||
if ($response === 'success' && $planId && $userId) {
|
||||
$plan = Plan::find($planId);
|
||||
$user = User::find($userId);
|
||||
|
||||
if ($plan && $user) {
|
||||
processPaymentSuccess([
|
||||
'user_id' => $user->id,
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => $billingCycle,
|
||||
'payment_method' => 'aamarpay',
|
||||
'coupon_code' => $coupon,
|
||||
'payment_id' => $orderId,
|
||||
]);
|
||||
|
||||
// Log the user in if not already authenticated
|
||||
if (!auth()->check()) {
|
||||
auth()->login($user);
|
||||
}
|
||||
|
||||
return redirect()->route('plans.index')->with('success', __('Payment completed successfully and plan activated'));
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('plans.index')->with('error', __('Payment failed or cancelled'));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->route('plans.index')->with('error', __('Payment processing failed'));
|
||||
}
|
||||
}
|
||||
|
||||
public function callback(Request $request)
|
||||
{
|
||||
try {
|
||||
$transactionId = $request->input('mer_txnid');
|
||||
$status = $request->input('pay_status');
|
||||
|
||||
if ($transactionId && $status === 'Successful') {
|
||||
$parts = explode('_', $transactionId);
|
||||
|
||||
if (count($parts) >= 3) {
|
||||
$planId = $parts[1];
|
||||
$userId = $parts[2];
|
||||
|
||||
$plan = Plan::find($planId);
|
||||
$user = User::find($userId);
|
||||
|
||||
if ($plan && $user) {
|
||||
processPaymentSuccess([
|
||||
'user_id' => $user->id,
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => 'monthly',
|
||||
'payment_method' => 'aamarpay',
|
||||
'payment_id' => $request->input('pg_txnid'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['status' => 'success']);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Callback processing failed')], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
248
app/Http/Controllers/ActionItemController.php
Normal file
248
app/Http/Controllers/ActionItemController.php
Normal file
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\ActionItem;
|
||||
use App\Models\Meeting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class ActionItemController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-action-items')) {
|
||||
$query = ActionItem::with(['meeting.type', 'assignee'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-action-items')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-action-items')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('assigned_to', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('title', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%')
|
||||
->orWhereHas('assignee', function ($aq) use ($request) {
|
||||
$aq->where('name', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
if ($request->has('priority') && !empty($request->priority) && $request->priority !== 'all') {
|
||||
$query->where('priority', $request->priority);
|
||||
}
|
||||
|
||||
if ($request->has('assigned_to') && !empty($request->assigned_to) && $request->assigned_to !== 'all') {
|
||||
$query->where('assigned_to', $request->assigned_to);
|
||||
}
|
||||
|
||||
if ($request->has('meeting_id') && !empty($request->meeting_id) && $request->meeting_id !== 'all') {
|
||||
$query->where('meeting_id', $request->meeting_id);
|
||||
}
|
||||
|
||||
// Auto-update overdue items
|
||||
ActionItem::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', '!=', 'Completed')
|
||||
->where('due_date', '<', Carbon::today())
|
||||
->update(['status' => 'Overdue']);
|
||||
|
||||
// Handle sorting
|
||||
if ($request->has('sort_field') && !empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field;
|
||||
$sortDirection = $request->sort_direction ?? 'asc';
|
||||
|
||||
if (in_array($sortField, ['title', 'due_date'])) {
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
$actionItems = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$meetings = Meeting::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('id', 'title', 'meeting_date')
|
||||
->orderBy('meeting_date', 'desc')
|
||||
->get();
|
||||
|
||||
$employees = User::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('type', 'employee')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
return Inertia::render('meetings/action-items/index', [
|
||||
'actionItems' => $actionItems,
|
||||
'meetings' => $meetings,
|
||||
'employees' => $employees,
|
||||
'filters' => $request->all(['search', 'status', 'priority', 'assigned_to', 'meeting_id', 'per_page', 'sort_field', 'sort_direction']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'meeting_id' => 'required|exists:meetings,id',
|
||||
'title' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'assigned_to' => 'required|exists:users,id',
|
||||
'due_date' => 'required|date|after_or_equal:today',
|
||||
'priority' => 'required|in:Low,Medium,High,Critical',
|
||||
'progress_percentage' => 'nullable|integer|min:0|max:100',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$status = 'Not Started';
|
||||
$progress = $request->progress_percentage ?? 0;
|
||||
|
||||
if ($progress > 0 && $progress < 100) {
|
||||
$status = 'In Progress';
|
||||
} elseif ($progress == 100) {
|
||||
$status = 'Completed';
|
||||
}
|
||||
|
||||
ActionItem::create([
|
||||
'meeting_id' => $request->meeting_id,
|
||||
'title' => $request->title,
|
||||
'description' => $request->description,
|
||||
'assigned_to' => $request->assigned_to,
|
||||
'due_date' => $request->due_date,
|
||||
'priority' => $request->priority,
|
||||
'status' => $status,
|
||||
'progress_percentage' => $progress,
|
||||
'notes' => $request->notes,
|
||||
'completed_date' => $status === 'Completed' ? now() : null,
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Action item created successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, ActionItem $actionItem)
|
||||
{
|
||||
if (!in_array($actionItem->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this action item'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'meeting_id' => 'required|exists:meetings,id',
|
||||
'title' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'assigned_to' => 'required|exists:users,id',
|
||||
'due_date' => 'required|date',
|
||||
'priority' => 'required|in:Low,Medium,High,Critical',
|
||||
'progress_percentage' => 'nullable|integer|min:0|max:100',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$progress = $request->progress_percentage ?? $actionItem->progress_percentage;
|
||||
$status = $actionItem->status;
|
||||
$completedDate = $actionItem->completed_date;
|
||||
|
||||
if ($progress == 0) {
|
||||
$status = 'Not Started';
|
||||
$completedDate = null;
|
||||
} elseif ($progress > 0 && $progress < 100) {
|
||||
$status = 'In Progress';
|
||||
$completedDate = null;
|
||||
} elseif ($progress == 100) {
|
||||
$status = 'Completed';
|
||||
$completedDate = $completedDate ?? now();
|
||||
}
|
||||
|
||||
// Check if overdue
|
||||
if ($status !== 'Completed' && Carbon::parse($request->due_date) < Carbon::today()) {
|
||||
$status = 'Overdue';
|
||||
}
|
||||
|
||||
$actionItem->update([
|
||||
'meeting_id' => $request->meeting_id,
|
||||
'title' => $request->title,
|
||||
'description' => $request->description,
|
||||
'assigned_to' => $request->assigned_to,
|
||||
'due_date' => $request->due_date,
|
||||
'priority' => $request->priority,
|
||||
'status' => $status,
|
||||
'progress_percentage' => $progress,
|
||||
'notes' => $request->notes,
|
||||
'completed_date' => $completedDate,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Action item updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(ActionItem $actionItem)
|
||||
{
|
||||
if (!in_array($actionItem->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this action item'));
|
||||
}
|
||||
|
||||
$actionItem->delete();
|
||||
return redirect()->back()->with('success', __('Action item deleted successfully'));
|
||||
}
|
||||
|
||||
public function updateProgress(Request $request, ActionItem $actionItem)
|
||||
{
|
||||
if (!in_array($actionItem->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this action item'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'progress_percentage' => 'required|integer|min:0|max:100',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator);
|
||||
}
|
||||
|
||||
$progress = $request->progress_percentage;
|
||||
$status = $actionItem->status;
|
||||
$completedDate = $actionItem->completed_date;
|
||||
|
||||
if ($progress == 0) {
|
||||
$status = 'Not Started';
|
||||
$completedDate = null;
|
||||
} elseif ($progress > 0 && $progress < 100) {
|
||||
$status = 'In Progress';
|
||||
$completedDate = null;
|
||||
} elseif ($progress == 100) {
|
||||
$status = 'Completed';
|
||||
$completedDate = now();
|
||||
}
|
||||
|
||||
$actionItem->update([
|
||||
'progress_percentage' => $progress,
|
||||
'status' => $status,
|
||||
'completed_date' => $completedDate,
|
||||
'notes' => $request->notes ?? $actionItem->notes,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Progress updated successfully'));
|
||||
}
|
||||
}
|
||||
631
app/Http/Controllers/AnnouncementController.php
Normal file
631
app/Http/Controllers/AnnouncementController.php
Normal file
@@ -0,0 +1,631 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Announcement;
|
||||
use App\Models\AnnouncementView;
|
||||
use App\Models\Branch;
|
||||
use App\Models\Department;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class AnnouncementController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-announcements')) {
|
||||
$query = Announcement::with(['departments', 'branches'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-announcements')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-announcements')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('title', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%')
|
||||
->orWhere('content', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle category filter
|
||||
if ($request->has('category') && !empty($request->category)) {
|
||||
$query->where('category', $request->category);
|
||||
}
|
||||
|
||||
// Handle department filter
|
||||
if ($request->has('department_id') && !empty($request->department_id)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('is_company_wide', true)
|
||||
->orWhereHas('departments', function ($q) use ($request) {
|
||||
$q->where('departments.id', $request->department_id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle branch filter
|
||||
if ($request->has('branch_id') && !empty($request->branch_id)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('is_company_wide', true)
|
||||
->orWhereHas('branches', function ($q) use ($request) {
|
||||
$q->where('branches.id', $request->branch_id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle status filter
|
||||
if ($request->has('status') && !empty($request->status)) {
|
||||
$today = now()->format('Y-m-d');
|
||||
|
||||
if ($request->status === 'active') {
|
||||
$query->where('start_date', '<=', $today)
|
||||
->where(function ($q) use ($today) {
|
||||
$q->whereNull('end_date')
|
||||
->orWhere('end_date', '>=', $today);
|
||||
});
|
||||
} elseif ($request->status === 'upcoming') {
|
||||
$query->where('start_date', '>', $today);
|
||||
} elseif ($request->status === 'expired') {
|
||||
$query->whereNotNull('end_date')
|
||||
->where('end_date', '<', $today);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle priority filter
|
||||
if ($request->has('priority') && !empty($request->priority)) {
|
||||
if ($request->priority === 'high') {
|
||||
$query->where('is_high_priority', true);
|
||||
} elseif ($request->priority === 'normal') {
|
||||
$query->where('is_high_priority', false);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle featured filter
|
||||
if ($request->has('featured') && $request->featured === 'true') {
|
||||
$query->where('is_featured', true);
|
||||
}
|
||||
|
||||
// Handle date range filter
|
||||
if ($request->has('date_from') && !empty($request->date_from)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('start_date', '>=', $request->date_from)
|
||||
->orWhere('end_date', '>=', $request->date_from);
|
||||
});
|
||||
}
|
||||
if ($request->has('date_to') && !empty($request->date_to)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('start_date', '<=', $request->date_to)
|
||||
->orWhere('end_date', '<=', $request->date_to);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$allowedSortFields = ['id', 'title', 'category', 'start_date', 'end_date', 'is_featured', 'is_high_priority', 'created_at'];
|
||||
if ($request->has('sort_field') && !empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field === 'date_range' ? 'start_date' : $request->sort_field;
|
||||
if (in_array($sortField, $allowedSortFields)) {
|
||||
$sortDirection = in_array($request->sort_direction, ['asc', 'desc']) ? $request->sort_direction : 'asc';
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('created_at', 'desc');
|
||||
}
|
||||
} else {
|
||||
$query->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
$announcements = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
// Get departments for filter dropdown
|
||||
$departments = Department::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
// Get branches for filter dropdown
|
||||
$branches = Branch::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
// Get categories for filter dropdown
|
||||
$categories = Announcement::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('category')
|
||||
->distinct()
|
||||
->pluck('category')
|
||||
->toArray();
|
||||
|
||||
return Inertia::render('hr/announcements/index', [
|
||||
'announcements' => $announcements,
|
||||
'departments' => $departments,
|
||||
'branches' => $branches,
|
||||
'categories' => $categories,
|
||||
'filters' => $request->all(['search', 'category', 'department_id', 'branch_id', 'status', 'priority', 'featured', 'date_from', 'date_to', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the dashboard view.
|
||||
*/
|
||||
public function dashboard(Request $request)
|
||||
{
|
||||
$today = now()->format('Y-m-d');
|
||||
|
||||
// Get all announcements (active, expired, upcoming)
|
||||
$allAnnouncements = Announcement::with(['departments', 'branches'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->orderBy('is_high_priority', 'desc')
|
||||
->orderBy('is_featured', 'desc')
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
// Get featured announcements (from all announcements)
|
||||
$featuredAnnouncements = Announcement::with(['departments', 'branches'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('is_featured', true)
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
// Get high priority announcements (from all announcements)
|
||||
$highPriorityAnnouncements = Announcement::with(['departments', 'branches'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('is_high_priority', true)
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
// Get upcoming announcements
|
||||
$upcomingAnnouncements = Announcement::with(['departments', 'branches'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('start_date', '>', $today)
|
||||
->orderBy('start_date', 'asc')
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
// Get categories for filter
|
||||
$categories = Announcement::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('category')
|
||||
->distinct()
|
||||
->pluck('category')
|
||||
->toArray();
|
||||
|
||||
// Get departments for filter
|
||||
$departments = Department::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
// Get branches for filter
|
||||
$branches = Branch::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
// Get employee for marking announcements as read
|
||||
$employee = null;
|
||||
if (Auth::user()->type !== 'company' && Auth::user()->type !== 'superadmin') {
|
||||
$employee = User::where('id', Auth::id())->first();
|
||||
}
|
||||
|
||||
return Inertia::render('hr/announcements/dashboard', [
|
||||
'allAnnouncements' => $allAnnouncements,
|
||||
'featuredAnnouncements' => $featuredAnnouncements,
|
||||
'highPriorityAnnouncements' => $highPriorityAnnouncements,
|
||||
'upcomingAnnouncements' => $upcomingAnnouncements,
|
||||
'categories' => $categories,
|
||||
'departments' => $departments,
|
||||
'branches' => $branches,
|
||||
'employee' => $employee,
|
||||
'filters' => $request->all(['category', 'department_id', 'branch_id']),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'title' => 'required|string|max:255',
|
||||
'category' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'content' => 'required|string',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'nullable|date|after_or_equal:start_date',
|
||||
'attachments' => 'nullable|string',
|
||||
'is_featured' => 'nullable|boolean',
|
||||
'is_high_priority' => 'nullable|boolean',
|
||||
'is_company_wide' => 'nullable|boolean',
|
||||
'department_ids' => 'nullable|string|required_if:is_company_wide,false',
|
||||
'branch_ids' => 'nullable|string|required_if:is_company_wide,false',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if departments and branches belong to current company
|
||||
if (
|
||||
!$request->is_company_wide &&
|
||||
(empty($request->department_ids) && empty($request->branch_ids))
|
||||
) {
|
||||
return redirect()->back()->with('error', 'You must select at least one department or branch if the announcement is not company-wide');
|
||||
}
|
||||
|
||||
if (!empty($request->department_ids)) {
|
||||
$validDepartment = Department::where('created_by', createdBy())
|
||||
->where('id', $request->department_ids)
|
||||
->exists();
|
||||
|
||||
if (!$validDepartment) {
|
||||
return redirect()->back()->with('error', 'Invalid department selection');
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($request->branch_ids)) {
|
||||
$validBranch = Branch::where('created_by', createdBy())
|
||||
->where('id', $request->branch_ids)
|
||||
->exists();
|
||||
|
||||
if (!$validBranch) {
|
||||
return redirect()->back()->with('error', 'Invalid branch selection');
|
||||
}
|
||||
}
|
||||
|
||||
$announcementData = [
|
||||
'title' => $request->title,
|
||||
'category' => $request->category,
|
||||
'description' => $request->description,
|
||||
'content' => $request->content,
|
||||
'start_date' => $request->start_date,
|
||||
'end_date' => $request->end_date,
|
||||
'is_featured' => $request->is_featured ?? false,
|
||||
'is_high_priority' => $request->is_high_priority ?? false,
|
||||
'is_company_wide' => $request->is_company_wide ?? true,
|
||||
'created_by' => creatorId(),
|
||||
];
|
||||
|
||||
// Handle attachment from media library
|
||||
if ($request->has('attachments')) {
|
||||
$announcementData['attachments'] = $request->attachments;
|
||||
}
|
||||
|
||||
$announcement = Announcement::create($announcementData);
|
||||
|
||||
// Attach departments and branches if not company-wide
|
||||
if (!$request->is_company_wide) {
|
||||
if (!empty($request->department_ids)) {
|
||||
$announcement->departments()->attach([$request->department_ids]);
|
||||
}
|
||||
|
||||
if (!empty($request->branch_ids)) {
|
||||
$announcement->branches()->attach([$request->branch_ids]);
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('Announcement created successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(Announcement $announcement)
|
||||
{
|
||||
// Check if announcement belongs to current company
|
||||
if (!in_array($announcement->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to view this announcement'));
|
||||
}
|
||||
|
||||
// Load relationships
|
||||
$announcement->load(['departments', 'branches']);
|
||||
|
||||
// Get view statistics
|
||||
$viewCount = $announcement->viewedBy()->count();
|
||||
$totalEmployees = User::where('type', 'employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->count();
|
||||
$viewPercentage = $totalEmployees > 0 ? round(($viewCount / $totalEmployees) * 100) : 0;
|
||||
|
||||
// Mark as viewed if current user is an employee
|
||||
if (Auth::user()->type !== 'company' && Auth::user()->type !== 'superadmin') {
|
||||
$employee = User::where('id', Auth::id())->first();
|
||||
|
||||
if ($employee) {
|
||||
// Check if already viewed
|
||||
$existingView = AnnouncementView::where('announcement_id', $announcement->id)
|
||||
->where('employee_id', $employee->id)
|
||||
->first();
|
||||
|
||||
if (!$existingView) {
|
||||
// Mark as viewed
|
||||
$announcement->viewedBy()->attach($employee->id, [
|
||||
'viewed_at' => now()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Inertia::render('hr/announcements/show', [
|
||||
'announcement' => $announcement,
|
||||
'viewCount' => $viewCount,
|
||||
'totalEmployees' => $totalEmployees,
|
||||
'viewPercentage' => $viewPercentage,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, Announcement $announcement)
|
||||
{
|
||||
// Check if announcement belongs to current company
|
||||
if (!in_array($announcement->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to update this announcement');
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'title' => 'required|string|max:255',
|
||||
'category' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'content' => 'required|string',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'nullable|date|after_or_equal:start_date',
|
||||
'attachments' => 'nullable|string',
|
||||
'is_featured' => 'nullable|boolean',
|
||||
'is_high_priority' => 'nullable|boolean',
|
||||
'is_company_wide' => 'nullable|boolean',
|
||||
'department_ids' => 'nullable|string|required_if:is_company_wide,false',
|
||||
'branch_ids' => 'nullable|string|required_if:is_company_wide,false',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if departments and branches belong to current company
|
||||
if (
|
||||
!$request->is_company_wide &&
|
||||
(empty($request->department_ids) && empty($request->branch_ids))
|
||||
) {
|
||||
return redirect()->back()->with('error', 'You must select at least one department or branch if the announcement is not company-wide');
|
||||
}
|
||||
|
||||
if (!empty($request->department_ids)) {
|
||||
$departmentId = $request->department_ids;
|
||||
$validDepartment = Department::where('created_by', createdBy())
|
||||
->where('id', $departmentId)
|
||||
->exists();
|
||||
|
||||
if (!$validDepartment) {
|
||||
return redirect()->back()->with('error', 'Invalid department selection');
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($request->branch_ids)) {
|
||||
$branchId = $request->branch_ids;
|
||||
$validBranch = Branch::where('created_by', createdBy())
|
||||
->where('id', $branchId)
|
||||
->exists();
|
||||
|
||||
if (!$validBranch) {
|
||||
return redirect()->back()->with('error', 'Invalid branch selection');
|
||||
}
|
||||
}
|
||||
|
||||
$announcementData = [
|
||||
'title' => $request->title,
|
||||
'category' => $request->category,
|
||||
'description' => $request->description,
|
||||
'content' => $request->content,
|
||||
'start_date' => $request->start_date,
|
||||
'end_date' => $request->end_date,
|
||||
'is_featured' => $request->is_featured ?? false,
|
||||
'is_high_priority' => $request->is_high_priority ?? false,
|
||||
'is_company_wide' => $request->is_company_wide ?? true,
|
||||
];
|
||||
|
||||
// Handle attachment from media library
|
||||
if ($request->has('attachments')) {
|
||||
$announcementData['attachments'] = $request->attachments;
|
||||
}
|
||||
|
||||
$announcement->update($announcementData);
|
||||
|
||||
// Sync departments and branches
|
||||
if ($request->is_company_wide) {
|
||||
$announcement->departments()->detach();
|
||||
$announcement->branches()->detach();
|
||||
} else {
|
||||
if (!empty($request->department_ids)) {
|
||||
$announcement->departments()->sync([$request->department_ids]);
|
||||
} else {
|
||||
$announcement->departments()->detach();
|
||||
}
|
||||
|
||||
if (!empty($request->branch_ids)) {
|
||||
$announcement->branches()->sync([$request->branch_ids]);
|
||||
} else {
|
||||
$announcement->branches()->detach();
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('Announcement updated successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(Announcement $announcement)
|
||||
{
|
||||
// Check if announcement belongs to current company
|
||||
if (!in_array($announcement->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to delete this announcement');
|
||||
}
|
||||
|
||||
// Detach all departments and branches
|
||||
$announcement->departments()->detach();
|
||||
$announcement->branches()->detach();
|
||||
|
||||
// Delete all views
|
||||
$announcement->viewedBy()->detach();
|
||||
|
||||
// Delete the announcement
|
||||
$announcement->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('Announcement deleted successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Download attachment file.
|
||||
*/
|
||||
public function downloadAttachment(Announcement $announcement)
|
||||
{
|
||||
// Check if announcement belongs to current company
|
||||
if (!in_array($announcement->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to access this attachment'));
|
||||
}
|
||||
|
||||
if (!$announcement->attachments) {
|
||||
return redirect()->back()->with('error', __('Attachment file not found'));
|
||||
}
|
||||
|
||||
$filePath = getStorageFilePath($announcement->attachments);
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
return redirect()->back()->with('error', __('Attachment file not found'));
|
||||
}
|
||||
|
||||
return response()->download($filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark announcement as read for current employee.
|
||||
*/
|
||||
public function markAsRead(Request $request, Announcement $announcement)
|
||||
{
|
||||
$employee = User::where('id', Auth::id())->first();
|
||||
|
||||
if (!$employee) {
|
||||
return response()->json(['error' => 'Employee not found'], 404);
|
||||
}
|
||||
|
||||
// Check if already viewed
|
||||
$existingView = AnnouncementView::where('announcement_id', $announcement->id)
|
||||
->where('employee_id', $employee->id)
|
||||
->first();
|
||||
|
||||
if (!$existingView) {
|
||||
// Mark as viewed
|
||||
$announcement->viewedBy()->attach($employee->id, [
|
||||
'viewed_at' => now()
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get announcement view statistics.
|
||||
*/
|
||||
public function viewStatistics(Announcement $announcement)
|
||||
{
|
||||
// Check if announcement belongs to current company
|
||||
if (!in_array($announcement->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to view these statistics'));
|
||||
}
|
||||
|
||||
// Load viewed by (employees)
|
||||
$views = $announcement->viewedBy()->get();
|
||||
|
||||
// Get total employees
|
||||
$totalEmployees = User::where('type', 'employee')->whereIn('created_by', getCompanyAndUsersId())->count();
|
||||
|
||||
// Get statistics only for announcement's target branch and department
|
||||
$departmentStats = [];
|
||||
$branchStats = [];
|
||||
|
||||
if (!$announcement->is_company_wide) {
|
||||
// Get target branch and department
|
||||
$targetBranch = $announcement->branches->first();
|
||||
$targetDepartment = $announcement->departments->first();
|
||||
|
||||
if ($targetBranch) {
|
||||
$branchEmployees = User::where('type', 'employee')->whereIn('created_by', getCompanyAndUsersId())->whereHas('employee', function ($q) use ($targetBranch) {
|
||||
$q->where('branch_id', $targetBranch->id);
|
||||
})->count();
|
||||
$branchViews = $announcement->viewedBy()
|
||||
->whereHas('employee', function ($q) use ($targetBranch) {
|
||||
$q->where('branch_id', $targetBranch->id);
|
||||
})
|
||||
->count();
|
||||
|
||||
$branchStats[] = [
|
||||
'branch' => $targetBranch->name,
|
||||
'total' => $branchEmployees,
|
||||
'viewed' => $branchViews,
|
||||
'percentage' => $branchEmployees > 0 ? round(($branchViews / $branchEmployees) * 100) : 0
|
||||
];
|
||||
}
|
||||
|
||||
if ($targetDepartment) {
|
||||
$departmentEmployees = User::where('type', 'employee')->whereIn('created_by', getCompanyAndUsersId())->whereHas('employee', function ($q) use ($targetDepartment) {
|
||||
$q->where('department_id', $targetDepartment->id);
|
||||
})->count();
|
||||
|
||||
$departmentViews = $announcement->viewedBy()
|
||||
->whereHas('employee', function ($q) use ($targetDepartment) {
|
||||
$q->where('department_id', $targetDepartment->id);
|
||||
})
|
||||
->count();
|
||||
|
||||
$departmentStats[] = [
|
||||
'branch_name' => $targetBranch ? $targetBranch->name : 'Unknown',
|
||||
'departments' => [[
|
||||
'department' => $targetDepartment->name,
|
||||
'total' => $departmentEmployees,
|
||||
'viewed' => $departmentViews,
|
||||
'percentage' => $departmentEmployees > 0 ? round(($departmentViews / $departmentEmployees) * 100) : 0
|
||||
]]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return Inertia::render('hr/announcements/statistics', [
|
||||
'announcement' => $announcement,
|
||||
'totalEmployees' => $totalEmployees,
|
||||
'viewedCount' => $views->count(),
|
||||
'viewPercentage' => $totalEmployees > 0 ? round(($views->count() / $totalEmployees) * 100) : 0,
|
||||
'departmentStats' => $departmentStats,
|
||||
'branchStats' => $branchStats,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get departments based on selected branches.
|
||||
*/
|
||||
public function getDepartments($branchIds)
|
||||
{
|
||||
$branchIdArray = explode(',', $branchIds);
|
||||
|
||||
$departments = Department::whereIn('branch_id', $branchIdArray)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('id', 'name')
|
||||
->get()
|
||||
->map(function ($dept) {
|
||||
return [
|
||||
'value' => $dept->id,
|
||||
'label' => $dept->name
|
||||
];
|
||||
});
|
||||
return response()->json($departments);
|
||||
}
|
||||
}
|
||||
1088
app/Http/Controllers/AssetController.php
Normal file
1088
app/Http/Controllers/AssetController.php
Normal file
File diff suppressed because it is too large
Load Diff
126
app/Http/Controllers/AssetTypeController.php
Normal file
126
app/Http/Controllers/AssetTypeController.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\AssetType;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class AssetTypeController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-asset-types')) {
|
||||
$query = AssetType::withCount('assets')->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-asset-types')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-asset-types')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$allowedSortFields = ['id', 'name', 'description', 'created_at'];
|
||||
if ($request->has('sort_field') && !empty($request->sort_field) && in_array($request->sort_field, $allowedSortFields)) {
|
||||
$sortDirection = in_array($request->sort_direction, ['asc', 'desc']) ? $request->sort_direction : 'asc';
|
||||
$query->orderBy($request->sort_field, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
$assetTypes = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
return Inertia::render('hr/assets/types/index', [
|
||||
'assetTypes' => $assetTypes,
|
||||
'filters' => $request->all(['search', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
AssetType::create([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Asset type created successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, AssetType $assetType)
|
||||
{
|
||||
// Check if asset type belongs to current company
|
||||
if (!in_array($assetType->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this asset type'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$assetType->update([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Asset type updated successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(AssetType $assetType)
|
||||
{
|
||||
// Check if asset type belongs to current company
|
||||
if (!in_array($assetType->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this asset type'));
|
||||
}
|
||||
|
||||
// Check if asset type is being used by any assets
|
||||
if ($assetType->assets()->count() > 0) {
|
||||
return redirect()->back()->with('error', __('Cannot delete asset type that is being used by assets'));
|
||||
}
|
||||
|
||||
$assetType->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('Asset type deleted successfully'));
|
||||
}
|
||||
}
|
||||
190
app/Http/Controllers/AttendancePolicyController.php
Normal file
190
app/Http/Controllers/AttendancePolicyController.php
Normal file
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\AttendancePolicy;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class AttendancePolicyController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-attendance-policies')) {
|
||||
$query = AttendancePolicy::with(['creator'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-attendance-policies')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-attendance-policies')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle status filter
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle overtime calculation filter
|
||||
if ($request->has('overtime_calculation') && !empty($request->overtime_calculation) && $request->overtime_calculation !== 'all') {
|
||||
$query->where('overtime_calculation', $request->overtime_calculation);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
if ($request->has('sort_field') && !empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field;
|
||||
$sortDirection = $request->sort_direction ?? 'asc';
|
||||
|
||||
if ($sortField === 'name') {
|
||||
$query->orderBy('name', $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('created_at', 'desc');
|
||||
}
|
||||
} else {
|
||||
$query->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
$attendancePolicies = $query->paginate($request->per_page ?? 9);
|
||||
|
||||
// Stats always calculated from ALL records — never affected by filters or pagination
|
||||
$allPolicies = AttendancePolicy::where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-attendance-policies')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-attendance-policies')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
$stats = [
|
||||
'total' => (clone $allPolicies)->count(),
|
||||
'active' => (clone $allPolicies)->where('status', 'active')->count(),
|
||||
'avg_late_grace' => (int) round((clone $allPolicies)->avg('late_arrival_grace') ?? 0),
|
||||
'avg_overtime_rate'=> (float) ((clone $allPolicies)->avg('overtime_rate_per_hour') ?? 0),
|
||||
];
|
||||
|
||||
return Inertia::render('hr/attendance-policies/index', [
|
||||
'attendancePolicies' => $attendancePolicies,
|
||||
'stats' => $stats,
|
||||
'filters' => $request->all(['search', 'status', 'overtime_calculation', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'late_arrival_grace' => 'required|integer|min:0',
|
||||
'early_departure_grace' => 'required|integer|min:0',
|
||||
'overtime_rate_per_hour' => 'required|numeric|min:0',
|
||||
'status' => 'nullable|in:active,inactive',
|
||||
]);
|
||||
|
||||
$validated['created_by'] = creatorId();
|
||||
$validated['status'] = $validated['status'] ?? 'active';
|
||||
|
||||
// Check if policy with same name already exists
|
||||
$exists = AttendancePolicy::where('name', $validated['name'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return redirect()->back()->with('error', __('Attendance policy with this name already exists.'));
|
||||
}
|
||||
|
||||
AttendancePolicy::create($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Attendance policy created successfully.'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $attendancePolicyId)
|
||||
{
|
||||
$attendancePolicy = AttendancePolicy::where('id', $attendancePolicyId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($attendancePolicy) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'late_arrival_grace' => 'required|integer|min:0',
|
||||
'early_departure_grace' => 'required|integer|min:0',
|
||||
'overtime_rate_per_hour' => 'required|numeric|min:0',
|
||||
'status' => 'nullable|in:active,inactive',
|
||||
]);
|
||||
|
||||
// Check if policy with same name already exists (excluding current)
|
||||
$exists = AttendancePolicy::where('name', $validated['name'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('id', '!=', $attendancePolicyId)
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return redirect()->back()->with('error', __('Attendance policy with this name already exists.'));
|
||||
}
|
||||
|
||||
$attendancePolicy->update($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Attendance policy updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update attendance policy'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Attendance policy Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($attendancePolicyId)
|
||||
{
|
||||
$attendancePolicy = AttendancePolicy::where('id', $attendancePolicyId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($attendancePolicy) {
|
||||
try {
|
||||
$attendancePolicy->delete();
|
||||
return redirect()->back()->with('success', __('Attendance policy deleted successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to delete attendance policy'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Attendance policy Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleStatus($attendancePolicyId)
|
||||
{
|
||||
$attendancePolicy = AttendancePolicy::where('id', $attendancePolicyId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($attendancePolicy) {
|
||||
try {
|
||||
$attendancePolicy->status = $attendancePolicy->status === 'active' ? 'inactive' : 'active';
|
||||
$attendancePolicy->save();
|
||||
|
||||
return redirect()->back()->with('success', __('Attendance policy status updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update attendance policy status'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Attendance policy Not Found.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
742
app/Http/Controllers/AttendanceRecordController.php
Normal file
742
app/Http/Controllers/AttendanceRecordController.php
Normal file
@@ -0,0 +1,742 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\AttendancePolicy;
|
||||
use App\Models\AttendanceRecord;
|
||||
use App\Models\Employee;
|
||||
use App\Models\IpRestriction;
|
||||
use App\Models\LeaveApplication;
|
||||
use App\Models\Shift;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class AttendanceRecordController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-attendance-records')) {
|
||||
$query = AttendanceRecord::with(['employee', 'shift', 'attendancePolicy', 'creator'])
|
||||
->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-attendance-records')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-attendance-records')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('employee_id', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && ! empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->whereHas('employee', function ($subQ) use ($request) {
|
||||
$subQ->where('name', 'like', '%'.$request->search.'%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle employee filter
|
||||
if ($request->has('employee_id') && ! empty($request->employee_id) && $request->employee_id !== 'all') {
|
||||
$query->where('employee_id', $request->employee_id);
|
||||
}
|
||||
|
||||
// Handle status filter
|
||||
if ($request->has('status') && ! empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle date range filter
|
||||
if ($request->has('date_from') && ! empty($request->date_from)) {
|
||||
$query->where('date', '>=', $request->date_from);
|
||||
}
|
||||
if ($request->has('date_to') && ! empty($request->date_to)) {
|
||||
$query->where('date', '<=', $request->date_to);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
if ($request->has('sort_field') && ! empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field;
|
||||
$sortDirection = $request->sort_direction ?? 'asc';
|
||||
|
||||
if ($sortField === 'date') {
|
||||
$query->orderBy('date', $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
} else {
|
||||
$query->orderBy('date', 'desc');
|
||||
}
|
||||
|
||||
$attendanceRecords = $query->paginate($request->per_page ?? 9);
|
||||
|
||||
// Load avatar dynamically — same pattern as AwardController
|
||||
$attendanceRecords->getCollection()->transform(function ($record) {
|
||||
if ($record->employee) {
|
||||
$rawAvatar = $record->employee->getRawOriginal('avatar');
|
||||
$record->employee->avatar = check_file($rawAvatar)
|
||||
? get_file($rawAvatar)
|
||||
: get_file('avatars/avatar.png');
|
||||
}
|
||||
|
||||
// Add leave type information for on_leave records
|
||||
if ($record->status === 'on_leave') {
|
||||
$leaveApplication = LeaveApplication::where('employee_id', $record->employee_id)
|
||||
->whereDate('start_date', '<=', $record->date)
|
||||
->whereDate('end_date', '>=', $record->date)
|
||||
->where('status', 'approved')
|
||||
->with('leaveType')
|
||||
->first();
|
||||
|
||||
$record->leave_type = $leaveApplication?->leaveType;
|
||||
}
|
||||
|
||||
return $record;
|
||||
});
|
||||
|
||||
// Get employees for filter dropdown
|
||||
$employees = User::where('type', 'employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->get(['id', 'name']);
|
||||
|
||||
$companyUserIds = getCompanyAndUsersId();
|
||||
|
||||
if (isDemo()) {
|
||||
$statsRecords = AttendanceRecord::whereIn('created_by', $companyUserIds)->get();
|
||||
|
||||
$todayStats = [
|
||||
'present' => $statsRecords->where('status', 'present')->count(),
|
||||
'on_leave' => LeaveApplication::whereIn('employee_id', function ($q) use ($companyUserIds) {
|
||||
$q->select('user_id')->from('employees')->whereIn('created_by', $companyUserIds);
|
||||
})->where('status', 'approved')->count(),
|
||||
'late_arrivals' => $statsRecords->where('is_late', true)->count(),
|
||||
'overtime' => $statsRecords->where('overtime_hours', '>', 0)->count(),
|
||||
];
|
||||
} else {
|
||||
$today = Carbon::today();
|
||||
|
||||
$todayRecords = AttendanceRecord::whereIn('created_by', $companyUserIds)
|
||||
->whereDate('date', $today)
|
||||
->get();
|
||||
|
||||
$todayStats = [
|
||||
'present' => $todayRecords->where('status', 'present')->count(),
|
||||
'on_leave' => LeaveApplication::whereIn('employee_id', function ($q) use ($companyUserIds) {
|
||||
$q->select('user_id')->from('employees')->whereIn('created_by', $companyUserIds);
|
||||
})->where('status', 'approved')->whereDate('start_date', '<=', $today)->whereDate('end_date', '>=', $today)->count(),
|
||||
'late_arrivals' => $todayRecords->where('is_late', true)->count(),
|
||||
'overtime' => $todayRecords->where('overtime_hours', '>', 0)->count(),
|
||||
];
|
||||
}
|
||||
|
||||
return Inertia::render('hr/attendance-records/index', [
|
||||
'attendanceRecords' => $attendanceRecords,
|
||||
'employees' => $this->getFilteredEmployees(),
|
||||
'hasSampleFile' => file_exists(storage_path('uploads/sample/sample-attendance-record.xlsx')),
|
||||
'filters' => $request->all(['search', 'employee_id', 'status', 'date_from', 'date_to', 'sort_field', 'sort_direction', 'per_page']),
|
||||
'todayStats' => $todayStats,
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
private function getFilteredEmployees()
|
||||
{
|
||||
// Get employees for filter dropdown (compatible with getFilteredEmployees logic)
|
||||
$employeeQuery = Employee::whereIn('created_by', getCompanyAndUsersId());
|
||||
|
||||
if (Auth::user()->can('manage-own-attendance-records') && ! Auth::user()->can('manage-any-attendance-records')) {
|
||||
$employeeQuery->where(function ($q) {
|
||||
$q->where('created_by', Auth::id())->orWhere('user_id', Auth::id());
|
||||
});
|
||||
}
|
||||
|
||||
$employees = User::emp()
|
||||
->with('employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->whereIn('id', $employeeQuery->pluck('user_id'))
|
||||
->select('id', 'name')
|
||||
->get()
|
||||
->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'employee_id' => $user->employee->employee_id ?? '',
|
||||
];
|
||||
});
|
||||
|
||||
return $employees;
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'date' => 'required|date',
|
||||
'clock_in' => 'nullable|date_format:H:i',
|
||||
'clock_out' => 'nullable|date_format:H:i',
|
||||
'is_holiday' => 'boolean',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
// Get employee with shift and policy
|
||||
$employee = Employee::where('user_id', $validated['employee_id'])->first();
|
||||
|
||||
// Get working days from settings
|
||||
$globalSettings = settings();
|
||||
$workingDaysIndices = json_decode($globalSettings['working_days'] ?? '[]', true);
|
||||
|
||||
if (empty($workingDaysIndices)) {
|
||||
return redirect()->back()->with('error', __('Please configure working days first.'));
|
||||
}
|
||||
|
||||
$dateIndex = Carbon::parse($validated['date'])->dayOfWeek;
|
||||
if (! in_array($dateIndex, $workingDaysIndices)) {
|
||||
return redirect()->back()->with('error', __('Cannot create attendance record for non-working day.'));
|
||||
}
|
||||
|
||||
// Check if employee has approved leave for this date
|
||||
$hasApprovedLeave = LeaveApplication::where('employee_id', $validated['employee_id'])
|
||||
->where('status', 'approved')
|
||||
->whereDate('start_date', '<=', $validated['date'])
|
||||
->whereDate('end_date', '>=', $validated['date'])
|
||||
->exists();
|
||||
|
||||
if ($hasApprovedLeave) {
|
||||
return redirect()->back()->with('error', __('Employee has approved leave for this date. Cannot create attendance record.'));
|
||||
}
|
||||
|
||||
// Check if record already exists
|
||||
$exists = AttendanceRecord::where('employee_id', $validated['employee_id'])
|
||||
->where('date', $validated['date'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return redirect()->back()->with('error', __('Attendance record already exists for this employee and date.'));
|
||||
}
|
||||
|
||||
// Use employee's assigned shift and policy, or get defaults
|
||||
$shift = $employee && $employee->shift_id ?
|
||||
Shift::find($employee->shift_id) :
|
||||
Shift::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->first();
|
||||
|
||||
$policy = $employee && $employee->attendance_policy_id ?
|
||||
AttendancePolicy::find($employee->attendance_policy_id) :
|
||||
AttendancePolicy::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->first();
|
||||
|
||||
$validated['shift_id'] = $shift?->id;
|
||||
$validated['attendance_policy_id'] = $policy?->id;
|
||||
$validated['created_by'] = creatorId();
|
||||
$validated['is_holiday'] = $validated['is_holiday'] ?? false;
|
||||
$validated['break_hours'] = $validated['break_hours'] ?? 0;
|
||||
|
||||
// Set weekend flag
|
||||
$validated['is_weekend'] = Carbon::parse($validated['date'])->isWeekend();
|
||||
|
||||
$record = AttendanceRecord::create($validated);
|
||||
|
||||
// Process complete attendance calculation
|
||||
$record->fresh(); // Reload to get relationships
|
||||
$record->processAttendance();
|
||||
|
||||
return redirect()->back()->with('success', __('Attendance record created successfully.'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $attendanceRecordId)
|
||||
{
|
||||
|
||||
$attendanceRecord = AttendanceRecord::where('id', $attendanceRecordId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
// Get working days from settings
|
||||
$globalSettings = settings();
|
||||
$workingDaysIndices = json_decode($globalSettings['working_days'] ?? '[]', true);
|
||||
|
||||
if (empty($workingDaysIndices)) {
|
||||
return redirect()->back()->with('error', __('Please configure working days first.'));
|
||||
}
|
||||
|
||||
$dateIndex = Carbon::parse($request->date)->dayOfWeek;
|
||||
if (! in_array($dateIndex, $workingDaysIndices)) {
|
||||
return redirect()->back()->with('error', __('Cannot create attendance record for non-working day.'));
|
||||
}
|
||||
|
||||
// Check if employee has approved leave for this date
|
||||
$hasApprovedLeave = LeaveApplication::where('employee_id', $request->employee_id)
|
||||
->where('status', 'approved')
|
||||
->whereDate('start_date', '<=', $request->date)
|
||||
->whereDate('end_date', '>=', $request->date)
|
||||
->exists();
|
||||
|
||||
if ($hasApprovedLeave) {
|
||||
return redirect()->back()->with('error', __('Employee has approved leave for this date. Cannot create attendance record.'));
|
||||
}
|
||||
|
||||
if ($attendanceRecord) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'date' => 'required|date',
|
||||
'clock_in' => 'nullable|date_format:H:i',
|
||||
'clock_out' => 'nullable|date_format:H:i',
|
||||
'break_hours' => 'nullable|numeric|min:0',
|
||||
'is_holiday' => 'boolean',
|
||||
'status' => 'required|in:present,absent,half_day,on_leave,holiday',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
// Check if employee or date changed and if duplicate exists
|
||||
if ($attendanceRecord->employee_id != $validated['employee_id'] || $attendanceRecord->date != $validated['date']) {
|
||||
$exists = AttendanceRecord::where('employee_id', $validated['employee_id'])
|
||||
->where('date', $validated['date'])
|
||||
->where('id', '!=', $attendanceRecordId)
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return redirect()->back()->with('error', __('Attendance record already exists for this employee and date.'));
|
||||
}
|
||||
}
|
||||
|
||||
// Get employee with shift and policy
|
||||
$employee = \App\Models\Employee::where('user_id', $validated['employee_id'])->first();
|
||||
|
||||
// Use employee's assigned shift and policy, or get defaults
|
||||
$shift = $employee && $employee->shift_id ?
|
||||
Shift::find($employee->shift_id) :
|
||||
Shift::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->first();
|
||||
|
||||
$policy = $employee && $employee->attendance_policy_id ?
|
||||
AttendancePolicy::find($employee->attendance_policy_id) :
|
||||
AttendancePolicy::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->first();
|
||||
|
||||
$validated['shift_id'] = $shift?->id;
|
||||
$validated['attendance_policy_id'] = $policy?->id;
|
||||
|
||||
// Set weekend flag
|
||||
$validated['is_weekend'] = Carbon::parse($validated['date'])->isWeekend();
|
||||
|
||||
$attendanceRecord->update($validated);
|
||||
|
||||
// Process complete attendance calculation
|
||||
$attendanceRecord->fresh(); // Reload to get relationships
|
||||
$attendanceRecord->processAttendance();
|
||||
|
||||
return redirect()->back()->with('success', __('Attendance record updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update attendance record'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Attendance record Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($attendanceRecordId)
|
||||
{
|
||||
$attendanceRecord = AttendanceRecord::where('id', $attendanceRecordId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($attendanceRecord) {
|
||||
try {
|
||||
$attendanceRecord->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('Attendance record deleted successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to delete attendance record'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Attendance record Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function clockIn(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('clock-in-out')) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
]);
|
||||
|
||||
$settings = settings();
|
||||
if (! empty($settings['ipRestrictionEnabled']) && $settings['ipRestrictionEnabled'] == 1) {
|
||||
$loginUserIp = request()->ip();
|
||||
$ip = IpRestriction::whereIn('created_by', getCompanyAndUsersId())->where('ip_address', $loginUserIp)->first();
|
||||
if (empty($ip) || is_null($ip)) {
|
||||
return redirect()->back()->with('error', __('This IP Address Is Not Allowed For Clock In & Clock Out.'));
|
||||
}
|
||||
}
|
||||
|
||||
$today = Carbon::today();
|
||||
$now = Carbon::now();
|
||||
|
||||
// Get working days from settings
|
||||
$globalSettings = settings();
|
||||
$workingDaysIndices = json_decode($globalSettings['working_days'] ?? '[]', true);
|
||||
|
||||
if (empty($workingDaysIndices)) {
|
||||
return redirect()->back()->with('error', __('Please configure working days first.'));
|
||||
}
|
||||
|
||||
$dateIndex = Carbon::parse($today)->dayOfWeek;
|
||||
if (! in_array($dateIndex, $workingDaysIndices)) {
|
||||
return redirect()->back()->with('error', __('Cannot create attendance record for non-working day.'));
|
||||
}
|
||||
|
||||
// Check if employee has approved leave for this date
|
||||
$hasApprovedLeave = LeaveApplication::where('employee_id', $validated['employee_id'])
|
||||
->where('status', 'approved')
|
||||
->whereDate('start_date', '<=', $today)
|
||||
->whereDate('end_date', '>=', $today)
|
||||
->exists();
|
||||
|
||||
if ($hasApprovedLeave) {
|
||||
return redirect()->back()->with('error', __('Employee has approved leave for this date. Cannot create attendance record.'));
|
||||
}
|
||||
|
||||
// Check if already clocked in today
|
||||
$existingRecord = AttendanceRecord::where('employee_id', $validated['employee_id'])
|
||||
->where('date', $today)
|
||||
->first();
|
||||
|
||||
if ($existingRecord && $existingRecord->clock_in) {
|
||||
return redirect()->back()->with('error', __('Already clocked in today.'));
|
||||
}
|
||||
|
||||
// Get employee with shift and policy
|
||||
$employee = \App\Models\Employee::where('user_id', $validated['employee_id'])->first();
|
||||
|
||||
if (! $employee) {
|
||||
return redirect()->back()->with('error', __('Employee profile not found.'));
|
||||
}
|
||||
|
||||
// Use employee's assigned shift and policy, or get defaults
|
||||
$shift = $employee->shift_id ?
|
||||
Shift::find($employee->shift_id) :
|
||||
Shift::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->first();
|
||||
|
||||
$policy = $employee->attendance_policy_id ?
|
||||
AttendancePolicy::find($employee->attendance_policy_id) :
|
||||
AttendancePolicy::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->first();
|
||||
|
||||
if (! $shift || ! $policy) {
|
||||
return redirect()->back()->with('error', __('No active shift or attendance policy found. Please contact HR.'));
|
||||
}
|
||||
|
||||
if ($existingRecord) {
|
||||
$existingRecord->update([
|
||||
'clock_in' => $now->format('H:i:s'),
|
||||
'shift_id' => $shift->id,
|
||||
'attendance_policy_id' => $policy->id,
|
||||
'status' => 'present',
|
||||
]);
|
||||
$record = $existingRecord;
|
||||
} else {
|
||||
$record = AttendanceRecord::create([
|
||||
'employee_id' => $validated['employee_id'],
|
||||
'date' => $today,
|
||||
'clock_in' => $now->format('H:i:s'),
|
||||
'shift_id' => $shift->id,
|
||||
'attendance_policy_id' => $policy->id,
|
||||
'is_weekend' => $today->isWeekend(),
|
||||
'status' => 'present',
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Check for late arrival if methods exist
|
||||
if (method_exists($record, 'checkLateArrival')) {
|
||||
$record->checkLateArrival();
|
||||
$record->save();
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('Clocked in successfully.'));
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Clock in failed: '.$e->getMessage());
|
||||
|
||||
return redirect()->back()->with('error', __('Failed to clock in. Please try again.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function clockOut(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('clock-in-out')) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
]);
|
||||
|
||||
$today = Carbon::today();
|
||||
$now = Carbon::now();
|
||||
|
||||
$record = AttendanceRecord::where('employee_id', $validated['employee_id'])
|
||||
->where('date', $today)
|
||||
->first();
|
||||
|
||||
if (! $record || ! $record->clock_in) {
|
||||
return redirect()->back()->with('error', __('Must clock in first.'));
|
||||
}
|
||||
|
||||
if ($record->clock_out) {
|
||||
return redirect()->back()->with('error', __('Already clocked out today.'));
|
||||
}
|
||||
|
||||
$record->update([
|
||||
'clock_out' => $now->format('H:i:s'),
|
||||
]);
|
||||
|
||||
// Process complete attendance calculation if method exists
|
||||
if (method_exists($record, 'processAttendance')) {
|
||||
$record->processAttendance();
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('Clocked out successfully.'));
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Clock out failed: '.$e->getMessage());
|
||||
|
||||
return redirect()->back()->with('error', __('Failed to clock out. Please try again.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getTodayAttendance(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
]);
|
||||
|
||||
$today = Carbon::today();
|
||||
$attendance = AttendanceRecord::where('employee_id', $validated['employee_id'])
|
||||
->where('date', $today)
|
||||
->first();
|
||||
|
||||
return Inertia::render('employee-dashboard', [
|
||||
'attendance' => $attendance,
|
||||
]);
|
||||
}
|
||||
|
||||
public function export()
|
||||
{
|
||||
if (Auth::user()->can('export-attendance-record')) {
|
||||
try {
|
||||
$attendanceRecords = AttendanceRecord::with(['employee', 'shift', 'attendancePolicy'])
|
||||
->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-attendance-records')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-attendance-records')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('employee_id', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
})->orderBy('date', 'desc')->get();
|
||||
|
||||
$fileName = 'attendance_records_'.date('Y-m-d_His').'.csv';
|
||||
$headers = [
|
||||
'Content-Type' => 'text/csv',
|
||||
'Content-Disposition' => 'attachment; filename="'.$fileName.'"',
|
||||
];
|
||||
|
||||
$callback = function () use ($attendanceRecords) {
|
||||
$file = fopen('php://output', 'w');
|
||||
fputcsv($file, [
|
||||
'Employee',
|
||||
'Date',
|
||||
'Shift',
|
||||
'Attedance Policy',
|
||||
'Clock In',
|
||||
'Clock Out',
|
||||
'Break Hours',
|
||||
'Total Hours',
|
||||
'Overtime Hours',
|
||||
'Status',
|
||||
'Is Late',
|
||||
'Is Early Departure',
|
||||
'Notes'
|
||||
]);
|
||||
|
||||
foreach ($attendanceRecords as $record) {
|
||||
fputcsv($file, [
|
||||
$record->employee->name ?? '',
|
||||
$record->date ? date('Y-m-d', strtotime($record->date)) : '',
|
||||
$record->shift->name ?? '',
|
||||
$record->attendancePolicy->name ?? '',
|
||||
$record->clock_in ?? '',
|
||||
$record->clock_out ?? '',
|
||||
$record->break_hours ?? '',
|
||||
$record->total_hours ?? '',
|
||||
$record->overtime_hours ?? '',
|
||||
$record->status ?? '',
|
||||
$record->is_late ? 'Yes' : 'No',
|
||||
$record->is_early_departure ? 'Yes' : 'No',
|
||||
$record->notes ?? ''
|
||||
]);
|
||||
}
|
||||
fclose($file);
|
||||
};
|
||||
|
||||
return response()->stream($callback, 200, $headers);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['message' => __('Failed to export attendance records: :message', ['message' => $e->getMessage()])], 500);
|
||||
}
|
||||
} else {
|
||||
return response()->json(['message' => __('Permission Denied.')], 403);
|
||||
}
|
||||
}
|
||||
|
||||
public function downloadTemplate()
|
||||
{
|
||||
$filePath = storage_path('uploads/sample/sample-attendance-record.xlsx');
|
||||
if (! file_exists($filePath)) {
|
||||
return response()->json(['error' => __('Template file not available')], 404);
|
||||
}
|
||||
|
||||
return response()->download($filePath, 'sample-attendance-record.xlsx');
|
||||
}
|
||||
|
||||
public function parseFile(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('import-attendance-record')) {
|
||||
$rules = ['file' => 'required|mimes:csv,txt,xlsx,xls'];
|
||||
$validator = Validator::make($request->all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json(['message' => $validator->getMessageBag()->first()]);
|
||||
}
|
||||
|
||||
try {
|
||||
$file = $request->file('file');
|
||||
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file->getRealPath());
|
||||
$worksheet = $spreadsheet->getActiveSheet();
|
||||
$highestColumn = $worksheet->getHighestColumn();
|
||||
$highestRow = $worksheet->getHighestRow();
|
||||
$headers = [];
|
||||
|
||||
for ($col = 'A'; $col <= $highestColumn; $col++) {
|
||||
$value = $worksheet->getCell($col.'1')->getValue();
|
||||
if ($value) {
|
||||
$headers[] = (string) $value;
|
||||
}
|
||||
}
|
||||
|
||||
$previewData = [];
|
||||
for ($row = 2; $row <= $highestRow; $row++) {
|
||||
$rowData = [];
|
||||
$colIndex = 0;
|
||||
for ($col = 'A'; $col <= $highestColumn; $col++) {
|
||||
if ($colIndex < count($headers)) {
|
||||
$rowData[$headers[$colIndex]] = (string) $worksheet->getCell($col.$row)->getValue();
|
||||
}
|
||||
$colIndex++;
|
||||
}
|
||||
$previewData[] = $rowData;
|
||||
}
|
||||
|
||||
return response()->json(['excelColumns' => $headers, 'previewData' => $previewData]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['message' => __('Failed to parse file: :error', ['error' => $e->getMessage()])]);
|
||||
}
|
||||
} else {
|
||||
return response()->json(['message' => __('Permission denied.')], 403);
|
||||
}
|
||||
}
|
||||
|
||||
public function fileImport(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('import-attendance-record')) {
|
||||
$rules = ['data' => 'required|array'];
|
||||
$validator = Validator::make($request->all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->with('error', $validator->getMessageBag()->first());
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $request->data;
|
||||
$imported = 0;
|
||||
$skipped = 0;
|
||||
|
||||
foreach ($data as $row) {
|
||||
try {
|
||||
if (empty($row['employee']) || empty($row['date'])) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$employee = User::where('name', $row['employee'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('type', 'employee')
|
||||
->first();
|
||||
|
||||
if (! $employee) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if attendance record already exists for this employee and date
|
||||
$exists = AttendanceRecord::where('employee_id', $employee->id)
|
||||
->whereDate('date', $row['date'])
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get employee with shift and policy
|
||||
$employeeModel = Employee::where('user_id', $employee->id)->first();
|
||||
|
||||
$shift = $employeeModel && $employeeModel->shift_id ?
|
||||
Shift::find($employeeModel->shift_id) :
|
||||
Shift::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->first();
|
||||
|
||||
$policy = $employeeModel && $employeeModel->attendance_policy_id ?
|
||||
AttendancePolicy::find($employeeModel->attendance_policy_id) :
|
||||
AttendancePolicy::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->first();
|
||||
|
||||
if (! $shift || ! $policy) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$record = AttendanceRecord::create([
|
||||
'employee_id' => $employee->id,
|
||||
'date' => $row['date'],
|
||||
'shift_id' => $shift->id,
|
||||
'attendance_policy_id' => $policy->id,
|
||||
'clock_in' => $row['clock_in'] ?? null,
|
||||
'clock_out' => $row['clock_out'] ?? null,
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
// Process attendance calculation
|
||||
if (method_exists($record, 'processAttendance')) {
|
||||
$record->processAttendance();
|
||||
}
|
||||
$imported++;
|
||||
} catch (\Exception $e) {
|
||||
$skipped++;
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('Import completed: :added attendance records added, :skipped attendance records skipped', ['added' => $imported, 'skipped' => $skipped]));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', __('Failed to import: :error', ['error' => $e->getMessage()]));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission denied.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
313
app/Http/Controllers/AttendanceRegularizationController.php
Normal file
313
app/Http/Controllers/AttendanceRegularizationController.php
Normal file
@@ -0,0 +1,313 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\AttendanceRegularization;
|
||||
use App\Models\AttendanceRecord;
|
||||
use App\Models\Employee;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class AttendanceRegularizationController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-attendance-regularizations')) {
|
||||
$query = AttendanceRegularization::with(['employee', 'attendanceRecord', 'approver', 'creator'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-attendance-regularizations')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-attendance-regularizations')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('employee_id', Auth::id())->orWhere('approved_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('reason', 'like', '%' . $request->search . '%')
|
||||
->orWhereHas('employee', function ($subQ) use ($request) {
|
||||
$subQ->where('name', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle employee filter
|
||||
if ($request->has('employee_id') && !empty($request->employee_id) && $request->employee_id !== 'all') {
|
||||
$query->where('employee_id', $request->employee_id);
|
||||
}
|
||||
|
||||
// Handle status filter
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle date range filter
|
||||
if ($request->has('date_from') && !empty($request->date_from)) {
|
||||
$query->where('date', '>=', $request->date_from);
|
||||
}
|
||||
if ($request->has('date_to') && !empty($request->date_to)) {
|
||||
$query->where('date', '<=', $request->date_to);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
if ($request->has('sort_field') && !empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field;
|
||||
$sortDirection = $request->sort_direction ?? 'asc';
|
||||
|
||||
if (in_array($sortField, ['date', 'created_at'])) {
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('created_at', 'desc');
|
||||
}
|
||||
} else {
|
||||
$query->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
$regularizations = $query->paginate($request->per_page ?? 9);
|
||||
|
||||
// Load avatar dynamically — same pattern as AttendanceRecordController
|
||||
$regularizations->getCollection()->transform(function ($record) {
|
||||
if ($record->employee) {
|
||||
$rawAvatar = $record->employee->getRawOriginal('avatar');
|
||||
$record->employee->avatar = check_file($rawAvatar)
|
||||
? get_file($rawAvatar)
|
||||
: get_file('avatars/avatar.png');
|
||||
}
|
||||
return $record;
|
||||
});
|
||||
|
||||
$employees = User::where('type', 'employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->get(['id', 'name']);
|
||||
|
||||
// Get attendance records for form dropdown
|
||||
$attendanceRecords = AttendanceRecord::whereIn('created_by', getCompanyAndUsersId())
|
||||
->with('employee')
|
||||
->orderBy('date', 'desc')
|
||||
->take(50)
|
||||
->get();
|
||||
|
||||
$companyUserIds = getCompanyAndUsersId();
|
||||
|
||||
$statsQuery = AttendanceRegularization::where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-attendance-regularizations')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-attendance-regularizations')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('employee_id', Auth::id())->orWhere('approved_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
$summaryStats = [
|
||||
'total' => (clone $statsQuery)->count(),
|
||||
'pending' => (clone $statsQuery)->where('status', 'pending')->count(),
|
||||
'approved' => (clone $statsQuery)->where('status', 'approved')->count(),
|
||||
'rejected' => (clone $statsQuery)->where('status', 'rejected')->count(),
|
||||
];
|
||||
|
||||
return Inertia::render('hr/attendance-regularizations/index', [
|
||||
'regularizations' => $regularizations,
|
||||
'employees' => $this->getFilteredEmployees(),
|
||||
'attendanceRecords' => $attendanceRecords,
|
||||
'filters' => $request->all(['search', 'employee_id', 'status', 'date_from', 'date_to', 'sort_field', 'sort_direction', 'per_page']),
|
||||
'summaryStats' => $summaryStats,
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
private function getFilteredEmployees()
|
||||
{
|
||||
// Get employees for filter dropdown (compatible with getFilteredEmployees logic)
|
||||
$employeeQuery = Employee::whereIn('created_by', getCompanyAndUsersId());
|
||||
|
||||
if (Auth::user()->can('manage-own-attendance-regularizations') && !Auth::user()->can('manage-any-attendance-regularizations')) {
|
||||
$employeeQuery->where(function ($q) {
|
||||
$q->where('created_by', Auth::id())->orWhere('user_id', Auth::id());
|
||||
});
|
||||
}
|
||||
|
||||
$employees = User::emp()
|
||||
->with('employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->whereIn('id', $employeeQuery->pluck('user_id'))
|
||||
->select('id', 'name')
|
||||
->get()
|
||||
->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'employee_id' => $user->employee->employee_id ?? '',
|
||||
];
|
||||
});
|
||||
return $employees;
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'attendance_record_id' => 'required|exists:attendance_records,id',
|
||||
'requested_clock_in' => 'nullable|date_format:H:i',
|
||||
'requested_clock_out' => 'nullable|date_format:H:i',
|
||||
'reason' => 'required|string',
|
||||
]);
|
||||
|
||||
$validated['created_by'] = creatorId();
|
||||
|
||||
// Get attendance record to populate original times and date
|
||||
$attendanceRecord = AttendanceRecord::find($validated['attendance_record_id']);
|
||||
if (!$attendanceRecord) {
|
||||
return redirect()->back()->with('error', __('Attendance record not found.'));
|
||||
}
|
||||
|
||||
$validated['date'] = $attendanceRecord->date;
|
||||
$validated['original_clock_in'] = $attendanceRecord->clock_in;
|
||||
$validated['original_clock_out'] = $attendanceRecord->clock_out;
|
||||
|
||||
// Check if regularization already exists for this record
|
||||
$exists = AttendanceRegularization::where('attendance_record_id', $validated['attendance_record_id'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return redirect()->back()->with('error', __('Regularization request already exists for this attendance record.'));
|
||||
}
|
||||
|
||||
AttendanceRegularization::create($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Regularization request created successfully.'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $regularizationId)
|
||||
{
|
||||
$regularization = AttendanceRegularization::where('id', $regularizationId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($regularization) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'attendance_record_id' => 'required|exists:attendance_records,id',
|
||||
'requested_clock_in' => 'nullable|date_format:H:i',
|
||||
'requested_clock_out' => 'nullable|date_format:H:i',
|
||||
'reason' => 'required|string',
|
||||
]);
|
||||
|
||||
// Only allow updates if status is pending
|
||||
if ($regularization->status !== 'pending') {
|
||||
return redirect()->back()->with('error', __('Cannot update processed regularization request.'));
|
||||
}
|
||||
|
||||
$regularization->update($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Regularization request updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update regularization request'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Regularization request Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($regularizationId)
|
||||
{
|
||||
$regularization = AttendanceRegularization::where('id', $regularizationId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($regularization) {
|
||||
try {
|
||||
// Only allow deletion if status is pending
|
||||
if ($regularization->status !== 'pending') {
|
||||
return redirect()->back()->with('error', __('Cannot delete processed regularization request.'));
|
||||
}
|
||||
|
||||
$regularization->delete();
|
||||
return redirect()->back()->with('success', __('Regularization request deleted successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to delete regularization request'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Regularization request Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function updateStatus(Request $request, $regularizationId)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'status' => 'required|in:approved,rejected',
|
||||
'manager_comments' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$regularization = AttendanceRegularization::where('id', $regularizationId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($regularization) {
|
||||
try {
|
||||
$regularization->update([
|
||||
'status' => $validated['status'],
|
||||
'manager_comments' => $validated['manager_comments'],
|
||||
'approved_by' => Auth::id(),
|
||||
'approved_at' => now(),
|
||||
]);
|
||||
|
||||
// Apply changes to attendance record if approved
|
||||
if ($validated['status'] === 'approved') {
|
||||
$regularization->applyToAttendanceRecord();
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('Regularization request status updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update regularization request status'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Regularization request Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function getEmployeeAttendance($employeeId)
|
||||
{
|
||||
try {
|
||||
// Get attendance records for the last 30 days
|
||||
$query = AttendanceRecord::where('employee_id', $employeeId)
|
||||
->with('employee')
|
||||
->orderBy('date', 'desc');
|
||||
|
||||
if (!isDemo()) {
|
||||
$query->whereDate('date', '>=', now()->subDays(30));
|
||||
}
|
||||
|
||||
$attendanceRecords = $query->get([
|
||||
'id',
|
||||
'employee_id',
|
||||
'date',
|
||||
'clock_in',
|
||||
'clock_out',
|
||||
'status',
|
||||
'is_late',
|
||||
'is_early_departure'
|
||||
]);
|
||||
|
||||
$datesForDropdown = $attendanceRecords->map(function ($record) {
|
||||
return [
|
||||
'label' => $record->date->format('d/m/Y'),
|
||||
'value' => $record->id,
|
||||
];
|
||||
});
|
||||
|
||||
|
||||
return response()->json($datesForDropdown);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
160
app/Http/Controllers/Auth/AuthenticatedSessionController.php
Normal file
160
app/Http/Controllers/Auth/AuthenticatedSessionController.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Auth\LoginRequest;
|
||||
use App\Models\LoginHistory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class AuthenticatedSessionController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the login page.
|
||||
*/
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
$demoBusinesses = [];
|
||||
|
||||
if (config('app.is_demo')) {
|
||||
// Get the company user
|
||||
$companyUser = \App\Models\User::where('email', 'company@example.com')->first();
|
||||
}
|
||||
|
||||
return Inertia::render('auth/login', [
|
||||
'canResetPassword' => Route::has('password.request'),
|
||||
'status' => $request->session()->get('status'),
|
||||
'settings' => settings(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming authentication request.
|
||||
*/
|
||||
public function store(LoginRequest $request): RedirectResponse
|
||||
{
|
||||
try {
|
||||
$request->authenticate();
|
||||
$request->session()->regenerate();
|
||||
|
||||
// Check if email verification is enabled and user is not verified
|
||||
$emailVerificationEnabled = getSetting('emailVerification', false);
|
||||
if ($emailVerificationEnabled && !$request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->route('verification.notice');
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
|
||||
// Safely get IP address
|
||||
$ip = $request->ip() ?? '127.0.0.1';
|
||||
try {
|
||||
// Get location data with timeout and error handling
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'timeout' => 5,
|
||||
'ignore_errors' => true
|
||||
]
|
||||
]);
|
||||
$response = @file_get_contents('http://ip-api.com/php/' . $ip, false, $context);
|
||||
$query = $response ? @unserialize($response) : [];
|
||||
if (!is_array($query)) {
|
||||
$query = [];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$query = [];
|
||||
}
|
||||
|
||||
try {
|
||||
// Browser detection with error handling
|
||||
$userAgent = $request->header('User-Agent', '');
|
||||
if (!empty($userAgent)) {
|
||||
$whichbrowser = new \WhichBrowser\Parser($userAgent);
|
||||
// Skip if it's a bot
|
||||
if (isset($whichbrowser->device->type) && $whichbrowser->device->type == 'bot') {
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
|
||||
$query['browser_name'] = $whichbrowser->browser->name ?? null;
|
||||
$query['os_name'] = $whichbrowser->os->name ?? null;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Continue without browser detection if it fails
|
||||
}
|
||||
|
||||
// Get referrer safely
|
||||
$referrer = $request->header('Referer') ? parse_url($request->header('Referer')) : null;
|
||||
|
||||
// Set additional details
|
||||
$query['browser_language'] = $request->header('Accept-Language') ? mb_substr($request->header('Accept-Language'), 0, 2) : null;
|
||||
$query['device_type'] = class_exists('Utility') ? getDeviceType($userAgent) : 'unknown';
|
||||
$query['referrer_host'] = !empty($referrer['host']) ? $referrer['host'] : null;
|
||||
$query['referrer_path'] = !empty($referrer['path']) ? $referrer['path'] : null;
|
||||
|
||||
// Set timezone safely
|
||||
if (isset($query['timezone']) && !empty($query['timezone'])) {
|
||||
try {
|
||||
date_default_timezone_set($query['timezone']);
|
||||
} catch (\Exception $e) {
|
||||
// Continue with default timezone if setting fails
|
||||
}
|
||||
}
|
||||
|
||||
// Save login details
|
||||
try {
|
||||
|
||||
if (isSaaS()) {
|
||||
if (Auth::user()->hasRole('superadmin')) {
|
||||
$createdBy = Auth::user()->id;
|
||||
} else if (Auth::user()->hasRole('company')) {
|
||||
$createdBy = Auth::user()->created_by;
|
||||
} else {
|
||||
$createdBy = getCompanyId(Auth::user()->id);
|
||||
}
|
||||
} else {
|
||||
if (Auth::user()->hasRole('company')) {
|
||||
$createdBy = Auth::user()->id;
|
||||
} else {
|
||||
$createdBy = getCompanyId(Auth::user()->id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$loginDetail = new LoginHistory();
|
||||
$loginDetail->user_id = $user->id;
|
||||
$loginDetail->ip = $ip;
|
||||
$loginDetail->date = now();
|
||||
$loginDetail->Details = json_encode($query);
|
||||
$loginDetail->created_by = $createdBy;
|
||||
$loginDetail->save();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::warning('Failed to save login details: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Login error: ' . $e->getMessage());
|
||||
return back()->withErrors(['email' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy an authenticated session.
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
Auth::guard('web')->logout();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
||||
41
app/Http/Controllers/Auth/ConfirmablePasswordController.php
Normal file
41
app/Http/Controllers/Auth/ConfirmablePasswordController.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ConfirmablePasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the confirm password page.
|
||||
*/
|
||||
public function show(): Response
|
||||
{
|
||||
return Inertia::render('auth/confirm-password');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm the user's password.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if (! Auth::guard('web')->validate([
|
||||
'email' => $request->user()->email,
|
||||
'password' => $request->password,
|
||||
])) {
|
||||
throw ValidationException::withMessages([
|
||||
'password' => __('auth.password'),
|
||||
]);
|
||||
}
|
||||
|
||||
$request->session()->put('auth.password_confirmed_at', time());
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EmailVerificationNotificationController extends Controller
|
||||
{
|
||||
/**
|
||||
* Send a new email verification notification.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if ($request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
|
||||
$request->user()->sendEmailVerificationNotification();
|
||||
|
||||
return back()->with('status', 'verification-link-sent');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class EmailVerificationPromptController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the email verification prompt page.
|
||||
*/
|
||||
public function __invoke(Request $request): Response|RedirectResponse
|
||||
{
|
||||
return $request->user()->hasVerifiedEmail()
|
||||
? redirect()->intended(route('dashboard', absolute: false))
|
||||
: Inertia::render('auth/verify-email', ['status' => $request->session()->get('status')]);
|
||||
}
|
||||
}
|
||||
69
app/Http/Controllers/Auth/NewPasswordController.php
Normal file
69
app/Http/Controllers/Auth/NewPasswordController.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Auth\Events\PasswordReset;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class NewPasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the password reset page.
|
||||
*/
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
return Inertia::render('auth/reset-password', [
|
||||
'email' => $request->email,
|
||||
'token' => $request->route('token'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming new password request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'token' => 'required',
|
||||
'email' => 'required|email',
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
// Here we will attempt to reset the user's password. If it is successful we
|
||||
// will update the password on an actual user model and persist it to the
|
||||
// database. Otherwise we will parse the error and return the response.
|
||||
$status = Password::reset(
|
||||
$request->only('email', 'password', 'password_confirmation', 'token'),
|
||||
function ($user) use ($request) {
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($request->password),
|
||||
'remember_token' => Str::random(60),
|
||||
])->save();
|
||||
|
||||
event(new PasswordReset($user));
|
||||
}
|
||||
);
|
||||
|
||||
// If the password was successfully reset, we will redirect the user back to
|
||||
// the application's home authenticated view. If there is an error we can
|
||||
// redirect them back to where they came from with their error message.
|
||||
if ($status == Password::PasswordReset) {
|
||||
return to_route('login')->with('status', __($status));
|
||||
}
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => [__($status)],
|
||||
]);
|
||||
}
|
||||
}
|
||||
87
app/Http/Controllers/Auth/PasswordResetLinkController.php
Normal file
87
app/Http/Controllers/Auth/PasswordResetLinkController.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class PasswordResetLinkController extends Controller
|
||||
{
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
return Inertia::render('auth/forgot-password', [
|
||||
'status' => $request->session()->get('status'),
|
||||
'settings' => settings(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'email' => 'required|email',
|
||||
]);
|
||||
|
||||
$this->setEmailConfig($request->email);
|
||||
|
||||
Password::sendResetLink(
|
||||
$request->only('email')
|
||||
);
|
||||
|
||||
return back()->with('status', __('A reset link will be sent if the account exists.'));
|
||||
} catch (\Exception $e) {
|
||||
return back()->withErrors(['email' => __('Unable to send reset link. Please try again.')]);
|
||||
}
|
||||
}
|
||||
|
||||
private function setEmailConfig($email): void
|
||||
{
|
||||
try {
|
||||
$user = User::where('email', $email)->first();
|
||||
if (! $user) {
|
||||
return;
|
||||
}
|
||||
if (isSaas()) {
|
||||
if ($user->type == 'company') {
|
||||
$user = User::where('id', $user->created_by)->first();
|
||||
} else {
|
||||
$user = User::where('id', $user->created_by)->first();
|
||||
}
|
||||
} else {
|
||||
$user = User::where('id', $user->created_by)->first();
|
||||
}
|
||||
|
||||
$getSettings = settings($user->id);
|
||||
|
||||
$settings = [
|
||||
'driver' => $getSettings['email_driver'] ?? '',
|
||||
'host' => $getSettings['email_host'] ?? '',
|
||||
'port' => $getSettings['email_port'] ?? '',
|
||||
'username' => $getSettings['email_username'] ?? '',
|
||||
'password' => $getSettings['email_password'] ?? '',
|
||||
'encryption' => $getSettings['email_encryption'] ?? '',
|
||||
'fromAddress' => $getSettings['email_from_address'] ?? '',
|
||||
'fromName' => $getSettings['email_from_name'] ?? '',
|
||||
];
|
||||
|
||||
Config::set([
|
||||
'mail.default' => $settings['driver'],
|
||||
'mail.mailers.smtp.host' => $settings['host'],
|
||||
'mail.mailers.smtp.port' => $settings['port'],
|
||||
'mail.mailers.smtp.encryption' => $settings['encryption'] === 'none' ? null : $settings['encryption'],
|
||||
'mail.mailers.smtp.username' => $settings['username'],
|
||||
'mail.mailers.smtp.password' => $settings['password'],
|
||||
'mail.from.address' => $settings['fromAddress'],
|
||||
'mail.from.name' => $settings['fromName'],
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception('Email config error: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
178
app/Http/Controllers/Auth/RegisteredUserController.php
Normal file
178
app/Http/Controllers/Auth/RegisteredUserController.php
Normal file
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use App\Models\Plan;
|
||||
use App\Models\Referral;
|
||||
use App\Models\ReferralSetting;
|
||||
use App\Services\UserService;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class RegisteredUserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the registration page.
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
|
||||
if (!isUserRegistrationEnabled()) {
|
||||
return redirect()->route('login');
|
||||
}
|
||||
|
||||
$referralCode = $request->get('ref');
|
||||
$encryptedPlanId = $request->get('plan');
|
||||
$planId = null;
|
||||
$referrer = null;
|
||||
|
||||
// Decrypt and validate plan ID
|
||||
if ($encryptedPlanId) {
|
||||
$planId = $this->decryptPlanId($encryptedPlanId);
|
||||
if ($planId && !Plan::find($planId)) {
|
||||
$planId = null; // Invalid plan ID
|
||||
}
|
||||
}
|
||||
|
||||
if ($referralCode) {
|
||||
$referrer = User::where('referral_code', $referralCode)
|
||||
->where('type', 'company')
|
||||
->first();
|
||||
}
|
||||
|
||||
return Inertia::render('auth/register', [
|
||||
'referralCode' => $referralCode,
|
||||
'planId' => $planId,
|
||||
'referrer' => $referrer ? $referrer->name : null,
|
||||
'settings' => settings(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming registration request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if (!isUserRegistrationEnabled()) {
|
||||
return redirect()->route('login');
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|string|lowercase|email|max:255|unique:' . User::class,
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
'terms' => 'required|accepted'
|
||||
]);
|
||||
|
||||
$superAdminSettings = settings();
|
||||
$userLang = isset($superAdminSettings['defaultLanguage']) ? $superAdminSettings['defaultLanguage'] : 'en';
|
||||
|
||||
$userData = [
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'password' => Hash::make($request->password),
|
||||
'type' => 'company',
|
||||
'is_active' => 1,
|
||||
'is_enable_login' => 1,
|
||||
'created_by' => 1,
|
||||
'plan_is_active' => 0,
|
||||
'lang' => $userLang,
|
||||
];
|
||||
|
||||
// Handle referral code
|
||||
if ($request->referral_code) {
|
||||
$referrer = User::where('referral_code', $request->referral_code)
|
||||
->where('type', 'company')
|
||||
->first();
|
||||
|
||||
if ($referrer) {
|
||||
$userData['used_referral_code'] = $request->referral_code;
|
||||
}
|
||||
}
|
||||
|
||||
$user = User::create($userData);
|
||||
|
||||
// Assign role and settings to the user
|
||||
defaultRoleAndSetting($user);
|
||||
|
||||
// Note: Referral record will be created when user purchases a plan
|
||||
// This is handled in the PlanController or payment controllers
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
// Check if email verification is enabled
|
||||
$emailVerificationEnabled = getSetting('emailVerification', false);
|
||||
if ($emailVerificationEnabled) {
|
||||
event(new Registered($user));
|
||||
return redirect()->route('verification.notice');
|
||||
}
|
||||
|
||||
// Redirect to plans page with selected plan
|
||||
$planId = $request->plan_id;
|
||||
if ($planId) {
|
||||
return redirect()->route('plans.index', ['selected' => $planId]);
|
||||
}
|
||||
return to_route('dashboard');
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt plan ID from encrypted string
|
||||
*/
|
||||
private function decryptPlanId($encryptedPlanId)
|
||||
{
|
||||
try {
|
||||
$key = 'vCardGo2024';
|
||||
$encrypted = base64_decode($encryptedPlanId);
|
||||
$decrypted = '';
|
||||
|
||||
for ($i = 0; $i < strlen($encrypted); $i++) {
|
||||
$decrypted .= chr(ord($encrypted[$i]) ^ ord($key[$i % strlen($key)]));
|
||||
}
|
||||
|
||||
return is_numeric($decrypted) ? (int) $decrypted : null;
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create referral record when user purchases a plan
|
||||
*/
|
||||
private function createReferralRecord(User $user)
|
||||
{
|
||||
$settings = ReferralSetting::current();
|
||||
|
||||
if (!$settings->is_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
$referrer = User::where('referral_code', $user->used_referral_code)->first();
|
||||
if (!$referrer || !$user->plan) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate commission based on plan price
|
||||
$planPrice = $user->plan->price ?? 0;
|
||||
$commissionAmount = ($planPrice * $settings->commission_percentage) / 100;
|
||||
|
||||
if ($commissionAmount > 0) {
|
||||
Referral::create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $referrer->id,
|
||||
'commission_percentage' => $settings->commission_percentage,
|
||||
'amount' => $commissionAmount,
|
||||
'plan_id' => $user->plan_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
app/Http/Controllers/Auth/VerifyEmailController.php
Normal file
30
app/Http/Controllers/Auth/VerifyEmailController.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Auth\Events\Verified;
|
||||
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
||||
class VerifyEmailController extends Controller
|
||||
{
|
||||
/**
|
||||
* Mark the authenticated user's email address as verified.
|
||||
*/
|
||||
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
||||
{
|
||||
if ($request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
|
||||
if ($request->user()->markEmailAsVerified()) {
|
||||
/** @var \Illuminate\Contracts\Auth\MustVerifyEmail $user */
|
||||
$user = $request->user();
|
||||
|
||||
event(new Verified($user));
|
||||
}
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
}
|
||||
359
app/Http/Controllers/AuthorizeNetPaymentController.php
Normal file
359
app/Http/Controllers/AuthorizeNetPaymentController.php
Normal file
@@ -0,0 +1,359 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Plan;
|
||||
use Illuminate\Http\Request;
|
||||
use net\authorize\api\contract\v1 as AnetAPI;
|
||||
use net\authorize\api\controller as AnetController;
|
||||
|
||||
class AuthorizeNetPaymentController extends Controller
|
||||
{
|
||||
// Supported countries and currencies for AuthorizeNet
|
||||
private const SUPPORTED_COUNTRIES = ['US', 'CA', 'GB', 'AU'];
|
||||
private const SUPPORTED_CURRENCIES = [
|
||||
'USD', 'CAD', 'CHF', 'DKK', 'EUR', 'GBP', 'NOK', 'PLN', 'SEK', 'AUD', 'NZD'
|
||||
];
|
||||
|
||||
public function createPaymentForm(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$pricing = calculatePlanPricing($plan, $validated['coupon_code'] ?? null);
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!isset($settings['payment_settings']['authorizenet_merchant_id']) ||
|
||||
!isset($settings['payment_settings']['authorizenet_transaction_key'])) {
|
||||
return response()->json(['error' => 'AuthorizeNet not properly configured'], 400);
|
||||
}
|
||||
|
||||
// Get currency from settings or default to USD
|
||||
$currency = $settings['general_settings']['currency'] ?? 'USD';
|
||||
|
||||
// Validate currency support
|
||||
if (!in_array($currency, self::SUPPORTED_CURRENCIES)) {
|
||||
$currency = 'USD';
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'merchant_id' => $settings['payment_settings']['authorizenet_merchant_id'],
|
||||
'amount' => number_format($pricing['final_price'], 2, '.', ''),
|
||||
'currency' => $currency,
|
||||
'is_sandbox' => $settings['payment_settings']['authorizenet_mode'] === 'sandbox',
|
||||
'supported_countries' => self::SUPPORTED_COUNTRIES,
|
||||
'supported_currencies' => self::SUPPORTED_CURRENCIES,
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Payment form creation failed')], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function processPayment(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request, [
|
||||
'card_number' => 'required|string',
|
||||
'expiry_month' => 'required|string|size:2',
|
||||
'expiry_year' => 'required|string|size:2',
|
||||
'cvv' => 'required|string|min:3|max:4',
|
||||
'cardholder_name' => 'required|string|min:2|max:50',
|
||||
]);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$pricing = calculatePlanPricing($plan, $validated['coupon_code'] ?? null);
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!isset($settings['payment_settings']['authorizenet_merchant_id']) ||
|
||||
!isset($settings['payment_settings']['authorizenet_transaction_key'])) {
|
||||
return back()->withErrors(['error' => __('AuthorizeNet not properly configured')]);
|
||||
}
|
||||
|
||||
// Validate minimum amount (AuthorizeNet requires minimum $0.50)
|
||||
if ($pricing['final_price'] < 0.50) {
|
||||
return back()->withErrors(['error' => __('Minimum payment amount is $0.50')]);
|
||||
}
|
||||
|
||||
$result = $this->createAuthorizeNetTransaction($validated, $pricing, $settings);
|
||||
|
||||
if ($result['success']) {
|
||||
processPaymentSuccess([
|
||||
'user_id' => auth()->id(),
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => $validated['billing_cycle'],
|
||||
'payment_method' => 'authorizenet',
|
||||
'coupon_code' => $validated['coupon_code'] ?? null,
|
||||
'payment_id' => $result['transaction_id'],
|
||||
]);
|
||||
|
||||
return back()->with('success', __('Payment successful and plan activated'));
|
||||
}
|
||||
|
||||
return back()->withErrors(['error' => $result['error']]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return back()->withErrors(['error' => __('Payment processing failed. Please try again.')]);
|
||||
}
|
||||
}
|
||||
|
||||
private function createAuthorizeNetTransaction($paymentData, $pricing, $settings)
|
||||
{
|
||||
try {
|
||||
// Set up merchant authentication
|
||||
$merchantAuthentication = new AnetAPI\MerchantAuthenticationType();
|
||||
$merchantAuthentication->setName($settings['payment_settings']['authorizenet_merchant_id']);
|
||||
$merchantAuthentication->setTransactionKey($settings['payment_settings']['authorizenet_transaction_key']);
|
||||
|
||||
// Set up credit card information
|
||||
$creditCard = new AnetAPI\CreditCardType();
|
||||
$creditCard->setCardNumber(preg_replace('/\s+/', '', $paymentData['card_number']));
|
||||
|
||||
// Fix expiration date format - AuthorizeNet expects YYYY-MM format
|
||||
$expiryYear = 2000 + intval($paymentData['expiry_year']);
|
||||
$expiryMonth = str_pad($paymentData['expiry_month'], 2, '0', STR_PAD_LEFT);
|
||||
$creditCard->setExpirationDate($expiryYear . '-' . $expiryMonth);
|
||||
$creditCard->setCardCode($paymentData['cvv']);
|
||||
|
||||
// Set up payment method
|
||||
$paymentOne = new AnetAPI\PaymentType();
|
||||
$paymentOne->setCreditCard($creditCard);
|
||||
|
||||
// Set up order information
|
||||
$order = new AnetAPI\OrderType();
|
||||
$order->setInvoiceNumber('INV-' . time());
|
||||
$order->setDescription('Plan Subscription Payment');
|
||||
|
||||
// Set up customer information
|
||||
$customer = new AnetAPI\CustomerDataType();
|
||||
$customer->setType('individual');
|
||||
$customer->setId(auth()->id());
|
||||
$customer->setEmail(auth()->user()->email);
|
||||
|
||||
// Set up billing information
|
||||
$billTo = new AnetAPI\CustomerAddressType();
|
||||
$billTo->setFirstName(explode(' ', $paymentData['cardholder_name'])[0]);
|
||||
$billTo->setLastName(implode(' ', array_slice(explode(' ', $paymentData['cardholder_name']), 1)) ?: 'Customer');
|
||||
$billTo->setCompany(auth()->user()->name ?? '');
|
||||
$billTo->setAddress('N/A');
|
||||
$billTo->setCity('N/A');
|
||||
$billTo->setState('N/A');
|
||||
$billTo->setZip('00000');
|
||||
$billTo->setCountry('US');
|
||||
|
||||
// Create transaction request
|
||||
$transactionRequestType = new AnetAPI\TransactionRequestType();
|
||||
$transactionRequestType->setTransactionType('authCaptureTransaction');
|
||||
$transactionRequestType->setAmount(number_format($pricing['final_price'], 2, '.', ''));
|
||||
$transactionRequestType->setPayment($paymentOne);
|
||||
$transactionRequestType->setOrder($order);
|
||||
$transactionRequestType->setBillTo($billTo);
|
||||
$transactionRequestType->setCustomer($customer);
|
||||
|
||||
// Add merchant defined fields for tracking
|
||||
$merchantDefinedField1 = new AnetAPI\UserFieldType();
|
||||
$merchantDefinedField1->setName('plan_id');
|
||||
$merchantDefinedField1->setValue($paymentData['plan_id']);
|
||||
|
||||
$merchantDefinedField2 = new AnetAPI\UserFieldType();
|
||||
$merchantDefinedField2->setName('user_id');
|
||||
$merchantDefinedField2->setValue(auth()->id());
|
||||
|
||||
$transactionRequestType->setUserFields([$merchantDefinedField1, $merchantDefinedField2]);
|
||||
|
||||
// Create the API request
|
||||
$request = new AnetAPI\CreateTransactionRequest();
|
||||
$request->setMerchantAuthentication($merchantAuthentication);
|
||||
$request->setTransactionRequest($transactionRequestType);
|
||||
|
||||
// Execute the request
|
||||
$controller = new AnetController\CreateTransactionController($request);
|
||||
|
||||
$environment = ($settings['payment_settings']['authorizenet_mode'] === 'sandbox')
|
||||
? \net\authorize\api\constants\ANetEnvironment::SANDBOX
|
||||
: \net\authorize\api\constants\ANetEnvironment::PRODUCTION;
|
||||
|
||||
$response = $controller->executeWithApiResponse($environment);
|
||||
|
||||
return $this->handleAuthorizeNetResponse($response);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => __('Transaction processing failed. Please check your card details and try again.'),
|
||||
'transaction_id' => null
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private function handleAuthorizeNetResponse($response)
|
||||
{
|
||||
if ($response === null) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => __('No response received from payment gateway'),
|
||||
'transaction_id' => null
|
||||
];
|
||||
}
|
||||
|
||||
$messages = $response->getMessages();
|
||||
|
||||
if ($messages->getResultCode() !== 'Ok') {
|
||||
$errorMessage = __('Payment gateway error');
|
||||
if ($messages->getMessage() && count($messages->getMessage()) > 0) {
|
||||
$errorMessage = $messages->getMessage()[0]->getText();
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $this->getFriendlyErrorMessage($errorMessage),
|
||||
'transaction_id' => null
|
||||
];
|
||||
}
|
||||
|
||||
$tresponse = $response->getTransactionResponse();
|
||||
|
||||
if ($tresponse === null) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => __('Invalid transaction response'),
|
||||
'transaction_id' => null
|
||||
];
|
||||
}
|
||||
|
||||
$responseCode = $tresponse->getResponseCode();
|
||||
|
||||
// Response codes: 1 = Approved, 2 = Declined, 3 = Error, 4 = Held for Review
|
||||
switch ($responseCode) {
|
||||
case '1': // Approved
|
||||
return [
|
||||
'success' => true,
|
||||
'error' => null,
|
||||
'transaction_id' => $tresponse->getTransId()
|
||||
];
|
||||
|
||||
case '2': // Declined
|
||||
$errorMessage = 'Transaction declined';
|
||||
if ($tresponse->getErrors() && count($tresponse->getErrors()) > 0) {
|
||||
$errorMessage = $tresponse->getErrors()[0]->getErrorText();
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $this->getFriendlyErrorMessage($errorMessage),
|
||||
'transaction_id' => null
|
||||
];
|
||||
|
||||
case '3': // Error
|
||||
$errorMessage = 'Transaction error';
|
||||
if ($tresponse->getErrors() && count($tresponse->getErrors()) > 0) {
|
||||
$errorMessage = $tresponse->getErrors()[0]->getErrorText();
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $this->getFriendlyErrorMessage($errorMessage),
|
||||
'transaction_id' => null
|
||||
];
|
||||
|
||||
case '4': // Held for Review
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => __('Transaction is being reviewed. Please contact support.'),
|
||||
'transaction_id' => $tresponse->getTransId()
|
||||
];
|
||||
|
||||
default:
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => __('Unknown transaction response'),
|
||||
'transaction_id' => null
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private function getFriendlyErrorMessage($errorMessage)
|
||||
{
|
||||
$friendlyMessages = [
|
||||
__('The credit card number is invalid') => __('Please check your card number and try again.'),
|
||||
__('The credit card has expired') => __('Your card has expired. Please use a different card.'),
|
||||
__('The credit card expiration date is invalid') => __('Please check the expiration date and try again.'),
|
||||
__('The transaction cannot be found') => __('Transaction not found. Please try again.'),
|
||||
__('A duplicate transaction has been submitted') => __('This transaction was already processed.'),
|
||||
__('The amount is invalid') => __('Invalid payment amount.'),
|
||||
__('This transaction has been declined') => __('Your card was declined. Please try a different payment method.'),
|
||||
__('Insufficient funds') => __('Insufficient funds. Please try a different card.'),
|
||||
__('The merchant does not accept this type of credit card') => __('This card type is not accepted.'),
|
||||
__('The transaction has been declined because of an AVS mismatch') => __('Address verification failed. Please check your billing address.'),
|
||||
__('The transaction has been declined because the CVV2 value is invalid') => __('Invalid security code. Please check your CVV.'),
|
||||
];
|
||||
|
||||
foreach ($friendlyMessages as $original => $friendly) {
|
||||
if (stripos($errorMessage, $original) !== false) {
|
||||
return $friendly;
|
||||
}
|
||||
}
|
||||
|
||||
return __('Payment processing failed. Please check your card details and try again.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test AuthorizeNet connection and credentials
|
||||
*/
|
||||
public function testConnection(Request $request)
|
||||
{
|
||||
try {
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!isset($settings['payment_settings']['authorizenet_merchant_id']) ||
|
||||
!isset($settings['payment_settings']['authorizenet_transaction_key'])) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => __('AuthorizeNet credentials not configured')
|
||||
]);
|
||||
}
|
||||
|
||||
// Test with AuthenticateTest API call
|
||||
$merchantAuthentication = new AnetAPI\MerchantAuthenticationType();
|
||||
$merchantAuthentication->setName($settings['payment_settings']['authorizenet_merchant_id']);
|
||||
$merchantAuthentication->setTransactionKey($settings['payment_settings']['authorizenet_transaction_key']);
|
||||
|
||||
$request = new AnetAPI\AuthenticateTestRequest();
|
||||
$request->setMerchantAuthentication($merchantAuthentication);
|
||||
|
||||
$controller = new AnetController\AuthenticateTestController($request);
|
||||
|
||||
$environment = ($settings['payment_settings']['authorizenet_mode'] === 'sandbox')
|
||||
? \net\authorize\api\constants\ANetEnvironment::SANDBOX
|
||||
: \net\authorize\api\constants\ANetEnvironment::PRODUCTION;
|
||||
|
||||
$response = $controller->executeWithApiResponse($environment);
|
||||
|
||||
if ($response && $response->getMessages()->getResultCode() === 'Ok') {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => __('AuthorizeNet connection successful'),
|
||||
'mode' => $settings['payment_settings']['authorizenet_mode']
|
||||
]);
|
||||
} else {
|
||||
$errorMessage = __('Connection failed');
|
||||
if ($response && $response->getMessages()->getMessage()) {
|
||||
$errorMessage = $response->getMessages()->getMessage()[0]->getText();
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => $errorMessage
|
||||
]);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => __('Connection test failed: ') . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
360
app/Http/Controllers/AwardController.php
Normal file
360
app/Http/Controllers/AwardController.php
Normal file
@@ -0,0 +1,360 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Award;
|
||||
use App\Models\AwardType;
|
||||
use App\Models\Employee;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class AwardController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-awards')) {
|
||||
// $query = Award::withPermissionCheck()->with(['employee.employee', 'awardType']);
|
||||
$query = Award::with(['employee.employee', 'awardType'])
|
||||
->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-awards')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-awards')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('employee_id', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($query) use ($request) {
|
||||
$query->whereHas('employee', function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhereHas('employee', function ($empQ) use ($request) {
|
||||
$empQ->where('employee_id', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
})
|
||||
->orWhereHas('awardType', function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%');
|
||||
})
|
||||
->orWhere('gift', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle award type filter
|
||||
if ($request->has('award_type_id') && !empty($request->award_type_id)) {
|
||||
$query->where('award_type_id', $request->award_type_id);
|
||||
}
|
||||
|
||||
// Handle employee filter
|
||||
if ($request->has('employee_id') && !empty($request->employee_id)) {
|
||||
$query->where('employee_id', $request->employee_id);
|
||||
}
|
||||
|
||||
// Handle date range filter
|
||||
if ($request->has('date_from') && !empty($request->date_from)) {
|
||||
$query->whereDate('award_date', '>=', $request->date_from);
|
||||
}
|
||||
if ($request->has('date_to') && !empty($request->date_to)) {
|
||||
$query->whereDate('award_date', '<=', $request->date_to);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field', 'id');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['award_date', 'id'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'id';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
$awards = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$awards->getCollection()->transform(function ($award) {
|
||||
if ($award->employee) {
|
||||
$rawAvatar = $award->employee->getRawOriginal('avatar');
|
||||
$award->employee->avatar = check_file($rawAvatar)
|
||||
? get_file($rawAvatar)
|
||||
: get_file('avatars/avatar.png');
|
||||
}
|
||||
return $award;
|
||||
});
|
||||
|
||||
|
||||
// Get award types for filter dropdown
|
||||
$awardTypes = AwardType::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
// // Get employees for filter dropdown
|
||||
// $employees = User::with('employee')
|
||||
// ->where('type', 'employee')
|
||||
// ->whereIn('created_by', getCompanyAndUsersId())
|
||||
// ->where('status', 'active')
|
||||
// ->select('id', 'name')
|
||||
// ->get()
|
||||
// ->map(function ($user) {
|
||||
// return [
|
||||
// 'id' => $user->id,
|
||||
// 'name' => $user->name,
|
||||
// 'employee_id' => $user->employee->employee_id ?? ''
|
||||
// ];
|
||||
// });
|
||||
|
||||
return Inertia::render('hr/awards/index', [
|
||||
'awards' => $awards,
|
||||
'awardTypes' => $awardTypes,
|
||||
'employees' => $this->getFilteredEmployees(),
|
||||
'filters' => $request->all(['search', 'award_type_id', 'employee_id', 'date_from', 'date_to', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function getFilteredEmployees()
|
||||
{
|
||||
// Get employees for filter dropdown (compatible with getFilteredEmployees logic)
|
||||
$employeeQuery = Employee::whereIn('created_by', getCompanyAndUsersId());
|
||||
|
||||
if (Auth::user()->can('manage-own-awards') && !Auth::user()->can('manage-any-awards')) {
|
||||
$employeeQuery->where(function ($q) {
|
||||
$q->where('created_by', Auth::id())->orWhere('user_id', Auth::id());
|
||||
});
|
||||
}
|
||||
|
||||
$employees = User::emp()
|
||||
->with('employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->whereIn('id', $employeeQuery->pluck('user_id'))
|
||||
->select('id', 'name')
|
||||
->get()
|
||||
->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'employee_id' => $user->employee->employee_id ?? '',
|
||||
];
|
||||
});
|
||||
return $employees;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('create-awards')) {
|
||||
$validator = Validator::make($request->all(), [
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'award_type_id' => 'required|exists:award_types,id',
|
||||
'award_date' => 'required|date',
|
||||
'gift' => 'nullable|string|max:255',
|
||||
'monetary_value' => 'nullable|numeric|min:0',
|
||||
'description' => 'nullable|string',
|
||||
'certificate' => 'nullable|string',
|
||||
'photo' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if award type belongs to current company
|
||||
$awardType = AwardType::find($request->award_type_id);
|
||||
if (!$awardType || !in_array($awardType->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('Invalid award type selected'));
|
||||
}
|
||||
|
||||
// Check if employee belongs to current company
|
||||
$user = User::where('id', $request->employee_id)
|
||||
->where('type', 'employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
if (!$user) {
|
||||
return redirect()->back()->with('error', __('Invalid employee selected'));
|
||||
}
|
||||
|
||||
$awardData = [
|
||||
'employee_id' => $request->employee_id,
|
||||
'award_type_id' => $request->award_type_id,
|
||||
'award_date' => $request->award_date,
|
||||
'gift' => $request->gift,
|
||||
'monetary_value' => $request->monetary_value,
|
||||
'description' => $request->description,
|
||||
'created_by' => creatorId(),
|
||||
];
|
||||
|
||||
// Handle certificate from media library
|
||||
if ($request->certificate) {
|
||||
$awardData['certificate'] = $request->certificate;
|
||||
}
|
||||
|
||||
// Handle photo from media library
|
||||
if ($request->photo) {
|
||||
$awardData['photo'] = $request->photo;
|
||||
}
|
||||
|
||||
Award::create($awardData);
|
||||
|
||||
return redirect()->back()->with('success', __('Award created successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, Award $award)
|
||||
{
|
||||
if (Auth::user()->can('edit-awards')) {
|
||||
// Check if award belongs to current company
|
||||
if (!in_array($award->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this award'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'award_type_id' => 'required|exists:award_types,id',
|
||||
'award_date' => 'required|date',
|
||||
'gift' => 'nullable|string|max:255',
|
||||
'monetary_value' => 'nullable|numeric|min:0',
|
||||
'description' => 'nullable|string',
|
||||
'certificate' => 'nullable|string',
|
||||
'photo' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if award type belongs to current company
|
||||
$awardType = AwardType::find($request->award_type_id);
|
||||
if (!$awardType || !in_array($awardType->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('Invalid award type selected'));
|
||||
}
|
||||
|
||||
// Check if employee belongs to current company
|
||||
$user = User::where('id', $request->employee_id)
|
||||
->where('type', 'employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
if (!$user) {
|
||||
return redirect()->back()->with('error', __('Invalid employee selected'));
|
||||
}
|
||||
|
||||
$awardData = [
|
||||
'employee_id' => $request->employee_id,
|
||||
'award_type_id' => $request->award_type_id,
|
||||
'award_date' => $request->award_date,
|
||||
'gift' => $request->gift,
|
||||
'monetary_value' => $request->monetary_value,
|
||||
'description' => $request->description,
|
||||
];
|
||||
|
||||
// Handle certificate from media library
|
||||
if ($request->certificate) {
|
||||
$awardData['certificate'] = $request->certificate;
|
||||
}
|
||||
|
||||
// Handle photo from media library
|
||||
if ($request->photo) {
|
||||
$awardData['photo'] = $request->photo;
|
||||
}
|
||||
|
||||
$award->update($awardData);
|
||||
|
||||
return redirect()->back()->with('success', __('Award updated successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(Award $award)
|
||||
{
|
||||
if (Auth::user()->can('delete-awards')) {
|
||||
// Check if award belongs to current company
|
||||
if (!in_array($award->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this award'));
|
||||
}
|
||||
|
||||
$award->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('Award deleted successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download certificate file.
|
||||
*/
|
||||
public function downloadCertificate(Award $award)
|
||||
{
|
||||
if (Auth::user()->can('view-awards')) {
|
||||
// Check if award belongs to current company
|
||||
if (!in_array($award->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to access this certificate'));
|
||||
}
|
||||
|
||||
if (!$award->certificate) {
|
||||
return redirect()->back()->with('error', __('Certificate file not found'));
|
||||
}
|
||||
|
||||
$filePath = getStorageFilePath($award->certificate);
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
return redirect()->back()->with('error', __('Certificate file not found'));
|
||||
}
|
||||
|
||||
return response()->download($filePath);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download photo file.
|
||||
*/
|
||||
public function downloadPhoto(Award $award)
|
||||
{
|
||||
if (Auth::user()->can('view-awards')) {
|
||||
// Check if award belongs to current company
|
||||
if (!in_array($award->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to access this photo'));
|
||||
}
|
||||
|
||||
if (!$award->photo) {
|
||||
return redirect()->back()->with('error', __('Photo file not found'));
|
||||
}
|
||||
|
||||
$filePath = getStorageFilePath($award->photo);
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
return redirect()->back()->with('error', __('Certificate file not found'));
|
||||
}
|
||||
|
||||
return response()->download($filePath);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
187
app/Http/Controllers/AwardTypeController.php
Normal file
187
app/Http/Controllers/AwardTypeController.php
Normal file
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\AwardType;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class AwardTypeController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-award-types')) {
|
||||
$query = AwardType::where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-award-types')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-award-types')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle status filter
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field', 'created_at');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['name', 'created_at', 'id'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'created_at';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
$awardTypes = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
return Inertia::render('hr/award-types/index', [
|
||||
'awardTypes' => $awardTypes,
|
||||
'filters' => $request->all(['search', 'status', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('create-award-types')) {
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
AwardType::create([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'status' => $request->status ?? 'active',
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Award type created successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, $awardTypeId)
|
||||
{
|
||||
if (Auth::user()->can('edit-award-types')) {
|
||||
$awardType = AwardType::where('id', $awardTypeId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($awardType) {
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$awardType->update([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'status' => $request->status ?? 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Award type updated successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Award Type Not Found.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy($awardTypeId)
|
||||
{
|
||||
if (Auth::user()->can('delete-award-types')) {
|
||||
$awardType = AwardType::where('id', $awardTypeId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($awardType) {
|
||||
try {
|
||||
// Check if award type is being used in awards
|
||||
if ($awardType->awards()->count() > 0) {
|
||||
return redirect()->back()->with('error', __('Cannot delete award type as it is being used in awards'));
|
||||
}
|
||||
|
||||
$awardType->delete();
|
||||
return redirect()->back()->with('success', __('Award type deleted successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to delete award type'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Award Type Not Found.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the status of the specified resource.
|
||||
*/
|
||||
public function toggleStatus($awardTypeId)
|
||||
{
|
||||
if (Auth::user()->can('edit-award-types')) {
|
||||
$awardType = AwardType::where('id', $awardTypeId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($awardType) {
|
||||
try {
|
||||
$awardType->status = $awardType->status === 'active' ? 'inactive' : 'active';
|
||||
$awardType->save();
|
||||
|
||||
return redirect()->back()->with('success', __('Award type status updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update award type status'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Award Type Not Found.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
39
app/Http/Controllers/BankPaymentController.php
Normal file
39
app/Http/Controllers/BankPaymentController.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Plan;
|
||||
use App\Models\User;
|
||||
use App\Models\Setting;
|
||||
use App\Models\PlanOrder;
|
||||
use App\Models\PaymentSetting;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class BankPaymentController extends Controller
|
||||
{
|
||||
public function processPayment(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request, [
|
||||
'amount' => 'required|numeric|min:0',
|
||||
]);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
|
||||
createPlanOrder([
|
||||
'user_id' => auth()->id(),
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => $validated['billing_cycle'],
|
||||
'payment_method' => 'bank',
|
||||
'coupon_code' => $validated['coupon_code'] ?? null,
|
||||
'payment_id' => 'BANK_' . strtoupper(uniqid()),
|
||||
'status' => 'pending',
|
||||
]);
|
||||
|
||||
return back()->with('success', __('Payment request submitted. Your plan will be activated after payment verification.'));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return handlePaymentError($e, 'bank');
|
||||
}
|
||||
}
|
||||
}
|
||||
10
app/Http/Controllers/BaseController.php
Normal file
10
app/Http/Controllers/BaseController.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Traits\AutoApplyPermissionCheck;
|
||||
|
||||
class BaseController extends Controller
|
||||
{
|
||||
use AutoApplyPermissionCheck;
|
||||
}
|
||||
303
app/Http/Controllers/BenefitPaymentController.php
Normal file
303
app/Http/Controllers/BenefitPaymentController.php
Normal file
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Plan;
|
||||
use App\Models\User;
|
||||
use App\Models\Setting;
|
||||
use App\Models\PlanOrder;
|
||||
use App\Models\PaymentSetting;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class BenefitPaymentController extends Controller
|
||||
{
|
||||
public function processPayment(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request, [
|
||||
'payment_id' => 'required|string',
|
||||
'transaction_id' => 'required|string',
|
||||
]);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$pricing = calculatePlanPricing($plan, $validated['coupon_code'] ?? null);
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!isset($settings['payment_settings']['benefit_secret_key']) || !isset($settings['payment_settings']['benefit_public_key'])) {
|
||||
return back()->withErrors(['error' => __('Benefit payment not configured')]);
|
||||
}
|
||||
|
||||
// Verify payment with Benefit API
|
||||
$isPaymentValid = $this->verifyBenefitPayment(
|
||||
$validated['payment_id'],
|
||||
$validated['transaction_id'],
|
||||
$settings['payment_settings']
|
||||
);
|
||||
|
||||
if ($isPaymentValid) {
|
||||
processPaymentSuccess([
|
||||
'user_id' => auth()->id(),
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => $validated['billing_cycle'],
|
||||
'payment_method' => 'benefit',
|
||||
'coupon_code' => $validated['coupon_code'] ?? null,
|
||||
'payment_id' => $validated['payment_id'],
|
||||
]);
|
||||
|
||||
return back()->with('success', __('Payment successful and plan activated'));
|
||||
}
|
||||
|
||||
return back()->withErrors(['error' => __('Payment verification failed')]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return handlePaymentError($e, 'benefit');
|
||||
}
|
||||
}
|
||||
|
||||
public function createPaymentSession(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$pricing = calculatePlanPricing($plan, $validated['coupon_code'] ?? null);
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!isset($settings['payment_settings']['benefit_secret_key'])) {
|
||||
return response()->json(['error' => __('Benefit payment not configured')], 400);
|
||||
}
|
||||
|
||||
$user = auth()->user();
|
||||
$orderID = strtoupper(str_replace('.', '', uniqid('', true)));
|
||||
|
||||
$userData = [
|
||||
"amount" => $pricing['final_price'],
|
||||
"currency" => "BHD",
|
||||
"customer_initiated" => true,
|
||||
"threeDSecure" => true,
|
||||
"save_card" => false,
|
||||
"description" => "Plan - " . $plan->name,
|
||||
"metadata" => ["udf1" => "Plan Payment"],
|
||||
"reference" => ["transaction" => $orderID, "order" => $orderID],
|
||||
"receipt" => ["email" => true, "sms" => true],
|
||||
"customer" => [
|
||||
"first_name" => $user->name ?? 'Customer',
|
||||
"middle_name" => "",
|
||||
"last_name" => "",
|
||||
"email" => $user->email,
|
||||
"phone" => ["country_code" => "973", "number" => "33123456"]
|
||||
],
|
||||
"source" => ["id" => "src_bh.benefit"],
|
||||
"post" => ["url" => route('benefit.callback')],
|
||||
"redirect" => ["url" => route('benefit.success', [
|
||||
'plan_id' => $plan->id,
|
||||
'amount' => $pricing['final_price'],
|
||||
'coupon' => $validated['coupon_code'] ?? '',
|
||||
'user_id' => $user->id,
|
||||
'billing_cycle' => $validated['billing_cycle']
|
||||
])]
|
||||
];
|
||||
|
||||
$responseData = json_encode($userData);
|
||||
$response = \Http::withHeaders([
|
||||
'Authorization' => 'Bearer ' . $settings['payment_settings']['benefit_secret_key'],
|
||||
'accept' => 'application/json',
|
||||
'content-type' => 'application/json',
|
||||
])->post('https://api.tap.company/v2/charges', $userData);
|
||||
|
||||
if ($response->successful()) {
|
||||
$res = $response->json();
|
||||
if (isset($res['transaction']['url'])) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'payment_url' => $res['transaction']['url'],
|
||||
'transaction_id' => $orderID
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['error' => $response->body()], 500);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Payment session creation failed')], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function callback(Request $request)
|
||||
{
|
||||
try {
|
||||
$paymentId = $request->input('payment_id');
|
||||
$transactionId = $request->input('transaction_id');
|
||||
$status = $request->input('status');
|
||||
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!$paymentId || !$transactionId) {
|
||||
return redirect()->route('plans.index')->withErrors(['error' => __('Invalid payment response')]);
|
||||
}
|
||||
|
||||
// Verify payment status with Benefit API
|
||||
$paymentResult = $this->retrieveBenefitPayment($paymentId, $settings['payment_settings']);
|
||||
|
||||
if ($paymentResult && $paymentResult['status'] === 'completed') {
|
||||
// Extract transaction ID to find the plan and user
|
||||
$parts = explode('_', $transactionId);
|
||||
|
||||
if (count($parts) >= 3) {
|
||||
$planId = $parts[1];
|
||||
$userId = $parts[2];
|
||||
|
||||
$plan = Plan::find($planId);
|
||||
$user = User::find($userId);
|
||||
|
||||
if ($plan && $user) {
|
||||
processPaymentSuccess([
|
||||
'user_id' => $user->id,
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => 'monthly', // Default, should be stored in session or passed
|
||||
'payment_method' => 'benefit',
|
||||
'payment_id' => $paymentId,
|
||||
]);
|
||||
|
||||
return redirect()->route('plans.index')->with('success', __('Payment successful and plan activated'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('plans.index')->withErrors(['error' => __('Payment failed or cancelled')]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->route('plans.index')->withErrors(['error' => __('Payment processing failed')]);
|
||||
}
|
||||
}
|
||||
|
||||
public function success(Request $request)
|
||||
{
|
||||
try {
|
||||
$planId = $request->input('plan_id');
|
||||
$userId = $request->input('user_id');
|
||||
$amount = $request->input('amount');
|
||||
$coupon = $request->input('coupon');
|
||||
$billingCycle = $request->input('billing_cycle', 'monthly');
|
||||
|
||||
if ($planId && $userId) {
|
||||
$plan = Plan::find($planId);
|
||||
$user = User::find($userId);
|
||||
|
||||
if ($plan && $user) {
|
||||
processPaymentSuccess([
|
||||
'user_id' => $user->id,
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => $billingCycle,
|
||||
'payment_method' => 'benefit',
|
||||
'coupon_code' => $coupon,
|
||||
'payment_id' => $request->input('tap_id', 'benefit_' . time()),
|
||||
]);
|
||||
|
||||
// Log the user in if not already authenticated
|
||||
if (!auth()->check()) {
|
||||
auth()->login($user);
|
||||
}
|
||||
|
||||
return redirect()->route('plans.index')->with('success', __('Payment completed successfully and plan activated'));
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('plans.index')->with('error', __('Payment verification failed'));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->route('plans.index')->with('error', __('Payment processing failed'));
|
||||
}
|
||||
}
|
||||
|
||||
public function webhook(Request $request)
|
||||
{
|
||||
try {
|
||||
$payload = $request->all();
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
// Verify webhook signature
|
||||
if (!$this->verifyBenefitWebhook($payload, $request->header('X-Benefit-Signature'), $settings['payment_settings'])) {
|
||||
return response()->json(['error' => 'Invalid signature'], 400);
|
||||
}
|
||||
|
||||
$paymentId = $payload['payment_id'] ?? null;
|
||||
$status = $payload['status'] ?? null;
|
||||
$transactionId = $payload['transaction_id'] ?? null;
|
||||
|
||||
if ($paymentId && $status === 'completed' && $transactionId) {
|
||||
// Process successful payment
|
||||
$parts = explode('_', $transactionId);
|
||||
|
||||
if (count($parts) >= 3) {
|
||||
$planId = $parts[1];
|
||||
$userId = $parts[2];
|
||||
|
||||
$plan = Plan::find($planId);
|
||||
$user = User::find($userId);
|
||||
|
||||
if ($plan && $user) {
|
||||
// Check if payment already processed
|
||||
$existingOrder = PlanOrder::where('payment_id', $paymentId)->first();
|
||||
|
||||
if (!$existingOrder) {
|
||||
processPaymentSuccess([
|
||||
'user_id' => $user->id,
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => 'monthly',
|
||||
'payment_method' => 'benefit',
|
||||
'payment_id' => $paymentId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['status' => 'success']);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Webhook processing failed')], 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function verifyBenefitPayment($paymentId, $transactionId, $settings)
|
||||
{
|
||||
// This is a simplified verification - in production, use Benefit API
|
||||
// For now, we'll assume the payment is valid if we have the required parameters
|
||||
return !empty($paymentId) && !empty($transactionId);
|
||||
}
|
||||
|
||||
private function createBenefitSession($paymentData, $settings)
|
||||
{
|
||||
// This is a simplified session creation - in production, use Benefit API
|
||||
// For now, return a mock session
|
||||
$baseUrl = $settings['benefit_mode'] === 'live'
|
||||
? 'https://api.benefit.bh'
|
||||
: 'https://sandbox-api.benefit.bh';
|
||||
|
||||
return [
|
||||
'session_id' => 'benefit_session_' . time(),
|
||||
'payment_url' => $baseUrl . '/payment/checkout?session=' . time()
|
||||
];
|
||||
}
|
||||
|
||||
private function retrieveBenefitPayment($paymentId, $settings)
|
||||
{
|
||||
// This is a simplified retrieval - in production, use Benefit API
|
||||
// For now, return a mock successful response
|
||||
return [
|
||||
'status' => 'completed',
|
||||
'payment_id' => $paymentId,
|
||||
'amount' => '10.000',
|
||||
'currency' => 'BHD'
|
||||
];
|
||||
}
|
||||
|
||||
private function verifyBenefitWebhook($payload, $signature, $settings)
|
||||
{
|
||||
// This is a simplified webhook verification - in production, verify the signature
|
||||
// using Benefit's webhook secret and HMAC
|
||||
return true;
|
||||
}
|
||||
}
|
||||
2924
app/Http/Controllers/BiometricAttendanceController.php
Normal file
2924
app/Http/Controllers/BiometricAttendanceController.php
Normal file
File diff suppressed because it is too large
Load Diff
195
app/Http/Controllers/BranchController.php
Normal file
195
app/Http/Controllers/BranchController.php
Normal file
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Branch;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class BranchController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-branches')) {
|
||||
$query = Branch::where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-branches')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-branches')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('email', 'like', '%' . $request->search . '%')
|
||||
->orWhere('phone', 'like', '%' . $request->search . '%');
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field', 'id');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['name', 'created_at', 'id'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'id';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
$branches = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
return Inertia::render('hr/branches/index', [
|
||||
'branches' => $branches,
|
||||
'filters' => $request->all(['search', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('create-branches')) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'address' => 'nullable|string',
|
||||
'city' => 'nullable|string|max:100',
|
||||
'state' => 'nullable|string|max:100',
|
||||
'country' => 'nullable|string|max:100',
|
||||
'zip_code' => 'nullable|string|max:20',
|
||||
'phone' => 'nullable|string|max:20',
|
||||
'email' => 'nullable|email|max:255',
|
||||
'status' => 'nullable|in:active,inactive',
|
||||
]);
|
||||
|
||||
$validated['created_by'] = creatorId();
|
||||
$validated['status'] = $validated['status'] ?? 'active';
|
||||
|
||||
// Check if branch with same name already exists
|
||||
$exists = Branch::where('name', $validated['name'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return redirect()->back()->with('error', __('Branch with this name already exists.'));
|
||||
}
|
||||
|
||||
Branch::create($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Branch created successfully.'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to create branch'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function update(Request $request, $branchId)
|
||||
{
|
||||
if (Auth::user()->can('edit-branches')) {
|
||||
$branch = Branch::where('id', $branchId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($branch) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'address' => 'nullable|string',
|
||||
'city' => 'nullable|string|max:100',
|
||||
'state' => 'nullable|string|max:100',
|
||||
'country' => 'nullable|string|max:100',
|
||||
'zip_code' => 'nullable|string|max:20',
|
||||
'phone' => 'nullable|string|max:20',
|
||||
'email' => 'nullable|email|max:255',
|
||||
'status' => 'nullable|in:active,inactive',
|
||||
]);
|
||||
|
||||
// Check if branch with same name already exists (excluding current branch)
|
||||
$exists = Branch::where('name', $validated['name'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('id', '!=', $branchId)
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return redirect()->back()->with('error', __('Branch with this name already exists.'));
|
||||
}
|
||||
|
||||
$branch->update($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Branch updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update branch'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Branch not found.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function destroy($branchId)
|
||||
{
|
||||
if (Auth::user()->can('delete-branches')) {
|
||||
$branch = Branch::where('id', $branchId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($branch) {
|
||||
try {
|
||||
// Check if branch has departments
|
||||
if (class_exists('App\\Models\\Department')) {
|
||||
$departmentCount = \App\Models\Department::where('branch_id', $branchId)->count();
|
||||
if ($departmentCount > 0) {
|
||||
return redirect()->back()->with('error', __('Cannot delete branch with assigned departments'));
|
||||
}
|
||||
}
|
||||
|
||||
$branch->delete();
|
||||
return redirect()->back()->with('success', __('Branch deleted successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to delete branch'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Branch not found.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleStatus($branchId)
|
||||
{
|
||||
if (Auth::user()->can('toggle-status-branches')) {
|
||||
$branch = Branch::where('id', $branchId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($branch) {
|
||||
try {
|
||||
$branch->status = $branch->status === 'active' ? 'inactive' : 'active';
|
||||
$branch->save();
|
||||
|
||||
return redirect()->back()->with('success', __('Branch status updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update branch status'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Branch not found.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
208
app/Http/Controllers/CalendarController.php
Normal file
208
app/Http/Controllers/CalendarController.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Holiday;
|
||||
use App\Models\LeaveApplication;
|
||||
use App\Models\Meeting;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class CalendarController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
if ($user->type === 'employee') {
|
||||
if (! $user->hasPermissionTo('view-calendar')) {
|
||||
abort(403, 'Unauthorized');
|
||||
}
|
||||
} else {
|
||||
if (! $user->hasPermissionTo('manage-calendar') && ! $user->hasPermissionTo('view-calendar')) {
|
||||
abort(403, 'Unauthorized');
|
||||
}
|
||||
}
|
||||
|
||||
$companyUserIds = getCompanyAndUsersId();
|
||||
|
||||
if (isDemo()) {
|
||||
// Static data for demo mode - 12 months
|
||||
$meetings = collect();
|
||||
$holidays = collect();
|
||||
$leaves = collect();
|
||||
|
||||
for ($month = 1; $month <= 12; $month++) {
|
||||
$date = now()->month($month);
|
||||
|
||||
// 3 meetings per month
|
||||
$meetings->push([
|
||||
'id' => 'meeting_' . $month . '_1',
|
||||
'title' => 'Team Meeting',
|
||||
'start' => $date->copy()->day(5)->format('Y-m-d').'T10:00:00',
|
||||
'end' => $date->copy()->day(5)->format('Y-m-d').'T11:00:00',
|
||||
'type' => 'meeting',
|
||||
'status' => 'scheduled',
|
||||
'backgroundColor' => '#3b82f6',
|
||||
'borderColor' => '#3b82f6',
|
||||
]);
|
||||
|
||||
$meetings->push([
|
||||
'id' => 'meeting_' . $month . '_2',
|
||||
'title' => 'Project Review',
|
||||
'start' => $date->copy()->day(12)->format('Y-m-d').'T14:00:00',
|
||||
'end' => $date->copy()->day(12)->format('Y-m-d').'T15:30:00',
|
||||
'type' => 'meeting',
|
||||
'status' => 'scheduled',
|
||||
'backgroundColor' => '#3b82f6',
|
||||
'borderColor' => '#3b82f6',
|
||||
]);
|
||||
|
||||
$meetings->push([
|
||||
'id' => 'meeting_' . $month . '_3',
|
||||
'title' => 'Client Presentation',
|
||||
'start' => $date->copy()->day(20)->format('Y-m-d').'T09:00:00',
|
||||
'end' => $date->copy()->day(20)->format('Y-m-d').'T10:30:00',
|
||||
'type' => 'meeting',
|
||||
'status' => 'scheduled',
|
||||
'backgroundColor' => '#3b82f6',
|
||||
'borderColor' => '#3b82f6',
|
||||
]);
|
||||
|
||||
// 3 holidays per month
|
||||
$holidays->push([
|
||||
'id' => 'holiday_' . $month . '_1',
|
||||
'title' => 'Company Foundation Day',
|
||||
'start' => $date->copy()->day(1)->format('Y-m-d'),
|
||||
'end' => $date->copy()->day(1)->format('Y-m-d'),
|
||||
'type' => 'holiday',
|
||||
'allDay' => true,
|
||||
'backgroundColor' => '#10b77f',
|
||||
'borderColor' => '#10b77f',
|
||||
]);
|
||||
|
||||
$holidays->push([
|
||||
'id' => 'holiday_' . $month . '_2',
|
||||
'title' => 'National Holiday',
|
||||
'start' => $date->copy()->day(15)->format('Y-m-d'),
|
||||
'end' => $date->copy()->day(15)->format('Y-m-d'),
|
||||
'type' => 'holiday',
|
||||
'allDay' => true,
|
||||
'backgroundColor' => '#10b77f',
|
||||
'borderColor' => '#10b77f',
|
||||
]);
|
||||
|
||||
$holidays->push([
|
||||
'id' => 'holiday_' . $month . '_3',
|
||||
'title' => 'Festival Holiday',
|
||||
'start' => $date->copy()->day(25)->format('Y-m-d'),
|
||||
'end' => $date->copy()->day(25)->format('Y-m-d'),
|
||||
'type' => 'holiday',
|
||||
'allDay' => true,
|
||||
'backgroundColor' => '#10b77f',
|
||||
'borderColor' => '#10b77f',
|
||||
]);
|
||||
|
||||
// 3 leaves per month
|
||||
$leaves->push([
|
||||
'id' => 'leave_' . $month . '_1',
|
||||
'title' => 'John Doe - Sick Leave',
|
||||
'start' => $date->copy()->day(3)->format('Y-m-d'),
|
||||
'end' => $date->copy()->day(5)->format('Y-m-d'),
|
||||
'type' => 'leave',
|
||||
'allDay' => true,
|
||||
'backgroundColor' => '#f59e0b',
|
||||
'borderColor' => '#f59e0b',
|
||||
]);
|
||||
|
||||
$leaves->push([
|
||||
'id' => 'leave_' . $month . '_2',
|
||||
'title' => 'Jane Smith - Annual Leave',
|
||||
'start' => $date->copy()->day(10)->format('Y-m-d'),
|
||||
'end' => $date->copy()->day(13)->format('Y-m-d'),
|
||||
'type' => 'leave',
|
||||
'allDay' => true,
|
||||
'backgroundColor' => '#f59e0b',
|
||||
'borderColor' => '#f59e0b',
|
||||
]);
|
||||
|
||||
$leaves->push([
|
||||
'id' => 'leave_' . $month . '_3',
|
||||
'title' => 'Mike Johnson - Casual Leave',
|
||||
'start' => $date->copy()->day(22)->format('Y-m-d'),
|
||||
'end' => $date->copy()->day(23)->format('Y-m-d'),
|
||||
'type' => 'leave',
|
||||
'allDay' => true,
|
||||
'backgroundColor' => '#f59e0b',
|
||||
'borderColor' => '#f59e0b',
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
// Get meetings
|
||||
$meetings = Meeting::query()
|
||||
->when($user->hasRole('employee'), function ($query) use ($user) {
|
||||
$query->where('organizer_id', $user->id)
|
||||
->orWhereHas('attendees', function ($q) use ($user) {
|
||||
$q->where('user_id', $user->id);
|
||||
});
|
||||
}, function ($query) use ($companyUserIds) {
|
||||
$query->whereIn('created_by', $companyUserIds);
|
||||
})
|
||||
->get()
|
||||
->map(function ($meeting) {
|
||||
return [
|
||||
'id' => $meeting->id,
|
||||
'title' => $meeting->title,
|
||||
'start' => Carbon::parse($meeting->meeting_date)->format('Y-m-d').'T'.Carbon::parse($meeting->start_time)->format('H:i:s'),
|
||||
'end' => Carbon::parse($meeting->meeting_date)->format('Y-m-d').'T'.Carbon::parse($meeting->end_time)->format('H:i:s'),
|
||||
'type' => 'meeting',
|
||||
'status' => $meeting->status,
|
||||
'backgroundColor' => '#3b82f6',
|
||||
'borderColor' => '#3b82f6',
|
||||
];
|
||||
});
|
||||
|
||||
// Get holidays
|
||||
$holidays = Holiday::whereIn('created_by', $companyUserIds)
|
||||
->get()
|
||||
->map(function ($holiday) {
|
||||
return [
|
||||
'id' => $holiday->id,
|
||||
'title' => $holiday->name,
|
||||
'start' => $holiday->start_date,
|
||||
'end' => $holiday->end_date ?: $holiday->start_date,
|
||||
'type' => 'holiday',
|
||||
'allDay' => true,
|
||||
'backgroundColor' => '#10b77f',
|
||||
'borderColor' => '#10b77f',
|
||||
];
|
||||
});
|
||||
|
||||
// Get leave applications
|
||||
$leaves = LeaveApplication::whereIn('created_by', $companyUserIds)
|
||||
->where('status', 'approved')
|
||||
->with(['employee', 'leaveType'])
|
||||
->get()
|
||||
->map(function ($leave) {
|
||||
return [
|
||||
'id' => $leave->id,
|
||||
'title' => $leave->employee->name.' - '.$leave->leaveType->name,
|
||||
'start' => $leave->start_date,
|
||||
'end' => Carbon::parse($leave->end_date)->addDay()->format('Y-m-d'),
|
||||
'type' => 'leave',
|
||||
'allDay' => true,
|
||||
'backgroundColor' => '#f59e0b',
|
||||
'borderColor' => '#f59e0b',
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
$events = $meetings->concat($holidays)->concat($leaves);
|
||||
|
||||
return Inertia::render('calendar/index', [
|
||||
'events' => $events,
|
||||
'canManage' => $user->hasPermissionTo('manage-calendar'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
162
app/Http/Controllers/CandidateAssessmentController.php
Normal file
162
app/Http/Controllers/CandidateAssessmentController.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\CandidateAssessment;
|
||||
use App\Models\Candidate;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class CandidateAssessmentController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-candidate-assessments')) {
|
||||
$query = CandidateAssessment::with(['candidate', 'conductor'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-candidate-assessments')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-candidate-assessments')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('conducted_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('assessment_name', 'like', '%' . $request->search . '%')
|
||||
->orWhereHas('candidate', function ($cq) use ($request) {
|
||||
$cq->where('first_name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('last_name', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('pass_fail_status', $request->status);
|
||||
}
|
||||
|
||||
if ($request->has('candidate_id') && !empty($request->candidate_id) && $request->candidate_id !== 'all') {
|
||||
$query->where('candidate_id', $request->candidate_id);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['assessment_name', 'assessment_date'];
|
||||
if ($sortField && in_array($sortField, $allowedSortFields)) {
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
$assessments = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$candidates = Candidate::whereIn('created_by', getCompanyAndUsersId())->where('is_employee',0)->get();
|
||||
|
||||
$employees = User::with('employee')
|
||||
->whereIn('type', ['manager', 'hr', 'employee'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get()
|
||||
->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'employee_id' => $user->employee->employee_id ?? ''
|
||||
];
|
||||
});
|
||||
|
||||
return Inertia::render('hr/recruitment/candidate-assessments/index', [
|
||||
'assessments' => $assessments,
|
||||
'candidates' => $candidates,
|
||||
'employees' => $employees,
|
||||
'filters' => $request->all(['search', 'status', 'candidate_id', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'candidate_id' => 'required|exists:candidates,id',
|
||||
'assessment_name' => 'required|string|max:255',
|
||||
'score' => 'nullable|integer|min:0',
|
||||
'max_score' => 'nullable|integer|min:1',
|
||||
'pass_fail_status' => 'required|in:Pass,Fail,Pending',
|
||||
'comments' => 'nullable|string',
|
||||
'conducted_by' => 'required|exists:users,id',
|
||||
'assessment_date' => 'required|date',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
CandidateAssessment::create([
|
||||
'candidate_id' => $request->candidate_id,
|
||||
'assessment_name' => $request->assessment_name,
|
||||
'score' => $request->score,
|
||||
'max_score' => $request->max_score,
|
||||
'pass_fail_status' => $request->pass_fail_status,
|
||||
'comments' => $request->comments,
|
||||
'conducted_by' => $request->conducted_by,
|
||||
'assessment_date' => $request->assessment_date,
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Assessment created successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, CandidateAssessment $candidateAssessment)
|
||||
{
|
||||
if (!in_array($candidateAssessment->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this assessment'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'candidate_id' => 'required|exists:candidates,id',
|
||||
'assessment_name' => 'required|string|max:255',
|
||||
'score' => 'nullable|integer|min:0',
|
||||
'max_score' => 'nullable|integer|min:1',
|
||||
'pass_fail_status' => 'required|in:Pass,Fail,Pending',
|
||||
'comments' => 'nullable|string',
|
||||
'conducted_by' => 'required|exists:users,id',
|
||||
'assessment_date' => 'required|date',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$candidateAssessment->update([
|
||||
'candidate_id' => $request->candidate_id,
|
||||
'assessment_name' => $request->assessment_name,
|
||||
'score' => $request->score,
|
||||
'max_score' => $request->max_score,
|
||||
'pass_fail_status' => $request->pass_fail_status,
|
||||
'comments' => $request->comments,
|
||||
'conducted_by' => $request->conducted_by,
|
||||
'assessment_date' => $request->assessment_date,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Assessment updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(CandidateAssessment $candidateAssessment)
|
||||
{
|
||||
if (!in_array($candidateAssessment->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this assessment'));
|
||||
}
|
||||
|
||||
$candidateAssessment->delete();
|
||||
return redirect()->back()->with('success', __('Assessment deleted successfully'));
|
||||
}
|
||||
}
|
||||
457
app/Http/Controllers/CandidateController.php
Normal file
457
app/Http/Controllers/CandidateController.php
Normal file
@@ -0,0 +1,457 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\AttendancePolicy;
|
||||
use App\Models\Branch;
|
||||
use App\Models\Candidate;
|
||||
use App\Models\JobPosting;
|
||||
use App\Models\CandidateSource;
|
||||
use App\Models\Department;
|
||||
use App\Models\Designation;
|
||||
use App\Models\DocumentType;
|
||||
use App\Models\Employee;
|
||||
use App\Models\Offer;
|
||||
use App\Models\Shift;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class CandidateController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-candidates')) {
|
||||
$query = Candidate::with(['job', 'source', 'referralEmployee'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-candidates')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-candidates')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('first_name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('last_name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('email', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
if ($request->has('job_id') && !empty($request->job_id) && $request->job_id !== 'all') {
|
||||
$query->where('job_id', $request->job_id);
|
||||
}
|
||||
|
||||
if ($request->has('source_id') && !empty($request->source_id) && $request->source_id !== 'all') {
|
||||
$query->where('source_id', $request->source_id);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field', 'created_at');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['first_name', 'application_date', 'created_at'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'created_at';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
$candidates = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$jobPostings = JobPosting::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('id', 'title', 'job_code')
|
||||
->get();
|
||||
|
||||
$sources = CandidateSource::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
$employees = User::with('employee')
|
||||
->where('type', 'employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get()
|
||||
->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'employee_id' => $user->employee->employee_id ?? ''
|
||||
];
|
||||
});
|
||||
|
||||
return Inertia::render('hr/recruitment/candidates/index', [
|
||||
'candidates' => $candidates,
|
||||
'jobPostings' => $jobPostings,
|
||||
'sources' => $sources,
|
||||
'employees' => $employees,
|
||||
'filters' => $request->all(['search', 'status', 'job_id', 'source_id', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'job_id' => 'required|exists:job_postings,id',
|
||||
'source_id' => 'required|exists:candidate_sources,id',
|
||||
'first_name' => 'required|string|max:255',
|
||||
'last_name' => 'required|string|max:255',
|
||||
'email' => 'required|email|max:255',
|
||||
'phone' => 'nullable|string|max:20',
|
||||
'current_company' => 'nullable|string|max:255',
|
||||
'current_position' => 'nullable|string|max:255',
|
||||
'experience_years' => 'required|integer|min:0',
|
||||
'current_salary' => 'nullable|numeric|min:0',
|
||||
'expected_salary' => 'nullable|numeric|min:0',
|
||||
'notice_period' => 'nullable|string|max:255',
|
||||
'skills' => 'nullable|string',
|
||||
'education' => 'nullable|string',
|
||||
'portfolio_url' => 'nullable|string',
|
||||
'linkedin_url' => 'nullable|string',
|
||||
'referral_employee_id' => 'nullable|exists:users,id',
|
||||
'application_date' => 'required|date',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
Candidate::create([
|
||||
'job_id' => $request->job_id,
|
||||
'source_id' => $request->source_id,
|
||||
'first_name' => $request->first_name,
|
||||
'last_name' => $request->last_name,
|
||||
'email' => $request->email,
|
||||
'phone' => $request->phone,
|
||||
'current_company' => $request->current_company,
|
||||
'current_position' => $request->current_position,
|
||||
'experience_years' => $request->experience_years,
|
||||
'current_salary' => $request->current_salary,
|
||||
'expected_salary' => $request->expected_salary,
|
||||
'notice_period' => $request->notice_period,
|
||||
'skills' => $request->skills,
|
||||
'education' => $request->education,
|
||||
'portfolio_url' => $request->portfolio_url ?: null,
|
||||
'linkedin_url' => $request->linkedin_url ?: null,
|
||||
'referral_employee_id' => $request->referral_employee_id ?: null,
|
||||
'application_date' => $request->application_date,
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Candidate created successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, Candidate $candidate)
|
||||
{
|
||||
if (!in_array($candidate->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to update this candidate');
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'job_id' => 'required|exists:job_postings,id',
|
||||
'source_id' => 'required|exists:candidate_sources,id',
|
||||
'first_name' => 'required|string|max:255',
|
||||
'last_name' => 'required|string|max:255',
|
||||
'email' => 'required|email|max:255',
|
||||
'phone' => 'nullable|string|max:20',
|
||||
'current_company' => 'nullable|string|max:255',
|
||||
'current_position' => 'nullable|string|max:255',
|
||||
'experience_years' => 'required|integer|min:0',
|
||||
'current_salary' => 'nullable|numeric|min:0',
|
||||
'expected_salary' => 'nullable|numeric|min:0',
|
||||
'notice_period' => 'nullable|string|max:255',
|
||||
'skills' => 'nullable|string',
|
||||
'education' => 'nullable|string',
|
||||
'portfolio_url' => 'nullable|string',
|
||||
'linkedin_url' => 'nullable|string',
|
||||
'referral_employee_id' => 'nullable|exists:users,id',
|
||||
'application_date' => 'required|date',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$candidate->update($request->only([
|
||||
'job_id',
|
||||
'source_id',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'phone',
|
||||
'current_company',
|
||||
'current_position',
|
||||
'experience_years',
|
||||
'current_salary',
|
||||
'expected_salary',
|
||||
'notice_period',
|
||||
'skills',
|
||||
'education',
|
||||
'portfolio_url',
|
||||
'linkedin_url',
|
||||
'referral_employee_id',
|
||||
'application_date'
|
||||
]));
|
||||
|
||||
return redirect()->back()->with('success', __('Candidate updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(Candidate $candidate)
|
||||
{
|
||||
if (!in_array($candidate->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to delete this candidate');
|
||||
}
|
||||
|
||||
$candidate->delete();
|
||||
return redirect()->back()->with('success', __('Candidate deleted successfully'));
|
||||
}
|
||||
|
||||
public function show(Candidate $candidate)
|
||||
{
|
||||
if (!in_array($candidate->created_by, getCompanyAndUsersId())) {
|
||||
return abort(404);
|
||||
}
|
||||
|
||||
$candidate->load([
|
||||
'job.location',
|
||||
'job.jobType',
|
||||
'source',
|
||||
'referralEmployee',
|
||||
'branch',
|
||||
'department'
|
||||
]);
|
||||
|
||||
return Inertia::render('hr/recruitment/candidates/show', [
|
||||
'candidate' => $candidate,
|
||||
]);
|
||||
}
|
||||
public function updateStatus(Request $request, Candidate $candidate)
|
||||
{
|
||||
if (!in_array($candidate->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to update this candidate');
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'status' => 'required|in:New,Screening,Interview,Offer,Hired,Rejected',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator);
|
||||
}
|
||||
|
||||
$candidate->update(['status' => $request->status]);
|
||||
return redirect()->back()->with('success', __('Candidate status updated successfully'));
|
||||
}
|
||||
|
||||
public function convertToEmployee(Candidate $candidate)
|
||||
{
|
||||
try {
|
||||
if (Auth::user()->can('convert-to-employee')) {
|
||||
if (!in_array($candidate->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to convert this candidate'));
|
||||
}
|
||||
|
||||
if ($candidate->status !== 'Hired') {
|
||||
return redirect()->back()->with('error', __('Only hired candidates can be converted to employees'));
|
||||
}
|
||||
|
||||
if ($candidate->is_employee) {
|
||||
return redirect()->back()->with('error', __('This candidate has already been converted to an employee'));
|
||||
}
|
||||
|
||||
// Check if candidate has an accepted offer
|
||||
$acceptedOffer = Offer::where('candidate_id', $candidate->id)
|
||||
->where('status', 'Accepted')
|
||||
->first();
|
||||
|
||||
if (!$acceptedOffer) {
|
||||
return redirect()->back()->with('error', __('Candidate must have an accepted offer before conversion to employee'));
|
||||
}
|
||||
|
||||
// Get data needed for employee creation form
|
||||
$branches = Branch::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->get(['id', 'name']);
|
||||
|
||||
$departments = Department::with('branch')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->get(['id', 'name', 'branch_id']);
|
||||
|
||||
$designations = Designation::with('department')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->get(['id', 'name', 'department_id']);
|
||||
|
||||
$documentTypes = DocumentType::whereIn('created_by', getCompanyAndUsersId())
|
||||
->get(['id', 'name', 'is_required']);
|
||||
|
||||
$shifts = Shift::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->get(['id', 'name', 'start_time', 'end_time']);
|
||||
|
||||
$attendancePolicies = AttendancePolicy::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->get(['id', 'name']);
|
||||
|
||||
return Inertia::render('hr/recruitment/candidates/convert-to-employee', [
|
||||
'candidate' => $candidate->load(['job', 'source']),
|
||||
'branches' => $branches,
|
||||
'departments' => $departments,
|
||||
'designations' => $designations,
|
||||
'documentTypes' => $documentTypes,
|
||||
'shifts' => $shifts,
|
||||
'attendancePolicies' => $attendancePolicies,
|
||||
'generatedEmployeeId' => Employee::generateEmployeeId(),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Convert to employee page load failed: ' . $e->getMessage());
|
||||
return redirect()->back()->with('error', __('Failed to load conversion page: :message', ['message' => $e->getMessage()]));
|
||||
}
|
||||
}
|
||||
|
||||
public function storeEmployee(Request $request)
|
||||
{
|
||||
try {
|
||||
// Validate the request
|
||||
$validator = Validator::make($request->all(), [
|
||||
'candidate_id' => 'required|exists:candidates,id',
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|max:255|unique:users,email',
|
||||
'password' => 'required|string|min:8',
|
||||
'phone' => 'required|string|max:20',
|
||||
'date_of_birth' => 'required|date',
|
||||
'gender' => 'required|in:male,female,other',
|
||||
'branch_id' => 'required|exists:branches,id',
|
||||
'department_id' => 'required|exists:departments,id',
|
||||
'designation_id' => 'required|exists:designations,id',
|
||||
'date_of_joining' => 'required|date',
|
||||
'employment_type' => 'required|string|max:50',
|
||||
'address_line_1' => 'required|string|max:255',
|
||||
'city' => 'required|string|max:100',
|
||||
'state' => 'required|string|max:100',
|
||||
'country' => 'required|string|max:100',
|
||||
'postal_code' => 'required|string|max:20',
|
||||
'emergency_contact_name' => 'required|string|max:255',
|
||||
'emergency_contact_relationship' => 'required|string|max:100',
|
||||
'emergency_contact_number' => 'required|string|max:20',
|
||||
'bank_name' => 'required|string|max:255',
|
||||
'account_holder_name' => 'required|string|max:255',
|
||||
'account_number' => 'required|string|max:50',
|
||||
'salary' => 'required|numeric|min:0',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Get candidate
|
||||
$candidate = \App\Models\Candidate::findOrFail($request->candidate_id);
|
||||
|
||||
// Check permissions and status
|
||||
if (!in_array($candidate->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'Permission denied');
|
||||
}
|
||||
|
||||
if ($candidate->status !== 'Hired' || $candidate->is_employee) {
|
||||
return redirect()->back()->with('error', 'Invalid candidate status for conversion');
|
||||
}
|
||||
|
||||
// Create User
|
||||
$user = \App\Models\User::create([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'password' => \Illuminate\Support\Facades\Hash::make($request->password),
|
||||
'type' => 'employee',
|
||||
'lang' => 'en',
|
||||
'avatar' => $request->profile_image,
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
// Assign Employee role
|
||||
if (isSaas()) {
|
||||
$employeeRole = \Spatie\Permission\Models\Role::where('created_by', createdBy())->where('name', 'employee')->first();
|
||||
} else {
|
||||
$employeeRole = \Spatie\Permission\Models\Role::where('name', 'employee')->first();
|
||||
}
|
||||
if ($employeeRole) {
|
||||
$user->assignRole($employeeRole);
|
||||
}
|
||||
|
||||
// Create Employee
|
||||
$employee = \App\Models\Employee::create([
|
||||
'user_id' => $user->id,
|
||||
'employee_id' => \App\Models\Employee::generateEmployeeId(),
|
||||
'biometric_emp_id' => $request->biometric_emp_id,
|
||||
'phone' => $request->phone,
|
||||
'date_of_birth' => $request->date_of_birth,
|
||||
'gender' => $request->gender,
|
||||
'branch_id' => $request->branch_id,
|
||||
'department_id' => $request->department_id,
|
||||
'designation_id' => $request->designation_id,
|
||||
'shift_id' => $request->shift_id,
|
||||
'attendance_policy_id' => $request->attendance_policy_id,
|
||||
'date_of_joining' => $request->date_of_joining,
|
||||
'employment_type' => $request->employment_type,
|
||||
'employee_status' => $request->employee_status ?? 'active',
|
||||
'address_line_1' => $request->address_line_1,
|
||||
'address_line_2' => $request->address_line_2,
|
||||
'city' => $request->city,
|
||||
'state' => $request->state,
|
||||
'country' => $request->country,
|
||||
'postal_code' => $request->postal_code,
|
||||
'emergency_contact_name' => $request->emergency_contact_name,
|
||||
'emergency_contact_relationship' => $request->emergency_contact_relationship,
|
||||
'emergency_contact_number' => $request->emergency_contact_number,
|
||||
'bank_name' => $request->bank_name,
|
||||
'account_holder_name' => $request->account_holder_name,
|
||||
'account_number' => $request->account_number,
|
||||
'bank_identifier_code' => $request->bank_identifier_code,
|
||||
'bank_branch' => $request->bank_branch,
|
||||
'tax_payer_id' => $request->tax_payer_id,
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
// Handle documents
|
||||
if ($request->has('documents') && is_array($request->documents)) {
|
||||
foreach ($request->documents as $document) {
|
||||
if (isset($document['file_path']) && !empty($document['file_path'])) {
|
||||
\App\Models\EmployeeDocument::create([
|
||||
'employee_id' => $employee->user_id,
|
||||
'document_type_id' => $document['document_type_id'],
|
||||
'file_path' => $document['file_path'],
|
||||
'expiry_date' => $document['expiry_date'] ?? null,
|
||||
'verification_status' => 'pending',
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark candidate as converted
|
||||
$candidate->update(['is_employee' => true]);
|
||||
|
||||
return redirect()->route('hr.employees.index')->with('success', __('Candidate converted to employee successfully'));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Candidate to employee conversion failed: ' . $e->getMessage());
|
||||
return redirect()->back()->with('error', __('Failed to convert candidate: :message', ['message' => $e->getMessage()]))->withInput();
|
||||
}
|
||||
}
|
||||
}
|
||||
226
app/Http/Controllers/CandidateOnboardingController.php
Normal file
226
app/Http/Controllers/CandidateOnboardingController.php
Normal file
@@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\CandidateOnboarding;
|
||||
use App\Models\Candidate;
|
||||
use App\Models\Employee;
|
||||
use App\Models\OnboardingChecklist;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class CandidateOnboardingController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-candidate-onboarding')) {
|
||||
$query = CandidateOnboarding::with(['employee', 'checklist', 'buddyEmployee'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-candidate-onboarding')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-candidate-onboarding')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('employee_id', Auth::id())->orWhere('buddy_employee_id', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->whereHas('employee', function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
if ($request->has('employee_id') && !empty($request->employee_id) && $request->employee_id !== 'all') {
|
||||
$query->where('employee_id', $request->employee_id);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['start_date', 'created_at'];
|
||||
if ($sortField && in_array($sortField, $allowedSortFields)) {
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
$candidateOnboarding = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$employees = User::with('employee')
|
||||
->where('type', 'employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->orderBy('id', 'desc')
|
||||
->get();
|
||||
|
||||
$checklists = OnboardingChecklist::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/recruitment/candidate-onboarding/index', [
|
||||
'candidateOnboarding' => $candidateOnboarding,
|
||||
'employees' => $this->getFilteredEmployees(),
|
||||
'checklists' => $checklists,
|
||||
'buddyEmployees' => $employees,
|
||||
'filters' => $request->all(['search', 'status', 'employee_id', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
private function getFilteredEmployees()
|
||||
{
|
||||
// Get employees for filter dropdown (compatible with getFilteredEmployees logic)
|
||||
$employeeQuery = Employee::whereIn('created_by', getCompanyAndUsersId());
|
||||
|
||||
if (Auth::user()->can('manage-own-candidate-onboarding') && !Auth::user()->can('manage-any-candidate-onboarding')) {
|
||||
$employeeQuery->where(function ($q) {
|
||||
$q->where('created_by', Auth::id())->orWhere('user_id', Auth::id());
|
||||
});
|
||||
}
|
||||
|
||||
$employees = User::emp()
|
||||
->with('employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->whereIn('id', $employeeQuery->pluck('user_id'))
|
||||
->select('id', 'name')
|
||||
->orderBy('id', 'desc')
|
||||
->get()
|
||||
->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'employee_id' => $user->employee->employee_id ?? '',
|
||||
];
|
||||
});
|
||||
return $employees;
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'checklist_id' => 'required|exists:onboarding_checklists,id',
|
||||
'start_date' => 'required|date',
|
||||
'buddy_employee_id' => 'nullable|exists:users,id',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if checklist is already assigned to this employee
|
||||
$exists = CandidateOnboarding::where('employee_id', $request->employee_id)
|
||||
->where('checklist_id', $request->checklist_id)
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return redirect()->back()->with('error', __('This checklist is already assigned to the selected employee'));
|
||||
}
|
||||
|
||||
CandidateOnboarding::create([
|
||||
'employee_id' => $request->employee_id,
|
||||
'checklist_id' => $request->checklist_id,
|
||||
'start_date' => $request->start_date,
|
||||
'buddy_employee_id' => $request->buddy_employee_id,
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Candidate onboarding created successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, CandidateOnboarding $candidateOnboarding)
|
||||
{
|
||||
if (!in_array($candidateOnboarding->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this onboarding'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'checklist_id' => 'required|exists:onboarding_checklists,id',
|
||||
'start_date' => 'required|date',
|
||||
'buddy_employee_id' => 'nullable|exists:users,id',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if checklist is already assigned to this employee (excluding current record)
|
||||
$exists = CandidateOnboarding::where('employee_id', $request->employee_id)
|
||||
->where('checklist_id', $request->checklist_id)
|
||||
->where('id', '!=', $candidateOnboarding->id)
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return redirect()->back()->with('error', __('This checklist is already assigned to the selected employee'));
|
||||
}
|
||||
|
||||
$candidateOnboarding->update([
|
||||
'employee_id' => $request->employee_id,
|
||||
'checklist_id' => $request->checklist_id,
|
||||
'start_date' => $request->start_date,
|
||||
'buddy_employee_id' => $request->buddy_employee_id,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Candidate onboarding updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(CandidateOnboarding $candidateOnboarding)
|
||||
{
|
||||
if (!in_array($candidateOnboarding->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this onboarding'));
|
||||
}
|
||||
|
||||
$candidateOnboarding->delete();
|
||||
return redirect()->back()->with('success', __('Candidate onboarding deleted successfully'));
|
||||
}
|
||||
|
||||
public function show(CandidateOnboarding $candidateOnboarding)
|
||||
{
|
||||
if (!in_array($candidateOnboarding->created_by, getCompanyAndUsersId())) {
|
||||
return abort(404);
|
||||
}
|
||||
|
||||
$candidateOnboarding->load([
|
||||
'employee',
|
||||
'checklist.checklistItems',
|
||||
'buddyEmployee',
|
||||
'creator'
|
||||
]);
|
||||
|
||||
return Inertia::render('hr/recruitment/candidate-onboarding/show', [
|
||||
'candidateOnboarding' => $candidateOnboarding,
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateStatus(Request $request, CandidateOnboarding $candidateOnboarding)
|
||||
{
|
||||
if (!in_array($candidateOnboarding->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this onboarding'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'status' => 'required|in:Pending,In Progress,Completed',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator);
|
||||
}
|
||||
|
||||
$candidateOnboarding->update(['status' => $request->status]);
|
||||
return redirect()->back()->with('success', __('Onboarding status updated successfully'));
|
||||
}
|
||||
}
|
||||
132
app/Http/Controllers/CandidateSourceController.php
Normal file
132
app/Http/Controllers/CandidateSourceController.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\CandidateSource;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class CandidateSourceController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-candidate-sources')) {
|
||||
$query = CandidateSource::where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-candidate-sources')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-candidate-sources')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field', 'created_at');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['name', 'created_at'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'created_at';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
$candidateSources = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
return Inertia::render('hr/recruitment/candidate-sources/index', [
|
||||
'candidateSources' => $candidateSources,
|
||||
'filters' => $request->all(['search', 'status', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
CandidateSource::create([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'status' => $request->status ?? 'active',
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Candidate source created successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, CandidateSource $candidateSource)
|
||||
{
|
||||
if (!in_array($candidateSource->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to update this candidate source');
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$candidateSource->update([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'status' => $request->status ?? 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Candidate source updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(CandidateSource $candidateSource)
|
||||
{
|
||||
if (!in_array($candidateSource->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to delete this candidate source');
|
||||
}
|
||||
|
||||
if ($candidateSource->candidates()->count() > 0) {
|
||||
return redirect()->back()->with('error', __('Cannot delete candidate source as it is being used by candidates'));
|
||||
}
|
||||
|
||||
$candidateSource->delete();
|
||||
return redirect()->back()->with('success', __('Candidate source deleted successfully'));
|
||||
}
|
||||
|
||||
public function toggleStatus(CandidateSource $candidateSource)
|
||||
{
|
||||
if (!in_array($candidateSource->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to update this candidate source');
|
||||
}
|
||||
|
||||
$candidateSource->update([
|
||||
'status' => $candidateSource->status === 'active' ? 'inactive' : 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Candidate source status updated successfully'));
|
||||
}
|
||||
}
|
||||
419
app/Http/Controllers/CareerController.php
Normal file
419
app/Http/Controllers/CareerController.php
Normal file
@@ -0,0 +1,419 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Candidate;
|
||||
use App\Models\CandidateSource;
|
||||
use App\Models\JobLocation;
|
||||
use App\Models\JobPosting;
|
||||
use App\Models\JobType;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class CareerController extends Controller
|
||||
{
|
||||
public function index(Request $request, $userSlug = null)
|
||||
{
|
||||
// Access shared data from middleware
|
||||
$companyId = $request->get('companyId');
|
||||
$companySettings = $request->get('companySettings');
|
||||
$userSlug = $request->get('userSlug');
|
||||
|
||||
$query = JobPosting::with(['jobType', 'location', 'branch', 'department'])
|
||||
->where('is_published', true)
|
||||
->where('status', 'Published');
|
||||
|
||||
if ($companyId) {
|
||||
$query->whereIn('created_by', getCompanyUsers($companyId));
|
||||
}
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('title', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('job_type') && !empty($request->job_type)) {
|
||||
$jobTypeIds = explode(',', $request->job_type);
|
||||
$query->whereIn('job_type_id', $jobTypeIds);
|
||||
}
|
||||
|
||||
if ($request->has('location') && !empty($request->location)) {
|
||||
$query->where('location_id', $request->location);
|
||||
}
|
||||
|
||||
if ($request->has('salary_range') && !empty($request->salary_range)) {
|
||||
switch ($request->salary_range) {
|
||||
case '0-50k':
|
||||
$query->where('max_salary', '<=', 50000);
|
||||
break;
|
||||
case '50k-100k':
|
||||
$query->whereBetween('min_salary', [50000, 100000]);
|
||||
break;
|
||||
case '100k+':
|
||||
$query->where('min_salary', '>=', 100000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->has('vacancies') && !empty($request->vacancies)) {
|
||||
$vacancyRanges = explode(',', $request->vacancies);
|
||||
$query->where(function ($q) use ($vacancyRanges) {
|
||||
foreach ($vacancyRanges as $range) {
|
||||
switch ($range) {
|
||||
case '1-5':
|
||||
$q->orWhereBetween('positions', [1, 5]);
|
||||
break;
|
||||
case '6-15':
|
||||
$q->orWhereBetween('positions', [6, 15]);
|
||||
break;
|
||||
case '16-25':
|
||||
$q->orWhereBetween('positions', [16, 25]);
|
||||
break;
|
||||
case '25+':
|
||||
$q->orWhere('positions', '>', 25);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// $query = $query->orderBy('is_featured', 'desc');
|
||||
|
||||
if ($request->has('sort') && !empty($request->sort)) {
|
||||
switch ($request->sort) {
|
||||
case 'oldest':
|
||||
$query = $query->orderBy('created_at', 'asc');
|
||||
break;
|
||||
case 'salary-high':
|
||||
$query = $query->orderBy('max_salary', 'desc');
|
||||
break;
|
||||
case 'salary-low':
|
||||
$query = $query->orderBy('min_salary', 'asc');
|
||||
break;
|
||||
default: // newest
|
||||
$query = $query->orderBy('created_at', 'desc');
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$query = $query->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
$jobPostings = $query->paginate(6);
|
||||
|
||||
$jobTypes = JobType::where('status', 'active')->whereIn('created_by', getCompanyUsers($companyId))->get();
|
||||
$locations = JobLocation::where('status', 'active')->whereIn('created_by', getCompanyUsers($companyId))->get();
|
||||
$vacancyRanges = [
|
||||
['value' => '1-5', 'label' => '1-5'],
|
||||
['value' => '6-15', 'label' => '6-15'],
|
||||
['value' => '16-25', 'label' => '16-25'],
|
||||
['value' => '25+', 'label' => '25+'],
|
||||
];
|
||||
|
||||
return Inertia::render('career/index', [
|
||||
'jobPostings' => $jobPostings,
|
||||
'jobTypes' => $jobTypes,
|
||||
'locations' => $locations,
|
||||
'companyId' => $companyId,
|
||||
'userSlug' => $userSlug,
|
||||
'companySettings' => $companySettings,
|
||||
'vacancyRanges' => $vacancyRanges,
|
||||
'filters' => $request->all(keys: ['search', 'job_type', 'location', 'salary_range', 'vacancies', 'sort']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(Request $request, $userSlug, $jobCode)
|
||||
{
|
||||
try {
|
||||
// Get company data from middleware
|
||||
$companyId = $request->get('companyId');
|
||||
$companySettings = $request->get('companySettings');
|
||||
|
||||
$query = JobPosting::with(['jobType', 'location', 'branch', 'department'])
|
||||
->where('code', $jobCode)
|
||||
->whereIn('created_by', getCompanyUsers($companyId))
|
||||
->where('is_published', true)
|
||||
->where('status', 'Published');
|
||||
|
||||
$jobPosting = $query->firstOrFail();
|
||||
|
||||
$relatedQuery = JobPosting::with(['jobType', 'location', 'branch', 'department'])
|
||||
->where('code', '!=', $jobCode)
|
||||
->where('is_published', true)
|
||||
->where('status', 'Published');
|
||||
|
||||
if ($companyId) {
|
||||
$relatedQuery->whereIn('created_by', getCompanyUsers($companyId));
|
||||
}
|
||||
|
||||
$relatedJobs = $relatedQuery->inRandomOrder()->limit(4)->get();
|
||||
|
||||
if ($companyId) {
|
||||
$companyUser = User::find($companyId);
|
||||
if ($companyUser) {
|
||||
$companySettings = array_merge($companySettings, [
|
||||
'company_name' => $companyUser->name,
|
||||
'company_email' => $companyUser->email,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return Inertia::render('career/job-details', [
|
||||
'jobPosting' => $jobPosting,
|
||||
'relatedJobs' => $relatedJobs,
|
||||
'companyId' => $companyId,
|
||||
'userSlug' => $userSlug,
|
||||
'companySettings' => $companySettings,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->route('career.index', $userSlug)
|
||||
->with('error', 'Job not found or no longer available.');
|
||||
}
|
||||
}
|
||||
|
||||
public function showApplicationForm(Request $request, $userSlug, $jobCode)
|
||||
{
|
||||
try {
|
||||
// Get company data from middleware
|
||||
$companyId = $request->get('companyId');
|
||||
$companySettings = $request->get('companySettings');
|
||||
|
||||
$jobPosting = JobPosting::with(['jobType', 'location', 'branch', 'department'])
|
||||
->where('code', $jobCode)
|
||||
->whereIn('created_by', getCompanyUsers($companyId))
|
||||
->where('is_published', true)
|
||||
->where('status', 'Published')
|
||||
->firstOrFail();
|
||||
|
||||
// Get custom questions based on IDs stored in job posting
|
||||
$customQuestions = [];
|
||||
if ($jobPosting->custom_question && is_array($jobPosting->custom_question)) {
|
||||
$customQuestions = \App\Models\CustomQuestion::whereIn('id', $jobPosting->custom_question)
|
||||
->get();
|
||||
}
|
||||
|
||||
// Get candidate sources
|
||||
$candidateSources = CandidateSource::where('status', 'active')
|
||||
->whereIn('created_by', getCompanyUsers($companyId))
|
||||
->get();
|
||||
|
||||
return Inertia::render('career/apply', [
|
||||
'jobPosting' => $jobPosting,
|
||||
'customQuestions' => $customQuestions,
|
||||
'candidateSources' => $candidateSources,
|
||||
'applicantFields' => $jobPosting->applicant ?? [],
|
||||
'visibilityFields' => $jobPosting->visibility ?? [],
|
||||
'companyId' => $companyId,
|
||||
'userSlug' => $userSlug,
|
||||
'companySettings' => $companySettings,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->route('career.index', $userSlug)
|
||||
->with('error', 'Job not found or no longer available.');
|
||||
}
|
||||
}
|
||||
|
||||
public function submitApplication(Request $request, $userSlug, $jobCode)
|
||||
{
|
||||
try {
|
||||
// Get company data from middleware
|
||||
$companyId = $request->get('companyId');
|
||||
|
||||
// Find the job posting
|
||||
$jobPosting = JobPosting::where('code', $jobCode)
|
||||
->whereIn('created_by', getCompanyUsers($companyId))
|
||||
->where('is_published', true)
|
||||
->where('status', 'Published')
|
||||
->firstOrFail();
|
||||
|
||||
// Base validation rules
|
||||
$rules = [
|
||||
'first_name' => 'required|string|max:255',
|
||||
'last_name' => 'required|string|max:255',
|
||||
'email' => 'required|email|max:255',
|
||||
'phone' => 'required|string|max:20',
|
||||
'address' => 'required|string|max:500',
|
||||
'city' => 'required|string|max:100',
|
||||
'state' => 'required|string|max:100',
|
||||
'zip_code' => 'required',
|
||||
'country' => 'required',
|
||||
'current_position' => 'required',
|
||||
'current_company' => 'required',
|
||||
'experience_years' => 'required|numeric|min:0|max:50',
|
||||
'current_salary' => 'required|numeric|min:0',
|
||||
'expected_salary' => 'required|numeric|min:0',
|
||||
'source_id' => 'required|exists:candidate_sources,id',
|
||||
'custom_question' => 'nullable|json',
|
||||
'resume' => 'required',
|
||||
];
|
||||
|
||||
// Check job posting applicant fields for conditional validation
|
||||
$applicantFields = $jobPosting->applicant ?? [];
|
||||
$visibilityFields = $jobPosting->visibility ?? [];
|
||||
|
||||
// Add conditional validation for gender
|
||||
if (in_array('gender', $applicantFields)) {
|
||||
$rules['gender'] = 'required|in:male,female,other';
|
||||
} else {
|
||||
$rules['gender'] = 'nullable|in:male,female,other';
|
||||
}
|
||||
|
||||
// Add conditional validation for date_of_birth
|
||||
if (in_array('date_of_birth', $applicantFields)) {
|
||||
$rules['date_of_birth'] = 'required|date';
|
||||
} else {
|
||||
$rules['date_of_birth'] = 'nullable|date';
|
||||
}
|
||||
|
||||
// Add conditional validation for cover letter fields
|
||||
if (in_array('cover_letter', $visibilityFields)) {
|
||||
$rules['coverletter_message'] = 'required|string|max:2000';
|
||||
$rules['cover_letter_file'] = 'required';
|
||||
} else {
|
||||
$rules['coverletter_message'] = 'nullable|string|max:2000';
|
||||
$rules['cover_letter_file'] = 'nullable|file';
|
||||
}
|
||||
|
||||
// Add conditional validation for terms and conditions
|
||||
if (in_array('terms_and_conditions', $visibilityFields)) {
|
||||
$rules['terms_condition_check'] = 'required|in:on,off,1,0';
|
||||
} else {
|
||||
// If terms not required, accept any value or make it optional
|
||||
$rules['terms_condition_check'] = 'sometimes|in:on,off,1,0';
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
// Check if candidate already applied for this job
|
||||
$existingCandidate = Candidate::where('email', $request->email)
|
||||
->where('job_id', $jobPosting->id)
|
||||
->first();
|
||||
|
||||
if ($existingCandidate) {
|
||||
return redirect()->back()
|
||||
->withErrors(['email' => 'You have already applied for this position.'])
|
||||
->withInput();
|
||||
}
|
||||
|
||||
// Handle file uploads
|
||||
$resumePath = null;
|
||||
$coverLetterPath = null;
|
||||
|
||||
if (!empty($request->resume) && $request->hasFile('resume')) {
|
||||
$filenameWithExt = $request->file('resume')->getClientOriginalName();
|
||||
$filename = pathinfo($filenameWithExt, PATHINFO_FILENAME);
|
||||
$extension = $request->file('resume')->getClientOriginalExtension();
|
||||
$fileNameToStore = $filename . '_' . time() . '.' . $extension;
|
||||
|
||||
$upload = upload_file($request, 'resume', $fileNameToStore, 'candidates/candidate_resumes');
|
||||
if ($upload['status'] == true) {
|
||||
$resumePath = $upload['url'];
|
||||
} else {
|
||||
return redirect()->back()
|
||||
->withErrors(['resume' => $upload['msg']])
|
||||
->withInput();
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($request->cover_letter_file) && $request->hasFile('cover_letter_file')) {
|
||||
$filenameWithExt = $request->file('cover_letter_file')->getClientOriginalName();
|
||||
$filename = pathinfo($filenameWithExt, PATHINFO_FILENAME);
|
||||
$extension = $request->file('cover_letter_file')->getClientOriginalExtension();
|
||||
$fileNameToStore = $filename . '_' . time() . '.' . $extension;
|
||||
|
||||
$upload = upload_file($request, 'cover_letter_file', $fileNameToStore, 'candidates/candidate_cover_letters');
|
||||
if ($upload['status'] == true) {
|
||||
$coverLetterPath = $upload['url'];
|
||||
} else {
|
||||
return redirect()->back()
|
||||
->withErrors(['cover_letter_file' => $upload['msg']])
|
||||
->withInput();
|
||||
}
|
||||
}
|
||||
|
||||
// Convert terms_condition_check from 1/0 to on/off
|
||||
if ($request->has('terms_condition_check')) {
|
||||
$termsValue = $request->terms_condition_check;
|
||||
$request->merge([
|
||||
'terms_condition_check' => ($termsValue == '1' || $termsValue === true) ? 'on' : 'off',
|
||||
]);
|
||||
}
|
||||
|
||||
// Get custom questions for processing answers
|
||||
$customQuestions = [];
|
||||
if ($jobPosting->custom_question && is_array($jobPosting->custom_question)) {
|
||||
$customQuestions = \App\Models\CustomQuestion::whereIn('id', $jobPosting->custom_question)
|
||||
->get();
|
||||
}
|
||||
|
||||
// Process custom questions into question-answer format
|
||||
$customQuestionData = [];
|
||||
if ($customQuestions && count($customQuestions) > 0) {
|
||||
foreach ($customQuestions as $question) {
|
||||
$fieldName = 'custom_question_' . $question->id;
|
||||
if ($request->has($fieldName) && !empty($request->input($fieldName))) {
|
||||
$customQuestionData[$question->question] = $request->input($fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create candidate record
|
||||
$candidate = new Candidate;
|
||||
$candidate->job_id = $jobPosting->id;
|
||||
$candidate->source_id = $request->source_id;
|
||||
$candidate->branch_id = $jobPosting->branch_id;
|
||||
$candidate->department_id = $jobPosting->department_id;
|
||||
$candidate->first_name = $request->first_name;
|
||||
$candidate->last_name = $request->last_name;
|
||||
$candidate->email = $request->email;
|
||||
$candidate->phone = $request->phone;
|
||||
$candidate->gender = $request->gender;
|
||||
$candidate->date_of_birth = $request->date_of_birth;
|
||||
$candidate->address = $request->address;
|
||||
$candidate->city = $request->city;
|
||||
$candidate->state = $request->state;
|
||||
$candidate->zip_code = $request->zip_code;
|
||||
$candidate->country = $request->country;
|
||||
$candidate->current_company = $request->current_company;
|
||||
$candidate->current_position = $request->current_position;
|
||||
$candidate->experience_years = $request->experience_years ?: 0;
|
||||
$candidate->current_salary = $request->current_salary ? str_replace(',', '', $request->current_salary) : null;
|
||||
$candidate->expected_salary = $request->expected_salary ? str_replace(',', '', $request->expected_salary) : null;
|
||||
$candidate->resume_path = $resumePath;
|
||||
$candidate->cover_letter_path = $coverLetterPath;
|
||||
$candidate->coverletter_message = $request->coverletter_message;
|
||||
$candidate->custom_question = $customQuestionData;
|
||||
$candidate->terms_condition_check = $request->terms_condition_check;
|
||||
$candidate->application_date = now()->toDateString();
|
||||
$candidate->created_by = $companyId;
|
||||
|
||||
$candidate->save();
|
||||
|
||||
return redirect()->back()
|
||||
->with('success', 'Your application has been submitted successfully! We will review it and get back to you soon.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// Clean up uploaded files if candidate creation fails
|
||||
if (isset($resumePath) && Storage::disk('public')->exists($resumePath)) {
|
||||
Storage::disk('public')->delete($resumePath);
|
||||
}
|
||||
if (isset($coverLetterPath) && Storage::disk('public')->exists($coverLetterPath)) {
|
||||
Storage::disk('public')->delete($coverLetterPath);
|
||||
}
|
||||
|
||||
return redirect()->back()
|
||||
->withErrors(['error' => 'An error occurred while submitting your application. Please try again.'])
|
||||
->withInput();
|
||||
}
|
||||
}
|
||||
}
|
||||
238
app/Http/Controllers/CashfreeController.php
Normal file
238
app/Http/Controllers/CashfreeController.php
Normal file
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Plan;
|
||||
use App\Models\User;
|
||||
use App\Models\PaymentSetting;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Cashfree\Cashfree;
|
||||
use Cashfree\Model\CreateOrderRequest;
|
||||
use Cashfree\Model\CustomerDetails;
|
||||
use Cashfree\Model\OrderMeta;
|
||||
use Cashfree\Api\OrdersApi;
|
||||
|
||||
class CashfreeController extends Controller
|
||||
{
|
||||
/**
|
||||
* Get Cashfree API credentials and configuration
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getCashfreeCredentials()
|
||||
{
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
$mode = $settings['payment_settings']['cashfree_mode'] ?? 'sandbox';
|
||||
$baseUrl = $mode === 'production'
|
||||
? 'https://api.cashfree.com/pg'
|
||||
: 'https://sandbox.cashfree.com/pg';
|
||||
|
||||
return [
|
||||
'app_id' => $settings['payment_settings']['cashfree_public_key'] ?? null,
|
||||
'secret_key' => $settings['payment_settings']['cashfree_secret_key'] ?? null,
|
||||
'mode' => $mode,
|
||||
'base_url' => $baseUrl,
|
||||
'currency' => $settings['general_settings']['defaultCurrency'] ?? 'INR'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Cashfree payment session
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function createPaymentSession(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$pricing = calculatePlanPricing($plan, $validated['coupon_code'] ?? null);
|
||||
|
||||
// Get Cashfree credentials
|
||||
$credentials = $this->getCashfreeCredentials();
|
||||
|
||||
if (!$credentials['app_id'] || !$credentials['secret_key']) {
|
||||
throw new \Exception(__('Cashfree API credentials not found'));
|
||||
}
|
||||
$orderId = 'plan_' . $plan->id . '_' . time() . '_' . uniqid();
|
||||
|
||||
// Configure Cashfree SDK
|
||||
$cashfree = new Cashfree(
|
||||
$credentials['mode'] === 'production' ? 1 : 0,
|
||||
$credentials['app_id'],
|
||||
$credentials['secret_key'],
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
false
|
||||
);
|
||||
|
||||
// Create customer details
|
||||
$customerDetails = new CustomerDetails();
|
||||
$customerDetails->setCustomerId('user_' . auth()->id());
|
||||
$customerDetails->setCustomerName(auth()->user()->name ?? 'Customer');
|
||||
$customerDetails->setCustomerEmail(auth()->user()->email ?? 'customer@example.com');
|
||||
$customerDetails->setCustomerPhone(auth()->user()->phone ?? '9999999999');
|
||||
|
||||
// Create order meta
|
||||
$orderMeta = new OrderMeta();
|
||||
$orderMeta->setReturnUrl(route('dashboard'));
|
||||
$orderMeta->setNotifyUrl(route('cashfree.webhook'));
|
||||
|
||||
// Create order request
|
||||
$orderRequest = new CreateOrderRequest();
|
||||
$orderRequest->setOrderId($orderId);
|
||||
$orderRequest->setOrderAmount($pricing['final_price']);
|
||||
$orderRequest->setOrderCurrency($credentials['currency']);
|
||||
$orderRequest->setCustomerDetails($customerDetails);
|
||||
$orderRequest->setOrderMeta($orderMeta);
|
||||
$orderRequest->setOrderNote('Plan Subscription - ' . $plan->name);
|
||||
$orderRequest->setOrderTags([
|
||||
'plan_id' => (string)$plan->id,
|
||||
'billing_cycle' => $request->billing_cycle,
|
||||
'user_id' => (string)auth()->id()
|
||||
]);
|
||||
|
||||
$apiResponse = $cashfree->PGCreateOrder($orderRequest);
|
||||
|
||||
return response()->json([
|
||||
'payment_session_id' => $apiResponse[0]->getPaymentSessionId(),
|
||||
'order_id' => $orderId,
|
||||
'amount' => $pricing['final_price'],
|
||||
'currency' => $credentials['currency'],
|
||||
'mode' => $credentials['mode']
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Failed to create payment session: ') . $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify Cashfree payment
|
||||
*
|
||||
* @param \Illuminate\\Http\\Request $request
|
||||
* @return \Illuminate\\Http\\JsonResponse
|
||||
*/
|
||||
public function verifyPayment(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request, [
|
||||
'order_id' => 'required|string',
|
||||
'cf_payment_id' => 'nullable|string'
|
||||
]);
|
||||
|
||||
try {
|
||||
$credentials = $this->getCashfreeCredentials();
|
||||
|
||||
if (!$credentials['app_id'] || !$credentials['secret_key']) {
|
||||
throw new \Exception(__('Cashfree API credentials not found'));
|
||||
}
|
||||
|
||||
// Configure Cashfree SDK
|
||||
$cashfree = new Cashfree(
|
||||
$credentials['mode'] === 'production' ? 1 : 0,
|
||||
$credentials['app_id'],
|
||||
$credentials['secret_key'],
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
false
|
||||
);
|
||||
$orderResponse = $cashfree->PGFetchOrder($validated['order_id']);
|
||||
|
||||
if ($orderResponse[0]->getOrderStatus() !== 'PAID') {
|
||||
throw new \Exception(__('Payment not completed successfully'));
|
||||
}
|
||||
|
||||
// Get payment details - response is array with payment objects
|
||||
$paymentsResponse = $cashfree->PGOrderFetchPayments($validated['order_id']);
|
||||
// Response structure: [payments_array, status_code, headers]
|
||||
if (is_array($paymentsResponse) && isset($paymentsResponse[0]) && is_array($paymentsResponse[0])) {
|
||||
$payments = $paymentsResponse[0]; // Direct array of payment objects
|
||||
} else {
|
||||
throw new \Exception(__('Invalid payment response structure'));
|
||||
}
|
||||
|
||||
$successfulPayment = null;
|
||||
foreach ($payments as $payment) {
|
||||
// Payment is already an object from the SDK
|
||||
if ($payment->getPaymentStatus() === 'SUCCESS') {
|
||||
$successfulPayment = $payment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$successfulPayment) {
|
||||
throw new \Exception(__('No successful payment found for this order'));
|
||||
}
|
||||
|
||||
$paymentData = [
|
||||
'user_id' => auth()->id(),
|
||||
'plan_id' => $validated['plan_id'],
|
||||
'billing_cycle' => $validated['billing_cycle'],
|
||||
'payment_method' => 'cashfree',
|
||||
'coupon_code' => $validated['coupon_code'] ?? null,
|
||||
'payment_id' => $successfulPayment->getCfPaymentId(),
|
||||
];
|
||||
|
||||
$planOrder = processPaymentSuccess($paymentData);
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Payment verification failed: ') . $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Cashfree webhook
|
||||
*
|
||||
* @param \Illuminate\\Http\\Request $request
|
||||
* @return \Illuminate\\Http\\JsonResponse
|
||||
*/
|
||||
public function webhook(Request $request)
|
||||
{
|
||||
try {
|
||||
$credentials = $this->getCashfreeCredentials();
|
||||
|
||||
// Verify webhook signature
|
||||
$signature = $request->header('x-webhook-signature');
|
||||
$timestamp = $request->header('x-webhook-timestamp');
|
||||
$rawBody = $request->getContent();
|
||||
|
||||
$expectedSignature = base64_encode(hash_hmac('sha256', $timestamp . $rawBody, $credentials['secret_key'], true));
|
||||
|
||||
if (!hash_equals($expectedSignature, $signature)) {
|
||||
return response()->json(['error' => 'Invalid signature'], 400);
|
||||
}
|
||||
|
||||
$data = $request->json()->all();
|
||||
|
||||
if ($data['type'] === 'PAYMENT_SUCCESS_WEBHOOK') {
|
||||
$paymentData = $data['data'];
|
||||
|
||||
// Extract plan and user info from order tags
|
||||
$orderTags = $paymentData['order']['order_tags'] ?? [];
|
||||
|
||||
if (isset($orderTags['plan_id']) && isset($orderTags['user_id'])) {
|
||||
processPaymentSuccess([
|
||||
'user_id' => $orderTags['user_id'],
|
||||
'plan_id' => $orderTags['plan_id'],
|
||||
'billing_cycle' => $orderTags['billing_cycle'] ?? 'monthly',
|
||||
'payment_method' => 'cashfree',
|
||||
'payment_id' => $paymentData['cf_payment_id'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['status' => 'success']);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Webhook processing failed')], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
111
app/Http/Controllers/ChatGptController.php
Normal file
111
app/Http/Controllers/ChatGptController.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Models\Setting;
|
||||
use OpenAI;
|
||||
|
||||
class ChatGptController extends Controller
|
||||
{
|
||||
public function generate(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'prompt' => 'required|string|max:1000',
|
||||
'language' => 'string|in:en,es,ar,da,de,fr,he,it,ja,nl,pl,pt,pt-BR,ru,tr,zh',
|
||||
'creativity' => 'string|in:low,medium,high',
|
||||
'num_results' => 'integer|min:1|max:5',
|
||||
'max_length' => 'integer|min:1|max:500'
|
||||
]);
|
||||
|
||||
try {
|
||||
$apiKey = Setting::where('key', 'chatgptKey')->value('value');
|
||||
$model = Setting::where('key', 'chatgptModel')->value('value') ?? 'gpt-3.5-turbo';
|
||||
|
||||
if (!$apiKey) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => __('Please set proper configuration for Api Key')
|
||||
], 400);
|
||||
}
|
||||
|
||||
$temperature = (float) $request->input('creativity', 0.7);
|
||||
if (is_string($request->input('creativity'))) {
|
||||
$temperature = match($request->input('creativity')) {
|
||||
'low' => 0.3,
|
||||
'high' => 0.9,
|
||||
default => 0.7
|
||||
};
|
||||
}
|
||||
|
||||
$language = $request->input('language', 'en');
|
||||
$langText = $language !== 'en' ? "Provide response in " . match($language) {
|
||||
'es' => 'Spanish',
|
||||
'ar' => 'Arabic',
|
||||
'da' => 'Danish',
|
||||
'de' => 'German',
|
||||
'fr' => 'French',
|
||||
'he' => 'Hebrew',
|
||||
'it' => 'Italian',
|
||||
'ja' => 'Japanese',
|
||||
'nl' => 'Dutch',
|
||||
'pl' => 'Polish',
|
||||
'pt' => 'Portuguese',
|
||||
'pt-BR' => 'Brazilian Portuguese',
|
||||
'ru' => 'Russian',
|
||||
'tr' => 'Turkish',
|
||||
'zh' => 'Chinese',
|
||||
default => 'English'
|
||||
} . " language.\n\n " : "";
|
||||
|
||||
$maxTokens = (int) $request->input('max_length', 150);
|
||||
$maxResults = (int) $request->input('num_results', 1);
|
||||
|
||||
$client = OpenAI::client($apiKey);
|
||||
|
||||
$response = $client->chat()->create([
|
||||
'model' => $model,
|
||||
'messages' => [
|
||||
[
|
||||
'role' => 'user',
|
||||
'content' => $request->prompt . ' ' . $langText
|
||||
]
|
||||
],
|
||||
'max_tokens' => $maxTokens,
|
||||
'temperature' => $temperature,
|
||||
'n' => $maxResults
|
||||
]);
|
||||
|
||||
if (isset($response->choices)) {
|
||||
$text = '';
|
||||
$counter = 1;
|
||||
|
||||
if (count($response->choices) > 1) {
|
||||
foreach ($response->choices as $choice) {
|
||||
$text .= $counter . '. ' . trim($choice->message->content) . "\r\n\r\n\r\n";
|
||||
$counter++;
|
||||
}
|
||||
} else {
|
||||
$text = $response->choices[0]->message->content;
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'content' => trim($text)
|
||||
]);
|
||||
} else {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => __('Text was not generated, please try again')
|
||||
], 500);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Error: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
163
app/Http/Controllers/ChecklistItemController.php
Normal file
163
app/Http/Controllers/ChecklistItemController.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\ChecklistItem;
|
||||
use App\Models\OnboardingChecklist;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class ChecklistItemController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-checklist-items')) {
|
||||
$query = ChecklistItem::with(['checklist'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-checklist-items')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-checklist-items')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('task_name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('category') && !empty($request->category) && $request->category !== 'all') {
|
||||
$query->where('category', $request->category);
|
||||
}
|
||||
|
||||
if ($request->has('checklist_id') && !empty($request->checklist_id) && $request->checklist_id !== 'all') {
|
||||
$query->where('checklist_id', $request->checklist_id);
|
||||
}
|
||||
|
||||
if ($request->has('is_required') && $request->is_required !== 'all') {
|
||||
$query->where('is_required', $request->is_required === 'true');
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['task_name'];
|
||||
if ($sortField && in_array($sortField, $allowedSortFields)) {
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
$checklistItems = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$checklists = OnboardingChecklist::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/recruitment/checklist-items/index', [
|
||||
'checklistItems' => $checklistItems,
|
||||
'checklists' => $checklists,
|
||||
'filters' => $request->all(['search', 'category', 'checklist_id', 'is_required', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'checklist_id' => 'required|exists:onboarding_checklists,id',
|
||||
'task_name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'category' => 'required|in:Documentation,IT Setup,Training,HR,Facilities,Other',
|
||||
'assigned_to_role' => 'nullable|string|max:255',
|
||||
'due_day' => 'nullable|integer|min:1',
|
||||
'is_required' => 'boolean',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
ChecklistItem::create([
|
||||
'checklist_id' => $request->checklist_id,
|
||||
'task_name' => $request->task_name,
|
||||
'description' => $request->description,
|
||||
'category' => $request->category,
|
||||
'assigned_to_role' => $request->assigned_to_role,
|
||||
'due_day' => $request->due_day ?? 0,
|
||||
'is_required' => $request->boolean('is_required'),
|
||||
'status' => $request->status ?? 'active',
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Checklist item created successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, ChecklistItem $checklistItem)
|
||||
{
|
||||
if (!in_array($checklistItem->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this item'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'checklist_id' => 'required|exists:onboarding_checklists,id',
|
||||
'task_name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'category' => 'required|in:Documentation,IT Setup,Training,HR,Facilities,Other',
|
||||
'assigned_to_role' => 'nullable|string|max:255',
|
||||
'due_day' => 'nullable|integer|min:1',
|
||||
'is_required' => 'boolean',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$checklistItem->update([
|
||||
'checklist_id' => $request->checklist_id,
|
||||
'task_name' => $request->task_name,
|
||||
'description' => $request->description,
|
||||
'category' => $request->category,
|
||||
'assigned_to_role' => $request->assigned_to_role,
|
||||
'due_day' => $request->due_day ?? 0,
|
||||
'is_required' => $request->boolean('is_required'),
|
||||
'status' => $request->status ?? 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Checklist item updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(ChecklistItem $checklistItem)
|
||||
{
|
||||
if (!in_array($checklistItem->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this item'));
|
||||
}
|
||||
|
||||
$checklistItem->delete();
|
||||
return redirect()->back()->with('success', __('Checklist item deleted successfully'));
|
||||
}
|
||||
|
||||
public function toggleStatus(ChecklistItem $checklistItem)
|
||||
{
|
||||
if (!in_array($checklistItem->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this item'));
|
||||
}
|
||||
|
||||
$checklistItem->update([
|
||||
'status' => $checklistItem->status === 'active' ? 'inactive' : 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Item status updated successfully'));
|
||||
}
|
||||
}
|
||||
136
app/Http/Controllers/CinetPayPaymentController.php
Normal file
136
app/Http/Controllers/CinetPayPaymentController.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Plan;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CinetPayPaymentController extends Controller
|
||||
{
|
||||
public function processPayment(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request, [
|
||||
'cpm_trans_id' => 'required|string',
|
||||
'cpm_result' => 'required|string',
|
||||
]);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!isset($settings['payment_settings']['cinetpay_site_id'])) {
|
||||
return back()->withErrors(['error' => __('CinetPay not configured')]);
|
||||
}
|
||||
|
||||
if ($validated['cpm_result'] === '00') { // Success status
|
||||
processPaymentSuccess([
|
||||
'user_id' => auth()->id(),
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => $validated['billing_cycle'],
|
||||
'payment_method' => 'cinetpay',
|
||||
'coupon_code' => $validated['coupon_code'] ?? null,
|
||||
'payment_id' => $validated['cpm_trans_id'],
|
||||
]);
|
||||
|
||||
return back()->with('success', __('Payment successful and plan activated'));
|
||||
}
|
||||
|
||||
return back()->withErrors(['error' => __('Payment failed or cancelled')]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return handlePaymentError($e, 'cinetpay');
|
||||
}
|
||||
}
|
||||
|
||||
public function createPayment(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$pricing = calculatePlanPricing($plan, $validated['coupon_code'] ?? null);
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!isset($settings['payment_settings']['cinetpay_site_id'])) {
|
||||
return response()->json(['error' => __('CinetPay not configured')], 400);
|
||||
}
|
||||
|
||||
$user = auth()->user();
|
||||
$transactionId = 'plan_' . $plan->id . '_' . $user->id . '_' . time();
|
||||
|
||||
$paymentData = [
|
||||
'cpm_site_id' => $settings['payment_settings']['cinetpay_site_id'],
|
||||
'cpm_trans_id' => $transactionId,
|
||||
'cpm_amount' => $pricing['final_price'],
|
||||
'cpm_currency' => 'XOF', // West African CFA franc
|
||||
'cpm_designation' => $plan->name,
|
||||
'cpm_custom' => json_encode([
|
||||
'plan_id' => $plan->id,
|
||||
'user_id' => $user->id,
|
||||
'billing_cycle' => $validated['billing_cycle'],
|
||||
]),
|
||||
'cpm_page_action' => 'PAYMENT',
|
||||
'cpm_version' => 'V2',
|
||||
'cpm_language' => 'fr',
|
||||
'cpm_return_url' => route('cinetpay.success'),
|
||||
'cpm_notify_url' => route('cinetpay.callback'),
|
||||
'cpm_error_url' => route('plans.index'),
|
||||
];
|
||||
|
||||
$baseUrl = 'https://www.cinetpay.com/payment/';
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'payment_url' => $baseUrl,
|
||||
'payment_data' => $paymentData,
|
||||
'transaction_id' => $transactionId
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Payment creation failed')], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function success(Request $request)
|
||||
{
|
||||
return redirect()->route('plans.index')->with('success', __('Payment completed successfully'));
|
||||
}
|
||||
|
||||
public function callback(Request $request)
|
||||
{
|
||||
try {
|
||||
$transactionId = $request->input('cpm_trans_id');
|
||||
$result = $request->input('cpm_result');
|
||||
|
||||
if ($transactionId && $result === '00') {
|
||||
$parts = explode('_', $transactionId);
|
||||
|
||||
if (count($parts) >= 3) {
|
||||
$planId = $parts[1];
|
||||
$userId = $parts[2];
|
||||
|
||||
$plan = Plan::find($planId);
|
||||
$user = User::find($userId);
|
||||
|
||||
if ($plan && $user) {
|
||||
$customData = json_decode($request->input('cpm_custom'), true);
|
||||
|
||||
processPaymentSuccess([
|
||||
'user_id' => $user->id,
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => $customData['billing_cycle'] ?? 'monthly',
|
||||
'payment_method' => 'cinetpay',
|
||||
'payment_id' => $transactionId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['status' => 'success']);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Callback processing failed')], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
129
app/Http/Controllers/CoinGatePaymentController.php
Normal file
129
app/Http/Controllers/CoinGatePaymentController.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Plan;
|
||||
use App\Models\PlanOrder;
|
||||
use App\Models\PaymentSetting;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Libraries\Coingate\Coingate;
|
||||
use CoinGate\Client;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class CoinGatePaymentController extends Controller
|
||||
{
|
||||
public function processPayment(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'plan_id' => 'required|exists:plans,id',
|
||||
'billing_cycle' => 'required|in:monthly,yearly',
|
||||
'coupon_code' => 'nullable|string'
|
||||
]);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$user = auth()->user();
|
||||
|
||||
// Get payment settings exactly like reference project
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
|
||||
if (!$settings['payment_settings']['is_coingate_enabled'] || !$settings['payment_settings']['coingate_api_token']) {
|
||||
return redirect()->route('plans.index')->with('error', __('CoinGate payment is not available'));
|
||||
}
|
||||
|
||||
if (!isset($settings['payment_settings']['coingate_api_token']) || empty($settings['payment_settings']['coingate_api_token'])) {
|
||||
return redirect()->route('plans.index')->with('error', __('CoinGate API token not configured'));
|
||||
}
|
||||
|
||||
// Calculate price
|
||||
$price = $validated['billing_cycle'] === 'yearly' ? $plan->yearly_price : $plan->price;
|
||||
|
||||
// Create plan order
|
||||
$orderId = time();
|
||||
$planOrder = PlanOrder::create([
|
||||
'user_id' => $user->id,
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => $validated['billing_cycle'],
|
||||
'payment_method' => 'coingate',
|
||||
'coupon_code' => $validated['coupon_code'],
|
||||
'payment_id' => $orderId,
|
||||
'original_price' => $price,
|
||||
'final_price' => $price,
|
||||
'status' => 'pending'
|
||||
]);
|
||||
|
||||
// Use official CoinGate package
|
||||
$client = new Client(
|
||||
$settings['payment_settings']['coingate_api_token'],
|
||||
($settings['payment_settings']['coingate_mode'] ?? 'sandbox') === 'sandbox'
|
||||
);
|
||||
|
||||
$orderParams = [
|
||||
'order_id' => $orderId,
|
||||
'price_amount' => $price,
|
||||
'price_currency' => $settings['general_settings']['defaultCurrency'] ?? 'USD',
|
||||
'receive_currency' => $settings['general_settings']['defaultCurrency'] ?? 'USD',
|
||||
'callback_url' => route('coingate.callback'),
|
||||
'cancel_url' => route('plans.index'),
|
||||
'success_url' => route('coingate.callback'),
|
||||
'title' => 'Plan #' . $orderId,
|
||||
];
|
||||
|
||||
$orderResponse = $client->order->create($orderParams);
|
||||
|
||||
if ($orderResponse && isset($orderResponse->payment_url)) {
|
||||
// Store in session like reference project
|
||||
session(['coingate_data' => $orderResponse]);
|
||||
|
||||
// Store gateway response
|
||||
$planOrder->payment_id = $orderResponse->order_id;
|
||||
$planOrder->save();
|
||||
|
||||
return redirect($orderResponse->payment_url);
|
||||
} else {
|
||||
$planOrder->update(['status' => 'cancelled']);
|
||||
return redirect()->route('plans.index')->with('error', __('Payment initialization failed'));
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->route('plans.index')->with('error', __('Payment failed: ') . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function callback(Request $request)
|
||||
{
|
||||
try {
|
||||
$user = auth()->user();
|
||||
$coingateData = session('coingate_data');
|
||||
|
||||
if (!$coingateData) {
|
||||
return redirect()->route('plans.index')->with('error', __('Payment session expired'));
|
||||
}
|
||||
|
||||
$orderId = is_object($coingateData) ? $coingateData->order_id : $coingateData['order_id'];
|
||||
$planOrder = PlanOrder::where('payment_id', $orderId)->first();
|
||||
|
||||
if (!$planOrder) {
|
||||
return redirect()->route('plans.index')->with('error', 'Order not found');
|
||||
}
|
||||
|
||||
// Mark as successful and activate subscription
|
||||
$planOrder->update([
|
||||
'status' => 'approved',
|
||||
'processed_at' => now()
|
||||
]);
|
||||
|
||||
$planOrder->activateSubscription();
|
||||
|
||||
// Clear session
|
||||
session()->forget('coingate_data');
|
||||
|
||||
return redirect()->route('plans.index')->with('success', __('Plan activated successfully!'));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('CoinGate callback error: ' . $e->getMessage());
|
||||
return redirect()->route('plans.index')->with('error', __('Payment processing failed'));
|
||||
}
|
||||
}
|
||||
}
|
||||
325
app/Http/Controllers/CompanyController.php
Normal file
325
app/Http/Controllers/CompanyController.php
Normal file
@@ -0,0 +1,325 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Plan;
|
||||
use App\Models\PlanOrder;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class CompanyController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = User::query()
|
||||
->where('type', 'company')
|
||||
->with('plan');
|
||||
|
||||
// Apply search filter
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', "%{$request->search}%")
|
||||
->orWhere('email', 'like', "%{$request->search}%");
|
||||
});
|
||||
}
|
||||
|
||||
// Apply status filter
|
||||
if ($request->has('status') && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Apply date filters
|
||||
if ($request->has('start_date') && !empty($request->start_date)) {
|
||||
$query->whereDate('created_at', '>=', $request->start_date);
|
||||
}
|
||||
|
||||
if ($request->has('end_date') && !empty($request->end_date)) {
|
||||
$query->whereDate('created_at', '<=', $request->end_date);
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
$sortField = $request->input('sort_field', 'created_at');
|
||||
$sortDirection = $request->input('sort_direction', 'desc');
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
// Get paginated results
|
||||
$perPage = $request->input('per_page', 10);
|
||||
$companies = $query->paginate($perPage)->withQueryString();
|
||||
|
||||
// Transform data for frontend
|
||||
$companies->getCollection()->transform(function ($company) {
|
||||
return [
|
||||
'id' => $company->id,
|
||||
'avatar' => check_file($company->avatar) ? get_file($company->avatar) : get_file('avatars/avatar.png'),
|
||||
'name' => $company->name,
|
||||
'email' => $company->email,
|
||||
'status' => $company->status,
|
||||
'created_at' => $company->created_at,
|
||||
'plan_name' => $company->plan ? $company->plan->name : __('No Plan'),
|
||||
'plan_expiry_date' => $company->plan_expire_date,
|
||||
];
|
||||
});
|
||||
|
||||
// Get plans for dropdown
|
||||
$plans = Plan::all(['id', 'name']);
|
||||
|
||||
return Inertia::render('companies/index', [
|
||||
'companies' => $companies,
|
||||
'plans' => $plans,
|
||||
'filters' => $request->only(['search', 'status', 'start_date', 'end_date', 'sort_field', 'sort_direction', 'per_page', 'view']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|string|email|max:255|unique:users',
|
||||
'password' => 'nullable|string|min:8',
|
||||
'status' => 'required|in:active,inactive',
|
||||
]);
|
||||
|
||||
$company = new User;
|
||||
$company->name = $validated['name'];
|
||||
$company->email = $validated['email'];
|
||||
|
||||
// Only set password if provided
|
||||
if (isset($validated['password'])) {
|
||||
$company->password = Hash::make($validated['password']);
|
||||
}
|
||||
|
||||
$company->type = 'company';
|
||||
$company->status = $validated['status'];
|
||||
$company->created_by = creatorId() ?? 1;
|
||||
|
||||
// Set company language same as creator (superadmin)
|
||||
$creator = auth()->user();
|
||||
$superAdminSettings = settings();
|
||||
$userLang = isset($superAdminSettings['defaultLanguage']) ? $superAdminSettings['defaultLanguage'] : $creator->lang;
|
||||
$company->lang = $userLang;
|
||||
|
||||
// Assign default plan
|
||||
$defaultPlan = Plan::where('is_default', true)->first();
|
||||
if ($defaultPlan) {
|
||||
$company->plan_id = $defaultPlan->id;
|
||||
|
||||
// Set plan expiry date based on plan duration
|
||||
if ($defaultPlan->duration === 'yearly') {
|
||||
$company->plan_expire_date = now()->addYear();
|
||||
} else {
|
||||
$company->plan_expire_date = now()->addMonth();
|
||||
}
|
||||
|
||||
// Set plan is active
|
||||
$company->plan_is_active = 1;
|
||||
}
|
||||
|
||||
$company->save();
|
||||
|
||||
// Assign role and settings to the user
|
||||
defaultRoleAndSetting($company);
|
||||
|
||||
// Trigger email notification
|
||||
event(new \App\Events\UserCreated($company, $validated['password'] ?? ''));
|
||||
|
||||
// Check for email errors
|
||||
if (session()->has('email_error')) {
|
||||
return redirect()->back()->with('warning', __('Company created successfully, but welcome email failed: ') . session('email_error'));
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('Company created successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, User $company)
|
||||
{
|
||||
// Ensure this is a company type user
|
||||
if ($company->type !== 'company') {
|
||||
return redirect()->back()->with('error', __('Invalid company record'));
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|string|email|max:255|unique:users,email,' . $company->id,
|
||||
]);
|
||||
|
||||
$company->name = $validated['name'];
|
||||
$company->email = $validated['email'];
|
||||
|
||||
$company->save();
|
||||
|
||||
return redirect()->back()->with('success', __('Company updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(User $company)
|
||||
{
|
||||
// Ensure this is a company type user
|
||||
if ($company->type !== 'company') {
|
||||
return redirect()->back()->with('error', __('Invalid company record'));
|
||||
}
|
||||
|
||||
$company->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('Company deleted successfully'));
|
||||
}
|
||||
|
||||
public function resetPassword(Request $request, User $company)
|
||||
{
|
||||
// Ensure this is a company type user
|
||||
if ($company->type !== 'company') {
|
||||
return redirect()->back()->with('error', __('Invalid company record'));
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'password' => ['required', 'string', 'min:8'],
|
||||
]);
|
||||
|
||||
$company->password = Hash::make($validated['password']);
|
||||
$company->save();
|
||||
|
||||
return redirect()->back()->with('success', __('Password reset successfully'));
|
||||
}
|
||||
|
||||
public function toggleStatus(User $company)
|
||||
{
|
||||
// Ensure this is a company type user
|
||||
if ($company->type !== 'company') {
|
||||
return redirect()->back()->with('error', __('Invalid company record'));
|
||||
}
|
||||
|
||||
$company->status = $company->status === 'active' ? 'inactive' : 'active';
|
||||
$company->save();
|
||||
|
||||
return redirect()->back()->with('success', __('Company status updated successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available plans for upgrade
|
||||
*/
|
||||
public function getPlans(User $company)
|
||||
{
|
||||
// Ensure this is a company type user
|
||||
if ($company->type !== 'company') {
|
||||
return response()->json(['error' => __('Invalid company record')], 400);
|
||||
}
|
||||
|
||||
$plans = Plan::where('is_plan_enable', 'on')->get();
|
||||
|
||||
$formattedPlans = [];
|
||||
|
||||
foreach ($plans as $plan) {
|
||||
// Format features using same logic as PlanController
|
||||
$features = [];
|
||||
if ($plan->features) {
|
||||
$enabledFeatures = $plan->getEnabledFeatures();
|
||||
$featureLabels = [
|
||||
'ai_integration' => __('AI Integration'),
|
||||
'password_protection' => __('Password Protection'),
|
||||
];
|
||||
foreach ($enabledFeatures as $feature) {
|
||||
if (isset($featureLabels[$feature])) {
|
||||
$features[] = $featureLabels[$feature];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($plan->enable_chatgpt === 'on') {
|
||||
$features[] = __('AI Integration');
|
||||
}
|
||||
}
|
||||
|
||||
// Monthly plan
|
||||
$formattedPlans[] = [
|
||||
'id' => $plan->id,
|
||||
'name' => $plan->name,
|
||||
'price' => $plan->price,
|
||||
'duration' => 'Monthly',
|
||||
'description' => $plan->description,
|
||||
'features' => $features,
|
||||
'max_employees' => $plan->max_employees,
|
||||
'max_users' => $plan->max_users,
|
||||
'storage_limit' => $plan->storage_limit . ' ' . __('GB'),
|
||||
'is_current' => $company->plan_id === $plan->id,
|
||||
'is_default' => $plan->is_default,
|
||||
];
|
||||
|
||||
// Yearly plan (create a separate entry)
|
||||
$yearlyPrice = $plan->yearly_price ?? ($plan->price * 12 * 0.8);
|
||||
$formattedPlans[] = [
|
||||
'id' => $plan->id,
|
||||
'name' => $plan->name,
|
||||
'price' => $yearlyPrice,
|
||||
'duration' => 'Yearly',
|
||||
'description' => $plan->description,
|
||||
'features' => $features,
|
||||
'max_employees' => $plan->max_employees,
|
||||
'max_users' => $plan->max_users,
|
||||
'storage_limit' => $plan->storage_limit . ' ' . __('GB'),
|
||||
'is_current' => $company->plan_id === $plan->id,
|
||||
'is_default' => $plan->is_default,
|
||||
];
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'plans' => $formattedPlans,
|
||||
'company' => [
|
||||
'id' => $company->id,
|
||||
'name' => $company->name,
|
||||
'current_plan_id' => $company->plan_id,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function upgradePlan(Request $request, User $company)
|
||||
{
|
||||
// Ensure this is a company type user
|
||||
if ($company->type !== 'company') {
|
||||
return back()->with('error', __('Invalid company record'));
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'plan_id' => 'required|exists:plans,id',
|
||||
'duration' => 'required|in:yearly,monthly',
|
||||
]);
|
||||
|
||||
$plan = Plan::find($validated['plan_id']);
|
||||
if (!$plan) {
|
||||
return back()->with('error', __('Plan not found'));
|
||||
}
|
||||
|
||||
$isYearly = $validated['duration'] === 'yearly';
|
||||
|
||||
// Create plan order entry for tracking
|
||||
$planOrder = new PlanOrder;
|
||||
$planOrder->user_id = $company->id;
|
||||
$planOrder->plan_id = $plan->id;
|
||||
$planOrder->billing_cycle = $request->duration === 'yearly' ? 'yearly' : 'monthly';
|
||||
$planOrder->original_price = $request->duration === 'yearly' ? ($plan->yearly_price ?? 0) : $plan->price;
|
||||
$planOrder->discount_amount = 0;
|
||||
$planOrder->final_price = $planOrder->original_price;
|
||||
$planOrder->payment_method = 'admin_upgrade';
|
||||
$planOrder->status = 'approved';
|
||||
$planOrder->ordered_at = now();
|
||||
$planOrder->processed_at = now();
|
||||
$planOrder->processed_by = auth()->id();
|
||||
$planOrder->notes = 'Plan upgraded by super admin';
|
||||
$planOrder->save();
|
||||
// Update company plan
|
||||
$company->plan_id = $plan->id;
|
||||
|
||||
// Set plan expiry date based on plan duration
|
||||
if ($request->duration === 'yearly') {
|
||||
$company->plan_expire_date = now()->addYear();
|
||||
} else {
|
||||
$company->plan_expire_date = now()->addMonth();
|
||||
}
|
||||
|
||||
// Set plan is active
|
||||
$company->plan_is_active = 1;
|
||||
|
||||
$company->save();
|
||||
|
||||
return back()->with('success', __('Plan upgraded successfully'));
|
||||
}
|
||||
}
|
||||
523
app/Http/Controllers/ComplaintController.php
Normal file
523
app/Http/Controllers/ComplaintController.php
Normal file
@@ -0,0 +1,523 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Complaint;
|
||||
use App\Models\Employee;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class ComplaintController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-complaints')) {
|
||||
$query = Complaint::with(['employee', 'againstEmployee', 'assignedUser'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-complaints')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-complaints')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('against_employee_id', Auth::id())->orWhere('assigned_to', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('subject', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%')
|
||||
->orWhere('complaint_type', 'like', '%' . $request->search . '%')
|
||||
->orWhereHas('employee', function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('employee_id', 'like', '%' . $request->search . '%');
|
||||
})
|
||||
->orWhereHas('againstEmployee', function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('employee_id', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle employee filter
|
||||
if ($request->has('employee_id') && !empty($request->employee_id)) {
|
||||
$query->where('employee_id', $request->employee_id);
|
||||
}
|
||||
|
||||
// Handle against employee filter
|
||||
if ($request->has('against_employee_id') && !empty($request->against_employee_id)) {
|
||||
$query->where('against_employee_id', $request->against_employee_id);
|
||||
}
|
||||
|
||||
// Handle complaint type filter
|
||||
if ($request->has('complaint_type') && !empty($request->complaint_type)) {
|
||||
$query->where('complaint_type', $request->complaint_type);
|
||||
}
|
||||
|
||||
// Handle status filter
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle date range filter
|
||||
if ($request->has('date_from') && !empty($request->date_from)) {
|
||||
$query->whereDate('complaint_date', '>=', $request->date_from);
|
||||
}
|
||||
if ($request->has('date_to') && !empty($request->date_to)) {
|
||||
$query->whereDate('complaint_date', '<=', $request->date_to);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$allowedSortFields = ['id', 'employee_id', 'against_employee_id', 'complaint_type', 'subject', 'complaint_date', 'status', 'created_at'];
|
||||
if ($request->has('sort_field') && !empty($request->sort_field) && in_array($request->sort_field, $allowedSortFields)) {
|
||||
$sortDirection = in_array($request->sort_direction, ['asc', 'desc']) ? $request->sort_direction : 'asc';
|
||||
$query->orderBy($request->sort_field, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
$complaints = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$complaints->getCollection()->transform(function ($complaint) {
|
||||
if ($complaint->employee) {
|
||||
$rawAvatar = $complaint->employee->getRawOriginal('avatar');
|
||||
$complaint->employee->avatar = check_file($rawAvatar)
|
||||
? get_file($rawAvatar)
|
||||
: get_file('avatars/avatar.png');
|
||||
}
|
||||
if ($complaint->againstEmployee) {
|
||||
$rawAvatar = $complaint->againstEmployee->getRawOriginal('avatar');
|
||||
$complaint->againstEmployee->avatar = check_file($rawAvatar)
|
||||
? get_file($rawAvatar)
|
||||
: get_file('avatars/avatar.png');
|
||||
}
|
||||
return $complaint;
|
||||
});
|
||||
|
||||
// Get employees for complainant dropdown
|
||||
$complainants = User::with('employee')
|
||||
->where('type', 'employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->get()
|
||||
->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'employee_id' => $user->employee->employee_id ?? $user->id
|
||||
];
|
||||
});
|
||||
|
||||
// Get employees for against dropdown
|
||||
$againstEmployees = User::emp()->with('employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->get()
|
||||
->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'employee_id' => $user->employee->employee_id ?? $user->id,
|
||||
'type' => $user->type,
|
||||
];
|
||||
});
|
||||
|
||||
// Get HR personnel for assignment dropdown
|
||||
$hrPersonnel = User::whereIn('created_by', getCompanyAndUsersId())
|
||||
->whereIn('type', ['hr', 'manager', 'company']) // <-- Add this line
|
||||
->select('id', 'name', 'type')
|
||||
->get();
|
||||
|
||||
// Get complaint types for filter dropdown
|
||||
$complaintTypes = Complaint::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('complaint_type')
|
||||
->distinct()
|
||||
->pluck('complaint_type')
|
||||
->toArray();
|
||||
|
||||
return Inertia::render('hr/complaints/index', [
|
||||
'complaints' => $complaints,
|
||||
'complainants' => $this->getFilteredEmployees(),
|
||||
'againstEmployees' => $againstEmployees,
|
||||
'hrPersonnel' => $hrPersonnel,
|
||||
'complaintTypes' => $complaintTypes,
|
||||
'filters' => $request->all(['search', 'employee_id', 'against_employee_id', 'complaint_type', 'status', 'date_from', 'date_to', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
private function getFilteredEmployees()
|
||||
{
|
||||
// Get employees for filter dropdown (compatible with getFilteredEmployees logic)
|
||||
$employeeQuery = Employee::whereIn('created_by', getCompanyAndUsersId());
|
||||
|
||||
if (Auth::user()->can('manage-own-complaints') && !Auth::user()->can('manage-any-complaints')) {
|
||||
$employeeQuery->where(function ($q) {
|
||||
$q->where('created_by', Auth::id())->orWhere('user_id', Auth::id());
|
||||
});
|
||||
}
|
||||
|
||||
$employees = User::emp()
|
||||
->with('employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->whereIn('id', $employeeQuery->pluck('user_id'))
|
||||
->select('id', 'name', 'type')
|
||||
->get()
|
||||
->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'employee_id' => $user->employee->employee_id ?? '',
|
||||
'type' => $user->type,
|
||||
];
|
||||
});
|
||||
return $employees;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('create-complaints')) {
|
||||
$validator = Validator::make($request->all(), [
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'against_employee_id' => 'nullable|exists:users,id|different:employee_id',
|
||||
'complaint_type' => 'required|string|max:255',
|
||||
'subject' => 'required|string|max:255',
|
||||
'complaint_date' => 'required|date',
|
||||
'description' => 'nullable|string',
|
||||
'documents' => 'nullable|string',
|
||||
'is_anonymous' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if employee belongs to current company
|
||||
$employee = User::find($request->employee_id);
|
||||
if (!$employee || !in_array($employee->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('Invalid employee selected'));
|
||||
}
|
||||
|
||||
// Check if against_employee belongs to current company
|
||||
if ($request->against_employee_id) {
|
||||
$againstEmployee = User::find($request->against_employee_id);
|
||||
if (!$againstEmployee || !in_array($againstEmployee->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('Invalid employee selected for complaint against'));
|
||||
}
|
||||
}
|
||||
|
||||
$complaintData = [
|
||||
'employee_id' => $request->employee_id,
|
||||
'against_employee_id' => $request->against_employee_id,
|
||||
'complaint_type' => $request->complaint_type,
|
||||
'subject' => $request->subject,
|
||||
'complaint_date' => $request->complaint_date,
|
||||
'description' => $request->description,
|
||||
'status' => 'submitted',
|
||||
'is_anonymous' => $request->is_anonymous ?? false,
|
||||
'created_by' => creatorId(),
|
||||
];
|
||||
|
||||
// Handle document from media library
|
||||
if ($request->has('documents')) {
|
||||
$complaintData['documents'] = $request->documents;
|
||||
}
|
||||
|
||||
Complaint::create($complaintData);
|
||||
|
||||
return redirect()->back()->with('success', __('Complaint created successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, Complaint $complaint)
|
||||
{
|
||||
if (Auth::user()->can('edit-complaints')) {
|
||||
// Check if complaint belongs to current company
|
||||
if (!in_array($complaint->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this complaint'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'against_employee_id' => 'nullable|exists:users,id|different:employee_id',
|
||||
'complaint_type' => 'required|string|max:255',
|
||||
'subject' => 'required|string|max:255',
|
||||
'complaint_date' => 'required|date',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:submitted,under investigation,resolved,dismissed',
|
||||
'documents' => 'nullable|string',
|
||||
'is_anonymous' => 'nullable|boolean',
|
||||
'assigned_to' => 'nullable|exists:users,id',
|
||||
'resolution_deadline' => 'nullable|date|after_or_equal:complaint_date',
|
||||
'investigation_notes' => 'nullable|string',
|
||||
'resolution_action' => 'nullable|string',
|
||||
'resolution_date' => 'nullable|date|after_or_equal:complaint_date',
|
||||
'follow_up_action' => 'nullable|string',
|
||||
'follow_up_date' => 'nullable|date|after_or_equal:resolution_date',
|
||||
'feedback' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if employee belongs to current company
|
||||
$employee = User::find($request->employee_id);
|
||||
if (!$employee || !in_array($employee->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('Invalid employee selected'));
|
||||
}
|
||||
|
||||
// Check if against_employee belongs to current company
|
||||
if ($request->against_employee_id) {
|
||||
$againstEmployee = User::find($request->against_employee_id);
|
||||
if (!$againstEmployee || !in_array($againstEmployee->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('Invalid employee selected for complaint against'));
|
||||
}
|
||||
}
|
||||
|
||||
// Check if assigned user belongs to current company
|
||||
if ($request->assigned_to) {
|
||||
$assignedUser = User::find($request->assigned_to);
|
||||
if (!$assignedUser || (!in_array($assignedUser->created_by, getCompanyAndUsersId()) && !in_array($assignedUser->id, getCompanyAndUsersId()))) {
|
||||
return redirect()->back()->with('error', __('Invalid user selected for assignment'));
|
||||
}
|
||||
}
|
||||
|
||||
$complaintData = [
|
||||
'employee_id' => $request->employee_id,
|
||||
'against_employee_id' => $request->against_employee_id,
|
||||
'complaint_type' => $request->complaint_type,
|
||||
'subject' => $request->subject,
|
||||
'complaint_date' => $request->complaint_date,
|
||||
'description' => $request->description,
|
||||
'status' => $request->status ?? $complaint->status,
|
||||
'is_anonymous' => $request->is_anonymous ?? $complaint->is_anonymous,
|
||||
'assigned_to' => $request->assigned_to,
|
||||
'resolution_deadline' => $request->resolution_deadline,
|
||||
'investigation_notes' => $request->investigation_notes,
|
||||
'resolution_action' => $request->resolution_action,
|
||||
'resolution_date' => $request->resolution_date,
|
||||
'follow_up_action' => $request->follow_up_action,
|
||||
'follow_up_date' => $request->follow_up_date,
|
||||
'feedback' => $request->feedback,
|
||||
];
|
||||
|
||||
// Handle document from media library
|
||||
if ($request->has('documents')) {
|
||||
$complaintData['documents'] = $request->documents;
|
||||
}
|
||||
|
||||
$complaint->update($complaintData);
|
||||
|
||||
return redirect()->back()->with('success', __('Complaint updated successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(Complaint $complaint)
|
||||
{
|
||||
if (Auth::user()->can('delete-complaints')) {
|
||||
// Check if complaint belongs to current company
|
||||
if (!in_array($complaint->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this complaint'));
|
||||
}
|
||||
|
||||
$complaint->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('Complaint deleted successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the status of the complaint.
|
||||
*/
|
||||
public function changeStatus(Request $request, Complaint $complaint)
|
||||
{
|
||||
if (Auth::user()->can('edit-complaints')) {
|
||||
// Check if complaint belongs to current company
|
||||
if (!in_array($complaint->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this complaint'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'status' => 'required|string|in:submitted,under investigation,resolved,dismissed',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$complaint->update([
|
||||
'status' => $request->status,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Complaint status updated successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign the complaint to an HR personnel.
|
||||
*/
|
||||
public function assignComplaint(Request $request, Complaint $complaint)
|
||||
{
|
||||
if (Auth::user()->can('assign-complaints')) {
|
||||
// Check if complaint belongs to current company
|
||||
if (!in_array($complaint->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this complaint'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'assigned_to' => 'required|exists:users,id',
|
||||
'resolution_deadline' => 'nullable|date|after_or_equal:today',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if assigned user belongs to current company
|
||||
$assignedUser = User::find($request->assigned_to);
|
||||
if (!$assignedUser || (!in_array($assignedUser->created_by, getCompanyAndUsersId()) && !in_array($assignedUser->id, getCompanyAndUsersId()))) {
|
||||
return redirect()->back()->with('error', __('Invalid user selected for assignment'));
|
||||
}
|
||||
|
||||
$updateData = [
|
||||
'assigned_to' => $request->assigned_to,
|
||||
'resolution_deadline' => $request->resolution_deadline,
|
||||
];
|
||||
|
||||
// If complaint is in submitted status, change to under investigation
|
||||
if ($complaint->status === 'submitted') {
|
||||
$updateData['status'] = 'under investigation';
|
||||
}
|
||||
|
||||
$complaint->update($updateData);
|
||||
|
||||
return redirect()->back()->with('success', __('Complaint assigned successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the complaint.
|
||||
*/
|
||||
public function resolveComplaint(Request $request, Complaint $complaint)
|
||||
{
|
||||
if (Auth::user()->can('resolve-complaints')) {
|
||||
// Check if complaint belongs to current company
|
||||
if (!in_array($complaint->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this complaint'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'status' => 'required|string|in:resolved,dismissed',
|
||||
'investigation_notes' => 'required|string',
|
||||
'resolution_action' => 'required|string',
|
||||
'resolution_date' => 'required|date',
|
||||
'follow_up_action' => 'nullable|string',
|
||||
'follow_up_date' => 'nullable|date|after_or_equal:resolution_date',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$complaint->update([
|
||||
'status' => $request->status,
|
||||
'investigation_notes' => $request->investigation_notes,
|
||||
'resolution_action' => $request->resolution_action,
|
||||
'resolution_date' => $request->resolution_date,
|
||||
'follow_up_action' => $request->follow_up_action,
|
||||
'follow_up_date' => $request->follow_up_date,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Complaint resolved successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update follow-up information.
|
||||
*/
|
||||
public function updateFollowUp(Request $request, Complaint $complaint)
|
||||
{
|
||||
if (Auth::user()->can('resolve-complaints')) {
|
||||
// Check if complaint belongs to current company
|
||||
if (!in_array($complaint->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this complaint'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'follow_up_action' => 'required|string',
|
||||
'follow_up_date' => 'required|date',
|
||||
'feedback' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$complaint->update([
|
||||
'follow_up_action' => $request->follow_up_action,
|
||||
'follow_up_date' => $request->follow_up_date,
|
||||
'feedback' => $request->feedback,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Follow-up information updated successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download document file.
|
||||
*/
|
||||
public function downloadDocument(Complaint $complaint)
|
||||
{
|
||||
if (Auth::user()->can('view-complaints')) {
|
||||
// Check if complaint belongs to current company
|
||||
if (!in_array($complaint->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to access this document'));
|
||||
}
|
||||
|
||||
if (!$complaint->documents) {
|
||||
return redirect()->back()->with('error', __('Document file not found'));
|
||||
}
|
||||
|
||||
$filePath = getStorageFilePath($complaint->documents);
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
return redirect()->back()->with('error', __('Document file not found'));
|
||||
}
|
||||
|
||||
return response()->download($filePath);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
118
app/Http/Controllers/ContactController.php
Normal file
118
app/Http/Controllers/ContactController.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Contact;
|
||||
use App\Services\EmailTemplateService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class ContactController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-contacts')) {
|
||||
$query = Contact::query();
|
||||
|
||||
// Search functionality
|
||||
if ($request->filled('search')) {
|
||||
$search = $request->search;
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('name', 'like', "%{$search}%")
|
||||
->orWhere('email', 'like', "%{$search}%")
|
||||
->orWhere('subject', 'like', "%{$search}%")
|
||||
->orWhere('message', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
// Sorting
|
||||
$sortField = $request->get('sort_field', 'created_at');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['name', 'email', 'subject', 'created_at'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'created_at';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
// Pagination
|
||||
$perPage = $request->get('per_page', 10);
|
||||
$contacts = $query->paginate($perPage)->withQueryString();
|
||||
|
||||
return Inertia::render('contacts/index', [
|
||||
'contacts' => $contacts,
|
||||
'filters' => $request->only(['search', 'sort_field', 'sort_direction', 'per_page'])
|
||||
]);
|
||||
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function sendReply(Request $request, Contact $contact)
|
||||
{
|
||||
if (Auth::user()->can('send-reply-contacts')) {
|
||||
$request->validate([
|
||||
'subject' => 'required|string|max:255',
|
||||
'message' => 'required|string'
|
||||
]);
|
||||
|
||||
try {
|
||||
// Send email directly without template
|
||||
$config = setEmailConfigurations();
|
||||
$fromEmail = getSetting('email_from_address') ?: config('mail.from.address');
|
||||
$fromName = getSetting('email_from_name') ?: config('mail.from.name');
|
||||
|
||||
Mail::send([], [], function ($message) use ($contact, $request, $fromEmail, $fromName) {
|
||||
$message->to($contact->email, $contact->name)
|
||||
->subject($request->subject)
|
||||
->html(nl2br(e($request->message)))
|
||||
->from($fromEmail, $fromName);
|
||||
});
|
||||
|
||||
// Update contact status to 'Contacted'
|
||||
$contact->update(['status' => 'Contacted']);
|
||||
|
||||
return redirect()->back()->with('success', 'Reply sent successfully.');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Failed to send reply: ' . $e->getMessage());
|
||||
return redirect()->back()->with('error', 'Failed to send reply. Please check email settings.');
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function updateStatus(Request $request, Contact $contact)
|
||||
{
|
||||
if (Auth::user()->can('update-contact-status')) {
|
||||
$request->validate([
|
||||
'status' => 'required|in:New,Contacted,Qualified,Converted,Closed'
|
||||
]);
|
||||
|
||||
$contact->update([
|
||||
'status' => $request->status
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', 'Contact status updated successfully.');
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(Contact $contact)
|
||||
{
|
||||
if (Auth::user()->can('delete-contacts')) {
|
||||
$contact->delete();
|
||||
|
||||
return redirect()->back()->with('success', 'Contact deleted successfully.');
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
233
app/Http/Controllers/ContractRenewalController.php
Normal file
233
app/Http/Controllers/ContractRenewalController.php
Normal file
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\ContractRenewal;
|
||||
use App\Models\EmployeeContract;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class ContractRenewalController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = ContractRenewal::withPermissionCheck()->with(['contract.employee', 'requester', 'approver']);
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('renewal_number', 'like', '%' . $request->search . '%')
|
||||
->orWhereHas('contract.employee', function ($eq) use ($request) {
|
||||
$eq->where('name', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
if ($request->has('contract_id') && !empty($request->contract_id) && $request->contract_id !== 'all') {
|
||||
$query->where('contract_id', $request->contract_id);
|
||||
}
|
||||
|
||||
$query->orderBy('id', 'desc');
|
||||
$contractRenewals = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$contracts = EmployeeContract::with('employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->whereNotNull('end_date')
|
||||
->select('id', 'contract_number', 'employee_id', 'end_date')
|
||||
->get();
|
||||
|
||||
$employees = User::whereIn('created_by', getCompanyAndUsersId())
|
||||
->whereIn('type', ['employee', 'manager','hr'])
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/contracts/contract-renewals/index', [
|
||||
'contractRenewals' => $contractRenewals,
|
||||
'contracts' => $contracts,
|
||||
'employees' => $employees,
|
||||
'filters' => $request->all(['search', 'status', 'contract_id', 'per_page']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'contract_id' => 'required|exists:employee_contracts,id',
|
||||
'new_start_date' => 'required|date',
|
||||
'new_end_date' => 'required|date|after:new_start_date',
|
||||
'new_basic_salary' => 'required|numeric|min:0',
|
||||
'new_allowances' => 'nullable|array',
|
||||
'new_benefits' => 'nullable|array',
|
||||
'new_terms_conditions' => 'nullable|string',
|
||||
'changes_summary' => 'nullable|string',
|
||||
'reason' => 'nullable|string',
|
||||
'requested_by' => 'required|exists:users,id',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$contract = EmployeeContract::find($request->contract_id);
|
||||
|
||||
// Generate renewal number
|
||||
$lastRenewal = ContractRenewal::where('contract_id', $request->contract_id)
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
$nextNumber = $lastRenewal ? (intval(substr($lastRenewal->renewal_number, -2)) + 1) : 1;
|
||||
$renewalNumber = 'REN-' . str_pad(creatorId(), 3, '0', STR_PAD_LEFT) . '-' . str_pad($request->contract_id, 3, '0', STR_PAD_LEFT) . '-' . str_pad($nextNumber, 2, '0', STR_PAD_LEFT);
|
||||
|
||||
ContractRenewal::create([
|
||||
'contract_id' => $request->contract_id,
|
||||
'renewal_number' => $renewalNumber,
|
||||
'current_end_date' => $contract->end_date,
|
||||
'new_start_date' => $request->new_start_date,
|
||||
'new_end_date' => $request->new_end_date,
|
||||
'new_basic_salary' => $request->new_basic_salary,
|
||||
'new_allowances' => $request->new_allowances,
|
||||
'new_benefits' => $request->new_benefits,
|
||||
'new_terms_conditions' => $request->new_terms_conditions,
|
||||
'changes_summary' => $request->changes_summary,
|
||||
'reason' => $request->reason,
|
||||
'requested_by' => $request->requested_by,
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Contract renewal created successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, ContractRenewal $contractRenewal)
|
||||
{
|
||||
if (!in_array($contractRenewal->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this renewal'));
|
||||
}
|
||||
|
||||
if ($contractRenewal->status !== 'Pending') {
|
||||
return redirect()->back()->with('error', __('Cannot update renewal that is not pending'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'contract_id' => 'required|exists:employee_contracts,id',
|
||||
'new_start_date' => 'required|date',
|
||||
'new_end_date' => 'required|date|after:new_start_date',
|
||||
'new_basic_salary' => 'required|numeric|min:0',
|
||||
'new_allowances' => 'nullable|array',
|
||||
'new_benefits' => 'nullable|array',
|
||||
'new_terms_conditions' => 'nullable|string',
|
||||
'changes_summary' => 'nullable|string',
|
||||
'reason' => 'nullable|string',
|
||||
'requested_by' => 'required|exists:users,id',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$contractRenewal->update([
|
||||
'new_start_date' => $request->new_start_date,
|
||||
'new_end_date' => $request->new_end_date,
|
||||
'new_basic_salary' => $request->new_basic_salary,
|
||||
'new_allowances' => $request->new_allowances,
|
||||
'new_benefits' => $request->new_benefits,
|
||||
'new_terms_conditions' => $request->new_terms_conditions,
|
||||
'changes_summary' => $request->changes_summary,
|
||||
'reason' => $request->reason,
|
||||
'requested_by' => $request->requested_by,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Contract renewal updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(ContractRenewal $contractRenewal)
|
||||
{
|
||||
if (!in_array($contractRenewal->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this renewal'));
|
||||
}
|
||||
|
||||
if ($contractRenewal->status === 'Processed') {
|
||||
return redirect()->back()->with('error', __('Cannot delete processed renewal'));
|
||||
}
|
||||
|
||||
$contractRenewal->delete();
|
||||
return redirect()->back()->with('success', __('Contract renewal deleted successfully'));
|
||||
}
|
||||
|
||||
public function approve(Request $request, ContractRenewal $contractRenewal)
|
||||
{
|
||||
if (!in_array($contractRenewal->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to approve this renewal'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'approval_notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator);
|
||||
}
|
||||
|
||||
$contractRenewal->update([
|
||||
'status' => 'Approved',
|
||||
'approved_by' => creatorId(),
|
||||
'approved_at' => now(),
|
||||
'approval_notes' => $request->approval_notes,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Renewal approved successfully'));
|
||||
}
|
||||
|
||||
public function reject(Request $request, ContractRenewal $contractRenewal)
|
||||
{
|
||||
if (!in_array($contractRenewal->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to reject this renewal'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'approval_notes' => 'required|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator);
|
||||
}
|
||||
|
||||
$contractRenewal->update([
|
||||
'status' => 'Rejected',
|
||||
'approved_by' => creatorId(),
|
||||
'approved_at' => now(),
|
||||
'approval_notes' => $request->approval_notes,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Renewal rejected successfully'));
|
||||
}
|
||||
|
||||
public function process(ContractRenewal $contractRenewal)
|
||||
{
|
||||
if (!in_array($contractRenewal->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to process this renewal'));
|
||||
}
|
||||
|
||||
if ($contractRenewal->status !== 'Approved') {
|
||||
return redirect()->back()->with('error', __('Can only process approved renewals'));
|
||||
}
|
||||
|
||||
// Update the original contract
|
||||
$contract = $contractRenewal->contract;
|
||||
$contract->update([
|
||||
'end_date' => $contractRenewal->new_end_date,
|
||||
'basic_salary' => $contractRenewal->new_basic_salary,
|
||||
'allowances' => $contractRenewal->new_allowances,
|
||||
'benefits' => $contractRenewal->new_benefits,
|
||||
'terms_conditions' => $contractRenewal->new_terms_conditions,
|
||||
'status' => 'Renewed',
|
||||
]);
|
||||
|
||||
$contractRenewal->update(['status' => 'Processed']);
|
||||
|
||||
return redirect()->back()->with('success', __('Renewal processed and contract updated successfully'));
|
||||
}
|
||||
}
|
||||
314
app/Http/Controllers/ContractTemplateController.php
Normal file
314
app/Http/Controllers/ContractTemplateController.php
Normal file
@@ -0,0 +1,314 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\ContractTemplate;
|
||||
use App\Models\ContractType;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class ContractTemplateController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-contract-templates')) {
|
||||
$query = ContractTemplate::with(['contractType'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-contract-templates')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-contract-templates')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('contract_type_id') && !empty($request->contract_type_id) && $request->contract_type_id !== 'all') {
|
||||
$query->where('contract_type_id', $request->contract_type_id);
|
||||
}
|
||||
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
if ($request->has('is_default') && $request->is_default !== 'all') {
|
||||
$query->where('is_default', $request->is_default === 'true');
|
||||
}
|
||||
|
||||
$allowedSortFields = ['id', 'name', 'status', 'is_default', 'created_at'];
|
||||
if ($request->has('sort_field') && !empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field === 'template_name' ? 'name' : $request->sort_field;
|
||||
if (in_array($sortField, $allowedSortFields)) {
|
||||
$sortDirection = in_array($request->sort_direction, ['asc', 'desc']) ? $request->sort_direction : 'asc';
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('is_default', 'desc')->orderBy('id', 'desc');
|
||||
}
|
||||
} else {
|
||||
$query->orderBy('is_default', 'desc')->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
$contractTemplates = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$contractTypes = ContractType::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/contracts/contract-templates/index', [
|
||||
'contractTemplates' => $contractTemplates,
|
||||
'contractTypes' => $contractTypes,
|
||||
'filters' => $request->all(['search', 'contract_type_id', 'status', 'is_default', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
if (Auth::user()->can('create-contract-templates')) {
|
||||
$contractTypes = ContractType::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/contracts/contract-templates/create', [
|
||||
'contractTypes' => $contractTypes,
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function show(ContractTemplate $contractTemplate)
|
||||
{
|
||||
if (Auth::user()->can('view-contract-templates')) {
|
||||
$contractTemplate->load('contractType');
|
||||
return Inertia::render('hr/contracts/contract-templates/show', [
|
||||
'contractTemplate' => $contractTemplate,
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function edit(ContractTemplate $contractTemplate)
|
||||
{
|
||||
if (Auth::user()->can('edit-contract-templates')) {
|
||||
$contractTypes = ContractType::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/contracts/contract-templates/edit', [
|
||||
'contractTemplate' => $contractTemplate,
|
||||
'contractTypes' => $contractTypes,
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('create-contract-templates')) {
|
||||
$variables = null;
|
||||
if ($request->filled('variables') && is_string($request->variables)) {
|
||||
$variables = array_values(array_filter(array_map('trim', explode(',', $request->variables))));
|
||||
} elseif (is_array($request->variables)) {
|
||||
$variables = $request->variables;
|
||||
}
|
||||
|
||||
$clauses = null;
|
||||
if ($request->filled('clauses') && is_string($request->clauses)) {
|
||||
$clauses = array_values(array_filter(array_map('trim', explode(',', $request->clauses))));
|
||||
} elseif (is_array($request->clauses)) {
|
||||
$clauses = $request->clauses;
|
||||
}
|
||||
|
||||
$validator = Validator::make(array_merge($request->all(), [
|
||||
'variables' => $variables,
|
||||
'clauses' => $clauses,
|
||||
]), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'contract_type_id' => 'required|exists:contract_types,id',
|
||||
'template_content' => 'required|string',
|
||||
'variables' => 'required|array',
|
||||
'clauses' => 'nullable|array',
|
||||
'is_default' => 'boolean',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
if ($request->boolean('is_default')) {
|
||||
ContractTemplate::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('contract_type_id', $request->contract_type_id)
|
||||
->where('is_default', true)
|
||||
->update(['is_default' => false]);
|
||||
}
|
||||
|
||||
ContractTemplate::create([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'contract_type_id' => $request->contract_type_id,
|
||||
'template_content' => $request->template_content,
|
||||
'variables' => $variables,
|
||||
'clauses' => $clauses,
|
||||
'is_default' => $request->boolean('is_default'),
|
||||
'status' => $request->status ?? 'active',
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->route('hr.contracts.contract-templates.index')
|
||||
->with('success', __('Contract template created successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function update(Request $request, ContractTemplate $contractTemplate)
|
||||
{
|
||||
if (Auth::user()->can('edit-contract-templates')) {
|
||||
if (!in_array($contractTemplate->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this template'));
|
||||
}
|
||||
|
||||
$variables = null;
|
||||
if ($request->filled('variables') && is_string($request->variables)) {
|
||||
$variables = array_values(array_filter(array_map('trim', explode(',', $request->variables))));
|
||||
} elseif (is_array($request->variables)) {
|
||||
$variables = $request->variables;
|
||||
}
|
||||
|
||||
$clauses = null;
|
||||
if ($request->filled('clauses') && is_string($request->clauses)) {
|
||||
$clauses = array_values(array_filter(array_map('trim', explode(',', $request->clauses))));
|
||||
} elseif (is_array($request->clauses)) {
|
||||
$clauses = $request->clauses;
|
||||
}
|
||||
|
||||
$validator = Validator::make(array_merge($request->all(), [
|
||||
'variables' => $variables,
|
||||
'clauses' => $clauses,
|
||||
]), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'contract_type_id' => 'required|exists:contract_types,id',
|
||||
'template_content' => 'required|string',
|
||||
'variables' => 'required|array',
|
||||
'clauses' => 'nullable|array',
|
||||
'is_default' => 'boolean',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
if ($request->boolean('is_default') && !$contractTemplate->is_default) {
|
||||
ContractTemplate::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('contract_type_id', $request->contract_type_id)
|
||||
->where('is_default', true)
|
||||
->update(['is_default' => false]);
|
||||
}
|
||||
|
||||
$contractTemplate->update([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'contract_type_id' => $request->contract_type_id,
|
||||
'template_content' => $request->template_content,
|
||||
'variables' => $variables,
|
||||
'clauses' => $clauses,
|
||||
'is_default' => $request->boolean('is_default'),
|
||||
'status' => $request->status ?? 'active',
|
||||
]);
|
||||
|
||||
return redirect()->route('hr.contracts.contract-templates.index')
|
||||
->with('success', __('Contract template updated successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(ContractTemplate $contractTemplate)
|
||||
{
|
||||
if (Auth::user()->can('delete-contract-templates')) {
|
||||
if (!in_array($contractTemplate->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this template'));
|
||||
}
|
||||
try {
|
||||
$contractTemplate->delete();
|
||||
return redirect()->back()->with('success', __('Contract template deleted successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to delete contract template'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleStatus(ContractTemplate $contractTemplate)
|
||||
{
|
||||
if (Auth::user()->can('edit-contract-templates')) {
|
||||
if (!in_array($contractTemplate->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this template'));
|
||||
}
|
||||
try {
|
||||
$contractTemplate->update([
|
||||
'status' => $contractTemplate->status === 'active' ? 'inactive' : 'active',
|
||||
]);
|
||||
return redirect()->back()->with('success', __('Template status updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update template status'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function preview(Request $request, ContractTemplate $contractTemplate)
|
||||
{
|
||||
if (!in_array($contractTemplate->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to preview this template'));
|
||||
}
|
||||
|
||||
$variables = $request->get('variables', []);
|
||||
$generatedContent = $contractTemplate->generateContract($variables);
|
||||
|
||||
return response()->json([
|
||||
'content' => $generatedContent,
|
||||
'variables' => $contractTemplate->variables,
|
||||
]);
|
||||
}
|
||||
|
||||
public function generate(Request $request, ContractTemplate $contractTemplate)
|
||||
{
|
||||
|
||||
$variables = $request->variables ?? [];
|
||||
|
||||
if (!is_array($variables)) {
|
||||
$variables = [];
|
||||
}
|
||||
|
||||
$generatedContent = $contractTemplate->generateContract($variables);
|
||||
$filename = $request->filename ?? ($contractTemplate->name . '_' . date('Y-m-d'));
|
||||
|
||||
$html = '<div style="font-family: Arial, sans-serif; line-height: 1.6; padding: 20px;">' . nl2br($generatedContent) . '</div>';
|
||||
$pdf = Pdf::loadHTML($html);
|
||||
return $pdf->download($filename . '.pdf');
|
||||
}
|
||||
}
|
||||
150
app/Http/Controllers/ContractTypeController.php
Normal file
150
app/Http/Controllers/ContractTypeController.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\ContractType;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class ContractTypeController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-contract-types')) {
|
||||
$query = ContractType::withCount('contracts')->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-contract-types')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-contract-types')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
if ($request->has('is_renewable') && $request->is_renewable !== 'all') {
|
||||
$query->where('is_renewable', $request->is_renewable === 'true');
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$allowedSortFields = ['id', 'name', 'status', 'default_duration_months', 'probation_period_months', 'notice_period_days', 'is_renewable', 'created_at'];
|
||||
if ($request->has('sort_field') && !empty($request->sort_field) && in_array($request->sort_field, $allowedSortFields)) {
|
||||
$sortDirection = in_array($request->sort_direction, ['asc', 'desc']) ? $request->sort_direction : 'asc';
|
||||
$query->orderBy($request->sort_field, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
$contractTypes = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
return Inertia::render('hr/contracts/contract-types/index', [
|
||||
'contractTypes' => $contractTypes,
|
||||
'filters' => $request->all(['search', 'status', 'is_renewable', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'default_duration_months' => 'nullable|integer|min:1|max:120',
|
||||
'probation_period_months' => 'required|integer|min:0|max:12',
|
||||
'notice_period_days' => 'required|integer|min:0|max:365',
|
||||
'is_renewable' => 'boolean',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
ContractType::create([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'default_duration_months' => $request->default_duration_months,
|
||||
'probation_period_months' => $request->probation_period_months,
|
||||
'notice_period_days' => $request->notice_period_days,
|
||||
'is_renewable' => $request->boolean('is_renewable'),
|
||||
'status' => $request->status ?? 'active',
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Contract type created successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, ContractType $contractType)
|
||||
{
|
||||
if (!in_array($contractType->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this contract type'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'default_duration_months' => 'nullable|integer|min:1|max:120',
|
||||
'probation_period_months' => 'required|integer|min:0|max:12',
|
||||
'notice_period_days' => 'required|integer|min:0|max:365',
|
||||
'is_renewable' => 'boolean',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$contractType->update([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'default_duration_months' => $request->default_duration_months,
|
||||
'probation_period_months' => $request->probation_period_months,
|
||||
'notice_period_days' => $request->notice_period_days,
|
||||
'is_renewable' => $request->boolean('is_renewable'),
|
||||
'status' => $request->status ?? 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Contract type updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(ContractType $contractType)
|
||||
{
|
||||
if (!in_array($contractType->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this contract type'));
|
||||
}
|
||||
|
||||
if ($contractType->contracts()->count() > 0) {
|
||||
return redirect()->back()->with('error', __('Cannot delete contract type as it is being used in contracts'));
|
||||
}
|
||||
|
||||
$contractType->delete();
|
||||
return redirect()->back()->with('success', __('Contract type deleted successfully'));
|
||||
}
|
||||
|
||||
public function toggleStatus(ContractType $contractType)
|
||||
{
|
||||
if (!in_array($contractType->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this contract type'));
|
||||
}
|
||||
|
||||
$contractType->update([
|
||||
'status' => $contractType->status === 'active' ? 'inactive' : 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Contract type status updated successfully'));
|
||||
}
|
||||
}
|
||||
8
app/Http/Controllers/Controller.php
Normal file
8
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
41
app/Http/Controllers/CookieConsentController.php
Normal file
41
app/Http/Controllers/CookieConsentController.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class CookieConsentController extends Controller
|
||||
{
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->all();
|
||||
$csvFile = storage_path('app/cookie-consents.csv');
|
||||
|
||||
// Create headers if file doesn't exist
|
||||
if (!file_exists($csvFile)) {
|
||||
$headers = array_keys($data);
|
||||
file_put_contents($csvFile, implode(',', $headers) . "\n");
|
||||
}
|
||||
|
||||
// Append data
|
||||
$values = array_map(function($value) {
|
||||
return is_string($value) ? '"' . str_replace('"', '""', $value) . '"' : $value;
|
||||
}, array_values($data));
|
||||
|
||||
file_put_contents($csvFile, implode(',', $values) . "\n", FILE_APPEND);
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
public function download()
|
||||
{
|
||||
$csvFile = storage_path('app/cookie-consents.csv');
|
||||
|
||||
if (!file_exists($csvFile)) {
|
||||
abort(404, 'No cookie consent data found');
|
||||
}
|
||||
|
||||
return response()->download($csvFile, 'cookie-consents-' . date('Y-m-d') . '.csv');
|
||||
}
|
||||
}
|
||||
241
app/Http/Controllers/CouponController.php
Normal file
241
app/Http/Controllers/CouponController.php
Normal file
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Coupon;
|
||||
use App\Http\Requests\CouponRequest;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Str;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class CouponController extends BaseController
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Coupon::with('creator');
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$search = $request->search;
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('name', 'like', "%{$search}%")
|
||||
->orWhere('code', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
// Handle type filter
|
||||
if ($request->has('type') && !empty($request->type) && $request->type !== 'all') {
|
||||
$query->where('type', $request->type);
|
||||
}
|
||||
|
||||
// Handle status filter
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle date range filter
|
||||
if ($request->has('date_from') && !empty($request->date_from)) {
|
||||
$query->whereDate('created_at', '>=', $request->date_from);
|
||||
}
|
||||
if ($request->has('date_to') && !empty($request->date_to)) {
|
||||
$query->whereDate('created_at', '<=', $request->date_to);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field', 'created_at');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['id', 'name', 'code', 'type', 'expiry_date', 'created_at'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'created_at';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
$perPage = $request->get('per_page', 10);
|
||||
$coupons = $query->paginate($perPage);
|
||||
|
||||
return Inertia::render('coupons/index', [
|
||||
'coupons' => $coupons,
|
||||
'filters' => $request->all(['search', 'type', 'status', 'date_from', 'date_to', 'sort_field', 'sort_direction', 'per_page'])
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(Request $request, Coupon $coupon)
|
||||
{
|
||||
$coupon->load('creator');
|
||||
|
||||
// Get usage history (mock data for now - you'll need to implement actual usage tracking)
|
||||
$usageHistory = collect([
|
||||
// Mock usage data - replace with actual usage model query
|
||||
[
|
||||
'id' => 1,
|
||||
'user_name' => 'John Doe',
|
||||
'user_email' => 'john@example.com',
|
||||
'order_id' => 'ORD-001',
|
||||
'amount' => 100.00,
|
||||
'discount_amount' => 10.00,
|
||||
'used_at' => now()->subDays(2)->toISOString()
|
||||
],
|
||||
[
|
||||
'id' => 2,
|
||||
'user_name' => 'Jane Smith',
|
||||
'user_email' => 'jane@example.com',
|
||||
'order_id' => 'ORD-002',
|
||||
'amount' => 150.00,
|
||||
'discount_amount' => 15.00,
|
||||
'used_at' => now()->subDays(1)->toISOString()
|
||||
]
|
||||
]);
|
||||
|
||||
// Paginate the usage history
|
||||
$perPage = $request->get('per_page', 10);
|
||||
$page = $request->get('page', 1);
|
||||
$total = $usageHistory->count();
|
||||
$items = $usageHistory->forPage($page, $perPage)->values();
|
||||
|
||||
$paginatedUsage = new \Illuminate\Pagination\LengthAwarePaginator(
|
||||
$items,
|
||||
$total,
|
||||
$perPage,
|
||||
$page,
|
||||
['path' => $request->url(), 'pageName' => 'page']
|
||||
);
|
||||
|
||||
// Add used_count to coupon (mock for now)
|
||||
$coupon->used_count = $usageHistory->count();
|
||||
|
||||
return Inertia::render('coupons/show', [
|
||||
'coupon' => $coupon,
|
||||
'usage_history' => $paginatedUsage
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(CouponRequest $request)
|
||||
{
|
||||
|
||||
$data = $request->all();
|
||||
$data['created_by'] = Auth::id();
|
||||
|
||||
// Generate code if auto-generate is selected
|
||||
if ($request->code_type === 'auto') {
|
||||
do {
|
||||
$data['code'] = strtoupper(Str::random(8));
|
||||
} while (Coupon::where('code', $data['code'])->exists());
|
||||
}
|
||||
|
||||
$coupon = Coupon::create($data);
|
||||
|
||||
return redirect()->route('coupons.index')->with('success', __('Coupon created successfully!'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(CouponRequest $request, Coupon $coupon)
|
||||
{
|
||||
|
||||
$data = $request->all();
|
||||
|
||||
// Generate new code if switching to auto-generate
|
||||
if ($request->code_type === 'auto' && $coupon->code_type !== 'auto') {
|
||||
do {
|
||||
$data['code'] = strtoupper(Str::random(8));
|
||||
} while (Coupon::where('code', $data['code'])->where('id', '!=', $coupon->id)->exists());
|
||||
}
|
||||
|
||||
$coupon->update($data);
|
||||
|
||||
return redirect()->route('coupons.index')->with('success', __('Coupon updated successfully!'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate coupon code
|
||||
*/
|
||||
public function validate(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'coupon_code' => 'required|string',
|
||||
'plan_id' => 'required|integer',
|
||||
'amount' => 'required|numeric|min:0'
|
||||
]);
|
||||
|
||||
$coupon = Coupon::where('code', $request->coupon_code)
|
||||
->where('status', 1)
|
||||
->first();
|
||||
|
||||
if (!$coupon) {
|
||||
return response()->json([
|
||||
'valid' => false,
|
||||
'message' => __('Invalid or inactive coupon code')
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Check if coupon is expired
|
||||
if ($coupon->expiry_date && $coupon->expiry_date < now()) {
|
||||
return response()->json([
|
||||
'valid' => false,
|
||||
'message' => __('Coupon has expired')
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Check usage limit
|
||||
if ($coupon->use_limit_per_coupon && $coupon->used_count >= $coupon->use_limit_per_coupon) {
|
||||
return response()->json([
|
||||
'valid' => false,
|
||||
'message' => __('Coupon usage limit exceeded')
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Check minimum amount
|
||||
if ($coupon->minimum_spend && $request->amount < $coupon->minimum_spend) {
|
||||
return response()->json([
|
||||
'valid' => false,
|
||||
'message' => __('Minimum spend requirement not met')
|
||||
], 400);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'valid' => true,
|
||||
'coupon' => [
|
||||
'id' => $coupon->id,
|
||||
'code' => $coupon->code,
|
||||
'type' => $coupon->type,
|
||||
'value' => $coupon->discount_amount
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the status of the specified coupon.
|
||||
*/
|
||||
public function toggleStatus(Coupon $coupon)
|
||||
{
|
||||
$coupon->update([
|
||||
'status' => !$coupon->status
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Coupon status updated successfully!'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(Coupon $coupon)
|
||||
{
|
||||
$coupon->delete();
|
||||
|
||||
return redirect()->route('coupons.index')->with('success', __('Coupon deleted successfully!'));
|
||||
}
|
||||
}
|
||||
114
app/Http/Controllers/CurrencyController.php
Normal file
114
app/Http/Controllers/CurrencyController.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Currency;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class CurrencyController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of currencies.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Currency::query();
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search')) {
|
||||
$searchTerm = $request->search;
|
||||
$query->where(function($q) use ($searchTerm) {
|
||||
$q->where('name', 'like', "%{$searchTerm}%")
|
||||
->orWhere('code', 'like', "%{$searchTerm}%")
|
||||
->orWhere('symbol', 'like', "%{$searchTerm}%");
|
||||
});
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->input('sort_field', 'created_at');
|
||||
$sortDirection = $request->input('sort_direction', 'desc');
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
// Pagination
|
||||
$perPage = $request->input('per_page', 10);
|
||||
$currencies = $query->paginate($perPage)->withQueryString();
|
||||
|
||||
return Inertia::render('currencies/index', [
|
||||
'currencies' => $currencies,
|
||||
'filters' => $request->all(['search', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created currency.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'code' => 'required|string|max:10|unique:currencies',
|
||||
'symbol' => 'required|string|max:10',
|
||||
'description' => 'nullable|string',
|
||||
'is_default' => 'boolean',
|
||||
]);
|
||||
|
||||
// If this is set as default, unset all other defaults
|
||||
if ($request->input('is_default')) {
|
||||
Currency::where('is_default', true)->update(['is_default' => false]);
|
||||
}
|
||||
|
||||
Currency::create($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Currency created successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified currency.
|
||||
*/
|
||||
public function update(Request $request, Currency $currency)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'code' => 'required|string|max:10|unique:currencies,code,' . $currency->id,
|
||||
'symbol' => 'required|string|max:10',
|
||||
'description' => 'nullable|string',
|
||||
'is_default' => 'boolean',
|
||||
]);
|
||||
|
||||
// If this is set as default, unset all other defaults
|
||||
if ($request->input('is_default')) {
|
||||
Currency::where('id', '!=', $currency->id)
|
||||
->where('is_default', true)
|
||||
->update(['is_default' => false]);
|
||||
}
|
||||
|
||||
$currency->update($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Currency updated successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified currency.
|
||||
*/
|
||||
public function destroy(Currency $currency)
|
||||
{
|
||||
// Don't allow deleting the default currency
|
||||
if ($currency->is_default) {
|
||||
return redirect()->back()->with('error', __('Cannot delete the default currency.'));
|
||||
}
|
||||
|
||||
$currency->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('Currency deleted successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all currencies for settings page.
|
||||
*/
|
||||
public function getAllCurrencies()
|
||||
{
|
||||
$currencies = Currency::all();
|
||||
return response()->json($currencies);
|
||||
}
|
||||
}
|
||||
144
app/Http/Controllers/CustomQuestionController.php
Normal file
144
app/Http/Controllers/CustomQuestionController.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\CustomQuestion;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class CustomQuestionController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-custom-questions')) {
|
||||
$query = CustomQuestion::where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-custom-questions')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-custom-questions')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && ! empty($request->search)) {
|
||||
$query->where('question', 'like', '%'.$request->search.'%');
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field', 'id');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['question', 'created_at', 'id'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'id';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
$customQuestions = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
return Inertia::render('hr/recruitment/custom-questions/index', [
|
||||
'customQuestions' => $customQuestions,
|
||||
'filters' => $request->all(['search', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('create-custom-questions')) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'question' => 'required|string',
|
||||
'required' => 'required|integer|in:0,1',
|
||||
]);
|
||||
|
||||
$validated['created_by'] = creatorId();
|
||||
|
||||
// Check if question already exists
|
||||
$exists = CustomQuestion::where('question', $validated['question'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return redirect()->back()->with('error', __('Custom question with this text already exists.'));
|
||||
}
|
||||
|
||||
CustomQuestion::create($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Custom question created successfully.'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to create custom question'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function update(Request $request, $customQuestionId)
|
||||
{
|
||||
if (Auth::user()->can('edit-custom-questions')) {
|
||||
$customQuestion = CustomQuestion::where('id', $customQuestionId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($customQuestion) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'question' => 'required|string',
|
||||
'required' => 'required|integer|in:0,1',
|
||||
]);
|
||||
|
||||
// Check if question already exists (excluding current question)
|
||||
$exists = CustomQuestion::where('question', $validated['question'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('id', '!=', $customQuestionId)
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return redirect()->back()->with('error', __('Custom question with this text already exists.'));
|
||||
}
|
||||
|
||||
$customQuestion->update($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Custom question updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update custom question'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Custom question not found.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($customQuestionId)
|
||||
{
|
||||
if (Auth::user()->can('delete-custom-questions')) {
|
||||
$customQuestion = CustomQuestion::where('id', $customQuestionId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($customQuestion) {
|
||||
try {
|
||||
$customQuestion->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('Custom question deleted successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to delete custom question'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Custom question not found.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
549
app/Http/Controllers/DashboardController.php
Normal file
549
app/Http/Controllers/DashboardController.php
Normal file
@@ -0,0 +1,549 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Announcement;
|
||||
use App\Models\AttendanceRecord;
|
||||
use App\Models\Branch;
|
||||
use App\Models\Candidate;
|
||||
use App\Models\Department;
|
||||
use App\Models\Employee;
|
||||
use App\Models\JobPosting;
|
||||
use App\Models\LeaveApplication;
|
||||
use App\Models\LeaveType;
|
||||
use App\Models\Meeting;
|
||||
use App\Models\Coupon;
|
||||
use App\Models\Plan;
|
||||
use App\Models\PlanOrder;
|
||||
use App\Models\PlanRequest;
|
||||
use App\Models\Shift;
|
||||
use App\Models\User;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
// Super admin always gets dashboard
|
||||
if ($user->type === 'superadmin' || $user->type === 'super admin') {
|
||||
return $this->renderDashboard();
|
||||
}
|
||||
|
||||
// Check if user has dashboard permission (skip if permission doesn't exist)
|
||||
try {
|
||||
if ($user->hasPermissionTo('manage-dashboard')) {
|
||||
return $this->renderDashboard();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Permission doesn't exist, continue to dashboard for authenticated users
|
||||
return $this->renderDashboard();
|
||||
}
|
||||
|
||||
// Redirect to first available page
|
||||
return $this->redirectToFirstAvailablePage();
|
||||
}
|
||||
|
||||
public function redirectToFirstAvailablePage()
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
// Define available routes with their permissions
|
||||
$routes = [
|
||||
['route' => 'users.index', 'permission' => 'manage-users'],
|
||||
['route' => 'roles.index', 'permission' => 'manage-roles'],
|
||||
|
||||
['route' => 'plans.index', 'permission' => 'manage-plans'],
|
||||
['route' => 'referral.index', 'permission' => 'manage-referral'],
|
||||
['route' => 'settings.index', 'permission' => 'manage-settings'],
|
||||
];
|
||||
|
||||
// Find first available route
|
||||
foreach ($routes as $routeData) {
|
||||
if ($user->hasPermissionTo($routeData['permission'])) {
|
||||
return redirect()->route($routeData['route']);
|
||||
}
|
||||
}
|
||||
|
||||
// If no permissions found, logout user
|
||||
auth()->logout();
|
||||
|
||||
return redirect()->route('login')->with('error', __('No access permissions found.'));
|
||||
}
|
||||
|
||||
private function renderDashboard()
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
if ($user->type === 'superadmin' || $user->type === 'super admin') {
|
||||
return $this->renderSuperAdminDashboard();
|
||||
} else {
|
||||
return $this->renderCompanyDashboard();
|
||||
}
|
||||
}
|
||||
|
||||
private function renderSuperAdminDashboard()
|
||||
{
|
||||
// Get system-wide statistics
|
||||
$totalCompanies = User::where('type', 'company')->count();
|
||||
$totalUsers = User::where('type', '!=', 'superadmin')->where('type', '!=', 'super admin')->count();
|
||||
$totalRevenue = PlanOrder::where('status', 'approved')->sum('final_price') ?? 0;
|
||||
$activePlans = Plan::where('is_plan_enable', 'on')->count();
|
||||
|
||||
$pendingRequests = PlanRequest::where('status', 'pending')->count();
|
||||
$activeCoupons = Coupon::where('status', true)->count();
|
||||
|
||||
// Calculate monthly growth for companies
|
||||
$currentMonthCompanies = User::where('type', 'company')
|
||||
->whereMonth('created_at', now()->month)
|
||||
->whereYear('created_at', now()->year)
|
||||
->count();
|
||||
$previousMonthCompanies = User::where('type', 'company')
|
||||
->whereMonth('created_at', now()->subMonth()->month)
|
||||
->whereYear('created_at', now()->subMonth()->year)
|
||||
->count();
|
||||
$monthlyGrowth = isDemo() ? 90 : ($previousMonthCompanies > 0
|
||||
? round((($currentMonthCompanies - $previousMonthCompanies) / $previousMonthCompanies) * 100, 1)
|
||||
: ($currentMonthCompanies > 0 ? 100 : 0));
|
||||
|
||||
$dashboardData = [
|
||||
'stats' => [
|
||||
'totalCompanies' => $totalCompanies,
|
||||
'totalUsers' => $totalUsers,
|
||||
'totalRevenue' => $totalRevenue,
|
||||
'activePlans' => $activePlans,
|
||||
'pendingRequests' => $pendingRequests,
|
||||
'monthlyGrowth' => $monthlyGrowth,
|
||||
'activeCoupons' => $activeCoupons,
|
||||
],
|
||||
'recentActivity' => User::where('type', 'company')
|
||||
->orderBy('created_at', 'desc')
|
||||
->take(5)
|
||||
->get(['id', 'name', 'email', 'created_at'])
|
||||
->map(function ($company) {
|
||||
return [
|
||||
'id' => $company->id,
|
||||
'name' => $company->name,
|
||||
'email' => $company->email,
|
||||
'registered_at' => $company->created_at->diffForHumans(),
|
||||
'status' => 'active',
|
||||
];
|
||||
}),
|
||||
'topPlans' => Plan::withCount('users')
|
||||
->orderBy('users_count', 'desc')
|
||||
->take(5)
|
||||
->get()
|
||||
->map(function ($plan) {
|
||||
return [
|
||||
'name' => $plan->name,
|
||||
'subscribers' => $plan->users_count,
|
||||
'revenue' => $plan->users_count * $plan->price,
|
||||
];
|
||||
}),
|
||||
];
|
||||
|
||||
return Inertia::render('superadmin/dashboard', props: [
|
||||
'dashboardData' => $dashboardData,
|
||||
]);
|
||||
}
|
||||
|
||||
private function renderCompanyDashboard()
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
// If user is employee, show limited dashboard
|
||||
if ($user->type === 'employee') {
|
||||
return $this->renderEmployeeDashboard();
|
||||
}
|
||||
|
||||
$companyUserIds = $this->getCompanyUserIds();
|
||||
|
||||
// Core HR Statistics
|
||||
$totalEmployees = User::where('type', 'employee')->whereIn('created_by', $companyUserIds)->count();
|
||||
$totalBranches = Branch::whereIn('created_by', $companyUserIds)->count();
|
||||
$totalDepartments = Department::whereIn('created_by', $companyUserIds)->count();
|
||||
|
||||
// Monthly Statistics
|
||||
if (isDemo()) {
|
||||
$newEmployeesThisMonth = Employee::whereIn('created_by', $companyUserIds)->count();
|
||||
$jobPostsThisMonth = JobPosting::whereIn('created_by', $companyUserIds)->count();
|
||||
$candidatesThisMonth = Candidate::whereIn('created_by', $companyUserIds)->count();
|
||||
} else {
|
||||
$newEmployeesThisMonth = Employee::whereIn('created_by', $companyUserIds)
|
||||
->whereMonth('created_at', now()->month)->count();
|
||||
$jobPostsThisMonth = JobPosting::whereIn('created_by', $companyUserIds)
|
||||
->whereMonth('created_at', now()->month)->count();
|
||||
$candidatesThisMonth = Candidate::whereIn('created_by', $companyUserIds)
|
||||
->whereMonth('created_at', now()->month)->count();
|
||||
}
|
||||
|
||||
// Attendance Statistics
|
||||
if (isDemo()) {
|
||||
$presentToday = 45;
|
||||
$attendanceRate = 85.5;
|
||||
} else {
|
||||
$presentToday = AttendanceRecord::whereIn('created_by', $companyUserIds)
|
||||
->whereDate('date', today())->where('status', 'present')->count();
|
||||
$attendanceRate = $totalEmployees > 0 ? round(($presentToday / $totalEmployees) * 100, 1) : 0;
|
||||
}
|
||||
|
||||
// Leave Statistics
|
||||
$pendingLeaves = LeaveApplication::whereIn('created_by', $companyUserIds)
|
||||
->where('status', 'pending')->count();
|
||||
|
||||
$onLeaveToday = LeaveApplication::whereIn('created_by', $companyUserIds)
|
||||
->where('status', 'approved');
|
||||
|
||||
$onLeaveToday = $onLeaveToday->whereDate('start_date', '<=', today())
|
||||
->whereDate('end_date', '>=', today())->count();
|
||||
|
||||
// Recruitment Statistics
|
||||
$activeJobPostings = JobPosting::whereIn('created_by', $companyUserIds)
|
||||
->where('status', 'Published')->count();
|
||||
$totalCandidates = Candidate::whereIn('created_by', $companyUserIds)->count();
|
||||
|
||||
// Department Distribution for Chart
|
||||
// $predefinedColors = ['#4F46E5', '#10b77f', '#F59E0B', '#EF4444', '#3B82F6', '#D946EF'];
|
||||
$predefinedColors = ['#0EA5E9', '#14B8A6', '#6366F1', '#0D9488', '#7C3AED', '#0369A1'];
|
||||
|
||||
$departmentStats = Department::whereIn('created_by', $companyUserIds)
|
||||
->withCount('employees')
|
||||
->with('branch')
|
||||
->orderBy('employees_count', 'desc')
|
||||
->when(config('app.is_demo') == true, function ($query) {
|
||||
return $query->take(6);
|
||||
})
|
||||
->get()
|
||||
->map(function ($dept, $index) use ($predefinedColors) {
|
||||
$displayName = $dept->name . ' (' . $dept->branch->name . ')';
|
||||
|
||||
return [
|
||||
'name' => $displayName,
|
||||
'value' => $dept->employees_count,
|
||||
'color' => config('app.is_demo') == true
|
||||
? ($predefinedColors[$index] ?? '#' . substr(md5($displayName), 0, 6))
|
||||
: '#' . substr(md5($displayName), 0, 6),
|
||||
];
|
||||
});
|
||||
|
||||
// Monthly Hiring Trend for Chart (last 6 months)
|
||||
if (isDemo()) {
|
||||
$hiringTrend = [
|
||||
['month' => now()->subMonths(5)->format('M Y'), 'hires' => 8],
|
||||
['month' => now()->subMonths(4)->format('M Y'), 'hires' => 12],
|
||||
['month' => now()->subMonths(3)->format('M Y'), 'hires' => 15],
|
||||
['month' => now()->subMonths(2)->format('M Y'), 'hires' => 10],
|
||||
['month' => now()->subMonths(1)->format('M Y'), 'hires' => 18],
|
||||
['month' => now()->format('M Y'), 'hires' => 14],
|
||||
];
|
||||
} else {
|
||||
$hiringTrend = [];
|
||||
for ($i = 5; $i >= 0; $i--) {
|
||||
$month = now()->subMonths($i);
|
||||
$count = Employee::whereIn('created_by', $companyUserIds)
|
||||
->whereMonth('created_at', $month->month)
|
||||
->whereYear('created_at', $month->year)
|
||||
->count();
|
||||
$hiringTrend[] = [
|
||||
'month' => $month->format('M Y'),
|
||||
'hires' => $count,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Candidate Status Distribution for Chart
|
||||
$candidateStatusStats = Candidate::whereIn('created_by', $companyUserIds)
|
||||
->selectRaw('status, COUNT(*) as count')
|
||||
->groupBy('status')
|
||||
->get()
|
||||
->map(function ($item) {
|
||||
$colors = [
|
||||
'New' => '#0EA5E9',
|
||||
'Screening' => '#F59E0B',
|
||||
'Interview' => '#8B5CF6',
|
||||
'Offer' => '#14B8A6',
|
||||
'Hired' => '#10B981',
|
||||
'Rejected' => '#EF4444',
|
||||
];
|
||||
|
||||
return [
|
||||
'name' => $item->status,
|
||||
'value' => $item->count,
|
||||
'color' => $colors[$item->status] ?? '#6b7280',
|
||||
];
|
||||
});
|
||||
|
||||
// Leave Types for Chart
|
||||
$leaveTypesStats = LeaveType::whereIn('created_by', $companyUserIds)
|
||||
->get()
|
||||
->map(function ($leaveType) {
|
||||
return [
|
||||
'name' => $leaveType->name,
|
||||
'value' => $leaveType->max_days_per_year,
|
||||
'color' => $leaveType->color ?: '#' . substr(md5($leaveType->name), 0, 6),
|
||||
];
|
||||
});
|
||||
|
||||
// Employee Growth Chart (Monthly for current year)
|
||||
if (isDemo()) {
|
||||
$employeeGrowthChart = [
|
||||
['month' => 'January', 'employees' => 15],
|
||||
['month' => 'February', 'employees' => 5],
|
||||
['month' => 'March', 'employees' => 22],
|
||||
['month' => 'April', 'employees' => 10],
|
||||
['month' => 'May', 'employees' => 28],
|
||||
['month' => 'June', 'employees' => 32],
|
||||
['month' => 'July', 'employees' => 35],
|
||||
['month' => 'August', 'employees' => 50],
|
||||
['month' => 'September', 'employees' => 42],
|
||||
['month' => 'October', 'employees' => 45],
|
||||
['month' => 'November', 'employees' => 48],
|
||||
['month' => 'December', 'employees' => 52],
|
||||
];
|
||||
} else {
|
||||
$employeeGrowthChart = [];
|
||||
for ($month = 1; $month <= 12; $month++) {
|
||||
$count = User::where('type', 'employee')
|
||||
->whereIn('created_by', $companyUserIds)
|
||||
->whereMonth('created_at', $month)
|
||||
->whereYear('created_at', now()->year)
|
||||
->count();
|
||||
$employeeGrowthChart[] = [
|
||||
'month' => date('F', mktime(0, 0, 0, $month, 1)),
|
||||
'employees' => $count,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Recent Activities
|
||||
$recentLeaves = LeaveApplication::whereIn('created_by', $companyUserIds)
|
||||
->with(['employee', 'leaveType']);
|
||||
if (config('app.is_demo') == true) {
|
||||
$recentLeaves = $recentLeaves->whereIn('status', ['approved', 'absent'])->get();
|
||||
} else {
|
||||
$recentLeaves = $recentLeaves->whereIn('status', ['approved', 'absent'])
|
||||
->whereDate('start_date', '<=', today())
|
||||
->whereDate('end_date', '>=', today())
|
||||
->get();
|
||||
}
|
||||
|
||||
$recentCandidates = Candidate::whereIn('created_by', $companyUserIds)
|
||||
->with(['job'])
|
||||
->orderBy('created_at', 'desc')
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
// Recent Announcements
|
||||
$recentAnnouncements = Announcement::whereIn('created_by', $companyUserIds)
|
||||
->orderBy('created_at', 'desc')
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
// Recent Meetings
|
||||
$recentMeetings = Meeting::whereIn('created_by', $companyUserIds)
|
||||
->orderBy('created_at', 'desc')
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
$dashboardData = [
|
||||
'stats' => [
|
||||
'totalEmployees' => $totalEmployees,
|
||||
'totalBranches' => $totalBranches,
|
||||
'totalDepartments' => $totalDepartments,
|
||||
'newEmployeesThisMonth' => $newEmployeesThisMonth,
|
||||
'jobPostsThisMonth' => $jobPostsThisMonth,
|
||||
'candidatesThisMonth' => $candidatesThisMonth,
|
||||
'attendanceRate' => $attendanceRate,
|
||||
'presentToday' => $presentToday,
|
||||
'pendingLeaves' => $pendingLeaves,
|
||||
'onLeaveToday' => $onLeaveToday,
|
||||
'activeJobPostings' => $activeJobPostings,
|
||||
'totalCandidates' => $totalCandidates,
|
||||
],
|
||||
'charts' => [
|
||||
'departmentStats' => $departmentStats,
|
||||
'hiringTrend' => $hiringTrend,
|
||||
'candidateStatusStats' => $candidateStatusStats,
|
||||
'leaveTypesStats' => $leaveTypesStats,
|
||||
'employeeGrowthChart' => $employeeGrowthChart,
|
||||
],
|
||||
'recentActivities' => [
|
||||
'leaves' => $recentLeaves,
|
||||
'candidates' => $recentCandidates,
|
||||
'announcements' => $recentAnnouncements,
|
||||
'meetings' => $recentMeetings,
|
||||
],
|
||||
'userType' => $user->type,
|
||||
];
|
||||
|
||||
return Inertia::render('dashboard', [
|
||||
'dashboardData' => $dashboardData,
|
||||
]);
|
||||
}
|
||||
|
||||
private function renderEmployeeDashboard()
|
||||
{
|
||||
$user = auth()->user();
|
||||
$companyUserIds = $this->getCompanyUserIds();
|
||||
|
||||
// Recent Announcements
|
||||
$recentAnnouncements = \App\Models\Announcement::whereIn('created_by', $companyUserIds)
|
||||
->orderBy('created_at', 'desc')
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
// Recent Meetings - get meetings where user is organizer
|
||||
$recentMeetings = \App\Models\Meeting::with('attendees')
|
||||
->whereIn('created_by', $companyUserIds)
|
||||
->where('organizer_id', $user->id)
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
// Get meetings where user is attendee
|
||||
$meetingAttendee = \App\Models\MeetingAttendee::with('meeting')
|
||||
->where('user_id', $user->id)
|
||||
->get();
|
||||
|
||||
// Extract meetings from attendee records
|
||||
$attendeeMeetings = $meetingAttendee->pluck(value: 'meeting')->filter();
|
||||
|
||||
// Merge and remove duplicates
|
||||
$recentMeetings = $recentMeetings->merge($attendeeMeetings)
|
||||
->unique('id')
|
||||
->filter(function ($meeting) {
|
||||
return $meeting->meeting_date >= today();
|
||||
})
|
||||
->sortByDesc('created_at')
|
||||
->values();
|
||||
|
||||
// Employee Stats
|
||||
$totalAwards = \App\Models\Award::where('employee_id', $user->id)->count();
|
||||
$totalWarnings = \App\Models\Warning::where('employee_id', $user->id)->count();
|
||||
$totalComplaints = \App\Models\Complaint::where('against_employee_id', $user->id)->count();
|
||||
|
||||
// Get shifts and attendance policies for clock in functionality
|
||||
$shifts = \App\Models\Shift::whereIn('created_by', $companyUserIds)
|
||||
->where('status', 'active')
|
||||
->get(['id', 'name', 'start_time', 'end_time']);
|
||||
|
||||
$attendancePolicies = \App\Models\AttendancePolicy::whereIn('created_by', $companyUserIds)
|
||||
->where('status', 'active')
|
||||
->get(['id', 'name']);
|
||||
|
||||
// Get today's attendance for the employee
|
||||
$todayAttendance = AttendanceRecord::where('employee_id', $user->id)
|
||||
->where('date', \Carbon\Carbon::today())
|
||||
->first();
|
||||
|
||||
// Get employee's assigned shift
|
||||
$employeeShift = null;
|
||||
$employee = Employee::where('user_id', $user->id)->first();
|
||||
if ($employee && $employee->shift_id) {
|
||||
$employeeShift = Shift::find($employee->shift_id);
|
||||
}
|
||||
|
||||
// Auto clock out previous days like yesterday and alll thing if not clocked out
|
||||
$previousAttendance = AttendanceRecord::where('employee_id', $user->id)
|
||||
->where('date', '<', \Carbon\Carbon::today())
|
||||
->whereNotNull('clock_in')
|
||||
->whereNull('clock_out')
|
||||
->get();
|
||||
|
||||
foreach ($previousAttendance as $record) {
|
||||
$recordDate = \Carbon\Carbon::parse($record->date);
|
||||
$shift = Shift::find($record->shift_id) ?? $employeeShift;
|
||||
|
||||
if ($shift) {
|
||||
$record->update([
|
||||
'clock_out' => $shift->end_time,
|
||||
]);
|
||||
|
||||
if (method_exists($record, 'processAttendance')) {
|
||||
$record->processAttendance();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Auto clock out if shift end time has passed for today
|
||||
// if ($todayAttendance && $todayAttendance->clock_in && !$todayAttendance->clock_out && $employeeShift) {
|
||||
// $now = \Carbon\Carbon::now();
|
||||
// $shiftEndTime = \Carbon\Carbon::today()->setTimeFromTimeString($employeeShift->end_time);
|
||||
|
||||
// if ($now->greaterThan($shiftEndTime)) {
|
||||
// $todayAttendance->update([
|
||||
// 'clock_out' => $employeeShift->end_time,
|
||||
// ]);
|
||||
|
||||
// if (method_exists($todayAttendance, 'processAttendance')) {
|
||||
// $todayAttendance->processAttendance();
|
||||
// }
|
||||
|
||||
// $todayAttendance = $todayAttendance->fresh();
|
||||
// }
|
||||
// }
|
||||
|
||||
$dashboardData = [
|
||||
'stats' => [
|
||||
'totalAwards' => $totalAwards,
|
||||
'totalWarnings' => $totalWarnings,
|
||||
'totalComplaints' => $totalComplaints,
|
||||
],
|
||||
'recentActivities' => [
|
||||
'announcements' => $recentAnnouncements,
|
||||
'meetings' => $recentMeetings,
|
||||
],
|
||||
'shifts' => $shifts,
|
||||
'attendancePolicies' => $attendancePolicies,
|
||||
'todayAttendance' => $todayAttendance,
|
||||
'currentTime' => \Carbon\Carbon::now()->format('H:i:s'),
|
||||
'employeeShift' => $employeeShift,
|
||||
'userType' => $user->type,
|
||||
];
|
||||
|
||||
return Inertia::render('employee-dashboard', [
|
||||
'dashboardData' => $dashboardData,
|
||||
]);
|
||||
}
|
||||
|
||||
// private function getCompanyUserIds()
|
||||
// {
|
||||
// $user = auth()->user();
|
||||
// if ($user->type === 'company') {
|
||||
// $companyUserIds = User::where('created_by', $user->id)->pluck('id')->toArray();
|
||||
// $companyUserIds[] = $user->id;
|
||||
// return $companyUserIds;
|
||||
// } else {
|
||||
// $userCreatedBy = User::where('id', $user->created_by)->value('id');
|
||||
// $companyUserIds = User::where('created_by', $userCreatedBy)->pluck('id')->toArray();
|
||||
// $companyUserIds[] = $userCreatedBy;
|
||||
// return $companyUserIds;
|
||||
// }
|
||||
// }
|
||||
|
||||
private function getCompanyUserIds()
|
||||
{
|
||||
$user = auth()->user();
|
||||
if ($user->type === 'company') {
|
||||
$companyId = getCompanyId($user->id);
|
||||
if ($companyId) {
|
||||
$allUsers = getAllCompanyUsers($companyId);
|
||||
$allUsers[] = $companyId; // Include company itself
|
||||
|
||||
return array_unique($allUsers);
|
||||
}
|
||||
|
||||
return [];
|
||||
} else {
|
||||
$companyId = getCompanyId($user->id);
|
||||
if ($companyId) {
|
||||
$allUsers = getAllCompanyUsers($companyId);
|
||||
$allUsers[] = $companyId; // Include company itself
|
||||
|
||||
return array_unique($allUsers);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
219
app/Http/Controllers/DepartmentController.php
Normal file
219
app/Http/Controllers/DepartmentController.php
Normal file
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Branch;
|
||||
use App\Models\Department;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class DepartmentController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-departments')) {
|
||||
|
||||
$query = Department::with(['branch', 'creator'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-departments')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-departments')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle branch filter
|
||||
if ($request->has('branch_id') && !empty($request->branch_id) && $request->branch_id !== 'all') {
|
||||
$query->where('branch_id', $request->branch_id);
|
||||
}
|
||||
|
||||
// Handle status filter
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field', 'created_at');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['name', 'created_at', 'id'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'created_at';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
$departments = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
// Get branches for filter dropdown
|
||||
$branches = Branch::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->get(['id', 'name']);
|
||||
|
||||
return Inertia::render('hr/departments/index', [
|
||||
'departments' => $departments,
|
||||
'branches' => $branches,
|
||||
'filters' => $request->all(['search', 'branch_id', 'status', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('create-departments')) {
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'branch_id' => 'required|exists:branches,id',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'nullable|in:active,inactive',
|
||||
]);
|
||||
|
||||
$validated['created_by'] = creatorId();
|
||||
$validated['status'] = $validated['status'] ?? 'active';
|
||||
|
||||
// Check if branch belongs to the current user's company
|
||||
$branch = Branch::where('id', $validated['branch_id'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if (!$branch) {
|
||||
return redirect()->back()->with('error', __('Invalid branch selected.'));
|
||||
}
|
||||
|
||||
// Check if department with same name already exists in this branch
|
||||
$exists = Department::where('name', $validated['name'])
|
||||
->where('branch_id', $validated['branch_id'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return redirect()->back()->with('error', __('Department with this name already exists in the selected branch.'));
|
||||
}
|
||||
|
||||
Department::create($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Department created successfully.'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function update(Request $request, $departmentId)
|
||||
{
|
||||
if (Auth::user()->can('edit-departments')) {
|
||||
$department = Department::where('id', $departmentId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($department) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'branch_id' => 'required|exists:branches,id',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'nullable|in:active,inactive',
|
||||
]);
|
||||
|
||||
// Check if branch belongs to the current user's company
|
||||
$branch = Branch::where('id', $validated['branch_id'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if (!$branch) {
|
||||
return redirect()->back()->with('error', __('Invalid branch selected.'));
|
||||
}
|
||||
|
||||
// Check if department with same name already exists in this branch (excluding current department)
|
||||
$exists = Department::where('name', $validated['name'])
|
||||
->where('branch_id', $validated['branch_id'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('id', '!=', $departmentId)
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return redirect()->back()->with('error', __('Department with this name already exists in the selected branch.'));
|
||||
}
|
||||
|
||||
$department->update($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Department updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update department'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Department Not Found.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($departmentId)
|
||||
{
|
||||
if (Auth::user()->can('delete-departments')) {
|
||||
$department = Department::where('id', $departmentId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($department) {
|
||||
try {
|
||||
// Check if department has employees
|
||||
if (class_exists('App\\Models\\Employee')) {
|
||||
$employeeCount = \App\Models\User::where('type', 'employee')
|
||||
->whereHas('employee', function ($q) use ($departmentId) {
|
||||
$q->where('department_id', $departmentId);
|
||||
})->count();
|
||||
if ($employeeCount > 0) {
|
||||
return response()->json(['message' => __('Cannot delete department with assigned employees')], 400);
|
||||
}
|
||||
}
|
||||
$department->delete();
|
||||
return redirect()->back()->with('success', __('Department deleted successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to delete department'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Department Not Found.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleStatus($departmentId)
|
||||
{
|
||||
if (Auth::user()->can('toggle-status-departments')) {
|
||||
$department = Department::where('id', $departmentId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($department) {
|
||||
try {
|
||||
$department->status = $department->status === 'active' ? 'inactive' : 'active';
|
||||
$department->save();
|
||||
|
||||
return redirect()->back()->with('success', __('Department status updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update department status'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Department Not Found.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
186
app/Http/Controllers/DesignationController.php
Normal file
186
app/Http/Controllers/DesignationController.php
Normal file
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Department;
|
||||
use App\Models\Designation;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class DesignationController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-designations')) {
|
||||
$query = Designation::with(['department', 'department.branch'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-designations')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-designations')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle department filter
|
||||
if ($request->has('department') && $request->department !== 'all') {
|
||||
$query->where('department_id', $request->department);
|
||||
}
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field', 'created_at');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['name', 'created_at', 'id'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'created_at';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
$designations = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
// Get departments for dropdown
|
||||
$departments = Department::with('branch')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/designations/index', [
|
||||
'designations' => $designations,
|
||||
'departments' => $departments,
|
||||
'filters' => $request->all(['search', 'sort_field', 'sort_direction', 'per_page', 'department']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('create-designations')) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'department_id' => 'required|exists:departments,id',
|
||||
'status' => 'nullable|in:active,inactive',
|
||||
]);
|
||||
|
||||
$validated['created_by'] = creatorId();
|
||||
|
||||
// Check if department belongs to current company
|
||||
$department = Department::where('id', $validated['department_id'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if (!$department) {
|
||||
return redirect()->back()->with('error', __('Selected department does not belong to your company'));
|
||||
}
|
||||
|
||||
Designation::create($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Designation created successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to create designation'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function update(Request $request, $designationId)
|
||||
{
|
||||
if (Auth::user()->can('edit-designations')) {
|
||||
$designation = Designation::where('id', $designationId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($designation) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'department_id' => 'required|exists:departments,id',
|
||||
'status' => 'nullable|in:active,inactive',
|
||||
]);
|
||||
|
||||
// Check if department belongs to current company
|
||||
$department = Department::where('id', $validated['department_id'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if (!$department) {
|
||||
return redirect()->back()->with('error', __('Selected department does not belong to your company.'));
|
||||
}
|
||||
|
||||
$designation->update($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Designation updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update designation'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Designation Not Found.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($designationId)
|
||||
{
|
||||
if (Auth::user()->can('delete-designations')) {
|
||||
$designation = Designation::where('id', $designationId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($designation) {
|
||||
try {
|
||||
$designation->delete();
|
||||
return redirect()->back()->with('success', __('Designation deleted successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to delete designation'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Designation Not Found.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleStatus($designationId)
|
||||
{
|
||||
if (Auth::user()->can('toggle-status-designations')) {
|
||||
$designation = Designation::where('id', $designationId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($designation) {
|
||||
try {
|
||||
$designation->status = $designation->status === 'active' ? 'inactive' : 'active';
|
||||
$designation->save();
|
||||
return redirect()->back()->with('success', __('Designation status updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update designation status'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Designation Not Found.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
254
app/Http/Controllers/DocumentAcknowledgmentController.php
Normal file
254
app/Http/Controllers/DocumentAcknowledgmentController.php
Normal file
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\DocumentAcknowledgment;
|
||||
use App\Models\HrDocument;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class DocumentAcknowledgmentController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-document-acknowledgments')) {
|
||||
$query = DocumentAcknowledgment::with(['document', 'user', 'assignedBy'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-document-acknowledgments')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-document-acknowledgments')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('user_id', Auth::id())->orWhere('assigned_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->whereHas('document', function ($dq) use ($request) {
|
||||
$dq->where('title', 'like', '%' . $request->search . '%');
|
||||
})
|
||||
->orWhereHas('user', function ($uq) use ($request) {
|
||||
$uq->where('name', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('document_id') && !empty($request->document_id) && $request->document_id !== 'all') {
|
||||
$query->where('document_id', $request->document_id);
|
||||
}
|
||||
|
||||
if ($request->has('user_id') && !empty($request->user_id) && $request->user_id !== 'all') {
|
||||
$query->where('user_id', $request->user_id);
|
||||
}
|
||||
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Auto-update overdue acknowledgments
|
||||
DocumentAcknowledgment::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'Pending')
|
||||
->where('due_date', '<', Carbon::today())
|
||||
->update(['status' => 'Overdue']);
|
||||
|
||||
// Handle sorting
|
||||
$allowedSortFields = ['id', 'status', 'due_date', 'acknowledged_at', 'assigned_at', 'created_at'];
|
||||
if ($request->has('sort_field') && !empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field;
|
||||
if ($sortField === 'acknowledge') $sortField = 'acknowledged_at';
|
||||
if ($sortField === 'assigned') $sortField = 'assigned_at';
|
||||
|
||||
if (in_array($sortField, $allowedSortFields)) {
|
||||
$sortDirection = in_array($request->sort_direction, ['asc', 'desc']) ? $request->sort_direction : 'asc';
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
$documentAcknowledgments = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$documents = HrDocument::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('requires_acknowledgment', true)
|
||||
->select('id', 'title')
|
||||
->get();
|
||||
|
||||
$users = User::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('type', 'employee')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/documents/document-acknowledgments/index', [
|
||||
'documentAcknowledgments' => $documentAcknowledgments,
|
||||
'documents' => $documents,
|
||||
'users' => $users,
|
||||
'filters' => $request->all(['search', 'document_id', 'user_id', 'status', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'document_id' => 'required|exists:hr_documents,id',
|
||||
'user_id' => 'required|exists:users,id',
|
||||
'due_date' => 'nullable|date|after_or_equal:today',
|
||||
'acknowledgment_note' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if acknowledgment already exists
|
||||
$existing = DocumentAcknowledgment::where('document_id', $request->document_id)
|
||||
->where('user_id', $request->user_id)
|
||||
->first();
|
||||
|
||||
if ($existing) {
|
||||
return redirect()->back()->with('error', __('Acknowledgment already exists for this user and document'));
|
||||
}
|
||||
|
||||
DocumentAcknowledgment::create([
|
||||
'document_id' => $request->document_id,
|
||||
'user_id' => $request->user_id,
|
||||
'due_date' => $request->due_date ?? Carbon::now()->addDays(7),
|
||||
'acknowledgment_note' => $request->acknowledgment_note,
|
||||
'assigned_by' => creatorId(),
|
||||
'assigned_at' => now(),
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Document acknowledgment assigned successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, DocumentAcknowledgment $documentAcknowledgment)
|
||||
{
|
||||
if (!in_array($documentAcknowledgment->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this acknowledgment'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'document_id' => 'required|exists:hr_documents,id',
|
||||
'user_id' => 'required|exists:users,id',
|
||||
'status' => 'required|in:Pending,Acknowledged,Overdue,Exempted',
|
||||
'due_date' => 'nullable|date',
|
||||
'acknowledgment_note' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator->errors())->withInput();
|
||||
}
|
||||
|
||||
// Check if acknowledgment already exists for different document/user combination
|
||||
if ($request->document_id != $documentAcknowledgment->document_id || $request->user_id != $documentAcknowledgment->user_id) {
|
||||
$existing = DocumentAcknowledgment::where('document_id', $request->document_id)
|
||||
->where('user_id', $request->user_id)
|
||||
->where('id', '!=', $documentAcknowledgment->id)
|
||||
->first();
|
||||
|
||||
if ($existing) {
|
||||
return redirect()->back()->with('error', __('Acknowledgment already exists for this user and document'));
|
||||
}
|
||||
}
|
||||
|
||||
$updateData = [
|
||||
'document_id' => $request->document_id,
|
||||
'user_id' => $request->user_id,
|
||||
'status' => $request->status,
|
||||
'due_date' => $request->due_date,
|
||||
'acknowledgment_note' => $request->acknowledgment_note,
|
||||
];
|
||||
|
||||
if ($request->status === 'Acknowledged' && !$documentAcknowledgment->acknowledged_at) {
|
||||
$updateData['acknowledged_at'] = now();
|
||||
$updateData['ip_address'] = $request->ip();
|
||||
$updateData['user_agent'] = $request->userAgent();
|
||||
}
|
||||
|
||||
$documentAcknowledgment->update($updateData);
|
||||
|
||||
return redirect()->back()->with('success', __('Document acknowledgment updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(DocumentAcknowledgment $documentAcknowledgment)
|
||||
{
|
||||
if (!in_array($documentAcknowledgment->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this acknowledgment'));
|
||||
}
|
||||
|
||||
$documentAcknowledgment->delete();
|
||||
return redirect()->back()->with('success', __('Document acknowledgment deleted successfully'));
|
||||
}
|
||||
|
||||
public function acknowledge(Request $request, DocumentAcknowledgment $documentAcknowledgment)
|
||||
{
|
||||
if (!in_array($documentAcknowledgment->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to acknowledge this document'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'acknowledgment_note' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator);
|
||||
}
|
||||
|
||||
$documentAcknowledgment->update([
|
||||
'status' => 'Acknowledged',
|
||||
'acknowledged_at' => now(),
|
||||
'acknowledgment_note' => $request->acknowledgment_note ?? 'Document acknowledged',
|
||||
'ip_address' => $request->ip(),
|
||||
'user_agent' => $request->userAgent(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Document acknowledged successfully'));
|
||||
}
|
||||
|
||||
public function bulkAssign(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'document_id' => 'required|exists:hr_documents,id',
|
||||
'user_ids' => 'required|array',
|
||||
'user_ids.*' => 'exists:users,id',
|
||||
'due_date' => 'nullable|date|after_or_equal:today',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$assignedCount = 0;
|
||||
$dueDate = $request->due_date ?? Carbon::now()->addDays(7);
|
||||
|
||||
foreach ($request->user_ids as $userId) {
|
||||
// Check if acknowledgment already exists
|
||||
$existing = DocumentAcknowledgment::where('document_id', $request->document_id)
|
||||
->where('user_id', $userId)
|
||||
->first();
|
||||
|
||||
if (!$existing) {
|
||||
DocumentAcknowledgment::create([
|
||||
'document_id' => $request->document_id,
|
||||
'user_id' => $userId,
|
||||
'due_date' => $dueDate,
|
||||
'assigned_by' => creatorId(),
|
||||
'assigned_at' => now(),
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
$assignedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('Document assigned to :count users successfully', ['count' => $assignedCount]));
|
||||
}
|
||||
}
|
||||
155
app/Http/Controllers/DocumentCategoryController.php
Normal file
155
app/Http/Controllers/DocumentCategoryController.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\DocumentCategory;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class DocumentCategoryController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-document-categories')) {
|
||||
$query = DocumentCategory::withCount('documents')->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-document-categories')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-document-categories')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
if ($request->has('is_mandatory') && $request->is_mandatory !== 'all') {
|
||||
$query->where('is_mandatory', $request->is_mandatory === 'true');
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$allowedSortFields = ['id', 'name', 'status', 'sort_order', 'is_mandatory', 'created_at'];
|
||||
if ($request->has('sort_field') && !empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field === 'category' ? 'name' : $request->sort_field;
|
||||
if (in_array($sortField, $allowedSortFields)) {
|
||||
$sortDirection = in_array($request->sort_direction, ['asc', 'desc']) ? $request->sort_direction : 'asc';
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('sort_order', 'asc')->orderBy('id', 'desc');
|
||||
}
|
||||
} else {
|
||||
$query->orderBy('sort_order', 'asc')->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
$documentCategories = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
return Inertia::render('hr/documents/document-categories/index', [
|
||||
'documentCategories' => $documentCategories,
|
||||
'filters' => $request->all(['search', 'status', 'is_mandatory', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'color' => 'required|string|regex:/^#[0-9A-Fa-f]{6}$/',
|
||||
'icon' => 'required|string|max:50',
|
||||
'sort_order' => 'nullable|integer|min:0',
|
||||
'is_mandatory' => 'boolean',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
DocumentCategory::create([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'color' => $request->color,
|
||||
'icon' => $request->icon,
|
||||
'sort_order' => $request->sort_order ?? 0,
|
||||
'is_mandatory' => $request->boolean('is_mandatory'),
|
||||
'status' => $request->status ?? 'active',
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Document category created successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, DocumentCategory $documentCategory)
|
||||
{
|
||||
if (!in_array($documentCategory->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this category'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'color' => 'required|string|regex:/^#[0-9A-Fa-f]{6}$/',
|
||||
'icon' => 'required|string|max:50',
|
||||
'sort_order' => 'nullable|integer|min:0',
|
||||
'is_mandatory' => 'boolean',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$documentCategory->update([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'color' => $request->color,
|
||||
'icon' => $request->icon,
|
||||
'sort_order' => $request->sort_order ?? 0,
|
||||
'is_mandatory' => $request->boolean('is_mandatory'),
|
||||
'status' => $request->status ?? 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Document category updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(DocumentCategory $documentCategory)
|
||||
{
|
||||
if (!in_array($documentCategory->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this category'));
|
||||
}
|
||||
|
||||
if ($documentCategory->documents()->count() > 0) {
|
||||
return redirect()->back()->with('error', __('Cannot delete category as it contains documents'));
|
||||
}
|
||||
|
||||
$documentCategory->delete();
|
||||
return redirect()->back()->with('success', __('Document category deleted successfully'));
|
||||
}
|
||||
|
||||
public function toggleStatus(DocumentCategory $documentCategory)
|
||||
{
|
||||
if (!in_array($documentCategory->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this category'));
|
||||
}
|
||||
|
||||
$documentCategory->update([
|
||||
'status' => $documentCategory->status === 'active' ? 'inactive' : 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Category status updated successfully'));
|
||||
}
|
||||
}
|
||||
353
app/Http/Controllers/DocumentTemplateController.php
Normal file
353
app/Http/Controllers/DocumentTemplateController.php
Normal file
@@ -0,0 +1,353 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\DocumentCategory;
|
||||
use App\Models\DocumentTemplate;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
use PhpOffice\PhpWord\IOFactory;
|
||||
use PhpOffice\PhpWord\PhpWord;
|
||||
|
||||
class DocumentTemplateController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-document-templates')) {
|
||||
$query = DocumentTemplate::with(['category'])
|
||||
->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-document-templates')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-document-templates')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && ! empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%'.$request->search.'%')
|
||||
->orWhere('description', 'like', '%'.$request->search.'%');
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('category_id') && ! empty($request->category_id) && $request->category_id !== 'all') {
|
||||
$query->where('category_id', $request->category_id);
|
||||
}
|
||||
|
||||
if ($request->has('status') && ! empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
if ($request->has('is_default') && $request->is_default !== 'all') {
|
||||
$query->where('is_default', $request->is_default === 'true');
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$allowedSortFields = ['id', 'name', 'status', 'is_default', 'created_at'];
|
||||
if ($request->has('sort_field') && ! empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field === 'template_name' ? 'name' : ($request->sort_field === 'created' ? 'created_at' : $request->sort_field);
|
||||
if (in_array($sortField, $allowedSortFields)) {
|
||||
$sortDirection = in_array($request->sort_direction, ['asc', 'desc']) ? $request->sort_direction : 'asc';
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('is_default', 'desc')->orderBy('created_at', 'desc');
|
||||
}
|
||||
} else {
|
||||
$query->orderBy('is_default', 'desc')->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
$documentTemplates = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$categories = DocumentCategory::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/documents/document-templates/index', [
|
||||
'documentTemplates' => $documentTemplates,
|
||||
'categories' => $categories,
|
||||
'filters' => $request->all(['search', 'category_id', 'status', 'is_default', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function show(DocumentTemplate $documentTemplate)
|
||||
{
|
||||
if (Auth::user()->can('view-document-templates')) {
|
||||
$documentTemplate->load('category');
|
||||
return Inertia::render('hr/documents/document-templates/show', [
|
||||
'documentTemplate' => $documentTemplate,
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
if (Auth::user()->can('create-document-templates')) {
|
||||
$categories = DocumentCategory::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/documents/document-templates/create', [
|
||||
'categories' => $categories,
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function edit(DocumentTemplate $documentTemplate)
|
||||
{
|
||||
if (Auth::user()->can('edit-document-templates')) {
|
||||
$categories = DocumentCategory::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/documents/document-templates/edit', [
|
||||
'documentTemplate' => $documentTemplate,
|
||||
'categories' => $categories,
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('create-document-templates')) {
|
||||
// Convert placeholders comma-separated string → array
|
||||
$placeholders = null;
|
||||
if ($request->filled('placeholders') && is_string($request->placeholders)) {
|
||||
$placeholders = array_values(array_filter(array_map('trim', explode(',', $request->placeholders))));
|
||||
} elseif (is_array($request->placeholders)) {
|
||||
$placeholders = $request->placeholders;
|
||||
}
|
||||
|
||||
// Convert default_values JSON string → array
|
||||
$defaultValues = null;
|
||||
if ($request->filled('default_values') && is_string($request->default_values)) {
|
||||
$decoded = json_decode($request->default_values, true);
|
||||
$defaultValues = is_array($decoded) ? $decoded : null;
|
||||
} elseif (is_array($request->default_values)) {
|
||||
$defaultValues = $request->default_values;
|
||||
}
|
||||
|
||||
$validator = Validator::make(array_merge($request->all(), [
|
||||
'placeholders' => $placeholders,
|
||||
'default_values' => $defaultValues,
|
||||
]), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'category_id' => 'required|exists:document_categories,id',
|
||||
'template_content' => 'required|string',
|
||||
'placeholders' => 'nullable|array',
|
||||
'default_values' => 'nullable|array',
|
||||
'is_default' => 'boolean',
|
||||
'file_format' => 'nullable|string|in:pdf,doc,docx,txt',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
if ($request->boolean('is_default')) {
|
||||
DocumentTemplate::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('category_id', $request->category_id)
|
||||
->where('is_default', true)
|
||||
->update(['is_default' => false]);
|
||||
}
|
||||
|
||||
DocumentTemplate::create([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'category_id' => $request->category_id,
|
||||
'template_content' => $request->template_content,
|
||||
'placeholders' => $placeholders,
|
||||
'default_values' => $defaultValues,
|
||||
'is_default' => $request->boolean('is_default'),
|
||||
'file_format' => $request->file_format ?? 'pdf',
|
||||
'status' => $request->status ?? 'active',
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->route('hr.documents.document-templates.index')
|
||||
->with('success', __('Document template created successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function update(Request $request, DocumentTemplate $documentTemplate)
|
||||
{
|
||||
if (Auth::user()->can('edit-document-templates')) {
|
||||
// Convert placeholders comma-separated string → array
|
||||
$placeholders = null;
|
||||
if ($request->filled('placeholders') && is_string($request->placeholders)) {
|
||||
$placeholders = array_values(array_filter(array_map('trim', explode(',', $request->placeholders))));
|
||||
} elseif (is_array($request->placeholders)) {
|
||||
$placeholders = $request->placeholders;
|
||||
}
|
||||
|
||||
// Convert default_values JSON string → array
|
||||
$defaultValues = null;
|
||||
if ($request->filled('default_values') && is_string($request->default_values)) {
|
||||
$decoded = json_decode($request->default_values, true);
|
||||
$defaultValues = is_array($decoded) ? $decoded : null;
|
||||
} elseif (is_array($request->default_values)) {
|
||||
$defaultValues = $request->default_values;
|
||||
}
|
||||
|
||||
$validator = Validator::make(array_merge($request->all(), [
|
||||
'placeholders' => $placeholders,
|
||||
'default_values' => $defaultValues,
|
||||
]), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'category_id' => 'required|exists:document_categories,id',
|
||||
'template_content' => 'required|string',
|
||||
'placeholders' => 'nullable|array',
|
||||
'default_values' => 'nullable|array',
|
||||
'is_default' => 'boolean',
|
||||
'file_format' => 'nullable|string|in:pdf,doc,docx,txt',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
if ($request->boolean('is_default') && ! $documentTemplate->is_default) {
|
||||
DocumentTemplate::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('category_id', $request->category_id)
|
||||
->where('is_default', true)
|
||||
->update(['is_default' => false]);
|
||||
}
|
||||
|
||||
$documentTemplate->update([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'category_id' => $request->category_id,
|
||||
'template_content' => $request->template_content,
|
||||
'placeholders' => $placeholders,
|
||||
'default_values' => $defaultValues,
|
||||
'is_default' => $request->boolean('is_default'),
|
||||
'file_format' => $request->file_format ?? 'pdf',
|
||||
'status' => $request->status ?? 'active',
|
||||
]);
|
||||
|
||||
return redirect()->route('hr.documents.document-templates.index')
|
||||
->with('success', __('Document template updated successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(DocumentTemplate $documentTemplate)
|
||||
{
|
||||
if (Auth::user()->can('delete-document-templates')) {
|
||||
try {
|
||||
$documentTemplate->delete();
|
||||
return redirect()->back()->with('success', __('Document template deleted successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to delete document template'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleStatus(DocumentTemplate $documentTemplate)
|
||||
{
|
||||
if (Auth::user()->can('edit-document-templates')) {
|
||||
try {
|
||||
$documentTemplate->update([
|
||||
'status' => $documentTemplate->status === 'active' ? 'inactive' : 'active',
|
||||
]);
|
||||
return redirect()->back()->with('success', __('Template status updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update template status'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function preview(Request $request, DocumentTemplate $documentTemplate)
|
||||
{
|
||||
if (Auth::user()->can('view-document-templates')) {
|
||||
$values = $request->get('values', []);
|
||||
$generatedContent = $documentTemplate->generateDocument($values);
|
||||
|
||||
return response()->json([
|
||||
'content' => $generatedContent,
|
||||
'placeholders' => $documentTemplate->getPlaceholderList(),
|
||||
'default_values' => $documentTemplate->default_values,
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function generate(Request $request, DocumentTemplate $documentTemplate)
|
||||
{
|
||||
$values = $request->values ?? [];
|
||||
|
||||
if (!is_array($values)) {
|
||||
$values = [];
|
||||
}
|
||||
|
||||
$generatedContent = $documentTemplate->generateDocument($values);
|
||||
$filename = $request->filename ?? ($documentTemplate->name.'_'.date('Y-m-d'));
|
||||
$fileFormat = $documentTemplate->file_format ?? 'txt';
|
||||
|
||||
switch ($fileFormat) {
|
||||
case 'pdf':
|
||||
$html = '<div style="font-family: Arial, sans-serif; line-height: 1.6; padding: 20px;">'.nl2br($generatedContent).'</div>';
|
||||
$pdf = Pdf::loadHTML($html);
|
||||
return $pdf->download($filename.'.pdf');
|
||||
|
||||
case 'doc':
|
||||
case 'docx':
|
||||
$phpWord = new PhpWord;
|
||||
$section = $phpWord->addSection();
|
||||
|
||||
$lines = explode("\n", $generatedContent);
|
||||
foreach ($lines as $line) {
|
||||
if (trim($line) !== '') {
|
||||
$section->addText($line);
|
||||
} else {
|
||||
$section->addTextBreak();
|
||||
}
|
||||
}
|
||||
|
||||
$writer = IOFactory::createWriter($phpWord, $fileFormat === 'docx' ? 'Word2007' : 'RTF');
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'document');
|
||||
$writer->save($tempFile);
|
||||
|
||||
$contentType = $fileFormat === 'docx'
|
||||
? 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
: 'application/msword';
|
||||
|
||||
return response()->download($tempFile, $filename.'.'.$fileFormat, [
|
||||
'Content-Type' => $contentType,
|
||||
])->deleteFileAfterSend(true);
|
||||
|
||||
default: // txt
|
||||
return response($generatedContent)
|
||||
->header('Content-Type', 'text/plain')
|
||||
->header('Content-Disposition', 'attachment; filename="'.$filename.'.txt"');
|
||||
}
|
||||
}
|
||||
}
|
||||
145
app/Http/Controllers/DocumentTypeController.php
Normal file
145
app/Http/Controllers/DocumentTypeController.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\DocumentType;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Inertia\Inertia;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DocumentTypeController extends Controller
|
||||
{
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-document-types')) {
|
||||
$query = DocumentType::where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-document-types')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-document-types')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle required filter
|
||||
if ($request->has('required') && $request->required !== 'all') {
|
||||
$isRequired = $request->required === 'yes';
|
||||
$query->where('is_required', $isRequired);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field', 'created_at');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['name', 'created_at', 'id'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'created_at';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
$documentTypes = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
// Cast is_required to boolean for each document type
|
||||
$documentTypes->getCollection()->transform(function ($documentType) {
|
||||
$documentType->is_required = (bool) $documentType->is_required;
|
||||
return $documentType;
|
||||
});
|
||||
|
||||
return Inertia::render('hr/document-types/index', [
|
||||
'documentTypes' => $documentTypes,
|
||||
'filters' => $request->all(['search', 'sort_field', 'sort_direction', 'per_page', 'required']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('create-document-types')) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'is_required' => 'boolean',
|
||||
]);
|
||||
|
||||
$validated['created_by'] = creatorId();
|
||||
|
||||
DocumentType::create($validated);
|
||||
return redirect()->back()->with('success', __('Document type created successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to create document type'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function update(Request $request, $documentTypeId)
|
||||
{
|
||||
if (Auth::user()->can('edit-document-types')) {
|
||||
$documentType = DocumentType::where('id', $documentTypeId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($documentType) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'is_required' => 'boolean',
|
||||
]);
|
||||
|
||||
$documentType->update($validated);
|
||||
return redirect()->back()->with('success', __('Document type updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update document type'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Document Type Not Found.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function destroy($documentTypeId)
|
||||
{
|
||||
if (Auth::user()->can('delete-document-types')) {
|
||||
$documentType = DocumentType::where('id', $documentTypeId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($documentType) {
|
||||
try {
|
||||
$documentType->delete();
|
||||
return redirect()->back()->with('success', __('Document type deleted successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to delete document type'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Document Type Not Found.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
203
app/Http/Controllers/EasebuzzPaymentController.php
Normal file
203
app/Http/Controllers/EasebuzzPaymentController.php
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Plan;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EasebuzzPaymentController extends Controller
|
||||
{
|
||||
public function processPayment(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request, [
|
||||
'easepayid' => 'required|string',
|
||||
'status' => 'required|string',
|
||||
]);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!isset($settings['payment_settings']['easebuzz_merchant_key'])) {
|
||||
return back()->withErrors(['error' => __('Easebuzz not configured')]);
|
||||
}
|
||||
|
||||
if ($validated['status'] === 'success') {
|
||||
processPaymentSuccess([
|
||||
'user_id' => auth()->id(),
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => $validated['billing_cycle'],
|
||||
'payment_method' => 'easebuzz',
|
||||
'coupon_code' => $validated['coupon_code'] ?? null,
|
||||
'payment_id' => $validated['easepayid'],
|
||||
]);
|
||||
|
||||
return back()->with('success', __('Payment successful and plan activated'));
|
||||
}
|
||||
|
||||
return back()->withErrors(['error' => __('Payment failed or cancelled')]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return handlePaymentError($e, 'easebuzz');
|
||||
}
|
||||
}
|
||||
|
||||
public function createPayment(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$pricing = calculatePlanPricing($plan, $validated['coupon_code'] ?? null);
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!isset($settings['payment_settings']['easebuzz_merchant_key']) || !isset($settings['payment_settings']['easebuzz_salt_key'])) {
|
||||
return response()->json(['error' => __('Easebuzz not configured')], 400);
|
||||
}
|
||||
|
||||
// Include Easebuzz library
|
||||
require_once app_path('Libraries/Easebuzz/easebuzz_payment_gateway.php');
|
||||
|
||||
$user = auth()->user();
|
||||
$txnid = 'plan_' . $plan->id . '_' . $user->id . '_' . time();
|
||||
$environment = $settings['payment_settings']['easebuzz_environment'] === 'prod' ? 'prod' : 'test';
|
||||
|
||||
// Initialize Easebuzz
|
||||
$easebuzz = new \Easebuzz(
|
||||
$settings['payment_settings']['easebuzz_merchant_key'],
|
||||
$settings['payment_settings']['easebuzz_salt_key'],
|
||||
$environment
|
||||
);
|
||||
|
||||
$postData = [
|
||||
'txnid' => $txnid,
|
||||
'amount' => number_format($pricing['final_price'], 2, '.', ''),
|
||||
'productinfo' => $plan->name,
|
||||
'firstname' => $user->name ?? 'Customer',
|
||||
'email' => $user->email,
|
||||
'phone' => '9999999999',
|
||||
'surl' => route('easebuzz.success'),
|
||||
'furl' => route('plans.index'),
|
||||
'udf1' => $validated['billing_cycle'],
|
||||
'udf2' => $validated['coupon_code'] ?? '',
|
||||
];
|
||||
|
||||
// Use Easebuzz library to initiate payment
|
||||
$result = $easebuzz->initiatePaymentAPI($postData, false);
|
||||
|
||||
$resultArray = json_decode($result, true);
|
||||
|
||||
if ($resultArray && isset($resultArray['status']) && $resultArray['status'] == 1) {
|
||||
$accessKey = $resultArray['access_key'] ?? null;
|
||||
if ($accessKey) {
|
||||
$baseUrl = $settings['payment_settings']['easebuzz_environment'] === 'prod'
|
||||
? 'https://pay.easebuzz.in'
|
||||
: 'https://testpay.easebuzz.in';
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'payment_url' => $baseUrl . '/pay/' . $accessKey,
|
||||
'transaction_id' => $txnid
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['error' => 'Payment initialization failed'], 400);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Payment creation failed')], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function success(Request $request)
|
||||
{
|
||||
try {
|
||||
// Include Easebuzz library
|
||||
require_once app_path('Libraries/Easebuzz/easebuzz_payment_gateway.php');
|
||||
|
||||
$settings = getPaymentGatewaySettings();
|
||||
$environment = $settings['payment_settings']['easebuzz_environment'] === 'prod' ? 'prod' : 'test';
|
||||
|
||||
$easebuzz = new \Easebuzz(
|
||||
$settings['payment_settings']['easebuzz_merchant_key'],
|
||||
$settings['payment_settings']['easebuzz_salt_key'],
|
||||
$environment
|
||||
);
|
||||
|
||||
// Verify payment response
|
||||
$result = $easebuzz->easebuzzResponse($request->all());
|
||||
$resultArray = json_decode($result, true);
|
||||
|
||||
if ($resultArray && $resultArray['status'] == 1 && $request->input('status') === 'success') {
|
||||
$txnid = $request->input('txnid');
|
||||
$parts = explode('_', $txnid);
|
||||
|
||||
if (count($parts) >= 3) {
|
||||
$planId = $parts[1];
|
||||
$userId = $parts[2];
|
||||
|
||||
$plan = Plan::find($planId);
|
||||
$user = User::find($userId);
|
||||
|
||||
if ($plan && $user) {
|
||||
processPaymentSuccess([
|
||||
'user_id' => $user->id,
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => $request->input('udf1', 'monthly'),
|
||||
'payment_method' => 'easebuzz',
|
||||
'payment_id' => $request->input('easepayid'),
|
||||
]);
|
||||
|
||||
// Log the user in if not already authenticated
|
||||
if (!auth()->check()) {
|
||||
auth()->login($user);
|
||||
}
|
||||
|
||||
return redirect()->route('plans.index')->with('success', __('Payment completed successfully and plan activated'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('plans.index')->with('error', __('Payment verification failed'));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->route('plans.index')->with('error', __('Payment processing failed'));
|
||||
}
|
||||
}
|
||||
|
||||
public function callback(Request $request)
|
||||
{
|
||||
try {
|
||||
$txnid = $request->input('txnid');
|
||||
$status = $request->input('status');
|
||||
|
||||
if ($txnid && $status === 'success') {
|
||||
$parts = explode('_', $txnid);
|
||||
|
||||
if (count($parts) >= 3) {
|
||||
$planId = $parts[1];
|
||||
$userId = $parts[2];
|
||||
|
||||
$plan = Plan::find($planId);
|
||||
$user = \App\Models\User::find($userId);
|
||||
|
||||
if ($plan && $user) {
|
||||
processPaymentSuccess([
|
||||
'user_id' => $user->id,
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => $request->input('udf1', 'monthly'),
|
||||
'payment_method' => 'easebuzz',
|
||||
'payment_id' => $request->input('easepayid'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['status' => 'success']);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Callback processing failed')], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
115
app/Http/Controllers/EmailTemplateController.php
Normal file
115
app/Http/Controllers/EmailTemplateController.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\EmailTemplate;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class EmailTemplateController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = EmailTemplate::with('emailTemplateLangs');
|
||||
|
||||
// Search functionality
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('from', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field', 'created_at');
|
||||
$sortDirection = $request->get('sort_direction', 'asc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['name', 'created_at', 'id'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'created_at';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
$templates = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
return Inertia::render('email-templates/index', [
|
||||
'templates' => $templates,
|
||||
'filters' => $request->all(['search', 'sort_field', 'sort_direction', 'per_page'])
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(EmailTemplate $emailTemplate)
|
||||
{
|
||||
$template = $emailTemplate->load('emailTemplateLangs');
|
||||
$languages = json_decode(file_get_contents(resource_path('lang/language.json')), true);
|
||||
|
||||
// Template-specific variables
|
||||
$variables = [];
|
||||
|
||||
if ($template->name === 'Appointment Created') {
|
||||
$variables = [
|
||||
'{app_name}' => 'App Name',
|
||||
'{appointment_name}' => 'Appointment Name',
|
||||
'{appointment_email}' => 'Appointment Email',
|
||||
'{appointment_phone}' => 'Appointment Phone',
|
||||
'{appointment_date}' => 'Appointment Date',
|
||||
'{appointment_time}' => 'Appointment Time'
|
||||
];
|
||||
} elseif ($template->name === 'User Created') {
|
||||
$variables = [
|
||||
'{app_url}' => 'App URL',
|
||||
'{user_name}' => 'User Name',
|
||||
'{user_email}' => 'User Email',
|
||||
'{user_password}' => 'User Password',
|
||||
'{user_type}' => 'User Type'
|
||||
];
|
||||
}
|
||||
|
||||
return Inertia::render('email-templates/show', [
|
||||
'template' => $template,
|
||||
'languages' => $languages,
|
||||
'variables' => $variables
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateSettings(EmailTemplate $emailTemplate, Request $request)
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'from' => 'required|string|max:255'
|
||||
]);
|
||||
|
||||
$emailTemplate->update([
|
||||
'from' => $request->from
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Template settings updated successfully.'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', __('Failed to update template settings: :error', ['error' => $e->getMessage()]));
|
||||
}
|
||||
}
|
||||
|
||||
public function updateContent(EmailTemplate $emailTemplate, Request $request)
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'lang' => 'required|string|max:10',
|
||||
'subject' => 'required|string|max:255',
|
||||
'content' => 'required|string'
|
||||
]);
|
||||
|
||||
$emailTemplate->emailTemplateLangs()
|
||||
->where('lang', $request->lang)
|
||||
->update([
|
||||
'subject' => $request->subject,
|
||||
'content' => $request->content
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Email content updated successfully.'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', __('Failed to update email content: :error', ['error' => $e->getMessage()]));
|
||||
}
|
||||
}
|
||||
}
|
||||
281
app/Http/Controllers/EmployeeContractController.php
Normal file
281
app/Http/Controllers/EmployeeContractController.php
Normal file
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\ContractType;
|
||||
use App\Models\EmployeeContract;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class EmployeeContractController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-employee-contracts')) {
|
||||
$query = EmployeeContract::with(['employee', 'contractType', 'approver'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-awards')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-employee-contracts')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('employee_id', Auth::id())->orWhere('approved_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && ! empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('contract_number', 'like', '%'.$request->search.'%')
|
||||
->orWhereHas('employee', function ($eq) use ($request) {
|
||||
$eq->where('name', 'like', '%'.$request->search.'%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('status') && ! empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
if ($request->has('contract_type_id') && ! empty($request->contract_type_id) && $request->contract_type_id !== 'all') {
|
||||
$query->where('contract_type_id', $request->contract_type_id);
|
||||
}
|
||||
|
||||
if ($request->has('employee_id') && ! empty($request->employee_id) && $request->employee_id !== 'all') {
|
||||
$query->where('employee_id', $request->employee_id);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$allowedSortFields = ['id', 'contract_number', 'start_date', 'end_date', 'basic_salary', 'status', 'created_at'];
|
||||
|
||||
if ($request->has('sort_field') && ! empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field;
|
||||
$sortDirection = in_array($request->sort_direction, ['asc', 'desc']) ? $request->sort_direction : 'asc';
|
||||
|
||||
if ($sortField === 'contract' || $sortField === 'contract_type') {
|
||||
$query->join('contract_types', 'employee_contracts.contract_type_id', '=', 'contract_types.id')
|
||||
->select('employee_contracts.*')
|
||||
->orderBy('contract_types.name', $sortDirection);
|
||||
} elseif ($sortField === 'contract_period') {
|
||||
$query->orderBy('start_date', $sortDirection);
|
||||
} elseif (in_array($sortField, $allowedSortFields)) {
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
// Auto-update expired contracts
|
||||
EmployeeContract::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'Active')
|
||||
->where('end_date', '<', Carbon::today())
|
||||
->update(['status' => 'Expired']);
|
||||
$employeeContracts = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$employeeContracts->getCollection()->transform(function ($contract) {
|
||||
if ($contract->employee) {
|
||||
$rawAvatar = $contract->employee->getRawOriginal('avatar');
|
||||
$contract->employee->avatar = check_file($rawAvatar)
|
||||
? get_file($rawAvatar)
|
||||
: get_file('avatars/avatar.png');
|
||||
}
|
||||
|
||||
return $contract;
|
||||
});
|
||||
|
||||
$contractTypes = ContractType::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
$employees = User::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('type', 'employee')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/contracts/employee-contracts/index', [
|
||||
'employeeContracts' => $employeeContracts,
|
||||
'contractTypes' => $contractTypes,
|
||||
'employees' => $employees,
|
||||
'filters' => $request->all(['search', 'status', 'contract_type_id', 'employee_id', 'per_page', 'sort_field', 'sort_direction']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'contract_type_id' => 'required|exists:contract_types,id',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'nullable|date|after:start_date',
|
||||
'basic_salary' => 'required|numeric|min:0',
|
||||
'terms_conditions' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check for duplicate contract
|
||||
$existingContract = EmployeeContract::where('employee_id', $request->employee_id)
|
||||
->where('contract_type_id', $request->contract_type_id)
|
||||
->where('start_date', $request->start_date)
|
||||
->where('end_date', $request->end_date)
|
||||
->first();
|
||||
|
||||
if ($existingContract) {
|
||||
return redirect()->back()->with('error', __('A contract with the same details already exists for this employee.'));
|
||||
}
|
||||
|
||||
// Generate contract number
|
||||
$lastContract = EmployeeContract::whereIn('created_by', getCompanyAndUsersId())
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
$nextNumber = $lastContract ? (intval(substr($lastContract->contract_number, -4)) + 1) : 1;
|
||||
$contractNumber = 'CON-'.str_pad(creatorId(), 3, '0', STR_PAD_LEFT).'-'.str_pad($nextNumber, 4, '0', STR_PAD_LEFT);
|
||||
|
||||
EmployeeContract::create([
|
||||
'contract_number' => $contractNumber,
|
||||
'employee_id' => $request->employee_id,
|
||||
'contract_type_id' => $request->contract_type_id,
|
||||
'start_date' => $request->start_date,
|
||||
'end_date' => $request->end_date,
|
||||
'basic_salary' => $request->basic_salary,
|
||||
'terms_conditions' => $request->terms_conditions,
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Employee contract created successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, EmployeeContract $employeeContract)
|
||||
{
|
||||
if (! in_array($employeeContract->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this contract'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'contract_type_id' => 'required|exists:contract_types,id',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'nullable|date|after:start_date',
|
||||
'basic_salary' => 'required|numeric|min:0',
|
||||
'terms_conditions' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check for duplicate contract (excluding current contract)
|
||||
$existingContract = EmployeeContract::where('employee_id', $request->employee_id)
|
||||
->where('contract_type_id', $request->contract_type_id)
|
||||
->where('start_date', $request->start_date)
|
||||
->where('end_date', $request->end_date)
|
||||
->where('id', '!=', $employeeContract->id)
|
||||
->first();
|
||||
|
||||
if ($existingContract) {
|
||||
return redirect()->back()->with('error', __('A contract with the same details already exists for this employee.'));
|
||||
}
|
||||
|
||||
$employeeContract->update([
|
||||
'employee_id' => $request->employee_id,
|
||||
'contract_type_id' => $request->contract_type_id,
|
||||
'start_date' => $request->start_date,
|
||||
'end_date' => $request->end_date,
|
||||
'basic_salary' => $request->basic_salary,
|
||||
'terms_conditions' => $request->terms_conditions,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Employee contract updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(EmployeeContract $employeeContract)
|
||||
{
|
||||
if (! in_array($employeeContract->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this contract'));
|
||||
}
|
||||
|
||||
if ($employeeContract->status === 'Active') {
|
||||
return redirect()->back()->with('error', __('Cannot delete active contract'));
|
||||
}
|
||||
|
||||
$employeeContract->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('Employee contract deleted successfully'));
|
||||
}
|
||||
|
||||
public function updateStatus(Request $request, EmployeeContract $employeeContract)
|
||||
{
|
||||
if (Auth::user()->can('approve-employee-contracts') || Auth::user()->can('reject-employee-contracts')) {
|
||||
$validator = Validator::make($request->all(), [
|
||||
'status' => 'required|in:Draft,Pending Approval,Active,Expired,Terminated,Renewed',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator);
|
||||
}
|
||||
|
||||
$updateData = ['status' => $request->status];
|
||||
|
||||
if ($request->status === 'Active') {
|
||||
$updateData['approved_by'] = creatorId();
|
||||
$updateData['approved_at'] = now();
|
||||
}
|
||||
|
||||
$employeeContract->update($updateData);
|
||||
|
||||
return redirect()->back()->with('success', __('Contract status updated successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this contract'));
|
||||
}
|
||||
}
|
||||
|
||||
public function approve(Request $request, EmployeeContract $employeeContract)
|
||||
{
|
||||
if (! in_array($employeeContract->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to approve this contract'));
|
||||
}
|
||||
|
||||
$employeeContract->update([
|
||||
'status' => 'Active',
|
||||
'approved_by' => creatorId(),
|
||||
'approved_at' => now(),
|
||||
'approval_notes' => $request->approval_notes,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Contract approved successfully'));
|
||||
}
|
||||
|
||||
public function reject(Request $request, EmployeeContract $employeeContract)
|
||||
{
|
||||
if (! in_array($employeeContract->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to reject this contract'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'rejection_reason' => 'required|string|max:1000',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator);
|
||||
}
|
||||
|
||||
$employeeContract->update([
|
||||
'status' => 'Draft',
|
||||
'rejection_reason' => $request->rejection_reason,
|
||||
'rejected_by' => creatorId(),
|
||||
'rejected_at' => now(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Contract rejected successfully'));
|
||||
}
|
||||
}
|
||||
1393
app/Http/Controllers/EmployeeController.php
Normal file
1393
app/Http/Controllers/EmployeeController.php
Normal file
File diff suppressed because it is too large
Load Diff
284
app/Http/Controllers/EmployeeGoalController.php
Normal file
284
app/Http/Controllers/EmployeeGoalController.php
Normal file
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Employee;
|
||||
use App\Models\User;
|
||||
use App\Models\EmployeeGoal;
|
||||
use App\Models\GoalType;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class EmployeeGoalController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-employee-goals')) {
|
||||
$query = EmployeeGoal::with(['employee', 'goalType'])
|
||||
->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-employee-goals')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-employee-goals')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('employee_id', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('title', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%')
|
||||
->orWhere('target', 'like', '%' . $request->search . '%')
|
||||
->orWhereHas('employee', function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('employee_id', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle employee filter
|
||||
if ($request->has('employee_id') && !empty($request->employee_id)) {
|
||||
$query->where('employee_id', $request->employee_id);
|
||||
}
|
||||
|
||||
// Handle goal type filter
|
||||
if ($request->has('goal_type_id') && !empty($request->goal_type_id)) {
|
||||
$query->where('goal_type_id', $request->goal_type_id);
|
||||
}
|
||||
|
||||
// Handle status filter
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field', 'created_at');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['title', 'start_date', 'end_date', 'created_at', 'id'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'created_at';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
$goals = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
|
||||
// Get goal types for filter dropdown
|
||||
$goalTypes = GoalType::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->orderBy('name')
|
||||
->get(['id', 'name']);
|
||||
|
||||
return Inertia::render('hr/performance/employee-goals/index', [
|
||||
'goals' => $goals,
|
||||
'employees' => $this->getFilteredEmployees(),
|
||||
'goalTypes' => $goalTypes,
|
||||
'filters' => $request->all(['search', 'employee_id', 'goal_type_id', 'status', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
private function getFilteredEmployees()
|
||||
{
|
||||
// Get employees for filter dropdown (compatible with getFilteredEmployees logic)
|
||||
$employeeQuery = Employee::whereIn('created_by', getCompanyAndUsersId());
|
||||
|
||||
if (Auth::user()->can('manage-own-employee-goals') && !Auth::user()->can('manage-any-employee-goals')) {
|
||||
$employeeQuery->where(function ($q) {
|
||||
$q->where('created_by', Auth::id())->orWhere('user_id', Auth::id());
|
||||
});
|
||||
}
|
||||
|
||||
$employees = User::emp()
|
||||
->with('employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->whereIn('id', $employeeQuery->pluck('user_id'))
|
||||
->select('id', 'name')
|
||||
->get()
|
||||
->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'employee_id' => $user->employee->employee_id ?? ''
|
||||
];
|
||||
});
|
||||
return $employees;
|
||||
}
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('create-employee-goals')) {
|
||||
$validator = Validator::make($request->all(), [
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'goal_type_id' => 'required|exists:goal_types,id',
|
||||
'title' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'required|date|after_or_equal:start_date',
|
||||
'target' => 'nullable|string|max:255',
|
||||
'progress' => 'nullable|integer|min:0|max:100',
|
||||
'status' => 'nullable|string|in:not_started,in_progress,completed',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Verify employee belongs to current company
|
||||
$employee = User::find($request->employee_id);
|
||||
if (!$employee || !in_array($employee->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('Invalid employee selected'))->withInput();
|
||||
}
|
||||
|
||||
// Verify goal type belongs to current company
|
||||
$goalType = GoalType::find($request->goal_type_id);
|
||||
if (!$goalType || !in_array($goalType->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('Invalid goal type selected'))->withInput();
|
||||
}
|
||||
|
||||
EmployeeGoal::create([
|
||||
'employee_id' => $employee->id,
|
||||
'goal_type_id' => $request->goal_type_id,
|
||||
'title' => $request->title,
|
||||
'description' => $request->description,
|
||||
'start_date' => $request->start_date,
|
||||
'end_date' => $request->end_date,
|
||||
'target' => $request->target,
|
||||
'progress' => $request->progress ?? 0,
|
||||
'status' => $request->status ?? 'not_started',
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Employee goal created successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, EmployeeGoal $employeeGoal)
|
||||
{
|
||||
if (Auth::user()->can('edit-employee-goals')) {
|
||||
// Check if goal belongs to current company
|
||||
if (!in_array($employeeGoal->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this goal'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'goal_type_id' => 'required|exists:goal_types,id',
|
||||
'title' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'required|date|after_or_equal:start_date',
|
||||
'target' => 'nullable|string|max:255',
|
||||
'progress' => 'nullable|integer|min:0|max:100',
|
||||
'status' => 'nullable|string|in:not_started,in_progress,completed',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Verify employee belongs to current company
|
||||
$employee = User::find($request->employee_id);
|
||||
if (!$employee || !in_array($employee->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('Invalid employee selected'))->withInput();
|
||||
}
|
||||
|
||||
// Verify goal type belongs to current company
|
||||
$goalType = GoalType::find($request->goal_type_id);
|
||||
if (!$goalType || !in_array($goalType->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('Invalid goal type selected'))->withInput();
|
||||
}
|
||||
|
||||
$employeeGoal->update([
|
||||
'employee_id' => $employee->id,
|
||||
'goal_type_id' => $request->goal_type_id,
|
||||
'title' => $request->title,
|
||||
'description' => $request->description,
|
||||
'start_date' => $request->start_date,
|
||||
'end_date' => $request->end_date,
|
||||
'target' => $request->target,
|
||||
'progress' => $request->progress ?? $employeeGoal->progress,
|
||||
'status' => $request->status ?? $employeeGoal->status,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Employee goal updated successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(EmployeeGoal $employeeGoal)
|
||||
{
|
||||
if (Auth::user()->can('delete-employee-goals')) {
|
||||
// Check if goal belongs to current company
|
||||
if (!in_array($employeeGoal->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this goal'));
|
||||
}
|
||||
|
||||
$employeeGoal->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('Employee goal deleted successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the progress of the specified resource.
|
||||
*/
|
||||
public function updateProgress(Request $request, EmployeeGoal $employeeGoal)
|
||||
{
|
||||
if (Auth::user()->can('edit-employee-goals')) {
|
||||
// Check if goal belongs to current company
|
||||
if (!in_array($employeeGoal->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this goal'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'progress' => 'required|integer|min:0|max:100',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Update progress and status based on progress value
|
||||
$status = $employeeGoal->status;
|
||||
if ($request->progress == 100) {
|
||||
$status = 'completed';
|
||||
} elseif ($request->progress > 0) {
|
||||
$status = 'in_progress';
|
||||
} elseif ($request->progress == 0) {
|
||||
$status = 'not_started';
|
||||
}
|
||||
|
||||
$employeeGoal->update([
|
||||
'progress' => $request->progress,
|
||||
'status' => $status,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Goal progress updated successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
495
app/Http/Controllers/EmployeeReviewController.php
Normal file
495
app/Http/Controllers/EmployeeReviewController.php
Normal file
@@ -0,0 +1,495 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Employee;
|
||||
use App\Models\User;
|
||||
use App\Models\EmployeeReview;
|
||||
use App\Models\EmployeeReviewRating;
|
||||
use App\Models\PerformanceIndicator;
|
||||
use App\Models\ReviewCycle;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class EmployeeReviewController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-employee-reviews')) {
|
||||
$query = EmployeeReview::with(['employee', 'reviewer', 'reviewCycle'])
|
||||
->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-employee-reviews')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-employee-reviews')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('employee_id', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->whereHas('employee', function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('employee_id', 'like', '%' . $request->search . '%');
|
||||
})
|
||||
->orWhereHas('reviewer', function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('employee_id', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle employee filter
|
||||
if ($request->has('employee_id') && !empty($request->employee_id)) {
|
||||
$query->where('employee_id', $request->employee_id);
|
||||
}
|
||||
|
||||
// Handle reviewer filter
|
||||
if ($request->has('reviewer_id') && !empty($request->reviewer_id)) {
|
||||
$query->where('reviewer_id', $request->reviewer_id);
|
||||
}
|
||||
|
||||
// Handle review cycle filter
|
||||
if ($request->has('review_cycle_id') && !empty($request->review_cycle_id)) {
|
||||
$query->where('review_cycle_id', $request->review_cycle_id);
|
||||
}
|
||||
|
||||
// Handle status filter
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle date range filter
|
||||
if ($request->has('date_from') && !empty($request->date_from)) {
|
||||
$query->whereDate('review_date', '>=', $request->date_from);
|
||||
}
|
||||
if ($request->has('date_to') && !empty($request->date_to)) {
|
||||
$query->whereDate('review_date', '<=', $request->date_to);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field', 'review_date');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['review_date', 'id'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'review_date';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
$reviews = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$reviews->getCollection()->transform(function ($review) {
|
||||
if ($review->employee) {
|
||||
$rawAvatar = $review->employee->getRawOriginal('avatar');
|
||||
$review->employee->avatar = check_file($rawAvatar)
|
||||
? get_file($rawAvatar)
|
||||
: get_file('avatars/avatar.png');
|
||||
}
|
||||
if ($review->reviewer) {
|
||||
$rawAvatar = $review->reviewer->getRawOriginal('avatar');
|
||||
$review->reviewer->avatar = check_file($rawAvatar)
|
||||
? get_file($rawAvatar)
|
||||
: get_file('avatars/avatar.png');
|
||||
}
|
||||
return $review;
|
||||
});
|
||||
|
||||
// Get review cycles for filter dropdown
|
||||
$reviewCycles = ReviewCycle::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->orderBy('name')
|
||||
->get(['id', 'name']);
|
||||
|
||||
return Inertia::render('hr/performance/employee-reviews/index', [
|
||||
'reviews' => $reviews,
|
||||
'employees' => $this->getFilteredEmployees(),
|
||||
'reviewCycles' => $reviewCycles,
|
||||
'filters' => $request->all(['search', 'employee_id', 'reviewer_id', 'review_cycle_id', 'status', 'date_from', 'date_to', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
private function getFilteredEmployees()
|
||||
{
|
||||
// Get employees for filter dropdown (compatible with getFilteredEmployees logic)
|
||||
$employeeQuery = Employee::whereIn('created_by', getCompanyAndUsersId());
|
||||
|
||||
if (Auth::user()->can('manage-own-employee-reviews') && !Auth::user()->can('manage-any-employee-reviews')) {
|
||||
$employeeQuery->where(function ($q) {
|
||||
$q->where('created_by', Auth::id())->orWhere('user_id', Auth::id());
|
||||
});
|
||||
}
|
||||
|
||||
$employees = User::emp()
|
||||
->with('employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->whereIn('id', $employeeQuery->pluck('user_id'))
|
||||
->select('id', 'name', 'type')
|
||||
->get()
|
||||
->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'employee_id' => $user->employee->employee_id ?? '',
|
||||
'type' => $user->type,
|
||||
];
|
||||
});
|
||||
return $employees;
|
||||
}
|
||||
public function create()
|
||||
{
|
||||
if (Auth::user()->can('create-employee-reviews')) {
|
||||
// Get employees for dropdown
|
||||
$employees = $this->getFilteredEmployees();
|
||||
// Get review cycles for dropdown
|
||||
$reviewCycles = ReviewCycle::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->orderBy('name')
|
||||
->get(['id', 'name']);
|
||||
|
||||
return Inertia::render('hr/performance/employee-reviews/create', [
|
||||
'employees' => $employees,
|
||||
'reviewCycles' => $reviewCycles,
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('create-employee-reviews')) {
|
||||
$validator = Validator::make($request->all(), [
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'reviewer_id' => 'required|exists:users,id',
|
||||
'review_cycle_id' => 'required|exists:review_cycles,id',
|
||||
'review_date' => 'required|date',
|
||||
'status' => 'nullable|string|in:scheduled,in_progress,completed',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Verify employee belongs to current company
|
||||
$employee = User::find($request->employee_id);
|
||||
if (!$employee || !in_array($employee->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('Invalid employee selected'))->withInput();
|
||||
}
|
||||
|
||||
// Verify reviewer belongs to current company
|
||||
$reviewer = User::find($request->reviewer_id);
|
||||
if (!$reviewer || !in_array($reviewer->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('Invalid reviewer selected'))->withInput();
|
||||
}
|
||||
|
||||
// Verify review cycle belongs to current company
|
||||
$reviewCycle = ReviewCycle::find($request->review_cycle_id);
|
||||
if (!$reviewCycle || !in_array($reviewCycle->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('Invalid review cycle selected'))->withInput();
|
||||
}
|
||||
|
||||
// Create the review
|
||||
$review = EmployeeReview::create([
|
||||
'employee_id' => $request->employee_id,
|
||||
'reviewer_id' => $request->reviewer_id,
|
||||
'review_cycle_id' => $request->review_cycle_id,
|
||||
'review_date' => $request->review_date,
|
||||
'status' => $request->status ?? 'scheduled',
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->route('hr.performance.employee-reviews.index')->with('success', __('Employee review scheduled successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(EmployeeReview $employeeReview)
|
||||
{
|
||||
if (Auth::user()->can('view-employee-reviews')) {
|
||||
// Check if review belongs to current company
|
||||
if (!in_array($employeeReview->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to view this review'));
|
||||
}
|
||||
|
||||
$employeeReview->load([
|
||||
'employee',
|
||||
'reviewer',
|
||||
'reviewCycle',
|
||||
'ratings.indicator.category'
|
||||
]);
|
||||
|
||||
return Inertia::render('hr/performance/employee-reviews/show', [
|
||||
'review' => $employeeReview,
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for conducting a review.
|
||||
*/
|
||||
public function conduct(EmployeeReview $employeeReview)
|
||||
{
|
||||
if (Auth::user()->can('edit-employee-reviews')) {
|
||||
// Check if review belongs to current company
|
||||
if (!in_array($employeeReview->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to conduct this review'));
|
||||
}
|
||||
|
||||
$employeeReview->load([
|
||||
'employee',
|
||||
'reviewer',
|
||||
'reviewCycle',
|
||||
'ratings.indicator'
|
||||
]);
|
||||
|
||||
// Get all active performance indicators with their categories
|
||||
$indicators = PerformanceIndicator::with('category')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->get()
|
||||
->map(function ($indicator) use ($employeeReview) {
|
||||
// Check if there's an existing rating for this indicator
|
||||
$existingRating = $employeeReview->ratings->where('performance_indicator_id', $indicator->id)->first();
|
||||
|
||||
return [
|
||||
'id' => $indicator->id,
|
||||
'name' => $indicator->name,
|
||||
'description' => $indicator->description,
|
||||
'measurement_unit' => $indicator->measurement_unit,
|
||||
'target_value' => $indicator->target_value,
|
||||
'category' => $indicator->category ? $indicator->category->name : 'Uncategorized',
|
||||
'weight' => 1, // Default weight since templates are removed
|
||||
'rating' => $existingRating ? $existingRating->rating : null,
|
||||
'comments' => $existingRating ? $existingRating->comments : null,
|
||||
];
|
||||
});
|
||||
|
||||
return Inertia::render('hr/performance/employee-reviews/conduct', [
|
||||
'review' => $employeeReview,
|
||||
'indicators' => $indicators,
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit the review ratings.
|
||||
*/
|
||||
public function submitRatings(Request $request, EmployeeReview $employeeReview)
|
||||
{
|
||||
if (Auth::user()->can('edit-employee-reviews')) {
|
||||
// Check if review belongs to current company
|
||||
if (!in_array($employeeReview->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this review'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'ratings' => 'required|array',
|
||||
'ratings.*.indicator_id' => 'required|exists:performance_indicators,id',
|
||||
'ratings.*.rating' => 'required|numeric|min:1|max:5',
|
||||
'ratings.*.comments' => 'nullable|string',
|
||||
'overall_comments' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
// Delete existing ratings
|
||||
$employeeReview->ratings()->delete();
|
||||
|
||||
// Create new ratings
|
||||
$totalRating = 0;
|
||||
$ratingCount = 0;
|
||||
|
||||
foreach ($request->ratings as $ratingData) {
|
||||
// Verify indicator belongs to current company
|
||||
$indicator = PerformanceIndicator::where('id', $ratingData['indicator_id'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if (!$indicator) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$totalRating += $ratingData['rating'];
|
||||
$ratingCount++;
|
||||
|
||||
// Create the rating
|
||||
EmployeeReviewRating::create([
|
||||
'employee_review_id' => $employeeReview->id,
|
||||
'performance_indicator_id' => $ratingData['indicator_id'],
|
||||
'rating' => $ratingData['rating'],
|
||||
'comments' => $ratingData['comments'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
// Calculate overall rating
|
||||
$overallRating = $ratingCount > 0 ? round($totalRating / $ratingCount, 1) : null;
|
||||
|
||||
// Update the review
|
||||
$employeeReview->update([
|
||||
'overall_rating' => $overallRating,
|
||||
'comments' => $request->overall_comments,
|
||||
'status' => 'completed',
|
||||
'completion_date' => now(),
|
||||
]);
|
||||
|
||||
DB::commit();
|
||||
|
||||
return redirect()->route('hr.performance.employee-reviews.show', $employeeReview->id)
|
||||
->with('success', __('Review completed successfully'));
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return redirect()->back()->with('error', __('An error occurred while submitting the review: :message', ['message' => $e->getMessage()]));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, EmployeeReview $employeeReview)
|
||||
{
|
||||
if (Auth::user()->can('edit-employee-reviews')) {
|
||||
// Check if review belongs to current company
|
||||
if (!in_array($employeeReview->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this review'));
|
||||
}
|
||||
|
||||
// Only allow updates if the review is not completed
|
||||
if ($employeeReview->status === 'completed') {
|
||||
return redirect()->back()->with('error', __('Cannot update a completed review'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'reviewer_id' => 'required|exists:users,id',
|
||||
'review_cycle_id' => 'required|exists:review_cycles,id',
|
||||
'review_date' => 'required|date',
|
||||
'status' => 'nullable|string|in:scheduled,in_progress,completed',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Verify employee belongs to current company
|
||||
$employee = User::find($request->employee_id);
|
||||
if (!$employee || !in_array($employee->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'Invalid employee selected')->withInput();
|
||||
}
|
||||
|
||||
// Verify reviewer belongs to current company
|
||||
$reviewer = User::find($request->reviewer_id);
|
||||
if (!$reviewer || !in_array($reviewer->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'Invalid reviewer selected')->withInput();
|
||||
}
|
||||
|
||||
// Verify review cycle belongs to current company
|
||||
$reviewCycle = ReviewCycle::find($request->review_cycle_id);
|
||||
if (!$reviewCycle || !in_array($reviewCycle->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'Invalid review cycle selected')->withInput();
|
||||
}
|
||||
|
||||
// Update the review
|
||||
$employeeReview->update([
|
||||
'employee_id' => $request->employee_id,
|
||||
'reviewer_id' => $request->reviewer_id,
|
||||
'review_cycle_id' => $request->review_cycle_id,
|
||||
'review_date' => $request->review_date,
|
||||
'status' => $request->status ?? $employeeReview->status,
|
||||
]);
|
||||
|
||||
return redirect()->route('hr.performance.employee-reviews.index')->with('success', __('Employee review updated successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(EmployeeReview $employeeReview)
|
||||
{
|
||||
if (Auth::user()->can('delete-employee-reviews')) {
|
||||
// Check if review belongs to current company
|
||||
if (!in_array($employeeReview->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this review'));
|
||||
}
|
||||
|
||||
// Only allow deletion if the review is not completed
|
||||
if ($employeeReview->status === 'completed') {
|
||||
return redirect()->back()->with('error', __('Cannot delete a completed review'));
|
||||
}
|
||||
|
||||
// Delete the review (this will also delete the ratings due to cascade)
|
||||
$employeeReview->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('Employee review deleted successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the status of the specified resource.
|
||||
*/
|
||||
public function updateStatus(Request $request, EmployeeReview $employeeReview)
|
||||
{
|
||||
if (Auth::user()->can('edit-employee-reviews')) {
|
||||
// Check if review belongs to current company
|
||||
if (!in_array($employeeReview->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this review'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'status' => 'required|string|in:scheduled,in_progress,completed',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Update the status
|
||||
$employeeReview->update([
|
||||
'status' => $request->status,
|
||||
// If status is completed, set completion date
|
||||
'completion_date' => $request->status === 'completed' ? now() : $employeeReview->completion_date,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Review status updated successfully'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
501
app/Http/Controllers/EmployeeSalaryController.php
Normal file
501
app/Http/Controllers/EmployeeSalaryController.php
Normal file
@@ -0,0 +1,501 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Employee;
|
||||
use App\Models\EmployeeSalary;
|
||||
use App\Models\SalaryComponent;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class EmployeeSalaryController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-employee-salaries')) {
|
||||
// Auto-create salary records for employees who don't have one
|
||||
$companyEmployees = User::with('employee')
|
||||
->where('type', 'employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->get();
|
||||
// if (Auth::user()->can('manage-any-employee-salaries')) {
|
||||
// foreach ($companyEmployees as $employee) {
|
||||
// $exists = EmployeeSalary::where('employee_id', $employee->id)->exists();
|
||||
// if (!$exists) {
|
||||
// EmployeeSalary::create([
|
||||
// 'employee_id' => $employee->id,
|
||||
// 'basic_salary' => $employee->employee?->base_salary ?? 0,
|
||||
// 'components' => null,
|
||||
// 'is_active' => true,
|
||||
// 'created_by' => creatorId(),
|
||||
// ]);
|
||||
// } else {
|
||||
// if (is_null($employee->employee->base_salary)) {
|
||||
// // If base salary is null in employee table then it will update the employee salary in employee table
|
||||
// $getEmployeeBaseSalary = EmployeeSalary::where('employee_id', $employee->employee->user_id)->first();
|
||||
// if ($getEmployeeBaseSalary) {
|
||||
// $employee->employee->base_salary = $getEmployeeBaseSalary->basic_salary;
|
||||
// $employee->employee->save();
|
||||
// }
|
||||
// } else {
|
||||
// // If salary update on employee table it will automatically affect on Employee salary table
|
||||
// $getEmployeeBaseSalary = EmployeeSalary::where('employee_id', $employee->employee->user_id)->first();
|
||||
// if ($getEmployeeBaseSalary) {
|
||||
// $getEmployeeBaseSalary->basic_salary = $employee->employee->base_salary;
|
||||
// $getEmployeeBaseSalary->save();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if (Auth::user()->can('manage-any-employee-salaries')) {
|
||||
foreach ($companyEmployees as $employee) {
|
||||
|
||||
// Safety check: employee relation must exist
|
||||
if (!isset($employee->employee)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$employeeModel = $employee->employee;
|
||||
|
||||
// Fetch salary record once
|
||||
$employeeSalary = EmployeeSalary::where('employee_id', $employee->id)->first();
|
||||
|
||||
// If salary record does not exist → create
|
||||
if (!$employeeSalary) {
|
||||
|
||||
EmployeeSalary::create([
|
||||
'employee_id' => $employee->id,
|
||||
'basic_salary' => $employeeModel->base_salary ?? 0,
|
||||
'components' => null,
|
||||
'is_active' => true,
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// If base_salary is NULL in employee table → update employee table
|
||||
if (is_null($employeeModel->base_salary)) {
|
||||
|
||||
if (!is_null($employeeSalary->basic_salary)) {
|
||||
$employeeModel->base_salary = $employeeSalary->basic_salary;
|
||||
$employeeModel->save();
|
||||
}
|
||||
|
||||
}
|
||||
// If base_salary exists → update salary table
|
||||
else {
|
||||
|
||||
if ($employeeSalary->basic_salary != $employeeModel->base_salary) {
|
||||
$employeeSalary->basic_salary = $employeeModel->base_salary;
|
||||
$employeeSalary->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$query = EmployeeSalary::with(['employee', 'creator'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-employee-salaries')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-employee-salaries')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('employee_id', Auth::id())->where('is_active', 1);
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->whereHas('employee', function ($subQ) use ($request) {
|
||||
$subQ->where('name', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle employee filter
|
||||
if ($request->has('employee_id') && !empty($request->employee_id) && $request->employee_id !== 'all') {
|
||||
$query->where('employee_id', $request->employee_id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Handle active status filter
|
||||
if ($request->has('is_active') && !empty($request->is_active) && $request->is_active !== 'all') {
|
||||
$query->where('is_active', $request->is_active === 'active');
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
if ($request->has('sort_field') && !empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field;
|
||||
$sortDirection = $request->sort_direction ?? 'asc';
|
||||
|
||||
if ($sortField === 'basic_salary') {
|
||||
$query->orderBy('basic_salary', $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
$employeeSalaries = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
// Load component names and types for each salary record
|
||||
$employeeSalaries->getCollection()->transform(function ($salary) {
|
||||
if ($salary->components) {
|
||||
$components = SalaryComponent::whereIn('id', $salary->components)
|
||||
->get(['id', 'name', 'type']);
|
||||
$salary->component_names = $components->pluck('name')->toArray();
|
||||
$salary->component_types = $components->pluck('type')->toArray();
|
||||
} else {
|
||||
$salary->component_names = [];
|
||||
$salary->component_types = [];
|
||||
}
|
||||
if ($salary->employee) {
|
||||
$rawAvatar = $salary->employee->getRawOriginal('avatar');
|
||||
$salary->employee->avatar = check_file($rawAvatar)
|
||||
? get_file($rawAvatar)
|
||||
: get_file('avatars/avatar.png');
|
||||
}
|
||||
return $salary;
|
||||
});
|
||||
|
||||
|
||||
// Get employees for filter dropdown
|
||||
$employees = User::where('type', 'employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->get(['id', 'name']);
|
||||
|
||||
// Get salary components for form
|
||||
$salaryComponents = SalaryComponent::where('status', 'active')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->get(['id', 'name', 'type', 'calculation_type', 'default_amount', 'percentage_of_basic']);
|
||||
|
||||
return Inertia::render('hr/employee-salaries/index', [
|
||||
'employeeSalaries' => $employeeSalaries,
|
||||
'employees' => $this->getFilteredEmployees(),
|
||||
'salaryComponents' => $salaryComponents,
|
||||
'filters' => $request->all(['search', 'employee_id', 'is_active', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
private function getFilteredEmployees()
|
||||
{
|
||||
// Get employees for filter dropdown (compatible with getFilteredEmployees logic)
|
||||
$employeeQuery = Employee::whereIn('created_by', getCompanyAndUsersId());
|
||||
|
||||
if (Auth::user()->can('manage-own-employee-salaries') && !Auth::user()->can('manage-any-employee-salaries')) {
|
||||
$employeeQuery->where(function ($q) {
|
||||
$q->where('created_by', Auth::id())->orWhere('user_id', Auth::id());
|
||||
});
|
||||
}
|
||||
|
||||
$employees = User::emp()
|
||||
->with('employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->whereIn('id', $employeeQuery->pluck('user_id'))
|
||||
->select('id', 'name')
|
||||
->get()
|
||||
->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'employee_id' => $user->employee->employee_id ?? '',
|
||||
];
|
||||
});
|
||||
return $employees;
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'basic_salary' => 'required|numeric|min:0',
|
||||
'components' => 'nullable|array',
|
||||
'components.*' => 'exists:salary_components,id',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
// Check if employee already has salary
|
||||
$exists = EmployeeSalary::where('employee_id', $validated['employee_id'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return redirect()->back()->with('error', __('Employee already has a salary record. Please update the existing one.'));
|
||||
}
|
||||
|
||||
$validated['created_by'] = creatorId();
|
||||
$validated['is_active'] = true;
|
||||
|
||||
EmployeeSalary::create($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Employee salary created successfully.'));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function update(Request $request, $employeeSalaryId)
|
||||
{
|
||||
$employeeSalary = EmployeeSalary::where('id', $employeeSalaryId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($employeeSalary) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'basic_salary' => 'required|numeric|min:0',
|
||||
'components' => 'nullable|array',
|
||||
'components.*' => 'exists:salary_components,id',
|
||||
'is_active' => 'boolean',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$employeeSalary->update($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Employee salary updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update employee salary'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Employee salary Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($employeeSalaryId)
|
||||
{
|
||||
$employeeSalary = EmployeeSalary::where('id', $employeeSalaryId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($employeeSalary) {
|
||||
try {
|
||||
$employeeSalary->delete();
|
||||
return redirect()->back()->with('success', __('Employee salary deleted successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to delete employee salary'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Employee salary Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleStatus($employeeSalaryId)
|
||||
{
|
||||
$employeeSalary = EmployeeSalary::where('id', $employeeSalaryId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($employeeSalary) {
|
||||
try {
|
||||
$employeeSalary->is_active = !$employeeSalary->is_active;
|
||||
$employeeSalary->save();
|
||||
|
||||
return redirect()->back()->with('success', __('Employee salary status updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update employee salary status'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Employee salary Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function showPayroll($employeeSalaryId)
|
||||
{
|
||||
if (Auth::user()->can('manage-employee-salaries')) {
|
||||
try {
|
||||
// $employeeSalary = EmployeeSalary::where('id', $employeeSalaryId)
|
||||
// ->whereIn('created_by', getCompanyAndUsersId())
|
||||
// ->with('employee')
|
||||
// ->first();
|
||||
|
||||
$employeeSalary = EmployeeSalary::with(['employee'])
|
||||
->where('id',$employeeSalaryId)
|
||||
->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-employee-salaries')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-employee-salaries')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('employee_id', Auth::id())->where('is_active', 1);
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
})->first();
|
||||
|
||||
if (!$employeeSalary) {
|
||||
return redirect()->route('hr.employee-salaries.index')
|
||||
->with('error', __('Employee salary record not found.'));
|
||||
}
|
||||
|
||||
// Get payroll runs for this employee
|
||||
$payrollRuns = \App\Models\PayrollRun::whereIn('created_by', getCompanyAndUsersId())
|
||||
->whereHas('payrollEntries', function ($query) use ($employeeSalary) {
|
||||
$query->where('employee_id', $employeeSalary->employee_id);
|
||||
})
|
||||
->orderBy('pay_period_end', 'desc')
|
||||
->get(['id', 'title', 'pay_period_start', 'pay_period_end', 'status']);
|
||||
|
||||
if ($payrollRuns->isEmpty()) {
|
||||
return redirect()->route('hr.employee-salaries.index')
|
||||
->with('error', __('No payroll runs found for this employee.'));
|
||||
}
|
||||
|
||||
// Get the latest payroll run
|
||||
$latestPayrollRun = $payrollRuns->first();
|
||||
|
||||
return Inertia::render('hr/employee-salaries/payroll-calculation', [
|
||||
'employeeSalary' => $employeeSalary,
|
||||
'payrollRuns' => $payrollRuns,
|
||||
'selectedPayrollRun' => $latestPayrollRun,
|
||||
'payrollData' => $this->getPayrollCalculationData($employeeSalary, $latestPayrollRun)
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->route('hr.employee-salaries.index')
|
||||
->with('error', __('Failed to load payroll calculation.'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function getPayrollCalculation($employeeSalaryId, $payrollRunId)
|
||||
{
|
||||
try {
|
||||
$employeeSalary = EmployeeSalary::where('id', $employeeSalaryId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->with('employee')
|
||||
->first();
|
||||
|
||||
$payrollRun = \App\Models\PayrollRun::where('id', $payrollRunId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if (!$employeeSalary || !$payrollRun) {
|
||||
return response()->json(['error' => 'Record not found'], 404);
|
||||
}
|
||||
|
||||
$payrollData = $this->getPayrollCalculationData($employeeSalary, $payrollRun);
|
||||
|
||||
return response()->json($payrollData);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => 'Failed to calculate payroll'], 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function getPayrollCalculationData($employeeSalary, $payrollRun)
|
||||
{
|
||||
// Get payroll entry for this employee and payroll run
|
||||
$payrollEntry = \App\Models\PayrollEntry::where('employee_id', $employeeSalary->employee_id)
|
||||
->where('payroll_run_id', $payrollRun->id)
|
||||
->first();
|
||||
|
||||
if (!$payrollEntry) {
|
||||
return [
|
||||
'payrollEntry' => null,
|
||||
'salaryBreakdown' => ['earnings' => [], 'deductions' => []],
|
||||
'attendanceSummary' => [],
|
||||
'payrollCalculation' => ['net_salary' => 0, 'total_earnings' => 0, 'total_deductions' => 0],
|
||||
'attendanceRecords' => []
|
||||
];
|
||||
}
|
||||
|
||||
// Get attendance records for the payroll period
|
||||
$attendanceRecords = \App\Models\AttendanceRecord::where('employee_id', $employeeSalary->employee_id)
|
||||
->whereBetween('date', [$payrollRun->pay_period_start, $payrollRun->pay_period_end])
|
||||
->orderBy('date')
|
||||
->get();
|
||||
|
||||
// Calculate attendance summary from payroll entry
|
||||
$attendanceSummary = [
|
||||
'total_working_days' => $payrollEntry->working_days,
|
||||
'present_days' => $payrollEntry->present_days,
|
||||
'absent_days' => $payrollEntry->absent_days,
|
||||
'half_days' => $payrollEntry->half_days,
|
||||
'leave_days' => $payrollEntry->paid_leave_days,
|
||||
'holiday_days' => $payrollEntry->holiday_days,
|
||||
'overtime_hours' => $payrollEntry->overtime_hours,
|
||||
'unpaid_leave_days' => $payrollEntry->unpaid_leave_days,
|
||||
'unpaid_leave_from_leave' => $payrollEntry->unpaid_leave_days - $payrollEntry->absent_days - ($payrollEntry->half_days * 0.5)
|
||||
];
|
||||
|
||||
// Get salary breakdown from payroll entry
|
||||
$salaryBreakdown = [
|
||||
'earnings' => is_array($payrollEntry->earnings_breakdown) ? $payrollEntry->earnings_breakdown : json_decode($payrollEntry->earnings_breakdown ?? '{}', true),
|
||||
'deductions' => is_array($payrollEntry->deductions_breakdown) ? $payrollEntry->deductions_breakdown : json_decode($payrollEntry->deductions_breakdown ?? '{}', true)
|
||||
];
|
||||
|
||||
$payrollCalculation = [
|
||||
'net_salary' => $payrollEntry->net_pay,
|
||||
'total_earnings' => $payrollEntry->total_earnings,
|
||||
'total_deductions' => $payrollEntry->total_deductions,
|
||||
'per_day_salary' => $payrollEntry->per_day_salary ?? 0,
|
||||
'overtime_amount' => $payrollEntry->overtime_amount ?? 0
|
||||
];
|
||||
|
||||
return [
|
||||
'payrollEntry' => $payrollEntry,
|
||||
'salaryBreakdown' => $salaryBreakdown,
|
||||
'attendanceSummary' => $attendanceSummary,
|
||||
'payrollCalculation' => $payrollCalculation,
|
||||
'attendanceRecords' => $attendanceRecords,
|
||||
'currentMonth' => $payrollRun->pay_period_end
|
||||
];
|
||||
}
|
||||
|
||||
private function calculateAttendanceSummary($attendanceRecords, $payrollRun)
|
||||
{
|
||||
$summary = [
|
||||
'total_working_days' => 0,
|
||||
'present_days' => 0,
|
||||
'absent_days' => 0,
|
||||
'half_days' => 0,
|
||||
'leave_days' => 0,
|
||||
'holiday_days' => 0,
|
||||
'overtime_hours' => 0,
|
||||
'unpaid_leave_days' => 0,
|
||||
'unpaid_leave_from_leave' => 0
|
||||
];
|
||||
|
||||
foreach ($attendanceRecords as $record) {
|
||||
switch ($record->status) {
|
||||
case 'present':
|
||||
$summary['present_days']++;
|
||||
break;
|
||||
case 'absent':
|
||||
$summary['absent_days']++;
|
||||
break;
|
||||
case 'half_day':
|
||||
$summary['half_days']++;
|
||||
break;
|
||||
case 'on_leave':
|
||||
$summary['leave_days']++;
|
||||
break;
|
||||
case 'holiday':
|
||||
$summary['holiday_days']++;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($record->overtime_hours > 0) {
|
||||
$summary['overtime_hours'] += $record->overtime_hours;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total working days (excluding holidays)
|
||||
$summary['total_working_days'] = $summary['present_days'] + $summary['absent_days'] + $summary['half_days'] + $summary['leave_days'];
|
||||
|
||||
// Calculate unpaid leave days
|
||||
$summary['unpaid_leave_days'] = $summary['absent_days'] + ($summary['half_days'] * 0.5);
|
||||
|
||||
return $summary;
|
||||
}
|
||||
}
|
||||
496
app/Http/Controllers/EmployeeTrainingController.php
Normal file
496
app/Http/Controllers/EmployeeTrainingController.php
Normal file
@@ -0,0 +1,496 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\EmployeeAssessmentResult;
|
||||
use App\Models\EmployeeTraining;
|
||||
use App\Models\TrainingAssessment;
|
||||
use App\Models\TrainingProgram;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class EmployeeTrainingController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-employee-trainings')) {
|
||||
$query = EmployeeTraining::with(['employee.employee', 'trainingProgram.trainingType'])
|
||||
->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-employee-trainings')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-employee-trainings')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('employee_id', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && ! empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->whereHas('employee', function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%'.$request->search.'%')
|
||||
->orWhere('employee_id', 'like', '%'.$request->search.'%');
|
||||
})
|
||||
->orWhereHas('trainingProgram', function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%'.$request->search.'%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle employee filter
|
||||
if ($request->has('employee_id') && ! empty($request->employee_id)) {
|
||||
$query->where('employee_id', $request->employee_id);
|
||||
}
|
||||
|
||||
// Handle program filter
|
||||
if ($request->has('training_program_id') && ! empty($request->training_program_id)) {
|
||||
$query->where('training_program_id', $request->training_program_id);
|
||||
}
|
||||
|
||||
// Handle status filter
|
||||
if ($request->has('status') && ! empty($request->status)) {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle date range filter
|
||||
if ($request->has('assigned_date_from') && ! empty($request->assigned_date_from)) {
|
||||
$query->whereDate('assigned_date', '>=', $request->assigned_date_from);
|
||||
}
|
||||
if ($request->has('assigned_date_to') && ! empty($request->assigned_date_to)) {
|
||||
$query->whereDate('assigned_date', '<=', $request->assigned_date_to);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$allowedSortFields = ['id', 'status', 'assigned_date', 'completion_date', 'score', 'created_at'];
|
||||
|
||||
if ($request->has('sort_field') && ! empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field;
|
||||
$sortDirection = in_array($request->sort_direction, ['asc', 'desc']) ? $request->sort_direction : 'asc';
|
||||
|
||||
if ($sortField === 'employee_name' || $sortField === 'employee') {
|
||||
$query->join('users', 'employee_trainings.employee_id', '=', 'users.id')
|
||||
->select('employee_trainings.*')
|
||||
->orderBy('users.name', $sortDirection);
|
||||
} elseif ($sortField === 'program_name') {
|
||||
$query->join('training_programs', 'employee_trainings.training_program_id', '=', 'training_programs.id')
|
||||
->select('employee_trainings.*')
|
||||
->orderBy('training_programs.name', $sortDirection);
|
||||
} elseif (in_array($sortField, $allowedSortFields)) {
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
// Add assessment results count
|
||||
$query->withCount(['assessmentResults']);
|
||||
|
||||
$employeeTrainings = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$employeeTrainings->getCollection()->transform(function ($training) {
|
||||
if ($training->employee) {
|
||||
$rawAvatar = $training->employee->getRawOriginal('avatar');
|
||||
$training->employee->avatar = check_file($rawAvatar)
|
||||
? get_file($rawAvatar)
|
||||
: get_file('avatars/avatar.png');
|
||||
}
|
||||
return $training;
|
||||
});
|
||||
|
||||
// Get employees for filter dropdown
|
||||
$employees = User::with('employee')
|
||||
->where('type', 'employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get()
|
||||
->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name.' ('.($user->employee->employee_id ?? '').')',
|
||||
];
|
||||
});
|
||||
|
||||
// Get training programs for filter dropdown
|
||||
$trainingPrograms = TrainingProgram::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/training/employee-trainings/index', [
|
||||
'employeeTrainings' => $employeeTrainings,
|
||||
'employees' => $employees,
|
||||
'trainingPrograms' => $trainingPrograms,
|
||||
'filters' => $request->all(['search', 'employee_id', 'training_program_id', 'status', 'assigned_date_from', 'assigned_date_to', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the dashboard view.
|
||||
*/
|
||||
public function dashboard(Request $request)
|
||||
{
|
||||
// Get training statistics with proper permission check
|
||||
$totalTrainings = EmployeeTraining::withPermissionCheck()->count();
|
||||
$completedTrainings = EmployeeTraining::withPermissionCheck()->where('status', 'completed')->count();
|
||||
$inProgressTrainings = EmployeeTraining::withPermissionCheck()->where('status', 'in_progress')->count();
|
||||
$assignedTrainings = EmployeeTraining::withPermissionCheck()->where('status', 'assigned')->count();
|
||||
$failedTrainings = EmployeeTraining::withPermissionCheck()->where('status', 'failed')->count();
|
||||
|
||||
// Get completion rate by program
|
||||
$programStats = TrainingProgram::whereIn('created_by', getCompanyAndUsersId())
|
||||
->withCount([
|
||||
'employeeTrainings as total_count' => function ($q) {
|
||||
$q->whereHas('employee', function ($q) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
});
|
||||
},
|
||||
'employeeTrainings as completed_count' => function ($q) {
|
||||
$q->where('status', 'completed')
|
||||
->whereHas('employee', function ($q) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
});
|
||||
},
|
||||
])
|
||||
->having('total_count', '>', 0)
|
||||
->get()
|
||||
->map(function ($program) {
|
||||
return [
|
||||
'name' => $program->name,
|
||||
'total' => $program->total_count,
|
||||
'completed' => $program->completed_count,
|
||||
'completion_rate' => $program->total_count > 0
|
||||
? round(($program->completed_count / $program->total_count) * 100)
|
||||
: 0,
|
||||
];
|
||||
});
|
||||
|
||||
// Get recent completions
|
||||
$recentCompletions = EmployeeTraining::with(['employee', 'trainingProgram'])
|
||||
->withPermissionCheck()
|
||||
->where('status', 'completed')
|
||||
->orderBy('completion_date', 'desc')
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
// Get upcoming trainings
|
||||
$upcomingTrainings = EmployeeTraining::with(['employee', 'trainingProgram'])
|
||||
->withPermissionCheck()
|
||||
->where('status', 'assigned')
|
||||
->orderBy('assigned_date', 'asc')
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/training/employee-trainings/dashboard', [
|
||||
'statistics' => [
|
||||
'totalTrainings' => $totalTrainings,
|
||||
'completedTrainings' => $completedTrainings,
|
||||
'inProgressTrainings' => $inProgressTrainings,
|
||||
'assignedTrainings' => $assignedTrainings,
|
||||
'failedTrainings' => $failedTrainings,
|
||||
'completionRate' => $totalTrainings > 0 ? round(($completedTrainings / $totalTrainings) * 100) : 0,
|
||||
],
|
||||
'programStats' => $programStats,
|
||||
'recentCompletions' => $recentCompletions,
|
||||
'upcomingTrainings' => $upcomingTrainings,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'training_program_id' => 'required|exists:training_programs,id',
|
||||
'status' => 'required|string|in:assigned,in_progress,completed,failed',
|
||||
'assigned_date' => 'required|date',
|
||||
'completion_date' => 'nullable|date|after_or_equal:assigned_date',
|
||||
'certification' => 'nullable|string',
|
||||
'score' => 'nullable|numeric|min:0|max:100',
|
||||
'is_passed' => 'nullable|boolean',
|
||||
'feedback' => 'nullable|string',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if employee belongs to current company
|
||||
$user = User::where('id', $request->employee_id)
|
||||
->where('type', 'employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
if (! $user) {
|
||||
return redirect()->back()->with('error', __('Invalid employee selected'));
|
||||
}
|
||||
|
||||
// Check if training program belongs to current company
|
||||
$trainingProgram = TrainingProgram::find($request->training_program_id);
|
||||
if (! $trainingProgram || ! in_array($trainingProgram->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('Invalid training program selected'));
|
||||
}
|
||||
|
||||
$trainingData = [
|
||||
'employee_id' => $request->employee_id,
|
||||
'training_program_id' => $request->training_program_id,
|
||||
'status' => $request->status,
|
||||
'assigned_date' => $request->assigned_date,
|
||||
'completion_date' => $request->completion_date,
|
||||
'score' => $request->score,
|
||||
'is_passed' => $request->is_passed,
|
||||
'feedback' => $request->feedback,
|
||||
'notes' => $request->notes,
|
||||
'assigned_by' => creatorId(),
|
||||
'created_by' => creatorId(),
|
||||
];
|
||||
|
||||
// Handle certification from media library
|
||||
if ($request->has('certification')) {
|
||||
$trainingData['certification'] = $request->certification;
|
||||
}
|
||||
|
||||
EmployeeTraining::create($trainingData);
|
||||
|
||||
return redirect()->back()->with('success', __('Employee training assigned successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(EmployeeTraining $employeeTraining)
|
||||
{
|
||||
// Check if employee training belongs to current company
|
||||
if (! in_array($employeeTraining->employee->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to view this employee training'));
|
||||
}
|
||||
|
||||
// Load relationships
|
||||
$employeeTraining->load([
|
||||
'employee.employee.department',
|
||||
'employee.employee.designation',
|
||||
'trainingProgram.trainingType',
|
||||
'assessmentResults.trainingAssessment',
|
||||
'assigner',
|
||||
]);
|
||||
|
||||
// Get available assessments for this training program
|
||||
$availableAssessments = TrainingAssessment::where('training_program_id', $employeeTraining->training_program_id)
|
||||
->whereDoesntHave('employeeResults', function ($q) use ($employeeTraining) {
|
||||
$q->where('employee_training_id', $employeeTraining->id);
|
||||
})
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/training/employee-trainings/show', [
|
||||
'employeeTraining' => $employeeTraining,
|
||||
'availableAssessments' => $availableAssessments,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, EmployeeTraining $employeeTraining)
|
||||
{
|
||||
// Check if employee training belongs to current company
|
||||
if (! in_array($employeeTraining->employee->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this employee training'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'status' => 'required|string|in:assigned,in_progress,completed,failed',
|
||||
'completion_date' => 'nullable|date|after_or_equal:assigned_date',
|
||||
'certification' => 'nullable|string',
|
||||
'score' => 'nullable|numeric|min:0|max:100',
|
||||
'is_passed' => 'nullable|boolean',
|
||||
'feedback' => 'nullable|string',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$trainingData = [
|
||||
'status' => $request->status,
|
||||
'completion_date' => $request->completion_date,
|
||||
'score' => $request->score,
|
||||
'is_passed' => $request->is_passed,
|
||||
'feedback' => $request->feedback,
|
||||
'notes' => $request->notes,
|
||||
];
|
||||
|
||||
// Handle certification from media library
|
||||
if ($request->has('certification')) {
|
||||
$trainingData['certification'] = $request->certification;
|
||||
}
|
||||
|
||||
$employeeTraining->update($trainingData);
|
||||
|
||||
return redirect()->back()->with('success', __('Employee training updated successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(EmployeeTraining $employeeTraining)
|
||||
{
|
||||
// Check if employee training belongs to current company
|
||||
if (! in_array($employeeTraining->employee->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this employee training'));
|
||||
}
|
||||
|
||||
// Delete certification if exists
|
||||
if ($employeeTraining->certification) {
|
||||
Storage::disk('public')->delete($employeeTraining->certification);
|
||||
}
|
||||
|
||||
// Delete assessment results
|
||||
$employeeTraining->assessmentResults()->delete();
|
||||
|
||||
// Delete the employee training
|
||||
$employeeTraining->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('Employee training deleted successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Download certification file.
|
||||
*/
|
||||
public function downloadCertification(EmployeeTraining $employeeTraining)
|
||||
{
|
||||
// Check if employee training belongs to current company
|
||||
if (! in_array($employeeTraining->employee->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to access this certification'));
|
||||
}
|
||||
|
||||
if (! $employeeTraining->certification) {
|
||||
return redirect()->back()->with('error', __('Certification file not found'));
|
||||
}
|
||||
|
||||
$filePath = getStorageFilePath($employeeTraining->certification);
|
||||
|
||||
if (! file_exists($filePath)) {
|
||||
return redirect()->back()->with('error', __('Certification file not found'));
|
||||
}
|
||||
|
||||
return response()->download($filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk assign training to employees.
|
||||
*/
|
||||
public function bulkAssign(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'employee_ids' => 'required|array',
|
||||
'employee_ids.*' => 'exists:users,id',
|
||||
'training_program_id' => 'required|exists:training_programs,id',
|
||||
'assigned_date' => 'required|date',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if employees belong to current company
|
||||
$employeeIds = $request->employee_ids;
|
||||
$validEmployees = User::whereIn('id', $employeeIds)
|
||||
->where('type', 'employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
if (count($validEmployees) !== count($employeeIds)) {
|
||||
return redirect()->back()->with('error', __('Invalid employee selection'));
|
||||
}
|
||||
|
||||
// Check if training program belongs to current company
|
||||
$trainingProgram = TrainingProgram::find($request->training_program_id);
|
||||
if (! $trainingProgram || ! in_array($trainingProgram->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('Invalid training program selected'));
|
||||
}
|
||||
|
||||
// Create training assignments for each employee
|
||||
foreach ($employeeIds as $employeeId) {
|
||||
EmployeeTraining::create([
|
||||
'employee_id' => $employeeId,
|
||||
'training_program_id' => $request->training_program_id,
|
||||
'status' => 'assigned',
|
||||
'assigned_date' => $request->assigned_date,
|
||||
'notes' => $request->notes,
|
||||
'assigned_by' => creatorId(),
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('Training assigned to '.count($employeeIds).' employees successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Record assessment result.
|
||||
*/
|
||||
public function recordAssessment(Request $request, EmployeeTraining $employeeTraining)
|
||||
{
|
||||
// Check if employee training belongs to current company
|
||||
if (! in_array($employeeTraining->employee->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to record assessment for this employee training'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'training_assessment_id' => 'required|exists:training_assessments,id',
|
||||
'score' => 'required|numeric|min:0|max:100',
|
||||
'is_passed' => 'required|boolean',
|
||||
'feedback' => 'nullable|string',
|
||||
'assessment_date' => 'required|date',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if assessment belongs to the training program
|
||||
$assessment = TrainingAssessment::find($request->training_assessment_id);
|
||||
if (! $assessment || $assessment->training_program_id != $employeeTraining->training_program_id) {
|
||||
return redirect()->back()->with('error', __('Invalid assessment selected'));
|
||||
}
|
||||
|
||||
// Create assessment result
|
||||
EmployeeAssessmentResult::create([
|
||||
'employee_training_id' => $employeeTraining->id,
|
||||
'training_assessment_id' => $request->training_assessment_id,
|
||||
'score' => $request->score,
|
||||
'is_passed' => $request->is_passed,
|
||||
'feedback' => $request->feedback,
|
||||
'assessment_date' => $request->assessment_date,
|
||||
'assessed_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
// Update employee training status if needed
|
||||
if ($request->update_training_status) {
|
||||
$employeeTraining->update([
|
||||
'status' => $request->is_passed ? 'completed' : 'failed',
|
||||
'is_passed' => $request->is_passed,
|
||||
'score' => $request->score,
|
||||
'completion_date' => $request->assessment_date,
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('Assessment result recorded successfully'));
|
||||
}
|
||||
}
|
||||
529
app/Http/Controllers/EmployeeTransferController.php
Normal file
529
app/Http/Controllers/EmployeeTransferController.php
Normal file
@@ -0,0 +1,529 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Branch;
|
||||
use App\Models\Department;
|
||||
use App\Models\Designation;
|
||||
use App\Models\Employee;
|
||||
use App\Models\User;
|
||||
use App\Models\EmployeeTransfer;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class EmployeeTransferController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-employee-transfers')) {
|
||||
$query = EmployeeTransfer::with([
|
||||
'employee',
|
||||
'fromBranch:id,name',
|
||||
'toBranch:id,name',
|
||||
'fromDepartment:id,name',
|
||||
'toDepartment:id,name',
|
||||
'fromDesignation:id,name',
|
||||
'toDesignation:id,name',
|
||||
'approver'
|
||||
])->where(function ($q) {
|
||||
|
||||
if (Auth::user()->can('manage-any-employee-transfers')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-employee-transfers')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('employee_id', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->whereHas('employee', function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('employee_id', 'like', '%' . $request->search . '%');
|
||||
})
|
||||
->orWhere('reason', 'like', '%' . $request->search . '%')
|
||||
->orWhere('notes', 'like', '%' . $request->search . '%');
|
||||
}
|
||||
|
||||
// Handle employee filter
|
||||
if ($request->has('employee_id') && !empty($request->employee_id)) {
|
||||
$query->where('employee_id', $request->employee_id);
|
||||
}
|
||||
|
||||
// Handle branch filter
|
||||
if ($request->has('branch_id') && !empty($request->branch_id)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('from_branch_id', $request->branch_id)
|
||||
->orWhere('to_branch_id', $request->branch_id);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle department filter
|
||||
if ($request->has('department_id') && !empty($request->department_id)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('from_department_id', $request->department_id)
|
||||
->orWhere('to_department_id', $request->department_id);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle status filter
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle date range filter
|
||||
if ($request->has('date_from') && !empty($request->date_from)) {
|
||||
$query->whereDate('transfer_date', '>=', $request->date_from);
|
||||
}
|
||||
if ($request->has('date_to') && !empty($request->date_to)) {
|
||||
$query->whereDate('transfer_date', '<=', $request->date_to);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$allowedSortFields = ['id', 'employee_id', 'transfer_date', 'effective_date', 'status', 'from_branch_id', 'to_branch_id', 'from_department_id', 'to_department_id'];
|
||||
if ($request->has('sort_field') && !empty($request->sort_field) && in_array($request->sort_field, $allowedSortFields)) {
|
||||
$sortDirection = in_array($request->sort_direction, ['asc', 'desc']) ? $request->sort_direction : 'asc';
|
||||
$query->orderBy($request->sort_field, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
$transfers = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$transfers->getCollection()->transform(function ($transfer) {
|
||||
if ($transfer->employee) {
|
||||
$rawAvatar = $transfer->employee->getRawOriginal('avatar');
|
||||
$transfer->employee->avatar = check_file($rawAvatar)
|
||||
? get_file($rawAvatar)
|
||||
: get_file('avatars/avatar.png');
|
||||
}
|
||||
return $transfer;
|
||||
});
|
||||
|
||||
// Get employees for filter dropdown
|
||||
$employees = User::with('employee')
|
||||
->where('type', 'employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name', 'type')
|
||||
->get()
|
||||
->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'employee_id' => $user->employee->employee_id ?? '',
|
||||
'type' => $user->type,
|
||||
];
|
||||
});
|
||||
|
||||
// Get branches for filter dropdown
|
||||
$branches = Branch::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
// Get departments for filter dropdown
|
||||
$departments = Department::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('id', 'name', 'branch_id')
|
||||
->get();
|
||||
|
||||
// Get designations for form dropdown
|
||||
$designations = \App\Models\Designation::whereIn('created_by', getCompanyAndUsersId())
|
||||
->with('department:id,name,branch_id')
|
||||
->select('id', 'name', 'department_id')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/transfers/index', [
|
||||
'transfers' => $transfers,
|
||||
'employees' => $this->getFilteredEmployees(),
|
||||
'branches' => $branches,
|
||||
'departments' => $departments,
|
||||
'designations' => $designations,
|
||||
'filters' => $request->all(['search', 'employee_id', 'branch_id', 'department_id', 'status', 'date_from', 'date_to', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
private function getFilteredEmployees()
|
||||
{
|
||||
// Get employees for filter dropdown (compatible with getFilteredEmployees logic)
|
||||
$employeeQuery = Employee::whereIn('created_by', getCompanyAndUsersId());
|
||||
|
||||
if (Auth::user()->can('manage-own-employee-transfers') && !Auth::user()->can('manage-any-employee-transfers')) {
|
||||
$employeeQuery->where(function ($q) {
|
||||
$q->where('created_by', Auth::id())->orWhere('user_id', Auth::id());
|
||||
});
|
||||
}
|
||||
|
||||
$employees = User::emp()
|
||||
->with('employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->whereIn('id', $employeeQuery->pluck('user_id'))
|
||||
->select('id', 'name')
|
||||
->get()
|
||||
->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'employee_id' => $user->employee->employee_id ?? ''
|
||||
];
|
||||
});
|
||||
return $employees;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'to_branch_id' => 'nullable|exists:branches,id',
|
||||
'to_department_id' => 'nullable|exists:departments,id',
|
||||
'to_designation_id' => 'nullable|exists:designations,id',
|
||||
'transfer_date' => 'required|date',
|
||||
'effective_date' => 'required|date|after_or_equal:transfer_date',
|
||||
'reason' => 'nullable|string',
|
||||
'documents' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if employee belongs to current company
|
||||
$employee = User::with('employee')->find($request->employee_id);
|
||||
if (!$employee || !in_array($employee->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('Invalid employee selected'));
|
||||
}
|
||||
|
||||
// Ensure at least one transfer destination is specified
|
||||
if (empty($request->to_branch_id) && empty($request->to_department_id) && empty($request->to_designation_id)) {
|
||||
return redirect()->back()->with('error', __('At least one transfer destination (branch, department, or designation) must be specified'));
|
||||
}
|
||||
|
||||
// Get current employee details
|
||||
$currentBranchId = $employee->employee->branch_id;
|
||||
$currentDepartmentId = $employee->employee->department_id;
|
||||
$currentDesignationId = $employee->employee->designation_id;
|
||||
|
||||
$transferData = [
|
||||
'employee_id' => $request->employee_id,
|
||||
'transfer_date' => $request->transfer_date,
|
||||
'effective_date' => $request->effective_date,
|
||||
'reason' => $request->reason,
|
||||
'status' => 'pending',
|
||||
'created_by' => creatorId(),
|
||||
];
|
||||
|
||||
// Set from and to branch IDs if branch transfer
|
||||
if ($request->to_branch_id) {
|
||||
$transferData['from_branch_id'] = $currentBranchId;
|
||||
$transferData['to_branch_id'] = $request->to_branch_id;
|
||||
}
|
||||
|
||||
// Set from and to department IDs if department transfer
|
||||
if ($request->to_department_id) {
|
||||
$transferData['from_department_id'] = $currentDepartmentId;
|
||||
$transferData['to_department_id'] = $request->to_department_id;
|
||||
}
|
||||
|
||||
// Set from and to designation IDs if designation transfer
|
||||
if ($request->to_designation_id) {
|
||||
$transferData['from_designation_id'] = $currentDesignationId;
|
||||
$transferData['to_designation_id'] = $request->to_designation_id;
|
||||
}
|
||||
|
||||
// Handle document from media library
|
||||
if ($request->has('documents')) {
|
||||
$transferData['documents'] = $request->documents;
|
||||
}
|
||||
|
||||
EmployeeTransfer::create($transferData);
|
||||
|
||||
return redirect()->back()->with('success', __('Transfer request created successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, EmployeeTransfer $transfer)
|
||||
{
|
||||
// Check if transfer belongs to current company
|
||||
if (!in_array($transfer->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to update this transfer');
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'to_branch_id' => 'nullable|exists:branches,id',
|
||||
'to_department_id' => 'nullable|exists:departments,id',
|
||||
'to_designation_id' => 'nullable|exists:designations,id',
|
||||
'transfer_date' => 'required|date',
|
||||
'effective_date' => 'required|date|after_or_equal:transfer_date',
|
||||
'reason' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:pending,approved,rejected',
|
||||
'documents' => 'nullable|string',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if employee belongs to current company
|
||||
$employee = User::find($request->employee_id);
|
||||
if (!$employee || !in_array($employee->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('Invalid employee selected'));
|
||||
}
|
||||
|
||||
// Ensure at least one transfer destination is specified
|
||||
if (empty($request->to_branch_id) && empty($request->to_department_id) && empty($request->to_designation_id)) {
|
||||
return redirect()->back()->with('error', __('At least one transfer destination (branch, department, or designation) must be specified'));
|
||||
}
|
||||
|
||||
$transferData = [
|
||||
'employee_id' => $request->employee_id,
|
||||
'transfer_date' => $request->transfer_date,
|
||||
'effective_date' => $request->effective_date,
|
||||
'reason' => $request->reason,
|
||||
'status' => $request->status ?? $transfer->status,
|
||||
'notes' => $request->notes,
|
||||
];
|
||||
|
||||
// Set from and to branch IDs if branch transfer
|
||||
if ($request->to_branch_id) {
|
||||
$transferData['to_branch_id'] = $request->to_branch_id;
|
||||
}
|
||||
|
||||
// Set from and to department IDs if department transfer
|
||||
if ($request->to_department_id) {
|
||||
$transferData['to_department_id'] = $request->to_department_id;
|
||||
}
|
||||
|
||||
// Set from and to designation IDs if designation transfer
|
||||
if ($request->to_designation_id) {
|
||||
$transferData['to_designation_id'] = $request->to_designation_id;
|
||||
}
|
||||
|
||||
// Handle document from media library
|
||||
if ($request->has('documents')) {
|
||||
$transferData['documents'] = $request->documents;
|
||||
}
|
||||
|
||||
// If status is being changed to approved or rejected, set approved_by and approved_at
|
||||
if ($request->has('status') && in_array($request->status, ['approved', 'rejected']) && $transfer->status === 'pending') {
|
||||
$transferData['approved_by'] = auth()->id();
|
||||
$transferData['approved_at'] = now();
|
||||
|
||||
// If approved and effective date has passed or is today, update employee details
|
||||
if ($request->status === 'approved') {
|
||||
$user = User::with('employee')->find($request->employee_id);
|
||||
|
||||
if ($user && $user->employee) {
|
||||
if (isset($transferData['to_branch_id'])) {
|
||||
$user->employee->branch_id = $transferData['to_branch_id'];
|
||||
}
|
||||
if (isset($transferData['to_department_id'])) {
|
||||
$user->employee->department_id = $transferData['to_department_id'];
|
||||
}
|
||||
if (isset($transferData['to_designation_id'])) {
|
||||
$user->employee->designation_id = $transferData['to_designation_id'];
|
||||
}
|
||||
|
||||
$user->employee->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$transfer->update($transferData);
|
||||
|
||||
return redirect()->back()->with('success', __('Transfer updated successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(EmployeeTransfer $transfer)
|
||||
{
|
||||
// Check if transfer belongs to current company
|
||||
if (!in_array($transfer->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to delete this transfer');
|
||||
}
|
||||
|
||||
// Only allow deletion of pending transfers
|
||||
if ($transfer->status !== 'pending') {
|
||||
return redirect()->back()->with('error', 'Only pending transfers can be deleted');
|
||||
}
|
||||
|
||||
$transfer->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('Transfer deleted successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve the transfer.
|
||||
*/
|
||||
public function approve(Request $request, EmployeeTransfer $transfer)
|
||||
{
|
||||
// Check if transfer belongs to current company
|
||||
if (!in_array($transfer->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to approve this transfer');
|
||||
}
|
||||
|
||||
// Only allow approval of pending transfers
|
||||
if ($transfer->status !== 'pending') {
|
||||
return redirect()->back()->with('error', 'Only pending transfers can be approved');
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$updateData = [
|
||||
'status' => 'approved',
|
||||
'approved_by' => auth()->id(),
|
||||
'approved_at' => now(),
|
||||
'notes' => $request->notes,
|
||||
];
|
||||
|
||||
$transfer->update($updateData);
|
||||
|
||||
// If effective date has passed or is today, update employee details
|
||||
|
||||
$user = User::with('employee')->find($transfer->employee_id);
|
||||
|
||||
if ($user && $user->employee) {
|
||||
if ($transfer->to_branch_id) {
|
||||
$user->employee->branch_id = $transfer->to_branch_id;
|
||||
}
|
||||
if ($transfer->to_department_id) {
|
||||
$user->employee->department_id = $transfer->to_department_id;
|
||||
}
|
||||
if ($transfer->to_designation_id) {
|
||||
$user->employee->designation_id = $transfer->to_designation_id;
|
||||
}
|
||||
|
||||
$user->employee->save();
|
||||
}
|
||||
|
||||
|
||||
return redirect()->back()->with('success', __('Transfer approved successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject the transfer.
|
||||
*/
|
||||
public function reject(Request $request, EmployeeTransfer $transfer)
|
||||
{
|
||||
// Check if transfer belongs to current company
|
||||
if (!in_array($transfer->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to reject this transfer');
|
||||
}
|
||||
|
||||
// Only allow rejection of pending transfers
|
||||
if ($transfer->status !== 'pending') {
|
||||
return redirect()->back()->with('error', 'Only pending transfers can be rejected');
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'notes' => 'required|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$transfer->update([
|
||||
'status' => 'rejected',
|
||||
'approved_by' => auth()->id(),
|
||||
'approved_at' => now(),
|
||||
'notes' => $request->notes,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Transfer rejected successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Download document file.
|
||||
*/
|
||||
public function downloadDocument(EmployeeTransfer $transfer)
|
||||
{
|
||||
// Check if transfer belongs to current company
|
||||
if (!in_array($transfer->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to access this document'));
|
||||
}
|
||||
|
||||
if (!$transfer->documents) {
|
||||
return redirect()->back()->with('error', __('Document file not found'));
|
||||
}
|
||||
|
||||
$filePath = getStorageFilePath($transfer->documents);
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
return redirect()->back()->with('error', __('Document file not found'));
|
||||
}
|
||||
|
||||
return response()->download($filePath);
|
||||
}
|
||||
|
||||
public function getDepartment($branchId)
|
||||
{
|
||||
try {
|
||||
$branch = Branch::with('departments')->find($branchId);
|
||||
|
||||
if (!$branch) {
|
||||
return response()->json(['error' => 'Branch not found'], 404);
|
||||
}
|
||||
|
||||
// Map departments into dropdown-friendly format
|
||||
$departmentsForDropdown = $branch->departments->map(function ($department) {
|
||||
return [
|
||||
'label' => $department->name,
|
||||
'value' => $department->id,
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json($departmentsForDropdown);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function getDesignation($departmentId)
|
||||
{
|
||||
try {
|
||||
$department = Department::with('desginations')->find($departmentId);
|
||||
|
||||
if (!$department) {
|
||||
return response()->json(['error' => 'Department not found'], 404);
|
||||
}
|
||||
|
||||
// Map departments into dropdown-friendly format
|
||||
$designationDropdown = $department->desginations->map(function ($designation) {
|
||||
return [
|
||||
'label' => $designation->name,
|
||||
'value' => $designation->id,
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json($designationDropdown);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\ExperienceCertificateTemplate;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class ExperienceCertificateTemplateController extends Controller
|
||||
{
|
||||
public function update(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('update-experience-certificate')) {
|
||||
$request->validate([
|
||||
'content' => 'required|string'
|
||||
]);
|
||||
|
||||
if ($request->templateId) {
|
||||
// Update existing template
|
||||
$template = ExperienceCertificateTemplate::where('id', $request->templateId)
|
||||
->where('created_by', auth::id())
|
||||
->firstOrFail();
|
||||
$template->update(['content' => $request->content]);
|
||||
} else {
|
||||
// Create or update by language
|
||||
$template = ExperienceCertificateTemplate::updateOrCreate(
|
||||
[
|
||||
'language' => $request->language,
|
||||
'created_by' => auth::id()
|
||||
],
|
||||
[
|
||||
'content' => $request->content
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('Experience Certificate template updated successfully.'));
|
||||
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
134
app/Http/Controllers/FedaPayPaymentController.php
Normal file
134
app/Http/Controllers/FedaPayPaymentController.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Plan;
|
||||
use Illuminate\Http\Request;
|
||||
use FedaPay\FedaPay;
|
||||
use FedaPay\Transaction;
|
||||
|
||||
class FedaPayPaymentController extends Controller
|
||||
{
|
||||
public function processPayment(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request, [
|
||||
'transaction_id' => 'required|string',
|
||||
]);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!isset($settings['payment_settings']['fedapay_secret_key'])) {
|
||||
return back()->withErrors(['error' => 'FedaPay not configured']);
|
||||
}
|
||||
|
||||
$this->configureFedaPay($settings['payment_settings']);
|
||||
|
||||
$transaction = Transaction::retrieve($validated['transaction_id']);
|
||||
|
||||
if ($transaction->status === 'approved') {
|
||||
processPaymentSuccess([
|
||||
'user_id' => auth()->id(),
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => $validated['billing_cycle'],
|
||||
'payment_method' => 'fedapay',
|
||||
'coupon_code' => $validated['coupon_code'] ?? null,
|
||||
'payment_id' => $validated['transaction_id'],
|
||||
]);
|
||||
|
||||
return back()->with('success', __('Payment successful and plan activated'));
|
||||
}
|
||||
|
||||
return back()->withErrors(['error' => __('Payment failed or cancelled')]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return back()->withErrors(['error' => __('Payment processing failed')]);
|
||||
}
|
||||
}
|
||||
|
||||
public function createPayment(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$pricing = calculatePlanPricing($plan, $validated['coupon_code'] ?? null);
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!isset($settings['payment_settings']['fedapay_secret_key'])) {
|
||||
return response()->json(['error' => __('FedaPay not configured')], 400);
|
||||
}
|
||||
|
||||
$this->configureFedaPay($settings['payment_settings']);
|
||||
|
||||
$user = auth()->user();
|
||||
|
||||
$transaction = Transaction::create([
|
||||
'description' => 'Plan: ' . $plan->name,
|
||||
'amount' => $pricing['final_price'] * 100, // Amount in cents
|
||||
'currency' => ['iso' => 'XOF'],
|
||||
'callback_url' => route('fedapay.callback'),
|
||||
'customer' => [
|
||||
'firstname' => $user->name ?? 'Customer',
|
||||
'email' => $user->email,
|
||||
],
|
||||
'custom_metadata' => [
|
||||
'plan_id' => $plan->id,
|
||||
'user_id' => $user->id,
|
||||
'billing_cycle' => $validated['billing_cycle'],
|
||||
'coupon_code' => $validated['coupon_code'] ?? null,
|
||||
]
|
||||
]);
|
||||
|
||||
$token = $transaction->generateToken();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'payment_url' => $token->url,
|
||||
'transaction_id' => $transaction->id,
|
||||
'token' => $token->token
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Payment creation failed')], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function callback(Request $request)
|
||||
{
|
||||
try {
|
||||
$settings = getPaymentGatewaySettings();
|
||||
$this->configureFedaPay($settings['payment_settings']);
|
||||
|
||||
$transactionId = $request->input('id');
|
||||
$transaction = Transaction::retrieve($transactionId);
|
||||
|
||||
if ($transaction->status === 'approved') {
|
||||
$metadata = $transaction->custom_metadata;
|
||||
|
||||
processPaymentSuccess([
|
||||
'user_id' => $metadata['user_id'],
|
||||
'plan_id' => $metadata['plan_id'],
|
||||
'billing_cycle' => $metadata['billing_cycle'],
|
||||
'payment_method' => 'fedapay',
|
||||
'coupon_code' => $metadata['coupon_code'] ?? null,
|
||||
'payment_id' => $transactionId,
|
||||
]);
|
||||
|
||||
return redirect()->route('plans.index')->with('success', __('Payment successful and plan activated'));
|
||||
}
|
||||
|
||||
return redirect()->route('plans.index')->with('error', __('Payment was not completed'));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Callback processing failed')], 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function configureFedaPay($settings)
|
||||
{
|
||||
FedaPay::setApiKey($settings['fedapay_secret_key']);
|
||||
FedaPay::setEnvironment($settings['fedapay_mode'] === 'live' ? 'live' : 'sandbox');
|
||||
}
|
||||
}
|
||||
77
app/Http/Controllers/FlutterwavePaymentController.php
Normal file
77
app/Http/Controllers/FlutterwavePaymentController.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Plan;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class FlutterwavePaymentController extends Controller
|
||||
{
|
||||
public function processPayment(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request, [
|
||||
'payment_id' => 'required|string',
|
||||
'tx_ref' => 'required|string',
|
||||
]);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!isset($settings['payment_settings']['flutterwave_secret_key'])) {
|
||||
return back()->withErrors(['error' => __('Flutterwave not configured')]);
|
||||
}
|
||||
|
||||
// Verify payment with Flutterwave API
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, array(
|
||||
CURLOPT_URL => "https://api.flutterwave.com/v3/transactions/" . $validated['payment_id'] . "/verify",
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Authorization: Bearer " . $settings['payment_settings']['flutterwave_secret_key'],
|
||||
"Content-Type: application/json",
|
||||
],
|
||||
));
|
||||
|
||||
$response = curl_exec($curl);
|
||||
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
curl_close($curl);
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
return back()->withErrors(['error' => __('Payment verification failed - API error')]);
|
||||
}
|
||||
|
||||
$result = json_decode($response, true);
|
||||
|
||||
if (!$result) {
|
||||
return back()->withErrors(['error' => __('Payment verification failed - Invalid response')]);
|
||||
}
|
||||
|
||||
if ($result['status'] === 'success' && $result['data']['status'] === 'successful') {
|
||||
// Check if payment amount matches plan price
|
||||
$expectedAmount = $plan->price;
|
||||
$paidAmount = $result['data']['amount'];
|
||||
|
||||
if (abs($paidAmount - $expectedAmount) > 0.01) {
|
||||
return back()->withErrors(['error' => __('Payment amount verification failed')]);
|
||||
}
|
||||
|
||||
processPaymentSuccess([
|
||||
'user_id' => auth()->id(),
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => $validated['billing_cycle'],
|
||||
'payment_method' => 'flutterwave',
|
||||
'coupon_code' => $validated['coupon_code'] ?? null,
|
||||
'payment_id' => $validated['payment_id'],
|
||||
]);
|
||||
|
||||
return redirect()->route('plans.index')->with('success', __('Payment successful! Your plan has been activated.'));
|
||||
}
|
||||
|
||||
return back()->withErrors(['error' => __('Payment verification failed')]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return handlePaymentError($e, 'flutterwave');
|
||||
}
|
||||
}
|
||||
}
|
||||
171
app/Http/Controllers/GoalTypeController.php
Normal file
171
app/Http/Controllers/GoalTypeController.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\GoalType;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class GoalTypeController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-goal-types')) {
|
||||
$query = GoalType::where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-goal-types')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-goal-types')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle status filter
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field', 'id');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['name', 'created_at', 'id'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'id';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
$goalTypes = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
return Inertia::render('hr/performance/goal-types/index', [
|
||||
'goalTypes' => $goalTypes,
|
||||
'filters' => $request->all(['search', 'status', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('create-goal-types')) {
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
GoalType::create([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'status' => $request->status ?? 'active',
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', 'Goal type created successfully');
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, GoalType $goalType)
|
||||
{
|
||||
if (Auth::user()->can('edit-goal-types')) {
|
||||
// Check if goal type belongs to current company
|
||||
if (!in_array($goalType->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to update this goal type');
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$goalType->update([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'status' => $request->status ?? 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', 'Goal type updated successfully');
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(GoalType $goalType)
|
||||
{
|
||||
if (Auth::user()->can('delete-goal-types')) {
|
||||
// Check if goal type belongs to current company
|
||||
if (!in_array($goalType->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to delete this goal type');
|
||||
}
|
||||
|
||||
// Check if goal type is being used in goals
|
||||
if ($goalType->goals()->count() > 0) {
|
||||
return redirect()->back()->with('error', 'Cannot delete goal type as it is being used in employee goals');
|
||||
}
|
||||
|
||||
$goalType->delete();
|
||||
|
||||
return redirect()->back()->with('success', 'Goal type deleted successfully');
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the status of the specified resource.
|
||||
*/
|
||||
public function toggleStatus(GoalType $goalType)
|
||||
{
|
||||
if (Auth::user()->can('edit-goal-types')) {
|
||||
// Check if goal type belongs to current company
|
||||
if (!in_array($goalType->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to update this goal type');
|
||||
}
|
||||
|
||||
$goalType->update([
|
||||
'status' => $goalType->status === 'active' ? 'inactive' : 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', 'Goal type status updated successfully');
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
434
app/Http/Controllers/HolidayController.php
Normal file
434
app/Http/Controllers/HolidayController.php
Normal file
@@ -0,0 +1,434 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Branch;
|
||||
use App\Models\Holiday;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class HolidayController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-holidays')) {
|
||||
$query = Holiday::with(['branches'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-holidays')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-holidays')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && ! empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%'.$request->search.'%')
|
||||
->orWhere('description', 'like', '%'.$request->search.'%');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle category filter
|
||||
if ($request->has('category') && ! empty($request->category)) {
|
||||
$query->where('category', $request->category);
|
||||
}
|
||||
|
||||
// Handle branch filter
|
||||
if ($request->has('branch_id') && ! empty($request->branch_id)) {
|
||||
$query->whereHas('branches', function ($q) use ($request) {
|
||||
$q->where('branches.id', $request->branch_id);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle date range filter
|
||||
if ($request->has('date_from') && ! empty($request->date_from)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('start_date', '>=', $request->date_from)
|
||||
->orWhere('end_date', '>=', $request->date_from);
|
||||
});
|
||||
}
|
||||
if ($request->has('date_to') && ! empty($request->date_to)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('start_date', '<=', $request->date_to)
|
||||
->orWhere('end_date', '<=', $request->date_to);
|
||||
});
|
||||
}
|
||||
|
||||
if (! isDemo()) {
|
||||
// Handle year filter
|
||||
if ($request->has('year') && ! empty($request->year)) {
|
||||
$year = $request->year;
|
||||
$query->where(function ($q) use ($year) {
|
||||
$q->whereYear('start_date', $year)
|
||||
->orWhereYear('end_date', $year);
|
||||
});
|
||||
} else {
|
||||
// Default to current year if no year specified and not in demo mode
|
||||
$currentYear = date('Y');
|
||||
$query->where(function ($q) use ($currentYear) {
|
||||
$q->whereYear('start_date', $currentYear)
|
||||
->orWhereYear('end_date', $currentYear);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$allowedSortFields = ['id', 'name', 'start_date', 'end_date', 'category', 'is_paid', 'is_recurring', 'is_half_day'];
|
||||
if ($request->has('sort_field') && ! empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field === 'date' ? 'start_date' : $request->sort_field;
|
||||
if (in_array($sortField, $allowedSortFields)) {
|
||||
$sortDirection = in_array($request->sort_direction, ['asc', 'desc']) ? $request->sort_direction : 'asc';
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
$holidays = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
// Get branches for filter dropdown
|
||||
$branches = Branch::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
// Get categories for filter dropdown
|
||||
$categories = Holiday::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('category')
|
||||
->distinct()
|
||||
->pluck('category')
|
||||
->toArray();
|
||||
|
||||
// Get available years for filter dropdown
|
||||
$years = Holiday::whereIn('created_by', getCompanyAndUsersId())
|
||||
->selectRaw('YEAR(start_date) as year')
|
||||
->distinct()
|
||||
->pluck('year')
|
||||
->toArray();
|
||||
|
||||
// Add current year if not in the list
|
||||
$currentYear = (int) date('Y');
|
||||
if (! in_array($currentYear, $years)) {
|
||||
$years[] = $currentYear;
|
||||
}
|
||||
sort($years);
|
||||
|
||||
return Inertia::render('hr/holidays/index', [
|
||||
'holidays' => $holidays,
|
||||
'branches' => $branches,
|
||||
'categories' => $categories,
|
||||
'years' => $years,
|
||||
'filters' => $request->all(['search', 'category', 'branch_id', 'date_from', 'date_to', 'year', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the calendar view.
|
||||
*/
|
||||
public function calendar(Request $request)
|
||||
{
|
||||
$year = $request->year ?? date('Y');
|
||||
|
||||
$holidays = Holiday::with(['branches'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where(function ($q) use ($year) {
|
||||
$q->whereYear('start_date', $year)
|
||||
->orWhereYear('end_date', $year);
|
||||
})
|
||||
->get();
|
||||
|
||||
// Get branches for filter dropdown
|
||||
$branches = Branch::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
// Get categories for filter dropdown
|
||||
$categories = Holiday::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('category')
|
||||
->distinct()
|
||||
->pluck('category')
|
||||
->toArray();
|
||||
|
||||
// Get available years for filter dropdown
|
||||
$years = Holiday::whereIn('created_by', getCompanyAndUsersId())
|
||||
->selectRaw('YEAR(start_date) as year')
|
||||
->distinct()
|
||||
->pluck('year')
|
||||
->toArray();
|
||||
|
||||
// Add current year if not in the list
|
||||
$currentYear = (int) date('Y');
|
||||
if (! in_array($currentYear, $years)) {
|
||||
$years[] = $currentYear;
|
||||
}
|
||||
sort($years);
|
||||
|
||||
// Format holidays for FullCalendar
|
||||
$calendarEvents = $holidays->map(function ($holiday) {
|
||||
return [
|
||||
'id' => $holiday->id,
|
||||
'title' => $holiday->name,
|
||||
'start' => $holiday->start_date,
|
||||
'end' => $holiday->end_date ? \Carbon\Carbon::parse($holiday->end_date)->addDay()->format('Y-m-d') : null,
|
||||
'allDay' => true,
|
||||
'backgroundColor' => $this->getCategoryColor($holiday->category),
|
||||
'borderColor' => $this->getCategoryColor($holiday->category),
|
||||
'extendedProps' => [
|
||||
'category' => $holiday->category,
|
||||
'description' => $holiday->description,
|
||||
'is_paid' => $holiday->is_paid,
|
||||
'is_half_day' => $holiday->is_half_day,
|
||||
'is_recurring' => $holiday->is_recurring,
|
||||
'branches' => $holiday->branches->pluck('name')->toArray(),
|
||||
],
|
||||
];
|
||||
});
|
||||
|
||||
return Inertia::render('hr/holidays/calendar', [
|
||||
'holidays' => $holidays,
|
||||
'calendarEvents' => $calendarEvents,
|
||||
'branches' => $branches,
|
||||
'categories' => $categories,
|
||||
'years' => $years,
|
||||
'currentYear' => (int) $year,
|
||||
'filters' => $request->all(['category', 'branch_id']),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'nullable|date|after_or_equal:start_date',
|
||||
'category' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'is_recurring' => 'nullable|boolean',
|
||||
'is_paid' => 'nullable|boolean',
|
||||
'is_half_day' => 'nullable|boolean',
|
||||
'branch_ids' => 'required|array',
|
||||
'branch_ids.*' => 'exists:branches,id',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if branches belong to current company
|
||||
$branchIds = $request->branch_ids;
|
||||
$validBranches = Branch::whereIn('created_by', getCompanyAndUsersId())
|
||||
->whereIn('id', $branchIds)
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
if (count($validBranches) !== count($branchIds)) {
|
||||
return redirect()->back()->with('error', __('Invalid branch selection'));
|
||||
}
|
||||
|
||||
$holiday = Holiday::create([
|
||||
'name' => $request->name,
|
||||
'start_date' => $request->start_date,
|
||||
'end_date' => $request->end_date,
|
||||
'category' => $request->category,
|
||||
'description' => $request->description,
|
||||
'is_recurring' => $request->is_recurring ?? false,
|
||||
'is_paid' => $request->is_paid ?? true,
|
||||
'is_half_day' => $request->is_half_day ?? false,
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
// Attach branches
|
||||
$holiday->branches()->attach($validBranches);
|
||||
|
||||
return redirect()->back()->with('success', __('Holiday created successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, Holiday $holiday)
|
||||
{
|
||||
// Check if holiday belongs to current company
|
||||
if (! in_array($holiday->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this holiday'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'nullable|date|after_or_equal:start_date',
|
||||
'category' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'is_recurring' => 'nullable|boolean',
|
||||
'is_paid' => 'nullable|boolean',
|
||||
'is_half_day' => 'nullable|boolean',
|
||||
'branch_ids' => 'required|array',
|
||||
'branch_ids.*' => 'exists:branches,id',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if branches belong to current company
|
||||
$branchIds = $request->branch_ids;
|
||||
$validBranches = Branch::whereIn('created_by', getCompanyAndUsersId())
|
||||
->whereIn('id', $branchIds)
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
if (count($validBranches) !== count($branchIds)) {
|
||||
return redirect()->back()->with('error', __('Invalid branch selection'));
|
||||
}
|
||||
|
||||
$holiday->update([
|
||||
'name' => $request->name,
|
||||
'start_date' => $request->start_date,
|
||||
'end_date' => $request->end_date,
|
||||
'category' => $request->category,
|
||||
'description' => $request->description,
|
||||
'is_recurring' => $request->is_recurring ?? false,
|
||||
'is_paid' => $request->is_paid ?? true,
|
||||
'is_half_day' => $request->is_half_day ?? false,
|
||||
]);
|
||||
|
||||
// Sync branches
|
||||
$holiday->branches()->sync($validBranches);
|
||||
|
||||
return redirect()->back()->with('success', __('Holiday updated successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(Holiday $holiday)
|
||||
{
|
||||
// Check if holiday belongs to current company
|
||||
if (! in_array($holiday->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this holiday'));
|
||||
}
|
||||
|
||||
// Detach all branches
|
||||
$holiday->branches()->detach();
|
||||
|
||||
// Delete the holiday
|
||||
$holiday->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('Holiday deleted successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Export holidays to PDF.
|
||||
*/
|
||||
public function exportPdf(Request $request)
|
||||
{
|
||||
$year = $request->year ?? date('Y');
|
||||
|
||||
$query = Holiday::with(['branches'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where(function ($q) use ($year) {
|
||||
$q->whereYear('start_date', $year)
|
||||
->orWhereYear('end_date', $year);
|
||||
});
|
||||
|
||||
if ($request->category) {
|
||||
$query->where('category', $request->category);
|
||||
}
|
||||
|
||||
if ($request->branch_id) {
|
||||
$query->whereHas('branches', function ($q) use ($request) {
|
||||
$q->where('branches.id', $request->branch_id);
|
||||
});
|
||||
}
|
||||
|
||||
$holidays = $query->orderBy('start_date', 'asc')->get();
|
||||
|
||||
$html = view('exports.holidays-pdf', compact('holidays', 'year'))->render();
|
||||
|
||||
return response($html)
|
||||
->header('Content-Type', 'text/html')
|
||||
->header('Content-Disposition', "attachment; filename=holidays-{$year}.html");
|
||||
}
|
||||
|
||||
/**
|
||||
* Export holidays to iCal format.
|
||||
*/
|
||||
public function exportIcal(Request $request)
|
||||
{
|
||||
$year = $request->year ?? date('Y');
|
||||
|
||||
$query = Holiday::with(['branches'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where(function ($q) use ($year) {
|
||||
$q->whereYear('start_date', $year)
|
||||
->orWhereYear('end_date', $year);
|
||||
});
|
||||
|
||||
if ($request->category) {
|
||||
$query->where('category', $request->category);
|
||||
}
|
||||
|
||||
if ($request->branch_id) {
|
||||
$query->whereHas('branches', function ($q) use ($request) {
|
||||
$q->where('branches.id', $request->branch_id);
|
||||
});
|
||||
}
|
||||
|
||||
$holidays = $query->orderBy('start_date', 'asc')->get();
|
||||
|
||||
$icalContent = "BEGIN:VCALENDAR\r\n";
|
||||
$icalContent .= "VERSION:2.0\r\n";
|
||||
$icalContent .= "PRODID:-//Company//Holidays//EN\r\n";
|
||||
$icalContent .= "CALSCALE:GREGORIAN\r\n";
|
||||
|
||||
foreach ($holidays as $holiday) {
|
||||
$startDate = \Carbon\Carbon::parse($holiday->start_date)->format('Ymd');
|
||||
$endDate = $holiday->end_date ? \Carbon\Carbon::parse($holiday->end_date)->addDay()->format('Ymd') : \Carbon\Carbon::parse($holiday->start_date)->addDay()->format('Ymd');
|
||||
|
||||
$icalContent .= "BEGIN:VEVENT\r\n";
|
||||
$icalContent .= 'UID:'.md5($holiday->id.$holiday->name)."@company.com\r\n";
|
||||
$icalContent .= "DTSTART;VALUE=DATE:{$startDate}\r\n";
|
||||
$icalContent .= "DTEND;VALUE=DATE:{$endDate}\r\n";
|
||||
$icalContent .= 'SUMMARY:'.str_replace(',', '\,', $holiday->name)."\r\n";
|
||||
if ($holiday->description) {
|
||||
$icalContent .= 'DESCRIPTION:'.str_replace(',', '\,', $holiday->description)."\r\n";
|
||||
}
|
||||
$icalContent .= "END:VEVENT\r\n";
|
||||
}
|
||||
|
||||
$icalContent .= "END:VCALENDAR\r\n";
|
||||
|
||||
return response($icalContent)
|
||||
->header('Content-Type', 'text/calendar')
|
||||
->header('Content-Disposition', "attachment; filename=holidays-{$year}.ics");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color for holiday category
|
||||
*/
|
||||
private function getCategoryColor($category)
|
||||
{
|
||||
$colors = [
|
||||
'national' => '#3b82f6',
|
||||
'religious' => '#8b5cf6',
|
||||
'company-specific' => '#10b77f',
|
||||
'regional' => '#f59e0b',
|
||||
];
|
||||
|
||||
return $colors[$category] ?? '#6b7280';
|
||||
}
|
||||
}
|
||||
236
app/Http/Controllers/HrDocumentController.php
Normal file
236
app/Http/Controllers/HrDocumentController.php
Normal file
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\HrDocument;
|
||||
use App\Models\DocumentCategory;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Inertia\Inertia;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class HrDocumentController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-hr-documents')) {
|
||||
$query = HrDocument::with(['category', 'uploader', 'approver'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-hr-documents')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-hr-documents')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('uploaded_by', Auth::id())->orWhere('approved_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('title', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%')
|
||||
->orWhere('file_name', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('category_id') && !empty($request->category_id) && $request->category_id !== 'all') {
|
||||
$query->where('category_id', $request->category_id);
|
||||
}
|
||||
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Auto-update expired documents
|
||||
HrDocument::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', '!=', 'Expired')
|
||||
->where('expiry_date', '<', Carbon::today())
|
||||
->update(['status' => 'Expired']);
|
||||
|
||||
// Handle sorting
|
||||
$allowedSortFields = ['id', 'title', 'status', 'effective_date', 'expiry_date', 'download_count', 'created_at'];
|
||||
if ($request->has('sort_field') && !empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field;
|
||||
if ($sortField === 'document') {
|
||||
$sortField = 'title';
|
||||
} elseif ($sortField === 'expires') {
|
||||
$sortField = 'expiry_date';
|
||||
}
|
||||
|
||||
if (in_array($sortField, $allowedSortFields)) {
|
||||
$sortDirection = in_array($request->sort_direction, ['asc', 'desc']) ? $request->sort_direction : 'asc';
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
$hrDocuments = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$categories = DocumentCategory::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/documents/hr-documents/index', [
|
||||
'hrDocuments' => $hrDocuments,
|
||||
'categories' => $categories,
|
||||
'filters' => $request->all(['search', 'category_id', 'status', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'title' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'category_id' => 'required|exists:document_categories,id',
|
||||
'file' => 'required|string',
|
||||
'effective_date' => 'nullable|date',
|
||||
'expiry_date' => 'nullable|date|after:effective_date',
|
||||
'requires_acknowledgment' => 'boolean',
|
||||
]);
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator->errors())->withInput();
|
||||
}
|
||||
|
||||
// Extract filename from URL or use default
|
||||
$fileUrl = $request->file;
|
||||
$fileName = basename(parse_url($fileUrl, PHP_URL_PATH)) ?: 'document_' . time();
|
||||
|
||||
HrDocument::create([
|
||||
'title' => $request->title,
|
||||
'description' => $request->description,
|
||||
'category_id' => $request->category_id,
|
||||
'file_name' => $fileName,
|
||||
'file_path' => $fileUrl,
|
||||
'file_type' => 'application/octet-stream',
|
||||
'file_size' => 0,
|
||||
'effective_date' => $request->effective_date,
|
||||
'expiry_date' => $request->expiry_date,
|
||||
'requires_acknowledgment' => $request->boolean('requires_acknowledgment'),
|
||||
'uploaded_by' => creatorId(),
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Document uploaded successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, HrDocument $hrDocument)
|
||||
{
|
||||
if (!in_array($hrDocument->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this document'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'title' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'category_id' => 'required|exists:document_categories,id',
|
||||
'file' => 'nullable|string',
|
||||
'effective_date' => 'nullable|date',
|
||||
'expiry_date' => 'nullable|date|after:effective_date',
|
||||
'requires_acknowledgment' => 'boolean',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator->errors())->withInput();
|
||||
}
|
||||
|
||||
$updateData = [
|
||||
'title' => $request->title,
|
||||
'description' => $request->description,
|
||||
'category_id' => $request->category_id,
|
||||
'effective_date' => $request->effective_date,
|
||||
'expiry_date' => $request->expiry_date,
|
||||
'requires_acknowledgment' => $request->boolean('requires_acknowledgment'),
|
||||
];
|
||||
|
||||
// Handle file replacement from media library
|
||||
if ($request->has('file') && !empty($request->file)) {
|
||||
$fileUrl = $request->file;
|
||||
$fileName = basename(parse_url($fileUrl, PHP_URL_PATH)) ?: 'document_' . time();
|
||||
|
||||
$updateData = array_merge($updateData, [
|
||||
'file_name' => $fileName,
|
||||
'file_path' => $fileUrl,
|
||||
'file_type' => 'application/octet-stream',
|
||||
'file_size' => 0,
|
||||
'version' => $this->incrementVersion($hrDocument->version),
|
||||
]);
|
||||
}
|
||||
|
||||
$hrDocument->update($updateData);
|
||||
|
||||
return redirect()->back()->with('success', __('Document updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(HrDocument $hrDocument)
|
||||
{
|
||||
if (!in_array($hrDocument->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this document'));
|
||||
}
|
||||
|
||||
$hrDocument->delete();
|
||||
return redirect()->back()->with('success', __('Document deleted successfully'));
|
||||
}
|
||||
|
||||
public function download(HrDocument $hrDocument)
|
||||
{
|
||||
if (!in_array($hrDocument->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to download this document'));
|
||||
}
|
||||
|
||||
// Increment download count
|
||||
$hrDocument->increment('download_count');
|
||||
|
||||
|
||||
$filePath = getStorageFilePath($hrDocument->file_path);
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
return redirect()->back()->with('error', __('Certificate file not found'));
|
||||
}
|
||||
|
||||
return response()->download($filePath);
|
||||
}
|
||||
|
||||
public function updateStatus(Request $request, HrDocument $hrDocument)
|
||||
{
|
||||
if (!in_array($hrDocument->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this document'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'status' => 'required|in:Draft,Under Review,Approved,Published,Archived,Expired',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator->errors());
|
||||
}
|
||||
|
||||
$updateData = ['status' => $request->status];
|
||||
|
||||
if ($request->status === 'Approved' && !$hrDocument->approved_at) {
|
||||
$updateData['approved_by'] = creatorId();
|
||||
$updateData['approved_at'] = now();
|
||||
}
|
||||
|
||||
$hrDocument->update($updateData);
|
||||
return redirect()->back()->with('success', __('Document status updated successfully'));
|
||||
}
|
||||
|
||||
private function incrementVersion($currentVersion)
|
||||
{
|
||||
$parts = explode('.', $currentVersion);
|
||||
$parts[1] = isset($parts[1]) ? (int)$parts[1] + 1 : 1;
|
||||
return implode('.', $parts);
|
||||
}
|
||||
}
|
||||
52
app/Http/Controllers/ImpersonateController.php
Normal file
52
app/Http/Controllers/ImpersonateController.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Lab404\Impersonate\Impersonate;
|
||||
|
||||
class ImpersonateController extends Controller
|
||||
{
|
||||
public function start(Request $request, $userId)
|
||||
{
|
||||
$user = User::findOrFail($userId);
|
||||
|
||||
// Log impersonation event
|
||||
Log::info('Impersonation started', [
|
||||
'acting_user_id' => auth()->id(),
|
||||
'impersonated_user_id' => $userId,
|
||||
'ip_address' => $request->ip(),
|
||||
'timestamp' => now()
|
||||
]);
|
||||
|
||||
$originalUserId = auth()->id();
|
||||
|
||||
// Login as the target user first
|
||||
auth()->loginUsingId($userId);
|
||||
// Then store original user ID in session
|
||||
session()->put('impersonated_user_id', $userId);
|
||||
session()->put('impersonated_by', $originalUserId);
|
||||
session()->save();
|
||||
|
||||
return redirect('/dashboard')->with('success', __('Now impersonating :name', ['name' => $user->name]));
|
||||
}
|
||||
|
||||
public function leave(Request $request)
|
||||
{
|
||||
Log::info('Impersonation ended', [
|
||||
'timestamp' => now()
|
||||
]);
|
||||
|
||||
$originalUserId = session('impersonated_by');
|
||||
if ($originalUserId) {
|
||||
auth()->loginUsingId($originalUserId);
|
||||
session()->forget('impersonated_by');
|
||||
session()->forget('impersonated_user_id');
|
||||
session()->save();
|
||||
}
|
||||
|
||||
return redirect('/companies')->with('success', __('Returned to admin panel'));
|
||||
}
|
||||
}
|
||||
228
app/Http/Controllers/InterviewController.php
Normal file
228
app/Http/Controllers/InterviewController.php
Normal file
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Interview;
|
||||
use App\Models\Candidate;
|
||||
use App\Models\InterviewRound;
|
||||
use App\Models\InterviewType;
|
||||
use App\Models\User;
|
||||
use App\Models\User as UserModel;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class InterviewController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-interviews')) {
|
||||
$query = Interview::with(['candidate', 'job', 'round', 'interviewType'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-interviews')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-interviews')) {
|
||||
$q->where('created_by', Auth::id())->orwhereJsonContains('interviewers', (string) Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->whereHas('candidate', function ($q) use ($request) {
|
||||
$q->where('first_name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('last_name', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
if ($request->has('candidate_id') && !empty($request->candidate_id) && $request->candidate_id !== 'all') {
|
||||
$query->where('candidate_id', $request->candidate_id);
|
||||
}
|
||||
|
||||
$query->orderBy('id', 'desc');
|
||||
$interviews = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$candidates = Candidate::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('id', 'first_name', 'last_name')
|
||||
->where('status', 'Interview')
|
||||
->get();
|
||||
|
||||
$interviewTypes = InterviewType::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
$employees = UserModel::with('employee')
|
||||
->whereIn('type', ['manager', 'hr', 'employee'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name', 'type')
|
||||
->get()
|
||||
->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'type' => $user->type,
|
||||
'employee_id' => $user->employee->employee_id ?? ''
|
||||
];
|
||||
});
|
||||
|
||||
return Inertia::render('hr/recruitment/interviews/index', [
|
||||
'interviews' => $interviews,
|
||||
'candidates' => $candidates,
|
||||
'interviewTypes' => $interviewTypes,
|
||||
'employees' => $employees,
|
||||
'filters' => $request->all(['search', 'status', 'candidate_id', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'candidate_id' => 'required|exists:candidates,id',
|
||||
'round_id' => 'required|exists:interview_rounds,id',
|
||||
'interview_type_id' => 'required|exists:interview_types,id',
|
||||
'scheduled_date' => 'required|date|after_or_equal:today',
|
||||
'scheduled_time' => 'required|date_format:H:i',
|
||||
'duration' => 'required|integer|min:15|max:480',
|
||||
'location' => 'nullable|string|max:255',
|
||||
'meeting_link' => 'nullable|url',
|
||||
'interviewers' => 'required|array|min:1',
|
||||
'interviewers.*' => 'exists:users,id',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if interview already exists for this candidate and round
|
||||
$existingInterview = Interview::where('candidate_id', $request->candidate_id)
|
||||
->where('round_id', $request->round_id)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($existingInterview) {
|
||||
return redirect()->back()->with('error', __('Interview already exists for this interview round'));
|
||||
}
|
||||
|
||||
$candidate = Candidate::find($request->candidate_id);
|
||||
|
||||
Interview::create([
|
||||
'candidate_id' => $request->candidate_id,
|
||||
'job_id' => $candidate->job_id,
|
||||
'round_id' => $request->round_id,
|
||||
'interview_type_id' => $request->interview_type_id,
|
||||
'scheduled_date' => $request->scheduled_date,
|
||||
'scheduled_time' => $request->scheduled_time,
|
||||
'duration' => $request->duration,
|
||||
'location' => $request->location,
|
||||
'meeting_link' => $request->meeting_link,
|
||||
'interviewers' => $request->interviewers,
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Interview scheduled successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, Interview $interview)
|
||||
{
|
||||
if (!in_array($interview->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this interview'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'candidate_id' => 'required|exists:candidates,id',
|
||||
'round_id' => 'required|exists:interview_rounds,id',
|
||||
'interview_type_id' => 'required|exists:interview_types,id',
|
||||
'scheduled_date' => 'required|date',
|
||||
'scheduled_time' => 'required',
|
||||
'duration' => 'required|integer|min:15|max:480',
|
||||
'location' => 'nullable|string|max:255',
|
||||
'meeting_link' => 'nullable|url',
|
||||
'interviewers' => 'required|array|min:1',
|
||||
'interviewers.*' => 'exists:users,id',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if interview already exists for this candidate and round (excluding current record)
|
||||
$existingInterview = Interview::where('candidate_id', $request->candidate_id)
|
||||
->where('round_id', $request->round_id)
|
||||
->where('id', '!=', $interview->id)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($existingInterview) {
|
||||
return redirect()->back()->with('error', __('Interview already exists for this interview round'));
|
||||
}
|
||||
|
||||
$candidate = Candidate::find($request->candidate_id);
|
||||
|
||||
$interview->update([
|
||||
'candidate_id' => $request->candidate_id,
|
||||
'job_id' => $candidate->job_id,
|
||||
'round_id' => $request->round_id,
|
||||
'interview_type_id' => $request->interview_type_id,
|
||||
'scheduled_date' => $request->scheduled_date,
|
||||
'scheduled_time' => $request->scheduled_time,
|
||||
'duration' => $request->duration,
|
||||
'location' => $request->location,
|
||||
'meeting_link' => $request->meeting_link,
|
||||
'interviewers' => $request->interviewers,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Interview updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(Interview $interview)
|
||||
{
|
||||
if (!in_array($interview->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this interview'));
|
||||
}
|
||||
|
||||
$interview->delete();
|
||||
return redirect()->back()->with('success', __('Interview deleted successfully'));
|
||||
}
|
||||
|
||||
public function updateStatus(Request $request, Interview $interview)
|
||||
{
|
||||
if (!in_array($interview->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this interview'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'status' => 'required|in:Scheduled,Completed,Cancelled,No-show',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator);
|
||||
}
|
||||
|
||||
$interview->update(['status' => $request->status]);
|
||||
return redirect()->back()->with('success', __('Interview status updated successfully'));
|
||||
}
|
||||
|
||||
public function getRoundsByCandidate(Candidate $candidate)
|
||||
{
|
||||
if (!in_array($candidate->created_by, getCompanyAndUsersId())) {
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
$rounds = InterviewRound::where('job_id', $candidate->job_id)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
return response()->json($rounds);
|
||||
}
|
||||
}
|
||||
220
app/Http/Controllers/InterviewFeedbackController.php
Normal file
220
app/Http/Controllers/InterviewFeedbackController.php
Normal file
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\InterviewFeedback;
|
||||
use App\Models\Interview;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class InterviewFeedbackController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-interview-feedback')) {
|
||||
$query = InterviewFeedback::with(['interview.candidate', 'interview.job', 'interview.round'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-interview-feedback')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-interview-feedback')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->whereHas('interview.candidate', function ($q) use ($request) {
|
||||
$q->where('first_name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('last_name', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('recommendation') && !empty($request->recommendation) && $request->recommendation !== 'all') {
|
||||
$query->where('recommendation', $request->recommendation);
|
||||
}
|
||||
|
||||
if ($request->has('interviewer_id') && !empty($request->interviewer_id) && $request->interviewer_id !== 'all') {
|
||||
$query->where('interviewer_id', $request->interviewer_id);
|
||||
}
|
||||
|
||||
$sortField = $request->get('sort_field');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
$allowedSortFields = ['created_at'];
|
||||
if ($sortField && in_array($sortField, $allowedSortFields)) {
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
$interviewFeedback = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$interviewFeedback->getCollection()->transform(function ($feedback) {
|
||||
$feedback->interviewer_names = $feedback->interviewer_names;
|
||||
return $feedback;
|
||||
});
|
||||
|
||||
$interviews = Interview::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'Completed')
|
||||
->with(['candidate', 'job', 'round'])
|
||||
->when(
|
||||
Auth::user()->can('manage-own-interview-feedback') && !Auth::user()->can('manage-any-interview-feedback'),
|
||||
function ($q) {
|
||||
$q->whereJsonContains('interviewers', (string) Auth::id());
|
||||
}
|
||||
)
|
||||
->when(
|
||||
!Auth::user()->can('manage-any-interview-feedback') && !Auth::user()->can('manage-own-interview-feedback'),
|
||||
function ($q) {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
)
|
||||
->get();
|
||||
|
||||
$interviewers = User::whereIn('created_by', getCompanyAndUsersId())
|
||||
->whereIn('type', ['manager', 'hr', 'employee'])
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/recruitment/interview-feedback/index', [
|
||||
'interviewFeedback' => $interviewFeedback,
|
||||
'interviews' => $interviews,
|
||||
'interviewers' => $interviewers,
|
||||
'filters' => $request->all(['search', 'recommendation', 'interviewer_id', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'interview_id' => 'required|exists:interviews,id',
|
||||
'interviewer_id' => 'required',
|
||||
'technical_rating' => 'nullable|numeric|min:0.5|max:5',
|
||||
'communication_rating' => 'nullable|numeric|min:0.5|max:5',
|
||||
'cultural_fit_rating' => 'nullable|numeric|min:0.5|max:5',
|
||||
'overall_rating' => 'nullable|numeric|min:0.5|max:5',
|
||||
'strengths' => 'nullable|string',
|
||||
'weaknesses' => 'nullable|string',
|
||||
'comments' => 'nullable|string',
|
||||
'recommendation' => 'nullable',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$existingFeedback = InterviewFeedback::where('interview_id', $request->interview_id)
|
||||
->where('interviewer_id', $request->interviewer_id)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($existingFeedback) {
|
||||
return redirect()->back()->with('error', __('Feedback already exists for this interview and interviewer'));
|
||||
}
|
||||
|
||||
InterviewFeedback::create([
|
||||
'interview_id' => $request->interview_id,
|
||||
'interviewer_id' => $request->interviewer_id,
|
||||
'technical_rating' => $request->technical_rating,
|
||||
'communication_rating' => $request->communication_rating,
|
||||
'cultural_fit_rating' => $request->cultural_fit_rating,
|
||||
'overall_rating' => $request->overall_rating,
|
||||
'strengths' => $request->strengths,
|
||||
'weaknesses' => $request->weaknesses,
|
||||
'comments' => $request->comments,
|
||||
'recommendation' => $request->recommendation,
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
Interview::where('id', $request->interview_id)->update(['feedback_submitted' => true]);
|
||||
|
||||
return redirect()->back()->with('success', __('Interview feedback submitted successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, InterviewFeedback $interviewFeedback)
|
||||
{
|
||||
if (!in_array($interviewFeedback->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this feedback'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'interview_id' => 'required|exists:interviews,id',
|
||||
'interviewer_id' => 'required',
|
||||
'technical_rating' => 'nullable|numeric|min:0.5|max:5',
|
||||
'communication_rating' => 'nullable|numeric|min:0.5|max:5',
|
||||
'cultural_fit_rating' => 'nullable|numeric|min:0.5|max:5',
|
||||
'overall_rating' => 'nullable|numeric|min:0.5|max:5',
|
||||
'strengths' => 'nullable|string',
|
||||
'weaknesses' => 'nullable|string',
|
||||
'comments' => 'nullable|string',
|
||||
'recommendation' => 'nullable',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$existingFeedback = InterviewFeedback::where('interview_id', $request->interview_id)
|
||||
->where('interviewer_id', $request->interviewer_id)
|
||||
->where('id', '!=', $interviewFeedback->id)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($existingFeedback) {
|
||||
return redirect()->back()->with('error', __('Feedback already exists for this interview and interviewer'));
|
||||
}
|
||||
|
||||
$interviewFeedback->update([
|
||||
'interview_id' => $request->interview_id,
|
||||
'interviewer_id' => $request->interviewer_id,
|
||||
'technical_rating' => $request->technical_rating,
|
||||
'communication_rating' => $request->communication_rating,
|
||||
'cultural_fit_rating' => $request->cultural_fit_rating,
|
||||
'overall_rating' => $request->overall_rating,
|
||||
'strengths' => $request->strengths,
|
||||
'weaknesses' => $request->weaknesses,
|
||||
'comments' => $request->comments,
|
||||
'recommendation' => $request->recommendation,
|
||||
]);
|
||||
|
||||
Interview::where('id', $request->interview_id)->update(['feedback_submitted' => true]);
|
||||
|
||||
return redirect()->back()->with('success', __('Interview feedback updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(InterviewFeedback $interviewFeedback)
|
||||
{
|
||||
if (!in_array($interviewFeedback->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this feedback'));
|
||||
}
|
||||
|
||||
$interviewId = $interviewFeedback->interview_id;
|
||||
$interviewFeedback->delete();
|
||||
|
||||
$remainingFeedback = InterviewFeedback::where('interview_id', $interviewId)->exists();
|
||||
Interview::where('id', $interviewId)->update(['feedback_submitted' => $remainingFeedback]);
|
||||
|
||||
return redirect()->back()->with('success', __('Interview feedback deleted successfully'));
|
||||
}
|
||||
|
||||
public function getInterviewers(Interview $interview)
|
||||
{
|
||||
if (!in_array($interview->created_by, getCompanyAndUsersId())) {
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
$interviewers = User::whereIn('id', $interview->interviewers)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
return response()->json($interviewers);
|
||||
}
|
||||
}
|
||||
171
app/Http/Controllers/InterviewRoundController.php
Normal file
171
app/Http/Controllers/InterviewRoundController.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\InterviewRound;
|
||||
use App\Models\JobPosting;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class InterviewRoundController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-interview-rounds')) {
|
||||
$query = InterviewRound::with(['job'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-interview-rounds')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-interview-rounds')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
if ($request->has('job_id') && !empty($request->job_id) && $request->job_id !== 'all') {
|
||||
$query->where('job_id', $request->job_id);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field');
|
||||
$sortDirection = $request->get('sort_direction', 'asc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['created_at'];
|
||||
if ($sortField && in_array($sortField, $allowedSortFields)) {
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
$interviewRounds = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$jobPostings = JobPosting::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('id', 'title', 'job_code')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/recruitment/interview-rounds/index', [
|
||||
'interviewRounds' => $interviewRounds,
|
||||
'jobPostings' => $jobPostings,
|
||||
'filters' => $request->all(['search', 'status', 'job_id', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'job_id' => 'required|exists:job_postings,id',
|
||||
'name' => 'required|string|max:255',
|
||||
'sequence_number' => 'required|integer|min:1',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if interview round with same job_id and sequence_number already exists
|
||||
$existingRound = InterviewRound::where('job_id', $request->job_id)
|
||||
->where('sequence_number', $request->sequence_number)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($existingRound) {
|
||||
return redirect()->back()->with('error', __('Interview round with sequence number :sequence already exists for this job posting', ['sequence' => $request->sequence_number]));
|
||||
}
|
||||
|
||||
InterviewRound::create([
|
||||
'job_id' => $request->job_id,
|
||||
'name' => $request->name,
|
||||
'sequence_number' => $request->sequence_number,
|
||||
'description' => $request->description,
|
||||
'status' => $request->status ?? 'active',
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Interview round created successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, InterviewRound $interviewRound)
|
||||
{
|
||||
if (!in_array($interviewRound->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this interview round'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'job_id' => 'required|exists:job_postings,id',
|
||||
'name' => 'required|string|max:255',
|
||||
'sequence_number' => 'required|integer|min:1',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
// Check if interview round with same job_id and sequence_number already exists (excluding current record)
|
||||
$existingRound = InterviewRound::where('job_id', $request->job_id)
|
||||
->where('sequence_number', $request->sequence_number)
|
||||
->where('id', '!=', $interviewRound->id)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($existingRound) {
|
||||
return redirect()->back()->with('error', __('Interview round with sequence number :sequence already exists for this job posting', ['sequence' => $request->sequence_number]));
|
||||
}
|
||||
|
||||
$interviewRound->update([
|
||||
'job_id' => $request->job_id,
|
||||
'name' => $request->name,
|
||||
'sequence_number' => $request->sequence_number,
|
||||
'description' => $request->description,
|
||||
'status' => $request->status ?? 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Interview round updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(InterviewRound $interviewRound)
|
||||
{
|
||||
if (!in_array($interviewRound->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this interview round'));
|
||||
}
|
||||
|
||||
if ($interviewRound->interviews()->count() > 0) {
|
||||
return redirect()->back()->with('error', __('Cannot delete interview round as it has associated interviews'));
|
||||
}
|
||||
|
||||
$interviewRound->delete();
|
||||
return redirect()->back()->with('success', __('Interview round deleted successfully'));
|
||||
}
|
||||
|
||||
public function toggleStatus(InterviewRound $interviewRound)
|
||||
{
|
||||
if (!in_array($interviewRound->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this interview round'));
|
||||
}
|
||||
|
||||
$interviewRound->update([
|
||||
'status' => $interviewRound->status === 'active' ? 'inactive' : 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Interview round status updated successfully'));
|
||||
}
|
||||
}
|
||||
132
app/Http/Controllers/InterviewTypeController.php
Normal file
132
app/Http/Controllers/InterviewTypeController.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\InterviewType;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class InterviewTypeController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-interview-types')) {
|
||||
$query = InterviewType::where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-interview-types')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-interview-types')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field', 'created_at');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['name', 'created_at'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'created_at';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
$interviewTypes = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
return Inertia::render('hr/recruitment/interview-types/index', [
|
||||
'interviewTypes' => $interviewTypes,
|
||||
'filters' => $request->all(['search', 'status', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
InterviewType::create([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'status' => $request->status ?? 'active',
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Interview type created successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, InterviewType $interviewType)
|
||||
{
|
||||
if (!in_array($interviewType->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this interview type'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$interviewType->update([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'status' => $request->status ?? 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Interview type updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(InterviewType $interviewType)
|
||||
{
|
||||
if (!in_array($interviewType->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this interview type'));
|
||||
}
|
||||
|
||||
if ($interviewType->interviews()->count() > 0) {
|
||||
return redirect()->back()->with('error', _('Cannot delete interview type as it is being used in interviews'));
|
||||
}
|
||||
|
||||
$interviewType->delete();
|
||||
return redirect()->back()->with('success', __('Interview type deleted successfully'));
|
||||
}
|
||||
|
||||
public function toggleStatus(InterviewType $interviewType)
|
||||
{
|
||||
if (!in_array($interviewType->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this interview type'));
|
||||
}
|
||||
|
||||
$interviewType->update([
|
||||
'status' => $interviewType->status === 'active' ? 'inactive' : 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Interview type status updated successfully'));
|
||||
}
|
||||
}
|
||||
57
app/Http/Controllers/IpRestrictionController.php
Normal file
57
app/Http/Controllers/IpRestrictionController.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\IpRestriction;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class IpRestrictionController extends Controller
|
||||
{
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('create-ip-restriction')) {
|
||||
|
||||
$request->validate([
|
||||
'ip_address' => 'required|unique:ip_restrictions,ip_address',
|
||||
]);
|
||||
|
||||
IpRestriction::create([
|
||||
'ip_address' => $request->ip_address,
|
||||
'created_by' => Auth::id(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('IP Address Added Successfully.'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function update(Request $request, IpRestriction $ipRestriction)
|
||||
{
|
||||
if (Auth::user()->can('edit-ip-restriction')) {
|
||||
$request->validate([
|
||||
'ip_address' => 'required|unique:ip_restrictions,ip_address,'.$ipRestriction->id,
|
||||
]);
|
||||
|
||||
$ipRestriction->update([
|
||||
'ip_address' => $request->ip_address,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('IP Address Update Successfully.'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(IpRestriction $ipRestriction)
|
||||
{
|
||||
if (Auth::user()->can('delete-ip-restriction')) {
|
||||
$ipRestriction->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('IP Address Delete Successfully.'));
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
230
app/Http/Controllers/IyzipayPaymentController.php
Normal file
230
app/Http/Controllers/IyzipayPaymentController.php
Normal file
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Plan;
|
||||
use App\Models\User;
|
||||
use App\Models\Setting;
|
||||
use App\Models\PlanOrder;
|
||||
use App\Models\PaymentSetting;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Iyzipay\Options;
|
||||
use Iyzipay\Model\CheckoutForm;
|
||||
use Iyzipay\Model\CheckoutFormInitialize;
|
||||
use Iyzipay\Request\CreateCheckoutFormInitializeRequest;
|
||||
use Iyzipay\Model\Locale;
|
||||
use Iyzipay\Model\Currency;
|
||||
use Iyzipay\Model\PaymentGroup;
|
||||
use Iyzipay\Model\BasketItemType;
|
||||
use Iyzipay\Model\BasketItem;
|
||||
use Iyzipay\Model\Buyer;
|
||||
use Iyzipay\Model\Address;
|
||||
use Iyzipay\Request\RetrieveCheckoutFormRequest;
|
||||
|
||||
class IyzipayPaymentController extends Controller
|
||||
{
|
||||
private function getIyzipayOptions($settings)
|
||||
{
|
||||
$options = new Options();
|
||||
$options->setApiKey($settings['iyzipay_public_key']);
|
||||
$options->setSecretKey($settings['iyzipay_secret_key']);
|
||||
$options->setBaseUrl($settings['iyzipay_mode'] === 'live'
|
||||
? 'https://api.iyzipay.com'
|
||||
: 'https://sandbox-api.iyzipay.com');
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function processPayment(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request, [
|
||||
'token' => 'required|string',
|
||||
]);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$pricing = calculatePlanPricing($plan, $validated['coupon_code'] ?? null);
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!isset($settings['payment_settings']['iyzipay_secret_key']) || !isset($settings['payment_settings']['iyzipay_public_key'])) {
|
||||
return back()->withErrors(['error' => __('Iyzipay not configured')]);
|
||||
}
|
||||
|
||||
// Retrieve payment result from Iyzipay
|
||||
$paymentResult = $this->retrieveIyzipayPayment($validated['token'], $settings['payment_settings']);
|
||||
|
||||
if ($paymentResult && $paymentResult->getPaymentStatus() === 'SUCCESS') {
|
||||
processPaymentSuccess([
|
||||
'user_id' => auth()->id(),
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => $validated['billing_cycle'],
|
||||
'payment_method' => 'iyzipay',
|
||||
'coupon_code' => $validated['coupon_code'] ?? null,
|
||||
'payment_id' => $paymentResult->getPaymentId(),
|
||||
]);
|
||||
|
||||
return back()->with('success', __('Payment successful and plan activated'));
|
||||
}
|
||||
|
||||
return back()->withErrors(['error' => __('Payment failed or cancelled')]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return handlePaymentError($e, 'iyzipay');
|
||||
}
|
||||
}
|
||||
|
||||
public function createPaymentForm(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$pricing = calculatePlanPricing($plan, $validated['coupon_code'] ?? null);
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!isset($settings['payment_settings']['iyzipay_secret_key']) || !isset($settings['payment_settings']['iyzipay_public_key'])) {
|
||||
return response()->json(['error' => __('Iyzipay not configured')], 400);
|
||||
}
|
||||
|
||||
$user = auth()->user();
|
||||
$conversationId = 'plan_' . $plan->id . '_' . $user->id . '_' . time();
|
||||
$options = $this->getIyzipayOptions($settings['payment_settings']);
|
||||
|
||||
// Create checkout form initialize request
|
||||
$checkoutRequest = new CreateCheckoutFormInitializeRequest();
|
||||
$checkoutRequest->setLocale(Locale::EN);
|
||||
$checkoutRequest->setConversationId($conversationId);
|
||||
$checkoutRequest->setPrice(number_format($pricing['final_price'], 2, '.', ''));
|
||||
$checkoutRequest->setPaidPrice(number_format($pricing['final_price'], 2, '.', ''));
|
||||
$checkoutRequest->setCurrency(Currency::USD);
|
||||
$checkoutRequest->setBasketId('plan_' . $plan->id);
|
||||
$checkoutRequest->setPaymentGroup(PaymentGroup::SUBSCRIPTION);
|
||||
$checkoutRequest->setCallbackUrl(route('iyzipay.callback'));
|
||||
$checkoutRequest->setEnabledInstallments([1]);
|
||||
|
||||
// Set buyer information
|
||||
$buyer = new Buyer();
|
||||
$buyer->setId($user->id);
|
||||
$buyer->setName($user->name ?? 'Customer');
|
||||
$buyer->setSurname('User');
|
||||
$buyer->setGsmNumber('+1234567890');
|
||||
$buyer->setEmail($user->email);
|
||||
$buyer->setIdentityNumber('11111111111');
|
||||
$buyer->setLastLoginDate(now()->format('Y-m-d H:i:s'));
|
||||
$buyer->setRegistrationDate($user->created_at->format('Y-m-d H:i:s'));
|
||||
$buyer->setRegistrationAddress('123 Main Street');
|
||||
$buyer->setIp($request->ip());
|
||||
$buyer->setCity('New York');
|
||||
$buyer->setCountry('United States');
|
||||
$buyer->setZipCode('10001');
|
||||
$checkoutRequest->setBuyer($buyer);
|
||||
|
||||
// Set shipping address
|
||||
$shippingAddress = new Address();
|
||||
$shippingAddress->setContactName($user->name ?? 'Customer User');
|
||||
$shippingAddress->setCity('New York');
|
||||
$shippingAddress->setCountry('United States');
|
||||
$shippingAddress->setAddress('123 Main Street');
|
||||
$shippingAddress->setZipCode('10001');
|
||||
$checkoutRequest->setShippingAddress($shippingAddress);
|
||||
|
||||
// Set billing address
|
||||
$billingAddress = new Address();
|
||||
$billingAddress->setContactName($user->name ?? 'Customer User');
|
||||
$billingAddress->setCity('New York');
|
||||
$billingAddress->setCountry('United States');
|
||||
$billingAddress->setAddress('123 Main Street');
|
||||
$billingAddress->setZipCode('10001');
|
||||
$checkoutRequest->setBillingAddress($billingAddress);
|
||||
|
||||
// Set basket items
|
||||
$basketItem = new BasketItem();
|
||||
$basketItem->setId($plan->id);
|
||||
$basketItem->setName($plan->name);
|
||||
$basketItem->setCategory1('Subscription');
|
||||
$basketItem->setItemType(BasketItemType::VIRTUAL);
|
||||
$basketItem->setPrice(number_format($pricing['final_price'], 2, '.', ''));
|
||||
$basketItems = [$basketItem];
|
||||
$checkoutRequest->setBasketItems($basketItems);
|
||||
|
||||
// Initialize checkout form
|
||||
$checkoutFormInitialize = CheckoutFormInitialize::create($checkoutRequest, $options);
|
||||
|
||||
if ($checkoutFormInitialize->getStatus() === 'success') {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'redirect_url' => $checkoutFormInitialize->getPaymentPageUrl(),
|
||||
'token' => $checkoutFormInitialize->getToken()
|
||||
]);
|
||||
} else {
|
||||
return response()->json(['error' => $checkoutFormInitialize->getErrorMessage()], 400);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Payment form creation failed')], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function callback(Request $request)
|
||||
{
|
||||
try {
|
||||
$token = $request->input('token');
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!$token) {
|
||||
return redirect()->route('plans.index')->withErrors(['error' => __('Invalid payment response')]);
|
||||
}
|
||||
|
||||
// Retrieve payment result from Iyzipay
|
||||
$paymentResult = $this->retrieveIyzipayPayment($token, $settings['payment_settings']);
|
||||
|
||||
if ($paymentResult && $paymentResult->getPaymentStatus() === 'SUCCESS') {
|
||||
// Extract conversation ID to find the plan and user
|
||||
$conversationId = $paymentResult->getConversationId();
|
||||
$parts = explode('_', $conversationId);
|
||||
|
||||
if (count($parts) >= 3) {
|
||||
$planId = $parts[1];
|
||||
$userId = $parts[2];
|
||||
|
||||
$plan = Plan::find($planId);
|
||||
$user = User::find($userId);
|
||||
|
||||
if ($plan && $user) {
|
||||
processPaymentSuccess([
|
||||
'user_id' => $user->id,
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => 'monthly', // Default, should be stored in session or passed
|
||||
'payment_method' => 'iyzipay',
|
||||
'payment_id' => $paymentResult->getPaymentId(),
|
||||
]);
|
||||
|
||||
return redirect()->route('plans.index')->with('success', __('Payment successful! Your plan has been activated.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('plans.index')->withErrors(['error' => __('Payment failed or cancelled')]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->route('plans.index')->withErrors(['error' => __('Payment processing failed')]);
|
||||
}
|
||||
}
|
||||
|
||||
private function retrieveIyzipayPayment($token, $settings)
|
||||
{
|
||||
try {
|
||||
$options = $this->getIyzipayOptions($settings);
|
||||
|
||||
$request = new RetrieveCheckoutFormRequest();
|
||||
$request->setToken($token);
|
||||
|
||||
$checkoutForm = CheckoutForm::retrieve($request, $options);
|
||||
|
||||
return $checkoutForm;
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
155
app/Http/Controllers/JobCategoryController.php
Normal file
155
app/Http/Controllers/JobCategoryController.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\JobCategory;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class JobCategoryController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-job-categories')) {
|
||||
$query = JobCategory::where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-job-categories')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-job-categories')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle status filter
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field', 'id');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['name', 'created_at', 'id'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'id';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
$jobCategories = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
return Inertia::render('hr/recruitment/job-categories/index', [
|
||||
'jobCategories' => $jobCategories,
|
||||
'filters' => $request->all(['search', 'status', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
JobCategory::create([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'status' => $request->status ?? 'active',
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Job category created successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, JobCategory $jobCategory)
|
||||
{
|
||||
// Check if job category belongs to current company
|
||||
if (!in_array($jobCategory->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this job category'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$jobCategory->update([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'status' => $request->status ?? 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Job category updated successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(JobCategory $jobCategory)
|
||||
{
|
||||
// Check if job category belongs to current company
|
||||
if (!in_array($jobCategory->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this job category'));
|
||||
}
|
||||
|
||||
// Check if job category is being used in job requisitions
|
||||
if ($jobCategory->jobRequisitions()->count() > 0) {
|
||||
return redirect()->back()->with('error', __('Cannot delete job category as it is being used in job requisitions'));
|
||||
}
|
||||
|
||||
$jobCategory->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('Job category deleted successfully'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the status of the specified resource.
|
||||
*/
|
||||
public function toggleStatus(JobCategory $jobCategory)
|
||||
{
|
||||
// Check if job category belongs to current company
|
||||
if (!in_array($jobCategory->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this job category'));
|
||||
}
|
||||
|
||||
$jobCategory->update([
|
||||
'status' => $jobCategory->status === 'active' ? 'inactive' : 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Job category status updated successfully'));
|
||||
}
|
||||
}
|
||||
148
app/Http/Controllers/JobLocationController.php
Normal file
148
app/Http/Controllers/JobLocationController.php
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\JobLocation;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class JobLocationController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-job-locations')) {
|
||||
$query = JobLocation::where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-job-locations')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-job-locations')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('city', 'like', '%' . $request->search . '%')
|
||||
->orWhere('address', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
if ($request->has('is_remote') && $request->is_remote !== 'all') {
|
||||
$query->where('is_remote', $request->is_remote === 'true');
|
||||
}
|
||||
|
||||
$query->orderBy('id', 'desc');
|
||||
$jobLocations = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
return Inertia::render('hr/recruitment/job-locations/index', [
|
||||
'jobLocations' => $jobLocations,
|
||||
'filters' => $request->all(['search', 'status', 'is_remote', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'address' => 'nullable|string',
|
||||
'city' => 'nullable|string|max:255',
|
||||
'state' => 'nullable|string|max:255',
|
||||
'country' => 'nullable|string|max:255',
|
||||
'postal_code' => 'nullable|string|max:20',
|
||||
'is_remote' => 'boolean',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
JobLocation::create([
|
||||
'name' => $request->name,
|
||||
'address' => $request->address,
|
||||
'city' => $request->city,
|
||||
'state' => $request->state,
|
||||
'country' => $request->country,
|
||||
'postal_code' => $request->postal_code,
|
||||
'is_remote' => $request->boolean('is_remote'),
|
||||
'status' => $request->status ?? 'active',
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Job location created successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, JobLocation $jobLocation)
|
||||
{
|
||||
if (!in_array($jobLocation->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to update this job location');
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'address' => 'nullable|string',
|
||||
'city' => 'nullable|string|max:255',
|
||||
'state' => 'nullable|string|max:255',
|
||||
'country' => 'nullable|string|max:255',
|
||||
'postal_code' => 'nullable|string|max:20',
|
||||
'is_remote' => 'boolean',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$jobLocation->update([
|
||||
'name' => $request->name,
|
||||
'address' => $request->address,
|
||||
'city' => $request->city,
|
||||
'state' => $request->state,
|
||||
'country' => $request->country,
|
||||
'postal_code' => $request->postal_code,
|
||||
'is_remote' => $request->boolean('is_remote'),
|
||||
'status' => $request->status ?? 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Job location updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(JobLocation $jobLocation)
|
||||
{
|
||||
if (!in_array($jobLocation->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to delete this job location');
|
||||
}
|
||||
|
||||
if ($jobLocation->jobPostings()->count() > 0) {
|
||||
return redirect()->back()->with('error', __('Cannot delete job location as it is being used in job postings'));
|
||||
}
|
||||
|
||||
$jobLocation->delete();
|
||||
return redirect()->back()->with('success', __('Job location deleted successfully'));
|
||||
}
|
||||
|
||||
public function toggleStatus(JobLocation $jobLocation)
|
||||
{
|
||||
if (!in_array($jobLocation->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to update this job location');
|
||||
}
|
||||
|
||||
$jobLocation->update([
|
||||
'status' => $jobLocation->status === 'active' ? 'inactive' : 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Job location status updated successfully'));
|
||||
}
|
||||
}
|
||||
341
app/Http/Controllers/JobPostingController.php
Normal file
341
app/Http/Controllers/JobPostingController.php
Normal file
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Branch;
|
||||
use App\Models\CustomQuestion;
|
||||
use App\Models\Department;
|
||||
use App\Models\JobLocation;
|
||||
use App\Models\JobPosting;
|
||||
use App\Models\JobType;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class JobPostingController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-job-postings')) {
|
||||
$query = JobPosting::with(['requisition', 'jobType', 'location', 'department'])->withCount('candidates')->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-job-postings')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-job-postings')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && ! empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('title', 'like', '%'.$request->search.'%')
|
||||
->orWhere('job_code', 'like', '%'.$request->search.'%');
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('status') && ! empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
if ($request->has('is_published') && $request->is_published !== 'all') {
|
||||
$query->where('is_published', $request->is_published === 'true');
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field', 'id');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field to prevent 500 errors
|
||||
$allowedSortFields = ['job_code', 'title', 'created_at', 'id'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'id';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
$jobPostings = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
return Inertia::render('hr/recruitment/job-postings/index', [
|
||||
'jobPostings' => $jobPostings,
|
||||
'filters' => $request->all(['search', 'status', 'is_published', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
if (! Auth::user()->can('create-job-postings')) {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
|
||||
$jobTypes = JobType::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
$locations = JobLocation::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
$branches = Branch::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
$departments = Department::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name', 'branch_id')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/recruitment/job-postings/create', [
|
||||
'jobTypes' => $jobTypes,
|
||||
'locations' => $locations,
|
||||
'branches' => $branches,
|
||||
'departments' => $departments,
|
||||
'customQuestions' => CustomQuestion::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('id', 'question', 'required')
|
||||
->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(JobPosting $jobPosting)
|
||||
{
|
||||
if (! Auth::user()->can('view-job-postings')) {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
|
||||
if (! in_array($jobPosting->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to view this job posting'));
|
||||
}
|
||||
|
||||
$jobPosting->load(['requisition', 'jobType', 'location', 'department.branch', 'branch']);
|
||||
|
||||
return Inertia::render('hr/recruitment/job-postings/show', [
|
||||
'jobPosting' => $jobPosting,
|
||||
'customQuestions' => CustomQuestion::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('id', 'question', 'required')
|
||||
->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(JobPosting $jobPosting)
|
||||
{
|
||||
if (! Auth::user()->can('edit-job-postings')) {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
|
||||
if (! in_array($jobPosting->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to edit this job posting'));
|
||||
}
|
||||
|
||||
$jobTypes = JobType::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
$locations = JobLocation::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
$branches = Branch::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
$departments = Department::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name', 'branch_id')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/recruitment/job-postings/edit', [
|
||||
'jobPosting' => $jobPosting,
|
||||
'jobTypes' => $jobTypes,
|
||||
'locations' => $locations,
|
||||
'branches' => $branches,
|
||||
'departments' => $departments,
|
||||
'customQuestions' => CustomQuestion::whereIn('created_by', getCompanyAndUsersId())
|
||||
->select('id', 'question', 'required')
|
||||
->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'title' => 'required|string|max:255',
|
||||
'job_type_id' => 'required|exists:job_types,id',
|
||||
'location_id' => 'required|exists:job_locations,id',
|
||||
'branch_id' => 'nullable|exists:branches,id',
|
||||
'department_id' => 'nullable|exists:departments,id',
|
||||
'priority' => 'nullable|in:Low,Medium,High',
|
||||
'skills' => 'required|array',
|
||||
'positions' => 'required|integer|min:1',
|
||||
'min_experience' => 'required|numeric|min:0',
|
||||
'max_experience' => 'required|numeric|min:0',
|
||||
'min_salary' => 'required|numeric|min:0',
|
||||
'max_salary' => 'required|numeric|min:0',
|
||||
'description' => 'required|string',
|
||||
'requirements' => 'required|string',
|
||||
'benefits' => 'nullable|string',
|
||||
'start_date' => 'required|date',
|
||||
'application_deadline' => 'required|date|after:today',
|
||||
'application_type' => 'required|in:existing,custom',
|
||||
'application_url' => 'required|string',
|
||||
'applicant' => 'nullable|array',
|
||||
'visibility' => 'nullable|array',
|
||||
'custom_question' => 'nullable|array',
|
||||
'is_featured' => 'boolean',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$jobPosting = new JobPosting;
|
||||
$jobPosting->job_code = JobPosting::generateJobCode(null);
|
||||
$jobPosting->title = $request->title;
|
||||
$jobPosting->job_type_id = $request->job_type_id;
|
||||
$jobPosting->location_id = $request->location_id;
|
||||
$jobPosting->branch_id = $request->branch_id;
|
||||
$jobPosting->department_id = $request->department_id;
|
||||
$jobPosting->priority = $request->priority ?: 'Medium';
|
||||
$jobPosting->skills = $request->skills;
|
||||
$jobPosting->positions = $request->positions;
|
||||
$jobPosting->min_experience = $request->min_experience;
|
||||
$jobPosting->max_experience = $request->max_experience;
|
||||
$jobPosting->min_salary = $request->min_salary;
|
||||
$jobPosting->max_salary = $request->max_salary;
|
||||
$jobPosting->description = $request->description;
|
||||
$jobPosting->requirements = $request->requirements;
|
||||
$jobPosting->benefits = $request->benefits;
|
||||
$jobPosting->start_date = $request->start_date;
|
||||
$jobPosting->application_deadline = $request->application_deadline;
|
||||
$jobPosting->visibility = $request->has('visibility') ? $request->visibility : null;
|
||||
$jobPosting->custom_question = $request->has('custom_question') ? $request->custom_question : null;
|
||||
$jobPosting->applicant = $request->has('applicant') ? $request->applicant : null;
|
||||
$jobPosting->code = uniqid();
|
||||
$jobPosting->application_type = $request->application_type;
|
||||
$jobPosting->application_url = $request->application_url;
|
||||
$jobPosting->is_featured = $request->boolean('is_featured');
|
||||
$jobPosting->created_by = creatorId();
|
||||
$jobPosting->save();
|
||||
|
||||
return redirect()->route('hr.recruitment.job-postings.index')->with('success', __('Job posting created successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, JobPosting $jobPosting)
|
||||
{
|
||||
if (! in_array($jobPosting->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this job posting'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'title' => 'required|string|max:255',
|
||||
'job_type_id' => 'required|exists:job_types,id',
|
||||
'location_id' => 'required|exists:job_locations,id',
|
||||
'branch_id' => 'required|exists:branches,id',
|
||||
'department_id' => 'nullable|exists:departments,id',
|
||||
'priority' => 'required|in:Low,Medium,High',
|
||||
'status' => 'required|in:Draft,Published,Closed',
|
||||
'positions' => 'required|integer|min:1',
|
||||
'min_experience' => 'required|numeric|min:0',
|
||||
'max_experience' => 'nullable|numeric|min:0',
|
||||
'min_salary' => 'nullable|numeric|min:0',
|
||||
'max_salary' => 'nullable|numeric|min:0',
|
||||
'description' => 'nullable|string',
|
||||
'requirements' => 'nullable|string',
|
||||
'education' => 'nullable|string',
|
||||
'benefits' => 'nullable|string',
|
||||
'start_date' => 'nullable|date',
|
||||
'application_deadline' => 'nullable|date',
|
||||
'application_type' => 'required|in:existing,custom',
|
||||
'application_url' => 'required|string',
|
||||
'skills' => 'required|array',
|
||||
'applicant' => 'nullable|array',
|
||||
'visibility' => 'nullable|array',
|
||||
'custom_question' => 'nullable|array',
|
||||
'is_featured' => 'boolean',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$jobPosting->title = $request->title;
|
||||
$jobPosting->job_type_id = $request->job_type_id;
|
||||
$jobPosting->location_id = $request->location_id;
|
||||
$jobPosting->branch_id = $request->branch_id;
|
||||
$jobPosting->department_id = $request->department_id;
|
||||
$jobPosting->priority = $request->priority;
|
||||
$jobPosting->status = $request->status;
|
||||
$jobPosting->skills = $request->skills;
|
||||
$jobPosting->positions = $request->positions;
|
||||
$jobPosting->min_experience = $request->min_experience;
|
||||
$jobPosting->max_experience = $request->max_experience;
|
||||
$jobPosting->min_salary = $request->min_salary;
|
||||
$jobPosting->max_salary = $request->max_salary;
|
||||
$jobPosting->description = $request->description;
|
||||
$jobPosting->requirements = $request->requirements;
|
||||
$jobPosting->benefits = $request->benefits;
|
||||
$jobPosting->start_date = $request->start_date;
|
||||
$jobPosting->application_deadline = $request->application_deadline;
|
||||
$jobPosting->visibility = $request->has('visibility') ? $request->visibility : null;
|
||||
$jobPosting->applicant = $request->has('applicant') ? $request->applicant : null;
|
||||
$jobPosting->custom_question = $request->has('custom_question') ? $request->custom_question : null;
|
||||
$jobPosting->application_type = $request->application_type;
|
||||
$jobPosting->application_url = $request->application_url;
|
||||
$jobPosting->is_featured = $request->boolean('is_featured');
|
||||
$jobPosting->save();
|
||||
|
||||
return redirect()->route('hr.recruitment.job-postings.index')->with('success', __('Job posting updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(JobPosting $jobPosting)
|
||||
{
|
||||
if (! in_array($jobPosting->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this job posting'));
|
||||
}
|
||||
|
||||
if ($jobPosting->candidates()->count() > 0) {
|
||||
return redirect()->back()->with('error', __('Cannot delete job posting as it has associated candidates'));
|
||||
}
|
||||
|
||||
$jobPosting->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('Job posting deleted successfully'));
|
||||
}
|
||||
|
||||
public function publish(JobPosting $jobPosting)
|
||||
{
|
||||
if (! in_array($jobPosting->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to publish this job posting'));
|
||||
}
|
||||
|
||||
$jobPosting->update([
|
||||
'is_published' => true,
|
||||
'publish_date' => now(),
|
||||
'status' => 'Published',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Job posting published successfully'));
|
||||
}
|
||||
|
||||
public function unpublish(JobPosting $jobPosting)
|
||||
{
|
||||
if (! in_array($jobPosting->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to unpublish this job posting'));
|
||||
}
|
||||
|
||||
$jobPosting->update([
|
||||
'is_published' => false,
|
||||
'status' => 'Draft',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Job posting unpublished successfully'));
|
||||
}
|
||||
}
|
||||
197
app/Http/Controllers/JobRequisitionController.php
Normal file
197
app/Http/Controllers/JobRequisitionController.php
Normal file
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\JobRequisition;
|
||||
use App\Models\JobCategory;
|
||||
use App\Models\Department;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class JobRequisitionController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-job-requisitions')) {
|
||||
$query = JobRequisition::with(['jobCategory', 'department.branch', 'creator'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-job-requisitions')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-job-requisitions')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('title', 'like', '%' . $request->search . '%')
|
||||
->orWhere('requisition_code', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
if ($request->has('priority') && !empty($request->priority) && $request->priority !== 'all') {
|
||||
$query->where('priority', $request->priority);
|
||||
}
|
||||
|
||||
$query->orderBy('id', 'desc');
|
||||
$jobRequisitions = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$jobCategories = JobCategory::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
|
||||
$departments = Department::with('branch')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->select('id', 'name', 'branch_id')
|
||||
->get();
|
||||
|
||||
return Inertia::render('hr/recruitment/job-requisitions/index', [
|
||||
'jobRequisitions' => $jobRequisitions,
|
||||
'jobCategories' => $jobCategories,
|
||||
'departments' => $departments,
|
||||
'filters' => $request->all(['search', 'status', 'priority', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'title' => 'required|string|max:255',
|
||||
'job_category_id' => 'required|exists:job_categories,id',
|
||||
'department_id' => 'nullable|exists:departments,id',
|
||||
'positions_count' => 'required|integer|min:1',
|
||||
'budget_min' => 'nullable|numeric|min:0',
|
||||
'budget_max' => 'nullable|numeric|min:0',
|
||||
'skills_required' => 'nullable|string',
|
||||
'education_required' => 'nullable|string',
|
||||
'experience_required' => 'nullable|string',
|
||||
'description' => 'nullable|string',
|
||||
'responsibilities' => 'nullable|string',
|
||||
'priority' => 'required|in:Low,Medium,High',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$requisitionCode = 'REQ-' . creatorId() . '-' . str_pad(
|
||||
JobRequisition::whereIn('created_by', getCompanyAndUsersId())->count() + 1,
|
||||
4,
|
||||
'0',
|
||||
STR_PAD_LEFT
|
||||
);
|
||||
|
||||
JobRequisition::create([
|
||||
'requisition_code' => $requisitionCode,
|
||||
'title' => $request->title,
|
||||
'job_category_id' => $request->job_category_id,
|
||||
'department_id' => $request->department_id,
|
||||
'positions_count' => $request->positions_count,
|
||||
'budget_min' => $request->budget_min,
|
||||
'budget_max' => $request->budget_max,
|
||||
'skills_required' => $request->skills_required,
|
||||
'education_required' => $request->education_required,
|
||||
'experience_required' => $request->experience_required,
|
||||
'description' => $request->description,
|
||||
'responsibilities' => $request->responsibilities,
|
||||
'priority' => $request->priority,
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Job requisition created successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, JobRequisition $jobRequisition)
|
||||
{
|
||||
if (!in_array($jobRequisition->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to update this job requisition');
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'title' => 'required|string|max:255',
|
||||
'job_category_id' => 'required|exists:job_categories,id',
|
||||
'department_id' => 'nullable|exists:departments,id',
|
||||
'positions_count' => 'required|integer|min:1',
|
||||
'budget_min' => 'nullable|numeric|min:0',
|
||||
'budget_max' => 'nullable|numeric|min:0',
|
||||
'skills_required' => 'nullable|string',
|
||||
'education_required' => 'nullable|string',
|
||||
'experience_required' => 'nullable|string',
|
||||
'description' => 'nullable|string',
|
||||
'responsibilities' => 'nullable|string',
|
||||
'priority' => 'required|in:Low,Medium,High',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$jobRequisition->update($request->only([
|
||||
'title',
|
||||
'job_category_id',
|
||||
'department_id',
|
||||
'positions_count',
|
||||
'budget_min',
|
||||
'budget_max',
|
||||
'skills_required',
|
||||
'education_required',
|
||||
'experience_required',
|
||||
'description',
|
||||
'responsibilities',
|
||||
'priority'
|
||||
]));
|
||||
|
||||
return redirect()->back()->with('success', __('Job requisition updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(JobRequisition $jobRequisition)
|
||||
{
|
||||
if (!in_array($jobRequisition->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to delete this job requisition');
|
||||
}
|
||||
|
||||
if ($jobRequisition->jobPostings()->count() > 0) {
|
||||
return redirect()->back()->with('error', __('Cannot delete job requisition as it has associated job postings'));
|
||||
}
|
||||
|
||||
$jobRequisition->delete();
|
||||
return redirect()->back()->with('success', __('Job requisition deleted successfully'));
|
||||
}
|
||||
|
||||
public function updateStatus(Request $request, JobRequisition $jobRequisition)
|
||||
{
|
||||
if (!in_array($jobRequisition->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', 'You do not have permission to update this job requisition');
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'status' => 'required|in:Draft,Pending Approval,Approved,On Hold,Closed',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator);
|
||||
}
|
||||
|
||||
$updateData = ['status' => $request->status];
|
||||
|
||||
if ($request->status === 'Approved') {
|
||||
$updateData['approved_by'] = creatorId();
|
||||
$updateData['approval_date'] = now();
|
||||
}
|
||||
|
||||
$jobRequisition->update($updateData);
|
||||
return redirect()->back()->with('success', __('Job requisition status updated successfully'));
|
||||
}
|
||||
}
|
||||
133
app/Http/Controllers/JobTypeController.php
Normal file
133
app/Http/Controllers/JobTypeController.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\JobType;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class JobTypeController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-job-types')) {
|
||||
$query = JobType::where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-job-types')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-job-types')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
$sortField = $request->get('sort_field', 'id');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
// Validate sort field
|
||||
$allowedSortFields = ['name', 'created_at', 'id'];
|
||||
if (!in_array($sortField, $allowedSortFields)) {
|
||||
$sortField = 'id';
|
||||
}
|
||||
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
|
||||
$jobTypes = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
return Inertia::render('hr/recruitment/job-types/index', [
|
||||
'jobTypes' => $jobTypes,
|
||||
'filters' => $request->all(['search', 'status', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
JobType::create([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'status' => $request->status ?? 'active',
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Job type created successfully'));
|
||||
}
|
||||
|
||||
public function update(Request $request, JobType $jobType)
|
||||
{
|
||||
if (!in_array($jobType->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this job type'));
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:active,inactive',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$jobType->update([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'status' => $request->status ?? 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Job type updated successfully'));
|
||||
}
|
||||
|
||||
public function destroy(JobType $jobType)
|
||||
{
|
||||
if (!in_array($jobType->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to delete this job type'));
|
||||
}
|
||||
|
||||
if ($jobType->jobPostings()->count() > 0) {
|
||||
return redirect()->back()->with('error', __('Cannot delete job type as it is being used in job postings'));
|
||||
}
|
||||
|
||||
$jobType->delete();
|
||||
return redirect()->back()->with('success', __('Job type deleted successfully'));
|
||||
}
|
||||
|
||||
public function toggleStatus(JobType $jobType)
|
||||
{
|
||||
if (!in_array($jobType->created_by, getCompanyAndUsersId())) {
|
||||
return redirect()->back()->with('error', __('You do not have permission to update this job type'));
|
||||
}
|
||||
|
||||
$jobType->update([
|
||||
'status' => $jobType->status === 'active' ? 'inactive' : 'active',
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', __('Job type status updated successfully'));
|
||||
}
|
||||
}
|
||||
43
app/Http/Controllers/JoiningLetterTemplateController.php
Normal file
43
app/Http/Controllers/JoiningLetterTemplateController.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\JoiningLetterTemplate;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class JoiningLetterTemplateController extends Controller
|
||||
{
|
||||
public function update(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('update-joining-letter')) {
|
||||
$request->validate([
|
||||
'content' => 'required|string'
|
||||
]);
|
||||
|
||||
if ($request->templateId) {
|
||||
// Update existing template
|
||||
$template = JoiningLetterTemplate::where('id', $request->templateId)
|
||||
->where('created_by', auth::id())
|
||||
->firstOrFail();
|
||||
$template->update(['content' => $request->content]);
|
||||
} else {
|
||||
// Create or update by language
|
||||
$template = JoiningLetterTemplate::updateOrCreate(
|
||||
[
|
||||
'language' => $request->language,
|
||||
'created_by' => auth::id()
|
||||
],
|
||||
[
|
||||
'content' => $request->content
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('Joining Letter template updated successfully.'));
|
||||
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
108
app/Http/Controllers/KhaltiPaymentController.php
Normal file
108
app/Http/Controllers/KhaltiPaymentController.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Plan;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class KhaltiPaymentController extends Controller
|
||||
{
|
||||
public function processPayment(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request, [
|
||||
'token' => 'required|string',
|
||||
'amount' => 'required|numeric',
|
||||
]);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!isset($settings['payment_settings']['khalti_secret_key'])) {
|
||||
return back()->withErrors(['error' => __('Khalti not configured')]);
|
||||
}
|
||||
|
||||
// Verify payment with Khalti API
|
||||
$isValid = $this->verifyKhaltiPayment($validated['token'], $validated['amount'], $settings['payment_settings']);
|
||||
|
||||
if ($isValid) {
|
||||
processPaymentSuccess([
|
||||
'user_id' => auth()->id(),
|
||||
'plan_id' => $plan->id,
|
||||
'billing_cycle' => $validated['billing_cycle'],
|
||||
'payment_method' => 'khalti',
|
||||
'coupon_code' => $validated['coupon_code'] ?? null,
|
||||
'payment_id' => $validated['token'],
|
||||
]);
|
||||
|
||||
return back()->with('success', __('Payment successful and plan activated'));
|
||||
}
|
||||
|
||||
return back()->withErrors(['error' => __('Payment verification failed')]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return handlePaymentError($e, 'khalti');
|
||||
}
|
||||
}
|
||||
|
||||
public function createPayment(Request $request)
|
||||
{
|
||||
$validated = validatePaymentRequest($request);
|
||||
|
||||
try {
|
||||
$plan = Plan::findOrFail($validated['plan_id']);
|
||||
$pricing = calculatePlanPricing($plan, $validated['coupon_code'] ?? null);
|
||||
$settings = getPaymentGatewaySettings();
|
||||
|
||||
if (!isset($settings['payment_settings']['khalti_public_key'])) {
|
||||
return response()->json(['error' => __('Khalti not configured')], 400);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'public_key' => $settings['payment_settings']['khalti_public_key'],
|
||||
'amount' => $pricing['final_price'] * 100, // Khalti uses paisa
|
||||
'product_identity' => 'plan_' . $plan->id,
|
||||
'product_name' => $plan->name,
|
||||
'product_url' => route('plans.index'),
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Payment creation failed')], 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function verifyKhaltiPayment($token, $amount, $settings)
|
||||
{
|
||||
try {
|
||||
$url = 'https://khalti.com/api/v2/payment/verify/';
|
||||
|
||||
$data = [
|
||||
'token' => $token,
|
||||
'amount' => $amount * 100, // Convert to paisa
|
||||
];
|
||||
|
||||
$headers = [
|
||||
'Authorization: Key ' . $settings['khalti_secret_key'],
|
||||
'Content-Type: application/json',
|
||||
];
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
$result = json_decode($response, true);
|
||||
|
||||
return isset($result['state']['name']) && $result['state']['name'] === 'Completed';
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
114
app/Http/Controllers/LandingPage/CustomPageController.php
Normal file
114
app/Http/Controllers/LandingPage/CustomPageController.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\LandingPage;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\LandingPageCustomPage;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class CustomPageController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = LandingPageCustomPage::query();
|
||||
|
||||
// Search functionality
|
||||
if ($request->filled('search')) {
|
||||
$search = $request->get('search');
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('title', 'like', "%{$search}%")
|
||||
->orWhere('content', 'like', "%{$search}%")
|
||||
->orWhere('slug', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
// Sorting
|
||||
$sortField = $request->get('sort_field', 'sort_order');
|
||||
$sortDirection = $request->get('sort_direction', 'asc');
|
||||
|
||||
if (in_array($sortField, ['title', 'created_at', 'sort_order'])) {
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->ordered();
|
||||
}
|
||||
|
||||
$pages = $query->paginate($request->get('per_page', 10))
|
||||
->withQueryString();
|
||||
|
||||
return Inertia::render('landing-page/custom-pages/index', [
|
||||
'pages' => $pages,
|
||||
'filters' => $request->only(['search', 'sort_field', 'sort_direction', 'per_page'])
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return Inertia::render('landing-page/custom-pages/create');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255|unique:landing_page_custom_pages,title,' . $customPage->id,
|
||||
'content' => 'required|string',
|
||||
'meta_title' => 'nullable|string|max:255',
|
||||
'meta_description' => 'nullable|string',
|
||||
'is_active' => 'boolean',
|
||||
'sort_order' => 'nullable|integer'
|
||||
]);
|
||||
|
||||
LandingPageCustomPage::create($validated);
|
||||
|
||||
return redirect()->route('landing-page.custom-pages.index')->with('success', __('Custom page created successfully!'));
|
||||
}
|
||||
|
||||
public function edit(LandingPageCustomPage $customPage)
|
||||
{
|
||||
return Inertia::render('landing-page/custom-pages/edit', [
|
||||
'page' => $customPage
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, LandingPageCustomPage $customPage)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255|unique:landing_page_custom_pages,title,' . $customPage->id,
|
||||
'content' => 'required|string',
|
||||
'meta_title' => 'nullable|string|max:255',
|
||||
'meta_description' => 'nullable|string',
|
||||
'is_active' => 'sometimes|boolean',
|
||||
'sort_order' => 'nullable|integer'
|
||||
]);
|
||||
|
||||
// Ensure is_active is properly handled
|
||||
if (!isset($validated['is_active'])) {
|
||||
$validated['is_active'] = $request->has('is_active') ? (bool)$request->input('is_active') : false;
|
||||
}
|
||||
|
||||
$customPage->update($validated);
|
||||
|
||||
return redirect()->route('landing-page.custom-pages.index')->with('success', __('Custom page updated successfully!'));
|
||||
}
|
||||
|
||||
public function destroy(LandingPageCustomPage $customPage)
|
||||
{
|
||||
$customPage->delete();
|
||||
return back()->with('success', __('Custom page deleted successfully!'));
|
||||
}
|
||||
|
||||
public function show($slug)
|
||||
{
|
||||
$page = LandingPageCustomPage::where('slug', $slug)->where('is_active', true)->firstOrFail();
|
||||
$landingSettings = \App\Models\LandingPageSetting::getSettings();
|
||||
|
||||
// Track page visit for super admin analytics
|
||||
// \Shetabit\Visitor\Facade\Visitor::visit();
|
||||
|
||||
return Inertia::render('landing-page/custom-page', [
|
||||
'page' => $page,
|
||||
'customPages' => LandingPageCustomPage::active()->ordered()->get(),
|
||||
'settings' => $landingSettings
|
||||
]);
|
||||
}
|
||||
}
|
||||
164
app/Http/Controllers/LandingPageController.php
Normal file
164
app/Http/Controllers/LandingPageController.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use App\Models\Plan;
|
||||
use App\Models\LandingPageSetting;
|
||||
use App\Models\LandingPageCustomPage;
|
||||
use App\Models\Business;
|
||||
use App\Models\Contact;
|
||||
use App\Models\User;
|
||||
use App\Models\NewsLetter;
|
||||
|
||||
class LandingPageController extends Controller
|
||||
{
|
||||
public function show(Request $request)
|
||||
{
|
||||
$host = $request->getHost();
|
||||
$hostParts = explode('.', $host);
|
||||
|
||||
// Track general landing page visit
|
||||
// \Shetabit\Visitor\Facade\Visitor::visit();
|
||||
|
||||
// Check if landing page is enabled in settings
|
||||
if (!isLandingPageEnabled()) {
|
||||
return redirect()->route('login');
|
||||
}
|
||||
|
||||
$landingSettings = LandingPageSetting::getSettings();
|
||||
|
||||
$plans = collect();
|
||||
|
||||
if (isSaas()) {
|
||||
$plans = Plan::where('is_plan_enable', 'on')->get()->map(function ($plan) {
|
||||
$features = [];
|
||||
if ($plan->enable_custdomain === 'on')
|
||||
$features[] = 'Custom Domain';
|
||||
if ($plan->enable_custsubdomain === 'on')
|
||||
$features[] = 'Subdomain';
|
||||
if ($plan->pwa_business === 'on')
|
||||
$features[] = 'PWA';
|
||||
if ($plan->enable_chatgpt === 'on')
|
||||
$features[] = 'AI Integration';
|
||||
|
||||
return [
|
||||
'id' => $plan->id,
|
||||
'name' => $plan->name,
|
||||
'price' => $plan->price,
|
||||
'yearly_price' => $plan->yearly_price,
|
||||
'duration' => $plan->duration,
|
||||
'description' => $plan->description,
|
||||
'features' => $features,
|
||||
'stats' => [
|
||||
'employees' => $plan->max_employees,
|
||||
'users' => $plan->max_users,
|
||||
'storage' => $plan->storage_limit . ' GB',
|
||||
],
|
||||
'is_plan_enable' => $plan->is_plan_enable,
|
||||
'is_popular' => false // Will be set based on subscriber count
|
||||
];
|
||||
});
|
||||
|
||||
// Mark most subscribed plan as popular
|
||||
$planSubscriberCounts = Plan::withCount('users')->get()->pluck('users_count', 'id');
|
||||
if ($planSubscriberCounts->isNotEmpty()) {
|
||||
$mostSubscribedPlanId = $planSubscriberCounts->keys()->sortByDesc(function ($planId) use ($planSubscriberCounts) {
|
||||
return $planSubscriberCounts[$planId];
|
||||
})->first();
|
||||
|
||||
$plans = $plans->map(function ($plan) use ($mostSubscribedPlanId) {
|
||||
if ($plan['id'] == $mostSubscribedPlanId && $plan['price'] != '0') {
|
||||
$plan['is_popular'] = true;
|
||||
}
|
||||
return $plan;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Inertia::render('landing-page/index', [
|
||||
'plans' => $plans,
|
||||
'testimonials' => [],
|
||||
'faqs' => [],
|
||||
'customPages' => LandingPageCustomPage::active()->ordered()->get() ?? [],
|
||||
'settings' => $landingSettings
|
||||
]);
|
||||
}
|
||||
|
||||
public function submitContact(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|max:255',
|
||||
'subject' => 'required|string|max:255',
|
||||
'message' => 'required|string'
|
||||
]);
|
||||
|
||||
if (isSaaS()) {
|
||||
$user = User::where('type', 'superadmin')->orWhere('type', 'super admin')->first();
|
||||
} else {
|
||||
$user = User::where('type', 'company')->first();
|
||||
}
|
||||
|
||||
$contact = new Contact();
|
||||
$contact->name = $request->name;
|
||||
$contact->email = $request->email;
|
||||
$contact->subject = $request->subject;
|
||||
$contact->message = $request->message;
|
||||
$contact->created_by = $user->id;
|
||||
$contact->save();
|
||||
|
||||
return back()->with('success', __('Thank you for your message. We will get back to you soon!'));
|
||||
}
|
||||
|
||||
public function subscribe(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'email' => 'required|email|max:255'
|
||||
]);
|
||||
|
||||
try {
|
||||
// Check if email already exists
|
||||
$existingSubscriber = NewsLetter::where('email', $request->email)->first();
|
||||
|
||||
if ($existingSubscriber) {
|
||||
return back()->with('error', __('This email is already subscribed to our newsletter.'));
|
||||
}
|
||||
|
||||
// Create new newsletter subscription
|
||||
NewsLetter::create([
|
||||
'email' => $request->email
|
||||
]);
|
||||
|
||||
return back()->with('success', __('Thank you for subscribing to our newsletter!'));
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Newsletter subscription failed: ' . $e->getMessage());
|
||||
return back()->with('error', __('Something went wrong. Please try again later.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function settings()
|
||||
{
|
||||
$landingSettings = LandingPageSetting::getSettings();
|
||||
|
||||
return Inertia::render('landing-page/settings', [
|
||||
'settings' => $landingSettings
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateSettings(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'company_name' => 'required|string|max:255',
|
||||
'contact_email' => 'required|email|max:255',
|
||||
'contact_phone' => 'required|string|max:255',
|
||||
'contact_address' => 'required|string|max:255',
|
||||
'config_sections' => 'required|array'
|
||||
]);
|
||||
$landingSettings = LandingPageSetting::getSettings();
|
||||
$landingSettings->update($request->all());
|
||||
|
||||
return back()->with('success', __('Landing page settings updated successfully!'));
|
||||
}
|
||||
}
|
||||
275
app/Http/Controllers/LanguageController.php
Normal file
275
app/Http/Controllers/LanguageController.php
Normal file
@@ -0,0 +1,275 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use App\Models\AddOn;
|
||||
|
||||
class LanguageController extends Controller
|
||||
{
|
||||
// Show the manage language Inertia page
|
||||
public function managePage(Request $request, $lang = null)
|
||||
{
|
||||
$langListPath = resource_path('lang/language.json');
|
||||
$languages = [];
|
||||
if (File::exists($langListPath)) {
|
||||
$languages = json_decode(File::get($langListPath), true);
|
||||
}
|
||||
$defaultLang = 'en';
|
||||
$selectedLang = $defaultLang;
|
||||
if ($lang && collect($languages)->pluck('code')->contains($lang)) {
|
||||
$selectedLang = $lang;
|
||||
}
|
||||
$defaultData = [];
|
||||
if (File::exists(resource_path("lang/{$selectedLang}.json"))) {
|
||||
$defaultData = json_decode(File::get(resource_path("lang/{$selectedLang}.json")), true);
|
||||
}
|
||||
return Inertia::render('manage-language', [
|
||||
'languages' => $languages,
|
||||
'defaultLang' => $selectedLang,
|
||||
'defaultData' => $defaultData,
|
||||
]);
|
||||
}
|
||||
|
||||
// Load a language file
|
||||
public function load(Request $request)
|
||||
{
|
||||
$langListPath = resource_path('lang/language.json');
|
||||
$languages = collect();
|
||||
if (File::exists($langListPath)) {
|
||||
$languages = collect(json_decode(File::get($langListPath), true));
|
||||
}
|
||||
$lang = $request->get('lang', 'en');
|
||||
if (!$languages->pluck('code')->contains($lang)) {
|
||||
return response()->json(['error' => __('Language not found')], 404);
|
||||
}
|
||||
$langPath = resource_path("lang/{$lang}.json");
|
||||
if (!File::exists($langPath)) {
|
||||
return response()->json(['error' => __('Language file not found')], 404);
|
||||
}
|
||||
$data = json_decode(File::get($langPath), true);
|
||||
return response()->json(['data' => $data]);
|
||||
}
|
||||
|
||||
// Save a language file
|
||||
public function save(Request $request)
|
||||
{
|
||||
try {
|
||||
$langListPath = resource_path('lang/language.json');
|
||||
$languages = collect();
|
||||
if (File::exists($langListPath)) {
|
||||
$languages = collect(json_decode(File::get($langListPath), true));
|
||||
}
|
||||
$lang = $request->get('lang');
|
||||
$data = $request->get('data');
|
||||
if (!$lang || !is_array($data) || !$languages->pluck('code')->contains($lang)) {
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['error' => __('Invalid request')], 400);
|
||||
}
|
||||
return redirect()->back()->with('error', __('Invalid request'));
|
||||
}
|
||||
$langPath = resource_path("lang/{$lang}.json");
|
||||
if (!File::exists($langPath)) {
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['error' => __('Language file not found')], 404);
|
||||
}
|
||||
return redirect()->back()->with('error', __('Language file not found'));
|
||||
}
|
||||
File::put($langPath, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['success' => __('Language updated successfully')]);
|
||||
}
|
||||
return redirect()->back()->with('success', __('Language updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['error' => __('Failed to update language file: ') . $e->getMessage()], 500);
|
||||
}
|
||||
return redirect()->back()->with('error', __('Failed to update language file: ') . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function createLanguage(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'code' => 'required|string|max:10',
|
||||
'name' => 'required|string|max:255',
|
||||
'countryCode' => 'required|string|size:2'
|
||||
], [
|
||||
'code.required' => __('Language code is required.'),
|
||||
'code.string' => __('Language code must be a valid string.'),
|
||||
'code.max' => __('Language code must not exceed 10 characters.'),
|
||||
'name.required' => __('Language name is required.'),
|
||||
'name.string' => __('Language name must be a valid string.'),
|
||||
'name.max' => __('Language name must not exceed 255 characters.'),
|
||||
'countryCode.required' => __('Country code is required.'),
|
||||
'countryCode.string' => __('Country code must be a valid string.'),
|
||||
'countryCode.size' => __('Country code must be exactly 2 characters.'),
|
||||
]);
|
||||
|
||||
try {
|
||||
// Check if language already exists in language.json
|
||||
$languagesFile = resource_path('lang/language.json');
|
||||
|
||||
if (!is_writable($languagesFile)) {
|
||||
return response()->json(['error' => __('Language file is not writable. Please check file permissions.')], 500);
|
||||
}
|
||||
|
||||
$languages = json_decode(File::get($languagesFile), true);
|
||||
|
||||
$existingLanguage = collect($languages)->firstWhere('code', $request->code);
|
||||
if ($existingLanguage) {
|
||||
return response()->json(['error' => __('The language code already exists')], 422);
|
||||
}
|
||||
|
||||
$languages[] = [
|
||||
'code' => $request->code,
|
||||
'name' => $request->name,
|
||||
'countryCode' => strtoupper($request->countryCode)
|
||||
];
|
||||
|
||||
$result = File::put($languagesFile, json_encode($languages, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
if ($result === false) {
|
||||
return response()->json(['error' => __('Failed to write to language file. Please check file permissions.')], 500);
|
||||
}
|
||||
|
||||
// Copy en.json to new language
|
||||
$enFile = resource_path('lang/en.json');
|
||||
$newLangFile = resource_path("lang/{$request->code}.json");
|
||||
if (File::exists($enFile)) {
|
||||
$enContent = File::get($enFile);
|
||||
File::put($newLangFile, $enContent);
|
||||
} else {
|
||||
// Create empty translation file if en.json doesn't exist
|
||||
File::put($newLangFile, json_encode([], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
return response()->json(['success' => true, 'message' => __('The language has been created successfully.')]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => 'Failed to create language: ' . $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteLanguage($languageCode)
|
||||
{
|
||||
if ($languageCode === 'en') {
|
||||
return response()->json(['error' => __('Cannot delete English language')], 422);
|
||||
}
|
||||
|
||||
try {
|
||||
// Remove from language.json
|
||||
$languagesFile = resource_path('lang/language.json');
|
||||
$languages = json_decode(File::get($languagesFile), true);
|
||||
$languages = array_filter($languages, fn($lang) => $lang['code'] !== $languageCode);
|
||||
File::put($languagesFile, json_encode(array_values($languages), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
|
||||
// Delete main language file
|
||||
$mainLangFile = resource_path("lang/{$languageCode}.json");
|
||||
if (File::exists($mainLangFile)) {
|
||||
File::delete($mainLangFile);
|
||||
}
|
||||
|
||||
return response()->json(['success' => true, 'message' => __('The language has been deleted.')]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Failed to delete language: :error', ['error' => $e->getMessage()])], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleLanguageStatus($languageCode)
|
||||
{
|
||||
if ($languageCode === 'en') {
|
||||
return response()->json(['error' => __('Cannot disable English language')], 422);
|
||||
}
|
||||
|
||||
try {
|
||||
$languagesFile = resource_path('lang/language.json');
|
||||
$languages = json_decode(File::get($languagesFile), true);
|
||||
|
||||
foreach ($languages as &$language) {
|
||||
if ($language['code'] === $languageCode) {
|
||||
$language['enabled'] = !($language['enabled'] ?? true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
File::put($languagesFile, json_encode($languages, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
return response()->json(['success' => true, 'message' => __('The language status updated successfully.')]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Failed to update language status: :error', ['error' => $e->getMessage()])], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function updateTranslations(Request $request, $locale)
|
||||
{
|
||||
$newTranslations = $request->input('translations');
|
||||
$path = resource_path("lang/{$locale}.json");
|
||||
|
||||
try {
|
||||
// Ensure directory exists
|
||||
$dir = dirname($path);
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
|
||||
// Try to make file writable if it exists
|
||||
if (file_exists($path)) {
|
||||
@chmod($path, 0666);
|
||||
}
|
||||
|
||||
// Load existing translations
|
||||
$existingTranslations = [];
|
||||
if (file_exists($path)) {
|
||||
$existingContent = File::get($path);
|
||||
$existingTranslations = json_decode($existingContent, true) ?? [];
|
||||
}
|
||||
|
||||
// Merge new translations with existing ones
|
||||
$mergedTranslations = array_merge($existingTranslations, $newTranslations);
|
||||
|
||||
$result = File::put($path, json_encode($mergedTranslations, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
|
||||
if ($result === false) {
|
||||
// If File::put fails, try alternative method
|
||||
$handle = @fopen($path, 'w');
|
||||
if ($handle) {
|
||||
fwrite($handle, json_encode($mergedTranslations, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
fclose($handle);
|
||||
@chmod($path, 0666);
|
||||
} else {
|
||||
return response()->json(['error' => __('Cannot write to translation file. Please check permissions.')], 500);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['success' => true, 'message' => __('Translations updated successfully')]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => __('Failed to save translations: ') . $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function changeLanguage(Request $request)
|
||||
{
|
||||
$languageCode = $request->input('language');
|
||||
|
||||
// RTL languages that should automatically set layoutDirection to 'right'
|
||||
$rtlLanguages = ['ar', 'he'];
|
||||
$isRtl = in_array($languageCode, $rtlLanguages);
|
||||
|
||||
if (config('app.is_demo')) {
|
||||
return redirect()->back()->cookie('app_language', $languageCode, 60 * 24 * 365);
|
||||
}
|
||||
|
||||
if ($request->user()) {
|
||||
$request->user()->update(['lang' => $languageCode]);
|
||||
|
||||
// Auto-update layoutDirection for RTL languages
|
||||
if ($isRtl) {
|
||||
updateSetting('layoutDirection', 'right', $request->user()->id);
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
}
|
||||
412
app/Http/Controllers/LeaveApplicationController.php
Normal file
412
app/Http/Controllers/LeaveApplicationController.php
Normal file
@@ -0,0 +1,412 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Employee;
|
||||
use App\Models\LeaveApplication;
|
||||
use App\Models\LeavePolicy;
|
||||
use App\Models\LeaveType;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class LeaveApplicationController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-leave-applications')) {
|
||||
$query = LeaveApplication::with(['employee', 'leaveType', 'leavePolicy', 'approver', 'creator'])
|
||||
->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-leave-applications')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-leave-applications')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('employee_id', Auth::id())->orWhere('approved_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && ! empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('reason', 'like', '%'.$request->search.'%')
|
||||
->orWhereHas('employee', function ($subQ) use ($request) {
|
||||
$subQ->where('name', 'like', '%'.$request->search.'%');
|
||||
})
|
||||
->orWhereHas('leaveType', function ($subQ) use ($request) {
|
||||
$subQ->where('name', 'like', '%'.$request->search.'%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle employee filter
|
||||
if ($request->has('employee_id') && ! empty($request->employee_id) && $request->employee_id !== 'all') {
|
||||
$query->where('employee_id', $request->employee_id);
|
||||
}
|
||||
|
||||
// Handle leave type filter
|
||||
if ($request->has('leave_type_id') && ! empty($request->leave_type_id) && $request->leave_type_id !== 'all') {
|
||||
$query->where('leave_type_id', $request->leave_type_id);
|
||||
}
|
||||
|
||||
// Handle status filter
|
||||
if ($request->has('status') && ! empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
if ($request->has('sort_field') && ! empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field;
|
||||
$sortDirection = $request->sort_direction ?? 'asc';
|
||||
|
||||
if (in_array($sortField, ['start_date', 'end_date', 'created_at'])) {
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
} else {
|
||||
$query->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
$leaveApplications = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$leaveApplications->getCollection()->transform(function ($application) {
|
||||
if ($application->employee) {
|
||||
$rawAvatar = $application->employee->getRawOriginal('avatar');
|
||||
$application->employee->avatar = check_file($rawAvatar)
|
||||
? get_file($rawAvatar)
|
||||
: get_file('avatars/avatar.png');
|
||||
}
|
||||
return $application;
|
||||
});
|
||||
|
||||
// Get employees for filter dropdown
|
||||
$employees = User::where('type', 'employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->get(['id', 'name']);
|
||||
|
||||
// Get leave types for filter dropdown
|
||||
$leaveTypes = LeaveType::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->get(['id', 'name', 'color']);
|
||||
|
||||
return Inertia::render('hr/leave-applications/index', [
|
||||
'leaveApplications' => $leaveApplications,
|
||||
'employees' => $this->getFilteredEmployees(),
|
||||
'leaveTypes' => $leaveTypes,
|
||||
'filters' => $request->all(['search', 'employee_id', 'leave_type_id', 'status', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
private function getFilteredEmployees()
|
||||
{
|
||||
// Get employees for filter dropdown (compatible with getFilteredEmployees logic)
|
||||
$employeeQuery = Employee::whereIn('created_by', getCompanyAndUsersId());
|
||||
|
||||
if (Auth::user()->can('manage-own-leave-applications') && ! Auth::user()->can('manage-any-leave-applications')) {
|
||||
$employeeQuery->where(function ($q) {
|
||||
$q->where('created_by', Auth::id())->orWhere('user_id', Auth::id());
|
||||
});
|
||||
}
|
||||
|
||||
$employees = User::emp()
|
||||
->with('employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->whereIn('id', $employeeQuery->pluck('user_id'))
|
||||
->select('id', 'name')
|
||||
->get()
|
||||
->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'employee_id' => $user->employee->employee_id ?? '',
|
||||
];
|
||||
});
|
||||
|
||||
return $employees;
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'leave_type_id' => 'required|exists:leave_types,id',
|
||||
'start_date' => 'required|date|after_or_equal:today',
|
||||
'end_date' => 'required|date|after_or_equal:start_date',
|
||||
'reason' => 'required|string',
|
||||
'attachment' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$validated['created_by'] = creatorId();
|
||||
|
||||
// Calculate total days
|
||||
$startDate = Carbon::parse($validated['start_date']);
|
||||
$endDate = Carbon::parse($validated['end_date']);
|
||||
$validated['total_days'] = $startDate->diffInDays($endDate) + 1;
|
||||
|
||||
// Get leave policy for this leave type
|
||||
$leavePolicy = LeavePolicy::where('leave_type_id', $validated['leave_type_id'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->first();
|
||||
|
||||
if (! $leavePolicy) {
|
||||
return redirect()->back()->with('error', __('No active policy found for selected leave type.'));
|
||||
}
|
||||
|
||||
$validated['leave_policy_id'] = $leavePolicy->id;
|
||||
|
||||
// Validate days per application
|
||||
if (
|
||||
$validated['total_days'] < $leavePolicy->min_days_per_application ||
|
||||
$validated['total_days'] > $leavePolicy->max_days_per_application
|
||||
) {
|
||||
return redirect()->back()->with(
|
||||
'error',
|
||||
__('Leave days must be between :min and :max days as per the leave policy.', [
|
||||
'min' => $leavePolicy->min_days_per_application,
|
||||
'max' => $leavePolicy->max_days_per_application,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
// Check if employee has enough leave balance
|
||||
$currentYear = now()->year;
|
||||
$leaveBalance = \App\Models\LeaveBalance::where('employee_id', $validated['employee_id'])
|
||||
->where('leave_type_id', $validated['leave_type_id'])
|
||||
->where('year', $currentYear)
|
||||
->first();
|
||||
|
||||
if (! $leaveBalance) {
|
||||
// Create initial balance if doesn't exist
|
||||
$leaveBalance = \App\Models\LeaveBalance::create([
|
||||
'employee_id' => $validated['employee_id'],
|
||||
'leave_type_id' => $validated['leave_type_id'],
|
||||
'leave_policy_id' => $leavePolicy->id,
|
||||
'year' => $currentYear,
|
||||
'allocated_days' => $leavePolicy->max_days_per_year ?? 10,
|
||||
'used_days' => 0,
|
||||
'remaining_days' => $leavePolicy->max_days_per_year ?? 10,
|
||||
'created_by' => creatorId(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Check if enough balance available
|
||||
if ($leaveBalance->remaining_days < $validated['total_days']) {
|
||||
return redirect()->back()->with(
|
||||
'error',
|
||||
__('Insufficient leave balance. Available: :available days, Requested: :requested days', [
|
||||
'available' => $leaveBalance->remaining_days,
|
||||
'requested' => $validated['total_days'],
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
// Handle attachment from media library
|
||||
if ($request->has('attachment')) {
|
||||
$validated['attachment'] = $request->attachment;
|
||||
}
|
||||
|
||||
// Set status based on policy
|
||||
$validated['status'] = $leavePolicy->requires_approval ? 'pending' : 'approved';
|
||||
|
||||
$leaveApplication = LeaveApplication::create($validated);
|
||||
|
||||
// Create attendance records if auto-approved
|
||||
if ($validated['status'] === 'approved') {
|
||||
$leaveApplication->createAttendanceRecords();
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('Leave application created successfully.'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $leaveApplicationId)
|
||||
{
|
||||
$leaveApplication = LeaveApplication::where('id', $leaveApplicationId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($leaveApplication) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'leave_type_id' => 'required|exists:leave_types,id',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'required|date|after_or_equal:start_date',
|
||||
'reason' => 'required|string',
|
||||
'attachment' => 'nullable|string',
|
||||
]);
|
||||
|
||||
// Calculate total days
|
||||
$startDate = Carbon::parse($validated['start_date']);
|
||||
$endDate = Carbon::parse($validated['end_date']);
|
||||
$validated['total_days'] = $startDate->diffInDays($endDate) + 1;
|
||||
|
||||
// Get leave policy
|
||||
$leavePolicy = LeavePolicy::where('leave_type_id', $validated['leave_type_id'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->first();
|
||||
|
||||
if (! $leavePolicy) {
|
||||
return redirect()->back()->with('error', __('No active policy found for selected leave type.'));
|
||||
}
|
||||
|
||||
$validated['leave_policy_id'] = $leavePolicy->id;
|
||||
|
||||
// Handle attachment from media library
|
||||
if ($request->has('attachment')) {
|
||||
$validated['attachment'] = $request->attachment;
|
||||
}
|
||||
|
||||
$leaveApplication->update($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Leave application updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update leave application'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Leave application Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($leaveApplicationId)
|
||||
{
|
||||
$leaveApplication = LeaveApplication::where('id', $leaveApplicationId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($leaveApplication) {
|
||||
try {
|
||||
$leaveApplication->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('Leave application deleted successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to delete leave application'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Leave application Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function updateStatus(Request $request, $leaveApplicationId)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'status' => 'required|in:approved,rejected',
|
||||
'manager_comments' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$leaveApplication = LeaveApplication::where('id', $leaveApplicationId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($leaveApplication) {
|
||||
try {
|
||||
$leaveApplication->update([
|
||||
'status' => $validated['status'],
|
||||
'manager_comments' => $validated['manager_comments'],
|
||||
'approved_by' => Auth::id(),
|
||||
'approved_at' => now(),
|
||||
]);
|
||||
|
||||
// Create attendance records if approved
|
||||
if ($validated['status'] === 'approved') {
|
||||
// Double-check balance before final approval
|
||||
$currentYear = now()->year;
|
||||
$leaveBalance = \App\Models\LeaveBalance::where('employee_id', $leaveApplication->employee_id)
|
||||
->where('leave_type_id', $leaveApplication->leave_type_id)
|
||||
->where('year', $currentYear)
|
||||
->first();
|
||||
|
||||
if ($leaveBalance && $leaveBalance->remaining_days < $leaveApplication->total_days) {
|
||||
return redirect()->back()->with(
|
||||
'error',
|
||||
__('Cannot approve: Insufficient leave balance. Available: :available days, Required: :required days', [
|
||||
'available' => $leaveBalance->remaining_days,
|
||||
'required' => $leaveApplication->total_days,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
$leaveApplication->createAttendanceRecords();
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('Leave application status updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update leave application status'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Leave application Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function export()
|
||||
{
|
||||
if (Auth::user()->can('export-leave-applications')) {
|
||||
try {
|
||||
$leaveApplications = LeaveApplication::with(['employee', 'leaveType', 'approver'])
|
||||
->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-leave-applications')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-leave-applications')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('employee_id', Auth::id())->orWhere('approved_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
})->get();
|
||||
|
||||
$fileName = 'leave_applications_'.date('Y-m-d_His').'.csv';
|
||||
$headers = [
|
||||
'Content-Type' => 'text/csv',
|
||||
'Content-Disposition' => 'attachment; filename="'.$fileName.'"',
|
||||
];
|
||||
|
||||
$callback = function () use ($leaveApplications) {
|
||||
$file = fopen('php://output', 'w');
|
||||
fputcsv($file, [
|
||||
'Employee',
|
||||
'Leave Type',
|
||||
'Start Date',
|
||||
'End Date',
|
||||
'Total Days',
|
||||
'Reason',
|
||||
'Status',
|
||||
'Approved By',
|
||||
'Approved At',
|
||||
'Manager Comments',
|
||||
'Applied On',
|
||||
]);
|
||||
|
||||
foreach ($leaveApplications as $application) {
|
||||
fputcsv($file, [
|
||||
$application->employee->name ?? '',
|
||||
$application->leaveType->name ?? '',
|
||||
$application->start_date ? date('Y-m-d', strtotime($application->start_date)) : '',
|
||||
$application->end_date ? date('Y-m-d', strtotime($application->end_date)) : '',
|
||||
$application->total_days ?? '',
|
||||
$application->reason ?? '',
|
||||
$application->status ?? '',
|
||||
$application->approver->name ?? '',
|
||||
$application->approved_at ?? '',
|
||||
$application->manager_comments ?? '',
|
||||
$application->created_at ?? '',
|
||||
]);
|
||||
}
|
||||
fclose($file);
|
||||
};
|
||||
|
||||
return response()->stream($callback, 200, $headers);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['message' => __('Failed to export leave applications: :message', ['message' => $e->getMessage()])], 500);
|
||||
}
|
||||
} else {
|
||||
return response()->json(['message' => __('Permission Denied.')], 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
284
app/Http/Controllers/LeaveBalanceController.php
Normal file
284
app/Http/Controllers/LeaveBalanceController.php
Normal file
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Employee;
|
||||
use App\Models\LeaveBalance;
|
||||
use App\Models\LeaveType;
|
||||
use App\Models\LeavePolicy;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Inertia\Inertia;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class LeaveBalanceController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-leave-balances')) {
|
||||
$query = LeaveBalance::with(['employee', 'leaveType', 'leavePolicy', 'creator'])
|
||||
->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-leave-balances')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-leave-balances')) {
|
||||
$q->where('created_by', Auth::id())->orWhere('employee_id', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->whereHas('employee', function ($subQ) use ($request) {
|
||||
$subQ->where('name', 'like', '%' . $request->search . '%');
|
||||
})
|
||||
->orWhereHas('leaveType', function ($subQ) use ($request) {
|
||||
$subQ->where('name', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle employee filter
|
||||
if ($request->has('employee_id') && !empty($request->employee_id) && $request->employee_id !== 'all') {
|
||||
$query->where('employee_id', $request->employee_id);
|
||||
}
|
||||
|
||||
// Handle leave type filter
|
||||
if ($request->has('leave_type_id') && !empty($request->leave_type_id) && $request->leave_type_id !== 'all') {
|
||||
$query->where('leave_type_id', $request->leave_type_id);
|
||||
}
|
||||
|
||||
// Handle year filter
|
||||
if ($request->has('year') && !empty($request->year) && $request->year !== 'all') {
|
||||
$query->where('year', $request->year);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
if ($request->has('sort_field') && !empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field;
|
||||
$sortDirection = $request->sort_direction ?? 'asc';
|
||||
|
||||
if ($sortField === 'year') {
|
||||
$query->orderBy('year', $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('created_at', 'desc');
|
||||
}
|
||||
} else {
|
||||
$query->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
$leaveBalances = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$leaveBalances->getCollection()->transform(function ($balance) {
|
||||
if ($balance->employee) {
|
||||
$rawAvatar = $balance->employee->getRawOriginal('avatar');
|
||||
$balance->employee->avatar = check_file($rawAvatar)
|
||||
? get_file($rawAvatar)
|
||||
: get_file('avatars/avatar.png');
|
||||
}
|
||||
return $balance;
|
||||
});
|
||||
|
||||
// Get employees for filter dropdown
|
||||
$employees = User::where('type', 'employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->get(['id', 'name']);
|
||||
|
||||
// Get leave types for filter dropdown
|
||||
$leaveTypes = LeaveType::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->get(['id', 'name', 'color']);
|
||||
|
||||
// Get years for filter
|
||||
$years = LeaveBalance::whereIn('created_by', getCompanyAndUsersId())
|
||||
->distinct()
|
||||
->pluck('year')
|
||||
->sort()
|
||||
->values();
|
||||
|
||||
return Inertia::render('hr/leave-balances/index', [
|
||||
'leaveBalances' => $leaveBalances,
|
||||
'employees' => $this->getFilteredEmployees(),
|
||||
'leaveTypes' => $leaveTypes,
|
||||
'years' => $years,
|
||||
'filters' => $request->all(['search', 'employee_id', 'leave_type_id', 'year', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
private function getFilteredEmployees()
|
||||
{
|
||||
// Get employees for filter dropdown (compatible with getFilteredEmployees logic)
|
||||
$employeeQuery = Employee::whereIn('created_by', getCompanyAndUsersId());
|
||||
|
||||
if (Auth::user()->can('manage-own-leave-balances') && !Auth::user()->can('manage-any-leave-balances')) {
|
||||
$employeeQuery->where(function ($q) {
|
||||
$q->where('created_by', Auth::id())->orWhere('user_id', Auth::id());
|
||||
});
|
||||
}
|
||||
|
||||
$employees = User::emp()
|
||||
->with('employee')
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->whereIn('id', $employeeQuery->pluck('user_id'))
|
||||
->select('id', 'name')
|
||||
->get()
|
||||
->map(function ($user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'employee_id' => $user->employee->employee_id ?? '',
|
||||
];
|
||||
});
|
||||
return $employees;
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'leave_type_id' => 'required|exists:leave_types,id',
|
||||
'year' => 'required|integer|min:2020|max:2030',
|
||||
'allocated_days' => 'required|numeric|min:0',
|
||||
'carried_forward' => 'nullable|numeric|min:0',
|
||||
'manual_adjustment' => 'nullable|numeric',
|
||||
'adjustment_reason' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$validated['created_by'] = creatorId();
|
||||
$validated['carried_forward'] = $validated['carried_forward'] ?? 0;
|
||||
$validated['manual_adjustment'] = $validated['manual_adjustment'] ?? 0;
|
||||
|
||||
// Get leave policy for this leave type
|
||||
$leavePolicy = LeavePolicy::where('leave_type_id', $validated['leave_type_id'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->first();
|
||||
|
||||
if (!$leavePolicy) {
|
||||
return redirect()->back()->with('error', __('No active policy found for selected leave type.'));
|
||||
}
|
||||
|
||||
$validated['leave_policy_id'] = $leavePolicy->id;
|
||||
|
||||
// Check if balance already exists
|
||||
$exists = LeaveBalance::where('employee_id', $validated['employee_id'])
|
||||
->where('leave_type_id', $validated['leave_type_id'])
|
||||
->where('year', $validated['year'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return redirect()->back()->with('error', __('Leave balance already exists for this employee, leave type, and year.'));
|
||||
}
|
||||
|
||||
// Calculate remaining days
|
||||
$validated['used_days'] = 0;
|
||||
$validated['remaining_days'] = ($validated['allocated_days'] + $validated['carried_forward'] + $validated['manual_adjustment']) - $validated['used_days'];
|
||||
|
||||
LeaveBalance::create($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Leave balance created successfully.'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $leaveBalanceId)
|
||||
{
|
||||
$leaveBalance = LeaveBalance::where('id', $leaveBalanceId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($leaveBalance) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
'leave_type_id' => 'required|exists:leave_types,id',
|
||||
'year' => 'required|integer|min:2020|max:2030',
|
||||
'allocated_days' => 'required|numeric|min:0',
|
||||
'carried_forward' => 'nullable|numeric|min:0',
|
||||
'manual_adjustment' => 'nullable|numeric',
|
||||
'adjustment_reason' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$validated['carried_forward'] = $validated['carried_forward'] ?? 0;
|
||||
$validated['manual_adjustment'] = $validated['manual_adjustment'] ?? 0;
|
||||
|
||||
// Get leave policy
|
||||
$leavePolicy = LeavePolicy::where('leave_type_id', $validated['leave_type_id'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->first();
|
||||
|
||||
if (!$leavePolicy) {
|
||||
return redirect()->back()->with('error', __('No active policy found for selected leave type.'));
|
||||
}
|
||||
|
||||
$validated['leave_policy_id'] = $leavePolicy->id;
|
||||
|
||||
// Recalculate remaining days
|
||||
$validated['remaining_days'] = ($validated['allocated_days'] + $validated['carried_forward'] + $validated['manual_adjustment']) - $leaveBalance->used_days;
|
||||
|
||||
$leaveBalance->update($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Leave balance updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update leave balance'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Leave balance Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($leaveBalanceId)
|
||||
{
|
||||
$leaveBalance = LeaveBalance::where('id', $leaveBalanceId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($leaveBalance) {
|
||||
try {
|
||||
$leaveBalance->delete();
|
||||
return redirect()->back()->with('success', __('Leave balance deleted successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to delete leave balance'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Leave balance Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function adjust(Request $request, $leaveBalanceId)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'manual_adjustment' => 'required|numeric',
|
||||
'adjustment_reason' => 'required|string',
|
||||
]);
|
||||
|
||||
$leaveBalance = LeaveBalance::where('id', $leaveBalanceId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($leaveBalance) {
|
||||
try {
|
||||
$leaveBalance->update([
|
||||
'manual_adjustment' => $validated['manual_adjustment'],
|
||||
'adjustment_reason' => $validated['adjustment_reason'],
|
||||
]);
|
||||
|
||||
// Recalculate remaining days
|
||||
$leaveBalance->calculateRemainingDays();
|
||||
$leaveBalance->save();
|
||||
|
||||
return redirect()->back()->with('success', __('Leave balance adjusted successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to adjust leave balance'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Leave balance Not Found.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
189
app/Http/Controllers/LeavePolicyController.php
Normal file
189
app/Http/Controllers/LeavePolicyController.php
Normal file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\LeavePolicy;
|
||||
use App\Models\LeaveType;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class LeavePolicyController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-leave-policies')) {
|
||||
$query = LeavePolicy::with(['leaveType', 'creator'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-leave-policies')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-leave-policies')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%')
|
||||
->orWhereHas('leaveType', function ($subQ) use ($request) {
|
||||
$subQ->where('name', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle leave type filter
|
||||
if ($request->has('leave_type_id') && !empty($request->leave_type_id) && $request->leave_type_id !== 'all') {
|
||||
$query->where('leave_type_id', $request->leave_type_id);
|
||||
}
|
||||
|
||||
// Handle status filter
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
if ($request->has('sort_field') && !empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field;
|
||||
$sortDirection = $request->sort_direction ?? 'asc';
|
||||
|
||||
if (in_array($sortField, ['name', 'created_at'])) {
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('created_at', 'desc');
|
||||
}
|
||||
} else {
|
||||
$query->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
$leavePolicies = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
// Get leave types for filter dropdown
|
||||
$leaveTypes = LeaveType::whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('status', 'active')
|
||||
->get(['id', 'name', 'color']);
|
||||
|
||||
return Inertia::render('hr/leave-policies/index', [
|
||||
'leavePolicies' => $leavePolicies,
|
||||
'leaveTypes' => $leaveTypes,
|
||||
'filters' => $request->all(['search', 'leave_type_id', 'status', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'leave_type_id' => 'required|exists:leave_types,id',
|
||||
'accrual_type' => 'required|in:monthly,yearly',
|
||||
'accrual_rate' => 'required|numeric|min:0',
|
||||
'carry_forward_limit' => 'required|integer|min:0',
|
||||
'min_days_per_application' => 'required|integer|min:1',
|
||||
'max_days_per_application' => 'required|integer|min:1',
|
||||
'requires_approval' => 'boolean',
|
||||
'status' => 'nullable|in:active,inactive',
|
||||
]);
|
||||
|
||||
$validated['created_by'] = creatorId();
|
||||
$validated['status'] = $validated['status'] ?? 'active';
|
||||
$validated['requires_approval'] = $validated['requires_approval'] ?? true;
|
||||
|
||||
// Check if leave type belongs to the current user's company
|
||||
$leaveType = LeaveType::where('id', $validated['leave_type_id'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if (!$leaveType) {
|
||||
return redirect()->back()->with('error', __('Invalid leave type selected.'));
|
||||
}
|
||||
|
||||
LeavePolicy::create($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Leave policy created successfully.'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $leavePolicyId)
|
||||
{
|
||||
$leavePolicy = LeavePolicy::where('id', $leavePolicyId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($leavePolicy) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'leave_type_id' => 'required|exists:leave_types,id',
|
||||
'accrual_type' => 'required|in:monthly,yearly',
|
||||
'accrual_rate' => 'required|numeric|min:0',
|
||||
'carry_forward_limit' => 'required|integer|min:0',
|
||||
'min_days_per_application' => 'required|integer|min:1',
|
||||
'max_days_per_application' => 'required|integer|min:1',
|
||||
'requires_approval' => 'boolean',
|
||||
'status' => 'nullable|in:active,inactive',
|
||||
]);
|
||||
|
||||
// Check if leave type belongs to the current user's company
|
||||
$leaveType = LeaveType::where('id', $validated['leave_type_id'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if (!$leaveType) {
|
||||
return redirect()->back()->with('error', __('Invalid leave type selected.'));
|
||||
}
|
||||
|
||||
$leavePolicy->update($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Leave policy updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update leave policy'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Leave policy Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($leavePolicyId)
|
||||
{
|
||||
$leavePolicy = LeavePolicy::where('id', $leavePolicyId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($leavePolicy) {
|
||||
try {
|
||||
$leavePolicy->delete();
|
||||
return redirect()->back()->with('success', __('Leave policy deleted successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to delete leave policy'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Leave policy Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleStatus($leavePolicyId)
|
||||
{
|
||||
$leavePolicy = LeavePolicy::where('id', $leavePolicyId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($leavePolicy) {
|
||||
try {
|
||||
$leavePolicy->status = $leavePolicy->status === 'active' ? 'inactive' : 'active';
|
||||
$leavePolicy->save();
|
||||
|
||||
return redirect()->back()->with('success', __('Leave policy status updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update leave policy status'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Leave policy Not Found.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
166
app/Http/Controllers/LeaveTypeController.php
Normal file
166
app/Http/Controllers/LeaveTypeController.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\LeaveType;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class LeaveTypeController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('manage-leave-types')) {
|
||||
$query = LeaveType::with(['creator'])->where(function ($q) {
|
||||
if (Auth::user()->can('manage-any-leave-types')) {
|
||||
$q->whereIn('created_by', getCompanyAndUsersId());
|
||||
} elseif (Auth::user()->can('manage-own-leave-types')) {
|
||||
$q->where('created_by', Auth::id());
|
||||
} else {
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
if ($request->has('search') && !empty($request->search)) {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('description', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle status filter
|
||||
if ($request->has('status') && !empty($request->status) && $request->status !== 'all') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
if ($request->has('sort_field') && !empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field;
|
||||
$sortDirection = $request->sort_direction ?? 'asc';
|
||||
|
||||
if (in_array($sortField, ['name', 'created_at'])) {
|
||||
$query->orderBy($sortField, $sortDirection);
|
||||
} else {
|
||||
$query->orderBy('created_at', 'desc');
|
||||
}
|
||||
} else {
|
||||
$query->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
$leaveTypes = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
return Inertia::render('hr/leave-types/index', [
|
||||
'leaveTypes' => $leaveTypes,
|
||||
'filters' => $request->all(['search', 'status', 'sort_field', 'sort_direction', 'per_page']),
|
||||
]);
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Permission Denied.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'max_days_per_year' => 'required|integer|min:0',
|
||||
'is_paid' => 'boolean',
|
||||
'color' => 'required|string|regex:/^#[0-9A-Fa-f]{6}$/',
|
||||
'status' => 'nullable|in:active,inactive',
|
||||
]);
|
||||
|
||||
$validated['created_by'] = creatorId();
|
||||
$validated['status'] = $validated['status'] ?? 'active';
|
||||
|
||||
// Check if leave type with same name already exists
|
||||
$exists = LeaveType::where('name', $validated['name'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return redirect()->back()->with('error', __('Leave type with this name already exists.'));
|
||||
}
|
||||
|
||||
LeaveType::create($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Leave type created successfully.'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $leaveTypeId)
|
||||
{
|
||||
$leaveType = LeaveType::where('id', $leaveTypeId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($leaveType) {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'max_days_per_year' => 'required|integer|min:0',
|
||||
'is_paid' => 'boolean',
|
||||
'color' => 'required|string|regex:/^#[0-9A-Fa-f]{6}$/',
|
||||
'status' => 'nullable|in:active,inactive',
|
||||
]);
|
||||
|
||||
// Check if leave type with same name already exists (excluding current)
|
||||
$exists = LeaveType::where('name', $validated['name'])
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->where('id', '!=', $leaveTypeId)
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return redirect()->back()->with('error', __('Leave type with this name already exists.'));
|
||||
}
|
||||
|
||||
$leaveType->update($validated);
|
||||
|
||||
return redirect()->back()->with('success', __('Leave type updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update leave type'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Leave type Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($leaveTypeId)
|
||||
{
|
||||
$leaveType = LeaveType::where('id', $leaveTypeId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($leaveType) {
|
||||
try {
|
||||
$leaveType->delete();
|
||||
return redirect()->back()->with('success', __('Leave type deleted successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to delete leave type'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Leave type Not Found.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleStatus($leaveTypeId)
|
||||
{
|
||||
$leaveType = LeaveType::where('id', $leaveTypeId)
|
||||
->whereIn('created_by', getCompanyAndUsersId())
|
||||
->first();
|
||||
|
||||
if ($leaveType) {
|
||||
try {
|
||||
$leaveType->status = $leaveType->status === 'active' ? 'inactive' : 'active';
|
||||
$leaveType->save();
|
||||
|
||||
return redirect()->back()->with('success', __('Leave type status updated successfully'));
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update leave type status'));
|
||||
}
|
||||
} else {
|
||||
return redirect()->back()->with('error', __('Leave type Not Found.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user