DONE: layout letter with letterhead and footer, notification for newly...
This commit is contained in:
@@ -1,209 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Services\ContactService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ContactController extends Controller
|
||||
{
|
||||
protected $contactService;
|
||||
|
||||
public function __construct(ContactService $contactService)
|
||||
{
|
||||
$this->contactService = $contactService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active contact persons
|
||||
*/
|
||||
public function getContacts(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$contacts = $this->contactService->getActiveContacts();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $contacts,
|
||||
'message' => 'Contact persons retrieved successfully'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to retrieve contact persons',
|
||||
'error' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contact settings for admin (including inactive)
|
||||
*/
|
||||
public function getContactSettings(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$settings = $this->contactService->getAllContactSettings();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $settings,
|
||||
'message' => 'Contact settings retrieved successfully'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to retrieve contact settings',
|
||||
'error' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update contact settings (Admin only)
|
||||
*/
|
||||
public function updateContacts(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'contacts' => 'required|array',
|
||||
'contacts.*.name' => 'required|string|max:100',
|
||||
'contacts.*.position' => 'nullable|string|max:100',
|
||||
'contacts.*.email' => 'nullable|email|max:255',
|
||||
'contacts.*.phone' => 'nullable|string|max:50',
|
||||
'contacts.*.department' => 'nullable|string|max:100',
|
||||
'contacts.*.is_active' => 'boolean',
|
||||
'contacts.*.sort_order' => 'integer|min:0'
|
||||
]);
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$result = $this->contactService->updateContactSettings($request->contacts);
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $result,
|
||||
'message' => 'Contact settings updated successfully'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to update contact settings',
|
||||
'error' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new contact person (Admin only)
|
||||
*/
|
||||
public function addContact(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:100',
|
||||
'position' => 'nullable|string|max:100',
|
||||
'email' => 'nullable|email|max:255',
|
||||
'phone' => 'nullable|string|max:50',
|
||||
'department' => 'nullable|string|max:100',
|
||||
'is_active' => 'boolean',
|
||||
'sort_order' => 'integer|min:0'
|
||||
]);
|
||||
|
||||
try {
|
||||
$result = $this->contactService->addContactPerson($request->all());
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $result,
|
||||
'message' => 'Contact person added successfully'
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to add contact person',
|
||||
'error' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a specific contact person (Admin only)
|
||||
*/
|
||||
public function updateContact(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:100',
|
||||
'position' => 'nullable|string|max:100',
|
||||
'email' => 'nullable|email|max:255',
|
||||
'phone' => 'nullable|string|max:50',
|
||||
'department' => 'nullable|string|max:100',
|
||||
'is_active' => 'boolean',
|
||||
'sort_order' => 'integer|min:0'
|
||||
]);
|
||||
|
||||
try {
|
||||
$result = $this->contactService->updateContactPerson($id, $request->all());
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $result,
|
||||
'message' => 'Contact person updated successfully'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to update contact person',
|
||||
'error' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a contact person (Admin only)
|
||||
*/
|
||||
public function deleteContact(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$result = $this->contactService->deleteContactPerson($id);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $result,
|
||||
'message' => 'Contact person deleted successfully'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to delete contact person',
|
||||
'error' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle active status of a contact person (Admin only)
|
||||
*/
|
||||
public function toggleContact(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$result = $this->contactService->toggleContactPerson($id);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $result,
|
||||
'message' => 'Contact person status toggled successfully'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to toggle contact person status',
|
||||
'error' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Country;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class CountryController extends Controller
|
||||
{
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$countries = Country::all();
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $countries,
|
||||
'message' => 'Countries fetched successfully',
|
||||
]);
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $countries,
|
||||
'message' => 'Countries fetched successfully',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\LetterService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class LetterPreviewController extends Controller
|
||||
{
|
||||
public function __construct(protected LetterService $letterService) {}
|
||||
|
||||
/**
|
||||
* Preview the base letter layout with sample content in the browser.
|
||||
*
|
||||
* GET /letters/preview?format=html|pdf
|
||||
*/
|
||||
public function show(Request $request): Response
|
||||
{
|
||||
if (! app()->environment('local', 'development')) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$view = 'pdf.preview';
|
||||
$format = $request->query('format', 'html');
|
||||
|
||||
if ($format === 'pdf') {
|
||||
return $this->letterService->pdfResponse($view, [], 'letter-preview.pdf');
|
||||
}
|
||||
|
||||
return $this->letterService->htmlResponse($view);
|
||||
}
|
||||
|
||||
public function showMembershipApprovedLetter(Request $request): Response
|
||||
{
|
||||
if (! app()->environment('local', 'development')) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$view = 'pdf.preview-approved-membership';
|
||||
$format = $request->query('format', 'html');
|
||||
|
||||
if ($format === 'pdf') {
|
||||
return $this->letterService->pdfResponse($view, [], 'letter-preview-approved-membership.pdf');
|
||||
}
|
||||
|
||||
return $this->letterService->htmlResponse($view);
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\OnlineUsersService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class OnlineUsersController extends Controller
|
||||
{
|
||||
protected OnlineUsersService $onlineUsersService;
|
||||
|
||||
public function __construct(OnlineUsersService $onlineUsersService)
|
||||
{
|
||||
$this->onlineUsersService = $onlineUsersService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of online users
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$timeoutMinutes = 5; // Fixed to 5 minutes
|
||||
|
||||
$onlineUsers = $this->onlineUsersService->getOnlineUsers($timeoutMinutes);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $onlineUsers,
|
||||
'meta' => [
|
||||
'timeout_minutes' => $timeoutMinutes,
|
||||
'total_online' => $onlineUsers->count(),
|
||||
'timestamp' => now()->toISOString()
|
||||
],
|
||||
'message' => 'Online users retrieved successfully.'
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching online users: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to retrieve online users.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : 'Internal server error'
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get online users count
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function count(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$timeoutMinutes = 5; // Fixed to 5 minutes
|
||||
|
||||
$count = $this->onlineUsersService->getOnlineUsersCount($timeoutMinutes);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'count' => $count,
|
||||
'timeout_minutes' => $timeoutMinutes
|
||||
],
|
||||
'message' => 'Online users count retrieved successfully.'
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching online users count: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to retrieve online users count.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : 'Internal server error'
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get online users statistics
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function stats(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$stats = $this->onlineUsersService->getOnlineUsersStats();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $stats,
|
||||
'message' => 'Online users statistics retrieved successfully.'
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching online users stats: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to retrieve online users statistics.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : 'Internal server error'
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current user's session information
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function mySession(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$user = $request->user();
|
||||
|
||||
if (!$user) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'User not authenticated.'
|
||||
], 401);
|
||||
}
|
||||
|
||||
$sessionInfo = $this->onlineUsersService->getUserSessionInfo($user->id);
|
||||
|
||||
if (!$sessionInfo) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'No active session found.'
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $sessionInfo,
|
||||
'message' => 'Session information retrieved successfully.'
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching user session info: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to retrieve session information.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : 'Internal server error'
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear online users cache (admin only)
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function clearCache(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$this->onlineUsersService->clearCache();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Online users cache cleared successfully.'
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error clearing online users cache: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to clear cache.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : 'Internal server error'
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Services\SocialMediaService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class SocialMediaController extends Controller
|
||||
{
|
||||
protected $socialMediaService;
|
||||
|
||||
public function __construct(SocialMediaService $socialMediaService)
|
||||
{
|
||||
$this->socialMediaService = $socialMediaService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active social media platforms
|
||||
*/
|
||||
public function getSocialMedia(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$socialMedia = $this->socialMediaService->getActiveSocialMedia();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $socialMedia,
|
||||
'message' => 'Social media platforms retrieved successfully'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to retrieve social media platforms',
|
||||
'error' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update social media settings (Admin only)
|
||||
*/
|
||||
public function updateSocialMedia(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'social_media' => 'required|array',
|
||||
'social_media.*.platform' => 'required|string|max:50',
|
||||
'social_media.*.name' => 'required|string|max:100',
|
||||
'social_media.*.url' => 'required|url|max:255',
|
||||
'social_media.*.icon' => 'nullable|string|max:100',
|
||||
'social_media.*.is_active' => 'boolean',
|
||||
'social_media.*.sort_order' => 'integer|min:0'
|
||||
]);
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$result = $this->socialMediaService->updateSocialMediaSettings($request->social_media);
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $result,
|
||||
'message' => 'Social media settings updated successfully'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to update social media settings',
|
||||
'error' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get social media settings for admin (including inactive)
|
||||
*/
|
||||
public function getSocialMediaSettings(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$settings = $this->socialMediaService->getAllSocialMediaSettings();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $settings,
|
||||
'message' => 'Social media settings retrieved successfully'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to retrieve social media settings',
|
||||
'error' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new social media platform (Admin only)
|
||||
*/
|
||||
public function addSocialMedia(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'platform' => 'required|string|max:50',
|
||||
'name' => 'required|string|max:100',
|
||||
'url' => 'required|url|max:255',
|
||||
'icon' => 'nullable|string|max:100',
|
||||
'is_active' => 'boolean',
|
||||
'sort_order' => 'integer|min:0'
|
||||
]);
|
||||
|
||||
try {
|
||||
$result = $this->socialMediaService->addSocialMediaPlatform($request->all());
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $result,
|
||||
'message' => 'Social media platform added successfully'
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to add social media platform',
|
||||
'error' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a specific social media platform (Admin only)
|
||||
*/
|
||||
public function updateSocialMediaPlatform(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'platform' => 'required|string|max:50',
|
||||
'name' => 'required|string|max:100',
|
||||
'url' => 'required|url|max:255',
|
||||
'icon' => 'nullable|string|max:100',
|
||||
'is_active' => 'boolean',
|
||||
'sort_order' => 'integer|min:0'
|
||||
]);
|
||||
|
||||
try {
|
||||
$result = $this->socialMediaService->updateSocialMediaPlatform($id, $request->all());
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $result,
|
||||
'message' => 'Social media platform updated successfully'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to update social media platform',
|
||||
'error' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a social media platform (Admin only)
|
||||
*/
|
||||
public function deleteSocialMediaPlatform(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$result = $this->socialMediaService->deleteSocialMediaPlatform($id);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $result,
|
||||
'message' => 'Social media platform deleted successfully'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to delete social media platform',
|
||||
'error' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle active status of a social media platform (Admin only)
|
||||
*/
|
||||
public function toggleSocialMediaPlatform(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$result = $this->socialMediaService->toggleSocialMediaPlatform($id);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $result,
|
||||
'message' => 'Social media platform status toggled successfully'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to toggle social media platform status',
|
||||
'error' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Modules\KJCAssetHolding\Entities\KJCAssetHolding;
|
||||
|
||||
class Country extends Model
|
||||
{
|
||||
protected $table = 'countries';
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Modules\Auth\Entities\User;
|
||||
|
||||
class Document extends Model
|
||||
{
|
||||
use HasUuids, SoftDeletes;
|
||||
|
||||
public const STORAGE_DISK = 'local';
|
||||
|
||||
protected $table = 'documents';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'documentable_type',
|
||||
'documentable_id',
|
||||
'name',
|
||||
'path',
|
||||
'file_size',
|
||||
'mime_type',
|
||||
'type',
|
||||
'description',
|
||||
'uploaded_by',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'string',
|
||||
'path' => 'string',
|
||||
'file_size' => 'integer',
|
||||
'mime_type' => 'string',
|
||||
'type' => 'string',
|
||||
'description' => 'string',
|
||||
];
|
||||
}
|
||||
|
||||
public function documentable(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function uploadedBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'uploaded_by');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Concerns;
|
||||
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
trait BuildsMailMessage
|
||||
{
|
||||
protected function mailMessage(string $subject, string $view, array $data = []): MailMessage
|
||||
{
|
||||
return (new MailMessage)
|
||||
->subject($subject)
|
||||
->markdown($view, array_merge([
|
||||
'logoPath' => config('mail.logo_path'),
|
||||
], $data));
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,18 @@ class FortifyServiceProvider extends ServiceProvider
|
||||
return Limit::perMinute(1)->by($throttleKey);
|
||||
});
|
||||
|
||||
RateLimiter::for('password-reset-request', function (Request $request) {
|
||||
$throttleKey = Str::transliterate(Str::lower($request->input('email', '')).'|'.$request->ip());
|
||||
|
||||
return Limit::perMinute(1)->by($throttleKey);
|
||||
});
|
||||
|
||||
RateLimiter::for('password-reset', function (Request $request) {
|
||||
$throttleKey = Str::transliterate(Str::lower($request->input('email', '')).'|'.$request->ip());
|
||||
|
||||
return Limit::perMinute(5)->by($throttleKey);
|
||||
});
|
||||
|
||||
Fortify::authenticateUsing(function (Request $request) {
|
||||
$user = User::where('email', $request->email)->first();
|
||||
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ContactService
|
||||
{
|
||||
/**
|
||||
* Get all active contact persons
|
||||
*/
|
||||
public function getActiveContacts(): array
|
||||
{
|
||||
try {
|
||||
$contacts = DB::table('contact_settings')
|
||||
->where('is_active', true)
|
||||
->orderBy('sort_order', 'asc')
|
||||
->orderBy('name', 'asc')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
return array_map(function ($item) {
|
||||
return [
|
||||
'id' => $item->id,
|
||||
'name' => $item->name,
|
||||
'position' => $item->position,
|
||||
'email' => $item->email,
|
||||
'phone' => $item->phone,
|
||||
'department' => $item->department,
|
||||
'sort_order' => $item->sort_order,
|
||||
];
|
||||
}, $contacts);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching active contacts: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all contact settings (including inactive) for admin
|
||||
*/
|
||||
public function getAllContactSettings(): array
|
||||
{
|
||||
try {
|
||||
$settings = DB::table('contact_settings')
|
||||
->orderBy('sort_order', 'asc')
|
||||
->orderBy('name', 'asc')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
return array_map(function ($item) {
|
||||
return [
|
||||
'id' => $item->id,
|
||||
'name' => $item->name,
|
||||
'position' => $item->position,
|
||||
'email' => $item->email,
|
||||
'phone' => $item->phone,
|
||||
'department' => $item->department,
|
||||
'is_active' => (bool) $item->is_active,
|
||||
'sort_order' => $item->sort_order,
|
||||
'created_at' => $item->created_at,
|
||||
'updated_at' => $item->updated_at,
|
||||
];
|
||||
}, $settings);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching all contact settings: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update contact settings (bulk update)
|
||||
*/
|
||||
public function updateContactSettings(array $contactData): array
|
||||
{
|
||||
try {
|
||||
// Clear existing settings
|
||||
DB::table('contact_settings')->truncate();
|
||||
|
||||
// Insert new settings
|
||||
$insertData = [];
|
||||
foreach ($contactData as $index => $item) {
|
||||
$insertData[] = [
|
||||
'name' => $item['name'],
|
||||
'position' => $item['position'] ?? null,
|
||||
'email' => $item['email'] ?? null,
|
||||
'phone' => $item['phone'] ?? null,
|
||||
'department' => $item['department'] ?? null,
|
||||
'is_active' => $item['is_active'] ?? true,
|
||||
'sort_order' => $item['sort_order'] ?? $index,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
];
|
||||
}
|
||||
|
||||
DB::table('contact_settings')->insert($insertData);
|
||||
|
||||
// Return updated settings
|
||||
return $this->getAllContactSettings();
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating contact settings: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single contact person
|
||||
*/
|
||||
public function addContactPerson(array $data): array
|
||||
{
|
||||
try {
|
||||
$id = DB::table('contact_settings')->insertGetId([
|
||||
'name' => $data['name'],
|
||||
'position' => $data['position'] ?? null,
|
||||
'email' => $data['email'] ?? null,
|
||||
'phone' => $data['phone'] ?? null,
|
||||
'department' => $data['department'] ?? null,
|
||||
'is_active' => $data['is_active'] ?? true,
|
||||
'sort_order' => $data['sort_order'] ?? 0,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
return $this->getAllContactSettings();
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error adding contact person: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a single contact person
|
||||
*/
|
||||
public function updateContactPerson(int $id, array $data): array
|
||||
{
|
||||
try {
|
||||
DB::table('contact_settings')
|
||||
->where('id', $id)
|
||||
->update([
|
||||
'name' => $data['name'],
|
||||
'position' => $data['position'] ?? null,
|
||||
'email' => $data['email'] ?? null,
|
||||
'phone' => $data['phone'] ?? null,
|
||||
'department' => $data['department'] ?? null,
|
||||
'is_active' => $data['is_active'] ?? true,
|
||||
'sort_order' => $data['sort_order'] ?? 0,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
return $this->getAllContactSettings();
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating contact person: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a contact person
|
||||
*/
|
||||
public function deleteContactPerson(int $id): array
|
||||
{
|
||||
try {
|
||||
DB::table('contact_settings')->where('id', $id)->delete();
|
||||
|
||||
return $this->getAllContactSettings();
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting contact person: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle active status of a contact person
|
||||
*/
|
||||
public function toggleContactPerson(int $id): array
|
||||
{
|
||||
try {
|
||||
$contact = DB::table('contact_settings')->where('id', $id)->first();
|
||||
|
||||
if (!$contact) {
|
||||
throw new \Exception('Contact person not found');
|
||||
}
|
||||
|
||||
DB::table('contact_settings')
|
||||
->where('id', $id)
|
||||
->update([
|
||||
'is_active' => !$contact->is_active,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
return $this->getAllContactSettings();
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error toggling contact person: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,22 +19,16 @@ class DocumentService
|
||||
string $documentType = 'general',
|
||||
?string $description = null
|
||||
): Document {
|
||||
// Generate unique filename
|
||||
$fileName = time().'_'.$file->getClientOriginalName();
|
||||
|
||||
// Store file in a folder named after the model
|
||||
$folderName = strtolower(class_basename($model));
|
||||
$filePath = $file->storeAs("documents/{$folderName}", $fileName, 'public');
|
||||
$filePath = $file->storeAs("documents/{$folderName}", $fileName, Document::STORAGE_DISK);
|
||||
|
||||
// Create document record
|
||||
return Document::create([
|
||||
'documentable_type' => get_class($model),
|
||||
'documentable_id' => $model->id,
|
||||
'document_name' => $file->getClientOriginalName(),
|
||||
'document_path' => $filePath,
|
||||
return $model->documents()->create([
|
||||
'name' => $file->getClientOriginalName(),
|
||||
'path' => $filePath,
|
||||
'file_size' => $file->getSize(),
|
||||
'mime_type' => $file->getClientMimeType(),
|
||||
'document_type' => $documentType,
|
||||
'type' => $documentType,
|
||||
'description' => $description,
|
||||
'uploaded_by' => auth()->id(),
|
||||
]);
|
||||
@@ -43,7 +37,7 @@ class DocumentService
|
||||
/**
|
||||
* Delete a document.
|
||||
*/
|
||||
public function deleteDocument(int $documentId): bool
|
||||
public function deleteDocument(string $documentId): bool
|
||||
{
|
||||
$document = Document::findOrFail($documentId);
|
||||
|
||||
@@ -58,7 +52,7 @@ class DocumentService
|
||||
$query = $model->documents();
|
||||
|
||||
if ($documentType) {
|
||||
$query->where('document_type', $documentType);
|
||||
$query->where('type', $documentType);
|
||||
}
|
||||
|
||||
return $query->with('uploadedBy')->get();
|
||||
@@ -67,7 +61,7 @@ class DocumentService
|
||||
/**
|
||||
* Get document by ID with validation.
|
||||
*/
|
||||
public function getDocument(int $documentId): Document
|
||||
public function getDocument(string $documentId): Document
|
||||
{
|
||||
return Document::with('uploadedBy')->findOrFail($documentId);
|
||||
}
|
||||
@@ -75,15 +69,17 @@ class DocumentService
|
||||
/**
|
||||
* Download a document.
|
||||
*/
|
||||
public function downloadDocument(int $documentId)
|
||||
public function downloadDocument(string $documentId)
|
||||
{
|
||||
$document = $this->getDocument($documentId);
|
||||
|
||||
if (! Storage::exists($document->document_path)) {
|
||||
$disk = Storage::disk(Document::STORAGE_DISK);
|
||||
|
||||
if (! $disk->exists($document->path)) {
|
||||
throw new Exception('File not found');
|
||||
}
|
||||
|
||||
return Storage::download($document->document_path, $document->document_name);
|
||||
return $disk->download($document->path, $document->name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,7 +90,7 @@ class DocumentService
|
||||
$query = $model->documents();
|
||||
|
||||
if ($documentType) {
|
||||
$query->where('document_type', $documentType);
|
||||
$query->where('type', $documentType);
|
||||
}
|
||||
|
||||
return $query->count();
|
||||
@@ -140,12 +136,10 @@ class DocumentService
|
||||
$supportedTypes = $this->getSupportedFileTypes();
|
||||
$maxSize = $this->getMaxFileSize() * 1024; // Convert to bytes
|
||||
|
||||
// Check file size
|
||||
if ($file->getSize() > $maxSize) {
|
||||
throw new Exception('File size exceeds maximum limit of '.$this->getMaxFileSize().'KB');
|
||||
}
|
||||
|
||||
// Check mime type
|
||||
if (! in_array($file->getClientMimeType(), $supportedTypes)) {
|
||||
throw new Exception('File type not supported. Supported types: '.implode(', ', array_keys($supportedTypes)));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Support\LetterLayout;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Spatie\Browsershot\Browsershot;
|
||||
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
|
||||
|
||||
class LetterService
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function renderHtml(string $view, array $data = []): string
|
||||
{
|
||||
return View::make($view, $this->prepareViewData($data))->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function htmlResponse(string $view, array $data = []): Response
|
||||
{
|
||||
return response($this->renderHtml($view, $data), SymfonyResponse::HTTP_OK, [
|
||||
'Content-Type' => 'text/html; charset=UTF-8',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function pdfResponse(string $view, array $data = [], string $filename = 'letter.pdf'): Response
|
||||
{
|
||||
$pdf = $this->makeBrowsershot($this->renderHtml($view, $data))->pdf();
|
||||
|
||||
return response($pdf, SymfonyResponse::HTTP_OK, [
|
||||
'Content-Type' => 'application/pdf',
|
||||
'Content-Disposition' => 'inline; filename="'.$filename.'"',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $overrides
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function prepareViewData(array $overrides = []): array
|
||||
{
|
||||
$logoPath = $overrides['logoPath'] ?? config('mail.logo_path');
|
||||
|
||||
return array_merge([
|
||||
'letterLayout' => LetterLayout::resolve(),
|
||||
'logoPath' => $logoPath,
|
||||
'logoUrl' => $this->resolveLogoDataUri($logoPath),
|
||||
'organizationAddress' => $overrides['organizationAddress'] ?? null,
|
||||
'organizationContact' => $overrides['organizationContact'] ?? null,
|
||||
], $overrides);
|
||||
}
|
||||
|
||||
protected function makeBrowsershot(string $html): Browsershot
|
||||
{
|
||||
$layout = LetterLayout::resolve();
|
||||
[$top, $right, $bottom, $left] = $layout['margins_mm'];
|
||||
|
||||
$browsershot = Browsershot::html($html)
|
||||
->setNodeModulePath(config('browsershot.node_module_path'))
|
||||
->timeout(config('browsershot.timeout'))
|
||||
->format($layout['page_format'])
|
||||
->margins($top, $right, $bottom, $left)
|
||||
->showBackground();
|
||||
|
||||
if ($nodeBinary = config('browsershot.node_binary')) {
|
||||
$browsershot->setNodeBinary($nodeBinary);
|
||||
}
|
||||
|
||||
if ($npmBinary = config('browsershot.npm_binary')) {
|
||||
$browsershot->setNpmBinary($npmBinary);
|
||||
}
|
||||
|
||||
if ($chromePath = config('browsershot.chrome_path')) {
|
||||
$browsershot->setChromePath($chromePath);
|
||||
}
|
||||
|
||||
if (config('browsershot.no_sandbox')) {
|
||||
$browsershot->noSandbox();
|
||||
}
|
||||
|
||||
return $browsershot;
|
||||
}
|
||||
|
||||
protected function resolveLogoDataUri(?string $logoPath): ?string
|
||||
{
|
||||
if (empty($logoPath) || ! file_exists($logoPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mime = mime_content_type($logoPath) ?: 'image/svg+xml';
|
||||
|
||||
return 'data:'.$mime.';base64,'.base64_encode(file_get_contents($logoPath));
|
||||
}
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Modules\Auth\Entities\User;
|
||||
use Carbon\Carbon;
|
||||
use Laravel\Sanctum\PersonalAccessToken;
|
||||
|
||||
class OnlineUsersService
|
||||
{
|
||||
/**
|
||||
* Get all currently online users
|
||||
*
|
||||
* @param int $timeoutMinutes Minutes of inactivity to consider user offline
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function getOnlineUsers(int $timeoutMinutes = 5)
|
||||
{
|
||||
$cacheKey = "online_users_{$timeoutMinutes}";
|
||||
|
||||
return Cache::remember($cacheKey, 30, function () use ($timeoutMinutes) {
|
||||
$timeoutSeconds = $timeoutMinutes * 60;
|
||||
$cutoffTime = now()->subSeconds($timeoutSeconds);
|
||||
|
||||
// Get active Sanctum tokens with user information
|
||||
$onlineTokens = PersonalAccessToken::with(['tokenable.unit', 'tokenable.rank', 'tokenable.position'])
|
||||
->where('tokenable_type', User::class)
|
||||
->where('last_used_at', '>', $cutoffTime)
|
||||
->where(function ($query) {
|
||||
$query->whereNull('expires_at')
|
||||
->orWhere('expires_at', '>', now());
|
||||
})
|
||||
->whereHas('tokenable', function ($query) {
|
||||
$query->whereNull('deleted_at');
|
||||
})
|
||||
->orderBy('last_used_at', 'desc')
|
||||
->get();
|
||||
|
||||
// Group by user to handle multiple tokens
|
||||
$onlineUsers = $onlineTokens->groupBy('tokenable_id')->map(function ($tokens) {
|
||||
$token = $tokens->first();
|
||||
$user = $token->tokenable;
|
||||
$latestToken = $tokens->sortByDesc('last_used_at')->first();
|
||||
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
'army_number' => $user->army_number,
|
||||
'image_url' => $user->image_url ? Storage::disk('public')->url($user->image_url) : null,
|
||||
'status' => $user->status,
|
||||
'unit_name' => $user->unit?->name,
|
||||
'rank_name' => $user->rank?->name,
|
||||
'position_name' => $user->position?->name,
|
||||
'ip_address' => 'N/A', // Sanctum doesn't store IP by default
|
||||
'user_agent' => $this->parseUserAgent('Sanctum Token'),
|
||||
'last_activity' => $latestToken->last_used_at?->timestamp ?? $latestToken->created_at->timestamp,
|
||||
'last_activity_human' => $latestToken->last_used_at?->diffForHumans() ?? $latestToken->created_at->diffForHumans(),
|
||||
'session_count' => $tokens->count(),
|
||||
'is_online' => true
|
||||
];
|
||||
});
|
||||
|
||||
return $onlineUsers->values();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get online users count
|
||||
*
|
||||
* @param int $timeoutMinutes Minutes of inactivity to consider user offline
|
||||
* @return int
|
||||
*/
|
||||
public function getOnlineUsersCount(int $timeoutMinutes = 5): int
|
||||
{
|
||||
$cacheKey = "online_users_count_{$timeoutMinutes}";
|
||||
|
||||
return Cache::remember($cacheKey, 30, function () use ($timeoutMinutes) {
|
||||
$timeoutSeconds = $timeoutMinutes * 60;
|
||||
$cutoffTime = now()->subSeconds($timeoutSeconds);
|
||||
|
||||
return PersonalAccessToken::where('tokenable_type', User::class)
|
||||
->where('last_used_at', '>', $cutoffTime)
|
||||
->where(function ($query) {
|
||||
$query->whereNull('expires_at')
|
||||
->orWhere('expires_at', '>', now());
|
||||
})
|
||||
->whereHas('tokenable', function ($query) {
|
||||
$query->whereNull('deleted_at');
|
||||
})
|
||||
->distinct('tokenable_id')
|
||||
->count('tokenable_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's current session information
|
||||
*
|
||||
* @param int $userId
|
||||
* @return array|null
|
||||
*/
|
||||
public function getUserSessionInfo(int $userId): ?array
|
||||
{
|
||||
$token = PersonalAccessToken::where('tokenable_type', User::class)
|
||||
->where('tokenable_id', $userId)
|
||||
->where(function ($query) {
|
||||
$query->whereNull('expires_at')
|
||||
->orWhere('expires_at', '>', now());
|
||||
})
|
||||
->orderBy('last_used_at', 'desc')
|
||||
->first();
|
||||
|
||||
if (!$token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'session_id' => $token->id,
|
||||
'ip_address' => 'N/A', // Sanctum doesn't store IP by default
|
||||
'user_agent' => $this->parseUserAgent('Sanctum Token'),
|
||||
'last_activity' => $token->last_used_at?->timestamp ?? $token->created_at->timestamp,
|
||||
'last_activity_human' => $token->last_used_at?->diffForHumans() ?? $token->created_at->diffForHumans(),
|
||||
'is_online' => $token->last_used_at && $token->last_used_at->gt(now()->subMinutes(5))
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse user agent string to extract browser and OS info
|
||||
*
|
||||
* @param string $userAgent
|
||||
* @return array
|
||||
*/
|
||||
private function parseUserAgent(string $userAgent): array
|
||||
{
|
||||
$browser = 'Unknown';
|
||||
$os = 'Unknown';
|
||||
|
||||
// Simple browser detection
|
||||
if (strpos($userAgent, 'Chrome') !== false) {
|
||||
$browser = 'Chrome';
|
||||
} elseif (strpos($userAgent, 'Firefox') !== false) {
|
||||
$browser = 'Firefox';
|
||||
} elseif (strpos($userAgent, 'Safari') !== false) {
|
||||
$browser = 'Safari';
|
||||
} elseif (strpos($userAgent, 'Edge') !== false) {
|
||||
$browser = 'Edge';
|
||||
}
|
||||
|
||||
// Simple OS detection
|
||||
if (strpos($userAgent, 'Windows') !== false) {
|
||||
$os = 'Windows';
|
||||
} elseif (strpos($userAgent, 'Mac') !== false) {
|
||||
$os = 'macOS';
|
||||
} elseif (strpos($userAgent, 'Linux') !== false) {
|
||||
$os = 'Linux';
|
||||
} elseif (strpos($userAgent, 'Android') !== false) {
|
||||
$os = 'Android';
|
||||
} elseif (strpos($userAgent, 'iOS') !== false) {
|
||||
$os = 'iOS';
|
||||
}
|
||||
|
||||
return [
|
||||
'browser' => $browser,
|
||||
'os' => $os,
|
||||
'raw' => $userAgent
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear online users cache
|
||||
*/
|
||||
public function clearCache(): void
|
||||
{
|
||||
Cache::forget('online_users_5');
|
||||
Cache::forget('online_users_10');
|
||||
Cache::forget('online_users_15');
|
||||
Cache::forget('online_users_30');
|
||||
Cache::forget('online_users_count_5');
|
||||
Cache::forget('online_users_count_10');
|
||||
Cache::forget('online_users_count_15');
|
||||
Cache::forget('online_users_count_30');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get online users statistics
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOnlineUsersStats(): array
|
||||
{
|
||||
$stats = [];
|
||||
|
||||
// Get stats for different timeout periods
|
||||
$timeouts = [5, 10, 15, 30];
|
||||
|
||||
foreach ($timeouts as $timeout) {
|
||||
$stats["online_{$timeout}min"] = $this->getOnlineUsersCount($timeout);
|
||||
}
|
||||
|
||||
// Get total registered users
|
||||
$stats['total_users'] = User::count();
|
||||
|
||||
// Get users by status
|
||||
$stats['active_users'] = User::where('status', 'active')->count();
|
||||
$stats['inactive_users'] = User::where('status', 'inactive')->count();
|
||||
|
||||
return $stats;
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class SocialMediaService
|
||||
{
|
||||
/**
|
||||
* Get all active social media platforms
|
||||
*/
|
||||
public function getActiveSocialMedia(): array
|
||||
{
|
||||
try {
|
||||
$socialMedia = DB::table('social_media_settings')
|
||||
->where('is_active', true)
|
||||
->orderBy('sort_order', 'asc')
|
||||
->orderBy('name', 'asc')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
return array_map(function ($item) {
|
||||
return [
|
||||
'id' => $item->id,
|
||||
'platform' => $item->platform,
|
||||
'name' => $item->name,
|
||||
'url' => $item->url,
|
||||
'icon' => $item->icon,
|
||||
'sort_order' => $item->sort_order,
|
||||
];
|
||||
}, $socialMedia);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching active social media: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all social media settings (including inactive) for admin
|
||||
*/
|
||||
public function getAllSocialMediaSettings(): array
|
||||
{
|
||||
try {
|
||||
$settings = DB::table('social_media_settings')
|
||||
->orderBy('sort_order', 'asc')
|
||||
->orderBy('name', 'asc')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
return array_map(function ($item) {
|
||||
return [
|
||||
'id' => $item->id,
|
||||
'platform' => $item->platform,
|
||||
'name' => $item->name,
|
||||
'url' => $item->url,
|
||||
'icon' => $item->icon,
|
||||
'is_active' => (bool) $item->is_active,
|
||||
'sort_order' => $item->sort_order,
|
||||
'created_at' => $item->created_at,
|
||||
'updated_at' => $item->updated_at,
|
||||
];
|
||||
}, $settings);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching all social media settings: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update social media settings (bulk update)
|
||||
*/
|
||||
public function updateSocialMediaSettings(array $socialMediaData): array
|
||||
{
|
||||
try {
|
||||
// Clear existing settings
|
||||
DB::table('social_media_settings')->truncate();
|
||||
|
||||
// Insert new settings
|
||||
$insertData = [];
|
||||
foreach ($socialMediaData as $index => $item) {
|
||||
$insertData[] = [
|
||||
'platform' => $item['platform'],
|
||||
'name' => $item['name'],
|
||||
'url' => $item['url'],
|
||||
'icon' => $item['icon'] ?? null,
|
||||
'is_active' => $item['is_active'] ?? true,
|
||||
'sort_order' => $item['sort_order'] ?? $index,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
];
|
||||
}
|
||||
|
||||
DB::table('social_media_settings')->insert($insertData);
|
||||
|
||||
// Return updated settings
|
||||
return $this->getAllSocialMediaSettings();
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating social media settings: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single social media platform
|
||||
*/
|
||||
public function addSocialMediaPlatform(array $data): array
|
||||
{
|
||||
try {
|
||||
$id = DB::table('social_media_settings')->insertGetId([
|
||||
'platform' => $data['platform'],
|
||||
'name' => $data['name'],
|
||||
'url' => $data['url'],
|
||||
'icon' => $data['icon'] ?? null,
|
||||
'is_active' => $data['is_active'] ?? true,
|
||||
'sort_order' => $data['sort_order'] ?? 0,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
return $this->getAllSocialMediaSettings();
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error adding social media platform: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a single social media platform
|
||||
*/
|
||||
public function updateSocialMediaPlatform(int $id, array $data): array
|
||||
{
|
||||
try {
|
||||
DB::table('social_media_settings')
|
||||
->where('id', $id)
|
||||
->update([
|
||||
'platform' => $data['platform'],
|
||||
'name' => $data['name'],
|
||||
'url' => $data['url'],
|
||||
'icon' => $data['icon'] ?? null,
|
||||
'is_active' => $data['is_active'] ?? true,
|
||||
'sort_order' => $data['sort_order'] ?? 0,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
return $this->getAllSocialMediaSettings();
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating social media platform: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a social media platform
|
||||
*/
|
||||
public function deleteSocialMediaPlatform(int $id): array
|
||||
{
|
||||
try {
|
||||
DB::table('social_media_settings')->where('id', $id)->delete();
|
||||
|
||||
return $this->getAllSocialMediaSettings();
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting social media platform: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle active status of a social media platform
|
||||
*/
|
||||
public function toggleSocialMediaPlatform(int $id): array
|
||||
{
|
||||
try {
|
||||
$platform = DB::table('social_media_settings')->where('id', $id)->first();
|
||||
|
||||
if (!$platform) {
|
||||
throw new \Exception('Social media platform not found');
|
||||
}
|
||||
|
||||
DB::table('social_media_settings')
|
||||
->where('id', $id)
|
||||
->update([
|
||||
'is_active' => !$platform->is_active,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
return $this->getAllSocialMediaSettings();
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error toggling social media platform: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,304 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Modules\Formation\Entities\Formation;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class VisibilityService
|
||||
{
|
||||
/**
|
||||
* Apply visibility scoping to a query builder based on user permissions
|
||||
*
|
||||
* @param bool $includeUnitsWithoutFormation When true (process models), SEL PERO 91 REJ
|
||||
* users can see units without formations. Government users never see units without formations.
|
||||
*/
|
||||
public function applyVisibilityToQuery(Builder $query, $user, string $unitColumn = 'unit_id', bool $includeUnitsWithoutFormation = false): Builder
|
||||
{
|
||||
// Check if user has akses peringkat keseluruhan permission (super admin)
|
||||
if ($user->can('akses peringkat keseluruhan', Unit::class)) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
// Get user's unit
|
||||
$userUnit = Unit::find($user->unit_id);
|
||||
if (!$userUnit) {
|
||||
return $query->whereRaw('1 = 0');
|
||||
}
|
||||
|
||||
// Government permission: Can view all units under their government
|
||||
// Government users do NOT see units without formations
|
||||
// SEL PERO 91 REJ (process models): ONLY see units without formation - not their gov/formation units
|
||||
if ($user->can('akses peringkat formasi')) {
|
||||
if ($includeUnitsWithoutFormation && $user->hasRole('SEL PERO 91 REJ')) {
|
||||
$unitIds = Unit::whereNull('formation_id')
|
||||
->whereNull('repair_formation_id')
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
} else {
|
||||
$unitIds = $this->getUnitsUnderGovernment($userUnit);
|
||||
$unitIds = $this->excludeUnitsWithoutFormation($unitIds);
|
||||
}
|
||||
|
||||
if (!empty($unitIds)) {
|
||||
return $query->whereIn($unitColumn, $unitIds);
|
||||
}
|
||||
return $query->whereRaw('1 = 0');
|
||||
}
|
||||
|
||||
// DIV permission: Can view all units under their formation (including repair formations)
|
||||
if ($user->can('akses peringkat divisyen')) {
|
||||
$unitIds = $this->getVisibleUnitIdsForFormationUser($user, $userUnit, $includeUnitsWithoutFormation);
|
||||
|
||||
if (!empty($unitIds)) {
|
||||
return $query->whereIn($unitColumn, $unitIds);
|
||||
}
|
||||
return $query->whereRaw('1 = 0');
|
||||
}
|
||||
|
||||
// Unit permission: Can only view data within their unit
|
||||
return $query->where($unitColumn, $user->unit_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply visibility scoping for models with indirect unit relationships
|
||||
*
|
||||
* @param bool $includeUnitsWithoutFormation When true (process models), SEL PERO 91 REJ
|
||||
* users can see units without formations. Government users never see units without formations.
|
||||
*/
|
||||
public function applyVisibilityToIndirectQuery(Builder $query, $user, array $unitRelationship, bool $includeUnitsWithoutFormation = false): Builder
|
||||
{
|
||||
// Check if user has akses peringkat keseluruhan permission (super admin)
|
||||
if ($user->can('akses peringkat keseluruhan', Unit::class)) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
// Get user's unit
|
||||
$userUnit = Unit::find($user->unit_id);
|
||||
if (!$userUnit) {
|
||||
return $query->whereRaw('1 = 0');
|
||||
}
|
||||
|
||||
// Government permission: Can view all units under their government
|
||||
// SEL PERO 91 REJ (process models): ONLY see units without formation - not their gov/formation units
|
||||
if ($user->can('akses peringkat formasi')) {
|
||||
if ($includeUnitsWithoutFormation && $user->hasRole('SEL PERO 91 REJ')) {
|
||||
$unitIds = Unit::whereNull('formation_id')
|
||||
->whereNull('repair_formation_id')
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
} else {
|
||||
$unitIds = $this->getUnitsUnderGovernment($userUnit);
|
||||
$unitIds = $this->excludeUnitsWithoutFormation($unitIds);
|
||||
}
|
||||
|
||||
if (!empty($unitIds)) {
|
||||
return $query->whereHas($unitRelationship['relationship'], function ($subQuery) use ($unitIds, $unitRelationship) {
|
||||
$subQuery->whereIn($unitRelationship['unitColumn'], $unitIds);
|
||||
});
|
||||
}
|
||||
return $query->whereRaw('1 = 0');
|
||||
}
|
||||
|
||||
// DIV permission: Can view all units under their formation (including repair formations)
|
||||
if ($user->can('akses peringkat divisyen')) {
|
||||
$unitIds = $this->getVisibleUnitIdsForFormationUser($user, $userUnit, $includeUnitsWithoutFormation);
|
||||
|
||||
if (!empty($unitIds)) {
|
||||
return $query->whereHas($unitRelationship['relationship'], function ($subQuery) use ($unitIds, $unitRelationship) {
|
||||
$subQuery->whereIn($unitRelationship['unitColumn'], $unitIds);
|
||||
});
|
||||
}
|
||||
return $query->whereRaw('1 = 0');
|
||||
}
|
||||
|
||||
// Unit permission: Can only view data within their unit
|
||||
return $query->whereHas($unitRelationship['relationship'], function ($subQuery) use ($user, $unitRelationship) {
|
||||
$subQuery->where($unitRelationship['unitColumn'], $user->unit_id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all units under the same government as the user's unit
|
||||
* Handles both direct government relationships and formation-government relationships
|
||||
* Priority: Direct government_id takes precedence over formation government
|
||||
*
|
||||
* @param Unit $userUnit
|
||||
* @return array
|
||||
*/
|
||||
public function getUnitsUnderGovernment(Unit $userUnit): array
|
||||
{
|
||||
$unitIds = collect();
|
||||
|
||||
// Case 1: User's unit has direct government relationship (HIGHEST PRIORITY)
|
||||
if ($userUnit->government_id) {
|
||||
$directUnits = Unit::where('government_id', $userUnit->government_id)
|
||||
->pluck('id');
|
||||
$unitIds = $unitIds->merge($directUnits);
|
||||
|
||||
// Also get units in formations under the same direct government
|
||||
$formationUnits = Unit::whereHas('formation', function ($query) use ($userUnit) {
|
||||
$query->where('government_id', $userUnit->government_id);
|
||||
})->pluck('id');
|
||||
$unitIds = $unitIds->merge($formationUnits);
|
||||
|
||||
return $unitIds->unique()->toArray();
|
||||
}
|
||||
|
||||
// Case 2: User's unit belongs to a formation that has a government (FALLBACK)
|
||||
if ($userUnit->formation_id) {
|
||||
$formation = Formation::find($userUnit->formation_id);
|
||||
if ($formation && $formation->government_id) {
|
||||
// Get all units in formations under the same government
|
||||
$formationUnits = Unit::whereHas('formation', function ($query) use ($formation) {
|
||||
$query->where('government_id', $formation->government_id);
|
||||
})->pluck('id');
|
||||
$unitIds = $unitIds->merge($formationUnits);
|
||||
|
||||
// Also get direct government units under the same government
|
||||
$directGovUnits = Unit::where('government_id', $formation->government_id)
|
||||
->pluck('id');
|
||||
$unitIds = $unitIds->merge($directGovUnits);
|
||||
}
|
||||
}
|
||||
|
||||
return $unitIds->unique()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the repair formation ID for a unit
|
||||
* Priority: repair_formation_id > formation_id > null
|
||||
*
|
||||
* @param Unit $unit
|
||||
* @return int|null
|
||||
*/
|
||||
public function getRepairFormationId(Unit $unit): ?int
|
||||
{
|
||||
// Priority 1: Check repair_formation_id (custom repair formation)
|
||||
if ($unit->repair_formation_id) {
|
||||
return $unit->repair_formation_id;
|
||||
}
|
||||
|
||||
// Priority 2: Fall back to regular formation_id
|
||||
if ($unit->formation_id) {
|
||||
return $unit->formation_id;
|
||||
}
|
||||
|
||||
// Priority 3: No formation (government-level only)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get visible unit IDs for a user
|
||||
*
|
||||
* @param bool $includeUnitsWithoutFormation When true (process models), SEL PERO 91 REJ
|
||||
* users can see units without formations. Government users never see units without formations.
|
||||
*/
|
||||
public function getVisibleUnitIds($user, bool $includeUnitsWithoutFormation = false): array
|
||||
{
|
||||
// Check if user has akses peringkat keseluruhan permission (super admin)
|
||||
if ($user->can('akses peringkat keseluruhan', Unit::class)) {
|
||||
return Unit::pluck('id')->toArray();
|
||||
}
|
||||
|
||||
// Get user's unit
|
||||
$userUnit = Unit::find($user->unit_id);
|
||||
if (!$userUnit) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Government permission: Can view all units under their government
|
||||
// SEL PERO 91 REJ (process models): ONLY see units without formation - not their gov/formation units
|
||||
if ($user->can('akses peringkat formasi')) {
|
||||
if ($includeUnitsWithoutFormation && $user->hasRole('SEL PERO 91 REJ')) {
|
||||
return Unit::whereNull('formation_id')
|
||||
->whereNull('repair_formation_id')
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
}
|
||||
$unitIds = $this->getUnitsUnderGovernment($userUnit);
|
||||
return $this->excludeUnitsWithoutFormation($unitIds);
|
||||
}
|
||||
|
||||
// DIV permission: Can view all units under their formation (including repair formations)
|
||||
if ($user->can('akses peringkat divisyen')) {
|
||||
return $this->getVisibleUnitIdsForFormationUser($user, $userUnit, $includeUnitsWithoutFormation);
|
||||
}
|
||||
|
||||
// Unit permission: Can only view data within their unit
|
||||
return [$user->unit_id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude units that have no formation (both formation_id and repair_formation_id are null).
|
||||
* Government users do not see these units.
|
||||
*/
|
||||
protected function excludeUnitsWithoutFormation(array $unitIds): array
|
||||
{
|
||||
if (empty($unitIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Unit::whereIn('id', $unitIds)
|
||||
->where(function ($q) {
|
||||
$q->whereNotNull('formation_id')
|
||||
->orWhereNotNull('repair_formation_id');
|
||||
})
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get visible unit IDs for a user with akses peringkat divisyen permission.
|
||||
*
|
||||
* Asset view (includeUnitsWithoutFormation=false): Only units where formation_id matches.
|
||||
* Excludes units that have repair_formation_id but formation_id null (e.g. 10 SKN RAJD uses
|
||||
* 3 DIV for repair only - 3 DIV users see their repairs but NOT their asset data).
|
||||
*
|
||||
* Process view (includeUnitsWithoutFormation=true): Units where repair_formation_id OR
|
||||
* formation_id matches. SEL PERO 91 REJ also sees units without any formation.
|
||||
*/
|
||||
protected function getVisibleUnitIdsForFormationUser($user, Unit $userUnit, bool $includeUnitsWithoutFormation): array
|
||||
{
|
||||
$userRepairFormationId = $this->getRepairFormationId($userUnit);
|
||||
|
||||
if ($userRepairFormationId) {
|
||||
if (!$includeUnitsWithoutFormation) {
|
||||
// Asset view: Only units that belong to the formation (formation_id matches).
|
||||
// Exclude units that use formation only for repair (formation_id null, repair_formation_id set).
|
||||
return Unit::where('formation_id', $userRepairFormationId)
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
// SEL PERO 91 REJ: ONLY see units without formation - not their formation's units
|
||||
// (formation units are handled by SEL PERO DIV)
|
||||
if ($user->hasRole('SEL PERO 91 REJ')) {
|
||||
return Unit::whereNull('formation_id')
|
||||
->whereNull('repair_formation_id')
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
// Process view: Units in user's formation (repair_formation_id or formation_id)
|
||||
return Unit::where(function ($q) use ($userRepairFormationId) {
|
||||
$q->where('repair_formation_id', $userRepairFormationId)
|
||||
->orWhere(function ($q2) use ($userRepairFormationId) {
|
||||
$q2->whereNull('repair_formation_id')
|
||||
->where('formation_id', $userRepairFormationId);
|
||||
});
|
||||
})->pluck('id')->toArray();
|
||||
}
|
||||
|
||||
// User's unit has no formation - check if they have SEL PERO 91 REJ (process models only)
|
||||
if ($includeUnitsWithoutFormation && $user->hasRole('SEL PERO 91 REJ')) {
|
||||
return Unit::whereNull('formation_id')
|
||||
->whereNull('repair_formation_id')
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
class LetterLayout
|
||||
{
|
||||
/**
|
||||
* @return array{
|
||||
* page_format: string,
|
||||
* margins_mm: array{0: int, 1: int, 2: int, 3: int},
|
||||
* css: array<string, string>
|
||||
* }
|
||||
*/
|
||||
public static function resolve(): array
|
||||
{
|
||||
$page = config('letter.page');
|
||||
$content = config('letter.content');
|
||||
$typography = config('letter.typography');
|
||||
$spacing = config('letter.spacing');
|
||||
$colors = config('letter.colors');
|
||||
|
||||
$marginTop = self::length($page['margin_top_mm']);
|
||||
$marginRight = self::length($page['margin_right_mm']);
|
||||
$marginBottom = self::length($page['margin_bottom_mm']);
|
||||
$marginLeft = self::length($page['margin_left_mm']);
|
||||
$contentPaddingX = self::length($content['padding_x_mm']);
|
||||
$pageHeight = $page['height_mm'].'mm';
|
||||
|
||||
return [
|
||||
'page_format' => $page['format'],
|
||||
'margins_mm' => [
|
||||
$page['margin_top_mm'],
|
||||
$page['margin_right_mm'],
|
||||
$page['margin_bottom_mm'],
|
||||
$page['margin_left_mm'],
|
||||
],
|
||||
'css' => [
|
||||
'margin_top' => $marginTop,
|
||||
'margin_right' => $marginRight,
|
||||
'margin_bottom' => $marginBottom,
|
||||
'margin_left' => $marginLeft,
|
||||
'content_padding_x' => $contentPaddingX,
|
||||
'page_height' => $pageHeight,
|
||||
'sheet_min_height' => sprintf(
|
||||
'calc(%s - %s - %s)',
|
||||
$pageHeight,
|
||||
$marginTop,
|
||||
$marginBottom,
|
||||
),
|
||||
'font_family' => $typography['font_family'],
|
||||
'font_size' => $typography['font_size'],
|
||||
'line_height' => (string) $typography['line_height'],
|
||||
'body_color' => $typography['body_color'],
|
||||
'signatory_title_size' => $typography['signatory_title_size'],
|
||||
'signatory_title_color' => $typography['signatory_title_color'],
|
||||
'footer_font_size' => $typography['footer_font_size'],
|
||||
'footer_line_height' => (string) $typography['footer_line_height'],
|
||||
'letterhead_registration_size' => $typography['letterhead_registration_size'],
|
||||
'letterhead_margin_bottom' => $spacing['letterhead_margin_bottom'].'px',
|
||||
'meta_margin_bottom' => $spacing['meta_margin_bottom'].'px',
|
||||
'recipient_margin_bottom' => $spacing['recipient_margin_bottom'].'px',
|
||||
'subject_margin_bottom' => $spacing['subject_margin_bottom'].'px',
|
||||
'body_margin_bottom' => $spacing['body_margin_bottom'].'px',
|
||||
'body_paragraph_margin_bottom' => $spacing['body_paragraph_margin_bottom'].'px',
|
||||
'signature_block_margin_bottom' => $spacing['signature_block_margin_bottom'].'px',
|
||||
'signature_company_margin_bottom' => $spacing['signature_company_margin_bottom'].'px',
|
||||
'signature_space_height' => $spacing['signature_space_height'].'px',
|
||||
'logo_height' => $spacing['logo_height'].'px',
|
||||
'color_blue' => $colors['blue'],
|
||||
'color_orange' => $colors['orange'],
|
||||
'color_white' => $colors['white'],
|
||||
'color_black' => $colors['black'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private static function length(int $millimetres): string
|
||||
{
|
||||
$centimetres = $millimetres / 10;
|
||||
|
||||
return rtrim(rtrim(number_format($centimetres, 2, '.', ''), '0'), '.').'cm';
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ trait HasDocuments
|
||||
*/
|
||||
public function documentsOfType($type)
|
||||
{
|
||||
return $this->documents()->where('document_type', $type);
|
||||
return $this->documents()->where('type', $type);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,15 +33,15 @@ trait HasDocuments
|
||||
|
||||
// Store file in a folder named after the model
|
||||
$folderName = strtolower(class_basename($this));
|
||||
$filePath = $file->storeAs("documents/{$folderName}", $fileName);
|
||||
$filePath = $file->storeAs("documents/{$folderName}", $fileName, Document::STORAGE_DISK);
|
||||
|
||||
// Create document record
|
||||
return $this->documents()->create([
|
||||
'document_name' => $file->getClientOriginalName(),
|
||||
'document_path' => $filePath,
|
||||
'name' => $file->getClientOriginalName(),
|
||||
'path' => $filePath,
|
||||
'file_size' => $file->getSize(),
|
||||
'mime_type' => $file->getClientMimeType(),
|
||||
'document_type' => $documentType,
|
||||
'type' => $documentType,
|
||||
'description' => $description,
|
||||
'uploaded_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Services\VisibilityService;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
trait HasVisibility
|
||||
{
|
||||
/**
|
||||
* Get the visibility service instance
|
||||
*/
|
||||
protected function getVisibilityService(): VisibilityService
|
||||
{
|
||||
return app(VisibilityService::class);
|
||||
}
|
||||
|
||||
public function scopeVisibleTo(Builder $query, $user)
|
||||
{
|
||||
$includeUnitsWithoutFormation = property_exists($this, 'includeUnitsWithoutFormation')
|
||||
? (bool) $this->includeUnitsWithoutFormation
|
||||
: false;
|
||||
|
||||
// Check if model has a direct unit column
|
||||
$unitColumn = property_exists($this, 'unitColumn') ? $this->unitColumn : 'unit_id';
|
||||
|
||||
// Check if model has an indirect unit relationship
|
||||
$unitRelationship = property_exists($this, 'unitRelationship') ? $this->unitRelationship : null;
|
||||
|
||||
if ($unitRelationship) {
|
||||
return $this->getVisibilityService()->applyVisibilityToIndirectQuery(
|
||||
$query,
|
||||
$user,
|
||||
$unitRelationship,
|
||||
$includeUnitsWithoutFormation
|
||||
);
|
||||
}
|
||||
|
||||
return $this->getVisibilityService()->applyVisibilityToQuery(
|
||||
$query,
|
||||
$user,
|
||||
$unitColumn,
|
||||
$includeUnitsWithoutFormation
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all units under the same government as the user's unit
|
||||
* Handles both direct government relationships and formation-government relationships
|
||||
* Priority: Direct government_id takes precedence over formation government
|
||||
*
|
||||
* @deprecated Use VisibilityService::getUnitsUnderGovernment() instead
|
||||
*/
|
||||
protected function getUnitsUnderGovernment($userUnit)
|
||||
{
|
||||
return $this->getVisibilityService()->getUnitsUnderGovernment($userUnit);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user