1089 lines
43 KiB
PHP
1089 lines
43 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Asset;
|
|
use App\Models\AssetAssignment;
|
|
use App\Models\AssetDepreciation;
|
|
use App\Models\AssetMaintenance;
|
|
use App\Models\AssetType;
|
|
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;
|
|
use SimpleSoftwareIO\QrCode\Facades\QrCode;
|
|
|
|
class AssetController extends Controller
|
|
{
|
|
/**
|
|
* Display a listing of the resource.
|
|
*/
|
|
public function index(Request $request)
|
|
{
|
|
if (Auth::user()->can('manage-assets')) {
|
|
// $query = Asset::withPermissionCheck()->with(['assetType', 'currentAssignment.employee']);
|
|
|
|
$query = Asset::with(['assetType', 'currentAssignment.employee', 'assignments'])->where(function ($q) {
|
|
if (Auth::user()->can('manage-any-assets')) {
|
|
$q->whereIn('created_by', getCompanyAndUsersId());
|
|
} elseif (Auth::user()->can('manage-own-assets')) {
|
|
$q->where('created_by', Auth::id())
|
|
->orWhereHas('currentAssignment', function ($aq) {
|
|
$aq->where('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('name', 'like', '%'.$request->search.'%')
|
|
->orWhere('serial_number', 'like', '%'.$request->search.'%')
|
|
->orWhere('asset_code', 'like', '%'.$request->search.'%')
|
|
->orWhere('description', 'like', '%'.$request->search.'%')
|
|
->orWhere('location', 'like', '%'.$request->search.'%')
|
|
->orWhere('supplier', 'like', '%'.$request->search.'%');
|
|
});
|
|
}
|
|
|
|
// Handle asset type filter
|
|
if ($request->has('asset_type_id') && ! empty($request->asset_type_id)) {
|
|
$query->where('asset_type_id', $request->asset_type_id);
|
|
}
|
|
|
|
// Handle status filter
|
|
if ($request->has('status') && ! empty($request->status)) {
|
|
$query->where('status', $request->status);
|
|
}
|
|
|
|
// Handle condition filter
|
|
if ($request->has('condition') && ! empty($request->condition)) {
|
|
$query->where('condition', $request->condition);
|
|
}
|
|
|
|
// Handle location filter
|
|
if ($request->has('location') && ! empty($request->location)) {
|
|
$query->where('location', $request->location);
|
|
}
|
|
|
|
// Handle purchase date range filter
|
|
if ($request->has('purchase_date_from') && ! empty($request->purchase_date_from)) {
|
|
$query->whereDate('purchase_date', '>=', $request->purchase_date_from);
|
|
}
|
|
if ($request->has('purchase_date_to') && ! empty($request->purchase_date_to)) {
|
|
$query->whereDate('purchase_date', '<=', $request->purchase_date_to);
|
|
}
|
|
|
|
// Handle sorting
|
|
$allowedSortFields = ['id', 'name', 'asset_code', 'serial_number', 'purchase_date', 'purchase_cost', 'status', 'condition', 'location', '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');
|
|
}
|
|
|
|
$assets = $query->paginate($request->per_page ?? 10);
|
|
|
|
// Get asset types for filter dropdown
|
|
$assetTypes = AssetType::whereIn('created_by', getCompanyAndUsersId())
|
|
->select('id', 'name')
|
|
->get();
|
|
|
|
// Get unique locations for filter dropdown
|
|
$locations = Asset::whereIn('created_by', getCompanyAndUsersId())
|
|
->select('location')
|
|
->distinct()
|
|
->whereNotNull('location')
|
|
->pluck('location')
|
|
->toArray();
|
|
|
|
// Get employees for assignment 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/assets/index', [
|
|
'assets' => $assets,
|
|
'assetTypes' => $assetTypes,
|
|
'locations' => $locations,
|
|
'employees' => $employees,
|
|
'filters' => $request->all(['search', 'asset_type_id', 'status', 'condition', 'location', 'purchase_date_from', 'purchase_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 asset counts by status
|
|
$assetCounts = [
|
|
'total' => Asset::whereIn('created_by', getCompanyAndUsersId())->count(),
|
|
'available' => Asset::whereIn('created_by', getCompanyAndUsersId())->where('status', 'available')->count(),
|
|
'assigned' => Asset::whereIn('created_by', getCompanyAndUsersId())->where('status', 'assigned')->count(),
|
|
'under_maintenance' => Asset::whereIn('created_by', getCompanyAndUsersId())->where('status', 'under_maintenance')->count(),
|
|
'disposed' => Asset::whereIn('created_by', getCompanyAndUsersId())->where('status', 'disposed')->count(),
|
|
];
|
|
|
|
// Get asset counts by type
|
|
$assetTypeData = AssetType::whereIn('created_by', getCompanyAndUsersId())
|
|
->withCount('assets')
|
|
->get()
|
|
->map(function ($type) {
|
|
return [
|
|
'name' => $type->name,
|
|
'count' => $type->assets_count,
|
|
];
|
|
});
|
|
|
|
// Get recent assignments
|
|
$recentAssignments = AssetAssignment::with(['asset', 'employee'])
|
|
->whereHas('asset', function ($q) {
|
|
$q->whereIn('created_by', getCompanyAndUsersId());
|
|
})
|
|
->orderBy('created_at', 'desc')
|
|
->take(5)
|
|
->get();
|
|
|
|
// Get upcoming maintenance
|
|
$upcomingMaintenance = AssetMaintenance::with('asset')
|
|
->whereHas('asset', function ($q) {
|
|
$q->whereIn('created_by', getCompanyAndUsersId());
|
|
})
|
|
->where('status', 'scheduled')
|
|
->orderBy('start_date', 'asc')
|
|
->take(5)
|
|
->get();
|
|
|
|
// Get assets with expiring warranties
|
|
$expiringWarranties = Asset::whereIn('created_by', getCompanyAndUsersId())
|
|
->whereNotNull('warranty_expiry_date')
|
|
->where('warranty_expiry_date', '>=', now())
|
|
->where('warranty_expiry_date', '<=', now()->addMonths(3))
|
|
->orderBy('warranty_expiry_date', 'asc')
|
|
->take(5)
|
|
->get();
|
|
|
|
// Get asset value summary
|
|
$assetValueSummary = [
|
|
'total_purchase_value' => Asset::whereIn('created_by', getCompanyAndUsersId())->sum('purchase_cost'),
|
|
'total_current_value' => AssetDepreciation::whereHas('asset', function ($q) {
|
|
$q->whereIn('created_by', getCompanyAndUsersId());
|
|
})->sum('current_value'),
|
|
'total_depreciation' => Asset::whereIn('created_by', getCompanyAndUsersId())->sum('purchase_cost') -
|
|
AssetDepreciation::whereHas('asset', function ($q) {
|
|
$q->whereIn('created_by', getCompanyAndUsersId());
|
|
})->sum('current_value'),
|
|
];
|
|
|
|
return Inertia::render('hr/assets/dashboard', [
|
|
'assetCounts' => $assetCounts,
|
|
'assetTypeData' => $assetTypeData,
|
|
'recentAssignments' => $recentAssignments,
|
|
'upcomingMaintenance' => $upcomingMaintenance,
|
|
'expiringWarranties' => $expiringWarranties,
|
|
'assetValueSummary' => $assetValueSummary,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Store a newly created resource in storage.
|
|
*/
|
|
public function store(Request $request)
|
|
{
|
|
$validator = Validator::make($request->all(), [
|
|
'name' => 'required|string|max:255',
|
|
'asset_type_id' => 'required|exists:asset_types,id',
|
|
'serial_number' => 'nullable|string|max:255',
|
|
'asset_code' => 'nullable|string|max:255',
|
|
'purchase_date' => 'nullable|date',
|
|
'purchase_cost' => 'nullable|numeric|min:0',
|
|
'status' => 'required|string|in:available,assigned,under_maintenance,disposed',
|
|
'condition' => 'nullable|string|in:new,good,fair,poor',
|
|
'description' => 'nullable|string',
|
|
'location' => 'nullable|string|max:255',
|
|
'supplier' => 'nullable|string|max:255',
|
|
'warranty_info' => 'nullable|string|max:255',
|
|
'warranty_expiry_date' => 'nullable|date',
|
|
'images' => 'nullable|string',
|
|
'documents' => 'nullable|string',
|
|
]);
|
|
|
|
if ($validator->fails()) {
|
|
return redirect()->back()->withErrors($validator)->withInput();
|
|
}
|
|
|
|
// Check if asset type belongs to current company
|
|
$assetType = AssetType::find($request->asset_type_id);
|
|
if (! $assetType || ! in_array($assetType->created_by, getCompanyAndUsersId())) {
|
|
return redirect()->back()->with('error', 'Invalid asset type selected');
|
|
}
|
|
|
|
$assetData = [
|
|
'name' => $request->name,
|
|
'asset_type_id' => $request->asset_type_id,
|
|
'serial_number' => $request->serial_number,
|
|
'asset_code' => $request->asset_code,
|
|
'purchase_date' => $request->purchase_date,
|
|
'purchase_cost' => $request->purchase_cost,
|
|
'status' => $request->status,
|
|
'condition' => $request->condition,
|
|
'description' => $request->description,
|
|
'location' => $request->location,
|
|
'supplier' => $request->supplier,
|
|
'warranty_info' => $request->warranty_info,
|
|
'warranty_expiry_date' => $request->warranty_expiry_date,
|
|
'created_by' => creatorId(),
|
|
];
|
|
|
|
// Handle image from media library
|
|
if ($request->has('images')) {
|
|
$assetData['images'] = $request->images;
|
|
}
|
|
|
|
// Handle document from media library
|
|
if ($request->has('documents')) {
|
|
$assetData['documents'] = $request->documents;
|
|
}
|
|
|
|
$asset = Asset::create($assetData);
|
|
|
|
// Generate QR code
|
|
// $qrCodeContent = json_encode([
|
|
// 'id' => $asset->id,
|
|
// 'name' => $asset->name,
|
|
// 'asset_code' => $asset->asset_code,
|
|
// 'serial_number' => $asset->serial_number,
|
|
// 'type' => $assetType->name,
|
|
// ]);
|
|
|
|
// $qrCodePath = 'assets/qrcodes/' . $asset->id . '.png';
|
|
// $qrCode = QrCode::format('png')
|
|
// ->size(200)
|
|
// ->generate($qrCodeContent);
|
|
|
|
// Storage::disk('public')->put($qrCodePath, $qrCode);
|
|
// $asset->update(['qr_code' => $qrCodePath]);
|
|
|
|
// Create depreciation record if purchase cost and date are provided
|
|
if ($request->has('depreciation_method') && $request->purchase_cost && $request->purchase_date) {
|
|
AssetDepreciation::create([
|
|
'asset_id' => $asset->id,
|
|
'method' => $request->depreciation_method,
|
|
'useful_life_years' => $request->useful_life_years ?? 5,
|
|
'salvage_value' => $request->salvage_value ?? ($request->purchase_cost * 0.1), // Default to 10% of purchase cost
|
|
'current_value' => $request->purchase_cost,
|
|
'last_calculated_date' => now(),
|
|
'created_by' => creatorId(),
|
|
]);
|
|
}
|
|
|
|
return redirect()->back()->with('success', __('Asset created successfully'));
|
|
}
|
|
|
|
/**
|
|
* Display the specified resource.
|
|
*/
|
|
public function show(Asset $asset)
|
|
{
|
|
// Check if asset belongs to current company
|
|
if (! in_array($asset->created_by, getCompanyAndUsersId())) {
|
|
return redirect()->back()->with('error', __('You do not have permission to view this asset'));
|
|
}
|
|
|
|
// Load relationships
|
|
$asset->load([
|
|
'assetType',
|
|
'assignments.employee',
|
|
'assignments.assigner',
|
|
'assignments.receiver',
|
|
'maintenances',
|
|
'depreciation',
|
|
'currentAssignment.employee',
|
|
]);
|
|
|
|
// Get asset types for form dropdown
|
|
$assetTypes = AssetType::whereIn('created_by', getCompanyAndUsersId())
|
|
->select('id', 'name')
|
|
->get();
|
|
|
|
// Get employees for assignment 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/assets/show', [
|
|
'asset' => $asset,
|
|
'assetTypes' => $assetTypes,
|
|
'employees' => $employees,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Update the specified resource in storage.
|
|
*/
|
|
public function update(Request $request, Asset $asset)
|
|
{
|
|
// Check if asset belongs to current company
|
|
if (! in_array($asset->created_by, getCompanyAndUsersId())) {
|
|
return redirect()->back()->with('error', 'You do not have permission to update this asset');
|
|
}
|
|
$validator = Validator::make($request->all(), [
|
|
'name' => 'required|string|max:255',
|
|
'asset_type_id' => 'required|exists:asset_types,id',
|
|
'serial_number' => 'nullable|string|max:255',
|
|
'asset_code' => 'nullable|string|max:255',
|
|
'purchase_date' => 'nullable|date',
|
|
'purchase_cost' => 'nullable|numeric|min:0',
|
|
'status' => 'required|string|in:available,assigned,under_maintenance,disposed',
|
|
'condition' => 'nullable|string|in:new,good,fair,poor',
|
|
'description' => 'nullable|string',
|
|
'location' => 'nullable|string|max:255',
|
|
'supplier' => 'nullable|string|max:255',
|
|
'warranty_info' => 'nullable|string|max:255',
|
|
'warranty_expiry_date' => 'nullable|date',
|
|
'images' => 'nullable|string',
|
|
'documents' => 'nullable|string',
|
|
]);
|
|
|
|
if ($validator->fails()) {
|
|
return redirect()->back()->withErrors($validator)->withInput();
|
|
}
|
|
|
|
// Check if asset type belongs to current company
|
|
$assetType = AssetType::find($request->asset_type_id);
|
|
if (! $assetType || ! in_array($assetType->created_by, getCompanyAndUsersId())) {
|
|
return redirect()->back()->with('error', 'Invalid asset type selected');
|
|
}
|
|
|
|
$assetData = [
|
|
'name' => $request->name,
|
|
'asset_type_id' => $request->asset_type_id,
|
|
'serial_number' => $request->serial_number,
|
|
'asset_code' => $request->asset_code,
|
|
'purchase_date' => $request->purchase_date,
|
|
'purchase_cost' => $request->purchase_cost,
|
|
'status' => $request->status,
|
|
'condition' => $request->condition,
|
|
'description' => $request->description,
|
|
'location' => $request->location,
|
|
'supplier' => $request->supplier,
|
|
'warranty_info' => $request->warranty_info,
|
|
'warranty_expiry_date' => $request->warranty_expiry_date,
|
|
];
|
|
|
|
// Handle image from media library
|
|
if ($request->has('images')) {
|
|
$assetData['images'] = $request->images;
|
|
}
|
|
|
|
// Handle document from media library
|
|
if ($request->has('documents')) {
|
|
$assetData['documents'] = $request->documents;
|
|
}
|
|
|
|
$asset->update($assetData);
|
|
|
|
// Update or create depreciation record if purchase cost and date are provided
|
|
if ($request->has('depreciation_method') && $request->purchase_cost && $request->purchase_date) {
|
|
$depreciation = $asset->depreciation;
|
|
|
|
if ($depreciation) {
|
|
$depreciation->update([
|
|
'method' => $request->depreciation_method,
|
|
'useful_life_years' => $request->useful_life_years ?? $depreciation->useful_life_years,
|
|
'salvage_value' => $request->salvage_value ?? $depreciation->salvage_value,
|
|
'last_calculated_date' => now(),
|
|
]);
|
|
|
|
// Recalculate current value
|
|
$depreciation->updateCurrentValue();
|
|
} else {
|
|
AssetDepreciation::create([
|
|
'asset_id' => $asset->id,
|
|
'method' => $request->depreciation_method,
|
|
'useful_life_years' => $request->useful_life_years ?? 5,
|
|
'salvage_value' => $request->salvage_value ?? ($request->purchase_cost * 0.1), // Default to 10% of purchase cost
|
|
'current_value' => $request->purchase_cost,
|
|
'last_calculated_date' => now(),
|
|
'created_by' => creatorId(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
return redirect()->back()->with('success', __('Asset updated successfully'));
|
|
}
|
|
|
|
/**
|
|
* Remove the specified resource from storage.
|
|
*/
|
|
public function destroy(Asset $asset)
|
|
{
|
|
// Check if asset belongs to current company
|
|
if (! in_array($asset->created_by, getCompanyAndUsersId())) {
|
|
return redirect()->back()->with('error', __('You do not have permission to delete this asset'));
|
|
}
|
|
|
|
// Check if asset is currently assigned
|
|
if ($asset->status === 'assigned') {
|
|
return redirect()->back()->with('error', __('Cannot delete an asset that is currently assigned'));
|
|
}
|
|
|
|
// Delete associated files
|
|
if ($asset->images) {
|
|
Storage::disk('public')->delete($asset->images);
|
|
}
|
|
if ($asset->documents) {
|
|
Storage::disk('public')->delete($asset->documents);
|
|
}
|
|
if ($asset->qr_code) {
|
|
Storage::disk('public')->delete($asset->qr_code);
|
|
}
|
|
|
|
// Delete associated records
|
|
$asset->assignments()->delete();
|
|
$asset->maintenances()->delete();
|
|
if ($asset->depreciation) {
|
|
$asset->depreciation->delete();
|
|
}
|
|
|
|
$asset->delete();
|
|
|
|
return redirect()->back()->with('success', __('Asset deleted successfully'));
|
|
}
|
|
|
|
/**
|
|
* Assign asset to an employee.
|
|
*/
|
|
public function assign(Request $request, Asset $asset)
|
|
{
|
|
// Check if asset belongs to current company
|
|
if (! in_array($asset->created_by, getCompanyAndUsersId())) {
|
|
return redirect()->back()->with('error', __('You do not have permission to assign this asset'));
|
|
}
|
|
|
|
// Check if asset is available
|
|
if ($asset->status !== 'available') {
|
|
return redirect()->back()->with('error', __('Only available assets can be assigned'));
|
|
}
|
|
|
|
$validator = Validator::make($request->all(), [
|
|
'employee_id' => 'required|exists:users,id',
|
|
'checkout_date' => 'required|date',
|
|
'expected_return_date' => 'nullable|date|after_or_equal:checkout_date',
|
|
'checkout_condition' => 'nullable|string|in:new,good,fair,poor',
|
|
'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'));
|
|
}
|
|
|
|
// Create assignment
|
|
AssetAssignment::create([
|
|
'asset_id' => $asset->id,
|
|
'employee_id' => $request->employee_id,
|
|
'checkout_date' => $request->checkout_date,
|
|
'expected_return_date' => $request->expected_return_date,
|
|
'checkout_condition' => $request->checkout_condition ?? $asset->condition,
|
|
'notes' => $request->notes,
|
|
'assigned_by' => auth()->id(),
|
|
]);
|
|
|
|
// Update asset status
|
|
$asset->update([
|
|
'status' => 'assigned',
|
|
]);
|
|
|
|
return redirect()->back()->with('success', __('Asset assigned successfully'));
|
|
}
|
|
|
|
/**
|
|
* Return an assigned asset.
|
|
*/
|
|
public function returnAsset(Request $request, Asset $asset)
|
|
{
|
|
// Check if asset belongs to current company
|
|
if (! in_array($asset->created_by, getCompanyAndUsersId())) {
|
|
return redirect()->back()->with('error', __('You do not have permission to return this asset'));
|
|
}
|
|
|
|
// Check if asset is assigned
|
|
if ($asset->status !== 'assigned') {
|
|
return redirect()->back()->with('error', __('Only assigned assets can be returned'));
|
|
}
|
|
|
|
$validator = Validator::make($request->all(), [
|
|
'checkin_date' => 'required|date',
|
|
'checkin_condition' => 'nullable|string|in:new,good,fair,poor',
|
|
'notes' => 'nullable|string',
|
|
]);
|
|
|
|
if ($validator->fails()) {
|
|
return redirect()->back()->withErrors($validator)->withInput();
|
|
}
|
|
|
|
// Get current assignment
|
|
$assignment = $asset->currentAssignment;
|
|
if (! $assignment) {
|
|
return redirect()->back()->with('error', __('No active assignment found for this asset'));
|
|
}
|
|
|
|
// Update assignment
|
|
$assignment->update([
|
|
'checkin_date' => $request->checkin_date,
|
|
'checkin_condition' => $request->checkin_condition ?? $asset->condition,
|
|
'notes' => $assignment->notes."\n\nReturn notes: ".($request->notes ?? 'No notes provided.'),
|
|
'received_by' => auth()->id(),
|
|
]);
|
|
|
|
// Update asset status and condition
|
|
$asset->update([
|
|
'status' => 'available',
|
|
'condition' => $request->checkin_condition ?? $asset->condition,
|
|
]);
|
|
|
|
return redirect()->back()->with('success', __('Asset returned successfully'));
|
|
}
|
|
|
|
/**
|
|
* Schedule maintenance for an asset.
|
|
*/
|
|
public function scheduleMaintenance(Request $request, Asset $asset)
|
|
{
|
|
// Check if asset belongs to current company
|
|
if (! in_array($asset->created_by, getCompanyAndUsersId())) {
|
|
return redirect()->back()->with('error', __('You do not have permission to schedule maintenance for this asset'));
|
|
}
|
|
|
|
$validator = Validator::make($request->all(), [
|
|
'maintenance_type' => 'required|string|max:255',
|
|
'start_date' => 'required|date',
|
|
'end_date' => 'nullable|date|after_or_equal:start_date',
|
|
'cost' => 'nullable|numeric|min:0',
|
|
'details' => 'nullable|string',
|
|
'supplier' => 'nullable|string|max:255',
|
|
]);
|
|
|
|
if ($validator->fails()) {
|
|
return redirect()->back()->withErrors($validator)->withInput();
|
|
}
|
|
|
|
// Create maintenance record
|
|
AssetMaintenance::create([
|
|
'asset_id' => $asset->id,
|
|
'maintenance_type' => $request->maintenance_type,
|
|
'start_date' => $request->start_date,
|
|
'end_date' => $request->end_date,
|
|
'cost' => $request->cost,
|
|
'status' => 'scheduled',
|
|
'details' => $request->details,
|
|
'supplier' => $request->supplier,
|
|
'created_by' => auth()->id(),
|
|
]);
|
|
|
|
// Update asset status if maintenance is starting today or has already started
|
|
if ($request->start_date <= now()->format('Y-m-d')) {
|
|
$asset->update([
|
|
'status' => 'under_maintenance',
|
|
]);
|
|
}
|
|
|
|
return redirect()->back()->with('success', __('Maintenance scheduled successfully'));
|
|
}
|
|
|
|
/**
|
|
* Update maintenance status.
|
|
*/
|
|
public function updateMaintenance(Request $request, AssetMaintenance $maintenance)
|
|
{
|
|
// Check if maintenance belongs to current company
|
|
$asset = $maintenance->asset;
|
|
if (! $asset || ! in_array($asset->created_by, getCompanyAndUsersId())) {
|
|
return redirect()->back()->with('error', __('You do not have permission to update this maintenance record'));
|
|
}
|
|
|
|
$validator = Validator::make($request->all(), [
|
|
'status' => 'required|string|in:scheduled,in_progress,completed,cancelled',
|
|
'end_date' => 'nullable|date|after_or_equal:'.$maintenance->start_date,
|
|
'completion_notes' => 'nullable|string',
|
|
'cost' => 'nullable|numeric|min:0',
|
|
]);
|
|
|
|
if ($validator->fails()) {
|
|
return redirect()->back()->withErrors($validator)->withInput();
|
|
}
|
|
|
|
// Update maintenance record
|
|
$maintenance->update([
|
|
'status' => $request->status,
|
|
'end_date' => $request->end_date,
|
|
'completion_notes' => $request->completion_notes,
|
|
'cost' => $request->cost ?? $maintenance->cost,
|
|
]);
|
|
|
|
// Update asset status based on maintenance status
|
|
if (in_array($request->status, ['completed', 'cancelled'])) {
|
|
$asset->update([
|
|
'status' => 'available',
|
|
]);
|
|
} elseif (in_array($request->status, ['scheduled', 'in_progress'])) {
|
|
if ($request->status === 'in_progress' || $maintenance->start_date <= now()->format('Y-m-d')) {
|
|
$asset->update([
|
|
'status' => 'under_maintenance',
|
|
]);
|
|
}
|
|
}
|
|
|
|
return redirect()->back()->with('success', __('Maintenance record updated successfully'));
|
|
}
|
|
|
|
/**
|
|
* Download document file.
|
|
*/
|
|
public function downloadDocument(Asset $asset)
|
|
{
|
|
// Check if asset belongs to current company
|
|
if (! in_array($asset->created_by, getCompanyAndUsersId())) {
|
|
return redirect()->back()->with('error', __('You do not have permission to access this document'));
|
|
}
|
|
|
|
if (! $asset->documents) {
|
|
return redirect()->back()->with('error', __('Document file not found'));
|
|
}
|
|
|
|
// Handle cloud storage URLs (already full URLs)
|
|
if (filter_var($asset->documents, FILTER_VALIDATE_URL)) {
|
|
return Storage::download($asset->documents);
|
|
}
|
|
|
|
// Handle local storage paths
|
|
$relativePath = str_replace('/Product/hrmgo-saas-react/storage/', '', $asset->documents);
|
|
|
|
if (! Storage::exists($relativePath)) {
|
|
return redirect()->back()->with('error', __('Document file not found'));
|
|
}
|
|
|
|
return Storage::download($relativePath);
|
|
}
|
|
|
|
/**
|
|
* View image file.
|
|
*/
|
|
public function viewImage(Asset $asset)
|
|
{
|
|
// Check if asset belongs to current company
|
|
if (! in_array($asset->created_by, getCompanyAndUsersId())) {
|
|
return redirect()->back()->with('error', __('You do not have permission to access this image'));
|
|
}
|
|
|
|
if (! $asset->images) {
|
|
return redirect()->back()->with('error', __('Image file not found'));
|
|
}
|
|
|
|
// Handle cloud storage URLs (already full URLs)
|
|
if (filter_var($asset->images, FILTER_VALIDATE_URL)) {
|
|
return redirect($asset->images);
|
|
}
|
|
|
|
// Handle local storage paths
|
|
$relativePath = str_replace('/Product/hrmgo-saas-react/storage/', '', $asset->images);
|
|
|
|
if (! Storage::exists($relativePath)) {
|
|
return redirect()->back()->with('error', __('Image file not found'));
|
|
}
|
|
|
|
return Storage::response($relativePath);
|
|
}
|
|
|
|
/**
|
|
* Generate depreciation report.
|
|
*/
|
|
public function depreciationReport(Request $request)
|
|
{
|
|
$companyUserIds = getCompanyAndUsersId();
|
|
|
|
$query = Asset::with(['depreciation', 'assetType'])
|
|
->whereHas('depreciation')
|
|
->whereIn('created_by', $companyUserIds);
|
|
|
|
// Handle asset type filter
|
|
if ($request->filled('asset_type_id')) {
|
|
$query->where('asset_type_id', $request->asset_type_id);
|
|
}
|
|
|
|
// Handle purchase date range filter
|
|
if ($request->filled('purchase_date_from')) {
|
|
$query->whereDate('purchase_date', '>=', $request->purchase_date_from);
|
|
}
|
|
if ($request->filled('purchase_date_to')) {
|
|
$query->whereDate('purchase_date', '<=', $request->purchase_date_to);
|
|
}
|
|
|
|
// Handle sorting
|
|
$allowedSortFields = ['name', 'purchase_date', 'purchase_cost'];
|
|
if ($request->filled('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('purchase_date', 'desc');
|
|
}
|
|
|
|
// Calculate totals across ALL filtered records (not just current page)
|
|
$totalsQuery = Asset::whereHas('depreciation')
|
|
->whereIn('created_by', $companyUserIds);
|
|
|
|
if ($request->filled('asset_type_id')) {
|
|
$totalsQuery->where('asset_type_id', $request->asset_type_id);
|
|
}
|
|
if ($request->filled('purchase_date_from')) {
|
|
$totalsQuery->whereDate('purchase_date', '>=', $request->purchase_date_from);
|
|
}
|
|
if ($request->filled('purchase_date_to')) {
|
|
$totalsQuery->whereDate('purchase_date', '<=', $request->purchase_date_to);
|
|
}
|
|
$totalPurchaseValue = $totalsQuery->sum('purchase_cost');
|
|
$totalCurrentValue = AssetDepreciation::whereIn('asset_id', (clone $totalsQuery)->pluck('id'))->sum('current_value');
|
|
$totalDepreciation = $totalPurchaseValue - $totalCurrentValue;
|
|
|
|
$assets = $query->paginate($request->per_page ?? 10);
|
|
|
|
// Get asset types for filter dropdown
|
|
$assetTypes = AssetType::whereIn('created_by', $companyUserIds)
|
|
->select('id', 'name')
|
|
->get();
|
|
|
|
return Inertia::render('hr/assets/depreciation-report', [
|
|
'assets' => $assets,
|
|
'assetTypes' => $assetTypes,
|
|
'totalPurchaseValue' => $totalPurchaseValue,
|
|
'totalCurrentValue' => $totalCurrentValue,
|
|
'totalDepreciation' => $totalDepreciation,
|
|
'filters' => $request->all(['asset_type_id', 'purchase_date_from', 'purchase_date_to', 'sort_field', 'sort_direction', 'per_page']),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Export depreciation report to CSV.
|
|
*/
|
|
public function exportDepreciationCsv(Request $request)
|
|
{
|
|
$query = Asset::with(['assetType', 'depreciation'])
|
|
->whereHas('depreciation')
|
|
->whereIn('created_by', getCompanyAndUsersId());
|
|
|
|
// Apply same filters as report
|
|
if ($request->has('asset_type_id') && ! empty($request->asset_type_id)) {
|
|
$query->where('asset_type_id', $request->asset_type_id);
|
|
}
|
|
|
|
if ($request->has('purchase_date_from') && ! empty($request->purchase_date_from)) {
|
|
$query->whereDate('purchase_date', '>=', $request->purchase_date_from);
|
|
}
|
|
if ($request->has('purchase_date_to') && ! empty($request->purchase_date_to)) {
|
|
$query->whereDate('purchase_date', '<=', $request->purchase_date_to);
|
|
}
|
|
|
|
$assets = $query->orderBy('purchase_date', 'desc')->get();
|
|
|
|
$csvData = [];
|
|
$csvData[] = ['Asset Name', 'Asset Type', 'Purchase Date', 'Purchase Cost', 'Current Value', 'Depreciation', 'Depreciation Method', 'Useful Life (Years)'];
|
|
|
|
foreach ($assets as $asset) {
|
|
$depreciation = $asset->depreciation;
|
|
$csvData[] = [
|
|
$asset->name,
|
|
$asset->assetType->name ?? '',
|
|
$asset->purchase_date ? date('Y-m-d', strtotime($asset->purchase_date)) : '',
|
|
number_format($asset->purchase_cost, 2),
|
|
number_format($depreciation->current_value ?? 0, 2),
|
|
number_format(($asset->purchase_cost - ($depreciation->current_value ?? 0)), 2),
|
|
ucfirst(str_replace('_', ' ', $depreciation->method ?? '')),
|
|
$depreciation->useful_life_years ?? '',
|
|
];
|
|
}
|
|
|
|
$filename = 'depreciation-report-'.date('Y-m-d').'.csv';
|
|
|
|
$headers = [
|
|
'Content-Type' => 'text/csv',
|
|
'Content-Disposition' => 'attachment; filename="'.$filename.'"',
|
|
];
|
|
|
|
$callback = function () use ($csvData) {
|
|
$file = fopen('php://output', 'w');
|
|
foreach ($csvData as $row) {
|
|
fputcsv($file, $row);
|
|
}
|
|
fclose($file);
|
|
};
|
|
|
|
return response()->stream($callback, 200, $headers);
|
|
}
|
|
|
|
/**
|
|
* Export assets to CSV.
|
|
*/
|
|
public function export()
|
|
{
|
|
if (Auth::user()->can('export-assets')) {
|
|
try {
|
|
$assets = Asset::with(['assetType', 'currentAssignment.employee', 'assignments'])->where(function ($q) {
|
|
if (Auth::user()->can('manage-any-assets')) {
|
|
$q->whereIn('created_by', getCompanyAndUsersId());
|
|
} elseif (Auth::user()->can('manage-own-assets')) {
|
|
$q->where('created_by', Auth::id())
|
|
->orWhereHas('currentAssignment', function ($aq) {
|
|
$aq->where('employee_id', Auth::id());
|
|
});
|
|
} else {
|
|
$q->whereRaw('1 = 0');
|
|
}
|
|
})->orderBy('id', 'desc')->get();
|
|
|
|
$fileName = 'assets_'.date('Y-m-d_His').'.csv';
|
|
$headers = [
|
|
'Content-Type' => 'text/csv',
|
|
'Content-Disposition' => 'attachment; filename="'.$fileName.'"',
|
|
];
|
|
|
|
$callback = function () use ($assets) {
|
|
$file = fopen('php://output', 'w');
|
|
fputcsv($file, [
|
|
'Name',
|
|
'Asset Type',
|
|
'Serial Number',
|
|
'Asset Code',
|
|
'Purchase Date',
|
|
'Purchase Cost',
|
|
'Status',
|
|
'Assigned To',
|
|
'Condition',
|
|
'Description',
|
|
'Location',
|
|
'Supplier',
|
|
'Warranty Info',
|
|
'Warranty Expiry Date',
|
|
]);
|
|
|
|
foreach ($assets as $asset) {
|
|
fputcsv($file, [
|
|
$asset->name,
|
|
$asset->assetType->name ?? '',
|
|
$asset->serial_number ?? '',
|
|
$asset->asset_code ?? '',
|
|
$asset->purchase_date ?? '',
|
|
$asset->purchase_cost ?? '',
|
|
$asset->status ?? '',
|
|
$asset->currentAssignment->employee->name ?? 'Not Assign',
|
|
$asset->condition ?? '',
|
|
$asset->description ?? '',
|
|
$asset->location ?? '',
|
|
$asset->supplier ?? '',
|
|
$asset->warranty_info ?? '',
|
|
$asset->warranty_expiry_date ?? '',
|
|
]);
|
|
}
|
|
fclose($file);
|
|
};
|
|
|
|
return response()->stream($callback, 200, $headers);
|
|
} catch (\Exception $e) {
|
|
return response()->json(['message' => __('Failed to export assets: :message', ['message' => $e->getMessage()])], 500);
|
|
}
|
|
} else {
|
|
return response()->json(['message' => __('Permission Denied.')], 403);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Download sample template.
|
|
*/
|
|
public function downloadTemplate()
|
|
{
|
|
$filePath = storage_path('uploads/sample/sample-asset.xlsx');
|
|
if (! file_exists($filePath)) {
|
|
return response()->json(['error' => __('Template file not available')], 404);
|
|
}
|
|
|
|
return response()->download($filePath, 'sample-asset.xlsx');
|
|
}
|
|
|
|
/**
|
|
* Parse uploaded file.
|
|
*/
|
|
public function parseFile(Request $request)
|
|
{
|
|
if (Auth::user()->can('import-assets')) {
|
|
$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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Import assets from file.
|
|
*/
|
|
public function fileImport(Request $request)
|
|
{
|
|
if (Auth::user()->can('import-assets')) {
|
|
$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['name'])) {
|
|
$skipped++;
|
|
|
|
continue;
|
|
}
|
|
|
|
// Resolve asset type
|
|
$assetTypeId = null;
|
|
if (! empty($row['asset_type'])) {
|
|
$assetType = AssetType::whereIn('created_by', getCompanyAndUsersId())
|
|
->where('name', $row['asset_type'])
|
|
->first();
|
|
$assetTypeId = $assetType ? $assetType->id : null;
|
|
}
|
|
|
|
if (! $assetTypeId) {
|
|
$skipped++;
|
|
|
|
continue;
|
|
}
|
|
|
|
// Check if asset with same name and asset type already exists
|
|
$existingAsset = Asset::whereIn('created_by', getCompanyAndUsersId())
|
|
->where('name', $row['name'])
|
|
->where('asset_type_id', $assetTypeId)
|
|
->exists();
|
|
|
|
if ($existingAsset) {
|
|
$skipped++;
|
|
|
|
continue;
|
|
}
|
|
|
|
Asset::create([
|
|
'name' => $row['name'],
|
|
'asset_type_id' => $assetTypeId,
|
|
'serial_number' => $row['serial_number'] ?? null,
|
|
'asset_code' => $row['asset_code'] ?? null,
|
|
'purchase_date' => ! empty($row['purchase_date']) ? $row['purchase_date'] : null,
|
|
'purchase_cost' => $row['purchase_cost'] ?? null,
|
|
'status' => $row['status'] ?? 'available',
|
|
'condition' => $row['condition'] ?? 'good',
|
|
'description' => $row['description'] ?? null,
|
|
'location' => $row['location'] ?? null,
|
|
'supplier' => $row['supplier'] ?? null,
|
|
'warranty_info' => $row['warranty_info'] ?? null,
|
|
'warranty_expiry_date' => ! empty($row['warranty_expiry_date']) ? $row['warranty_expiry_date'] : null,
|
|
'created_by' => creatorId(),
|
|
]);
|
|
|
|
$imported++;
|
|
} catch (\Exception $e) {
|
|
$skipped++;
|
|
}
|
|
}
|
|
|
|
return redirect()->back()->with('success',
|
|
__('Import completed: :added assets added, :skipped assets 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.'));
|
|
}
|
|
}
|
|
}
|