Merge pull request 'DONE: merge heir tab into profile tab, santize userIcNum and name, rename to malay, add stat card on user list' (#10) from feature/stat-card-on-users into main
Reviewed-on: KoPKB/My-KOPKB#10
This commit is contained in:
@@ -269,6 +269,43 @@ class UserController extends BaseCrudController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function stats(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$this->authorize('viewAny', $this->modelClass);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$request->validate([
|
||||||
|
'join_date_from' => 'nullable|date',
|
||||||
|
'join_date_to' => 'nullable|date',
|
||||||
|
'leave_date_from' => 'nullable|date',
|
||||||
|
'leave_date_to' => 'nullable|date',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$dateFilters = array_filter([
|
||||||
|
'join_date_from' => $request->get('join_date_from'),
|
||||||
|
'join_date_to' => $request->get('join_date_to'),
|
||||||
|
'leave_date_from' => $request->get('leave_date_from'),
|
||||||
|
'leave_date_to' => $request->get('leave_date_to'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$stats = $this->userService->getListStats(
|
||||||
|
$request->get('search', ''),
|
||||||
|
$request->get('status', ''),
|
||||||
|
$dateFilters
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'data' => $stats,
|
||||||
|
'message' => 'User stats retrieved successfully.',
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::error("Error fetching {$this->resourceNamePlural} stats: ".$e->getMessage());
|
||||||
|
|
||||||
|
return $this->errorResponse('Failed to retrieve user stats.', 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function restore(string $id): JsonResponse
|
public function restore(string $id): JsonResponse
|
||||||
{
|
{
|
||||||
$this->authorize('restore', $this->modelClass);
|
$this->authorize('restore', $this->modelClass);
|
||||||
|
|||||||
@@ -55,6 +55,17 @@ interface UserRepositoryInterface
|
|||||||
string $sortOrder = 'desc'
|
string $sortOrder = 'desc'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get stats for the Users list (filtered).
|
||||||
|
*
|
||||||
|
* @return array{total: int, joined_this_month: int}
|
||||||
|
*/
|
||||||
|
public function getListStats(
|
||||||
|
string $search = '',
|
||||||
|
string $status = '',
|
||||||
|
array $dateFilters = []
|
||||||
|
): array;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find soft-deleted User by ID
|
* Find soft-deleted User by ID
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace Modules\User\Repositories;
|
namespace Modules\User\Repositories;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
use Modules\Auth\Entities\User;
|
use Modules\Auth\Entities\User;
|
||||||
use Modules\User\Repositories\Contracts\UserRepositoryInterface;
|
use Modules\User\Repositories\Contracts\UserRepositoryInterface;
|
||||||
|
|
||||||
@@ -85,7 +86,18 @@ class UserRepository implements UserRepositoryInterface
|
|||||||
|
|
||||||
$query = User::excludeDevelopersUnlessDeveloper()
|
$query = User::excludeDevelopersUnlessDeveloper()
|
||||||
->with([
|
->with([
|
||||||
'roles:id,name,guard_name'
|
'roles:id,name,guard_name',
|
||||||
|
'employments' => function ($q) {
|
||||||
|
$q->select([
|
||||||
|
'id',
|
||||||
|
'user_id',
|
||||||
|
'company_name',
|
||||||
|
'is_current',
|
||||||
|
'start_date',
|
||||||
|
])
|
||||||
|
->orderByDesc('is_current')
|
||||||
|
->orderByDesc('start_date');
|
||||||
|
},
|
||||||
])
|
])
|
||||||
->orderBy($sortBy, $sortOrder);
|
->orderBy($sortBy, $sortOrder);
|
||||||
|
|
||||||
@@ -129,6 +141,17 @@ class UserRepository implements UserRepositoryInterface
|
|||||||
->excludeDevelopersUnlessDeveloper()
|
->excludeDevelopersUnlessDeveloper()
|
||||||
->with([
|
->with([
|
||||||
'roles:id,name,guard_name',
|
'roles:id,name,guard_name',
|
||||||
|
'employments' => function ($q) {
|
||||||
|
$q->select([
|
||||||
|
'id',
|
||||||
|
'user_id',
|
||||||
|
'company_name',
|
||||||
|
'is_current',
|
||||||
|
'start_date',
|
||||||
|
])
|
||||||
|
->orderByDesc('is_current')
|
||||||
|
->orderByDesc('start_date');
|
||||||
|
},
|
||||||
])
|
])
|
||||||
->orderBy($sortBy, $sortOrder);
|
->orderBy($sortBy, $sortOrder);
|
||||||
|
|
||||||
@@ -141,6 +164,36 @@ class UserRepository implements UserRepositoryInterface
|
|||||||
return $query->paginate($perPage);
|
return $query->paginate($perPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getListStats(
|
||||||
|
string $search = '',
|
||||||
|
string $status = '',
|
||||||
|
array $dateFilters = []
|
||||||
|
): array {
|
||||||
|
$baseQuery = User::excludeDevelopersUnlessDeveloper();
|
||||||
|
|
||||||
|
$this->applySearch($baseQuery, $search);
|
||||||
|
|
||||||
|
if (! empty($status)) {
|
||||||
|
$baseQuery->where('status', $status);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->applyDateRangeFilters($baseQuery, $dateFilters);
|
||||||
|
|
||||||
|
$total = (int) (clone $baseQuery)->count();
|
||||||
|
|
||||||
|
$monthStart = Carbon::now()->startOfMonth()->toDateString();
|
||||||
|
$monthEnd = Carbon::now()->endOfMonth()->toDateString();
|
||||||
|
$joinedThisMonth = (int) (clone $baseQuery)
|
||||||
|
->whereDate('join_date', '>=', $monthStart)
|
||||||
|
->whereDate('join_date', '<=', $monthEnd)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'total' => $total,
|
||||||
|
'joined_this_month' => $joinedThisMonth,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all Users with their relationships and search
|
* Get all Users with their relationships and search
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ Route::prefix('v1/public')->group(function () {
|
|||||||
|
|
||||||
Route::middleware(['auth:sanctum', 'single.session'])->prefix('v1')->group(function () {
|
Route::middleware(['auth:sanctum', 'single.session'])->prefix('v1')->group(function () {
|
||||||
Route::get('users/deleted', [UserController::class, 'deletedIndex'])->name('users.deleted.index');
|
Route::get('users/deleted', [UserController::class, 'deletedIndex'])->name('users.deleted.index');
|
||||||
|
Route::get('users/stats', [UserController::class, 'stats'])->name('users.stats');
|
||||||
Route::post('users/{id}/restore', [UserController::class, 'restore'])->name('users.restore');
|
Route::post('users/{id}/restore', [UserController::class, 'restore'])->name('users.restore');
|
||||||
Route::apiResource('users', UserController::class)->names('user');
|
Route::apiResource('users', UserController::class)->names('user');
|
||||||
Route::apiResource('addresses', AddressController::class)->names('address');
|
Route::apiResource('addresses', AddressController::class)->names('address');
|
||||||
|
|||||||
@@ -50,6 +50,14 @@ class UserService
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{total: int, joined_this_month: int}
|
||||||
|
*/
|
||||||
|
public function getListStats(string $search, string $status, array $dateFilters = []): array
|
||||||
|
{
|
||||||
|
return $this->repository->getListStats($search, $status, $dateFilters);
|
||||||
|
}
|
||||||
|
|
||||||
public function restoreUser(string $id): ?User
|
public function restoreUser(string $id): ?User
|
||||||
{
|
{
|
||||||
if (! $this->repository->restore($id)) {
|
if (! $this->repository->restore($id)) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace Modules\User\Transformers;
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Modules\User\Transformers\EmploymentResource;
|
||||||
|
|
||||||
class UserListResource extends JsonResource
|
class UserListResource extends JsonResource
|
||||||
{
|
{
|
||||||
@@ -31,6 +32,7 @@ class UserListResource extends JsonResource
|
|||||||
'created_at' => $this->created_at,
|
'created_at' => $this->created_at,
|
||||||
'updated_at' => $this->updated_at,
|
'updated_at' => $this->updated_at,
|
||||||
'deleted_at' => $this->deleted_at,
|
'deleted_at' => $this->deleted_at,
|
||||||
|
'employments' => EmploymentResource::collection($this->whenLoaded('employments')),
|
||||||
'roles' => $this->roles->map(function ($role) {
|
'roles' => $this->roles->map(function ($role) {
|
||||||
return [
|
return [
|
||||||
'id' => $role->id,
|
'id' => $role->id,
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Modules\KJCReport\Entities\KJCReport;
|
|
||||||
use Modules\KJCReport\Jobs\GenerateTeamWeeklyReports;
|
|
||||||
use Modules\Unit\Entities\Unit;
|
|
||||||
use Modules\Auth\Entities\User;
|
|
||||||
|
|
||||||
class AutoGenerateWeeklyReportForUnit extends Command
|
|
||||||
{
|
|
||||||
protected $signature = 'kjc:auto-generate-weekly-report
|
|
||||||
{unit : Unit name to generate reports for (e.g. "Jabatan Arah RAJD")}
|
|
||||||
{--force : Force generation even if reports already exist}';
|
|
||||||
|
|
||||||
protected $description = 'Auto-generate weekly KJC reports for a specific unit';
|
|
||||||
|
|
||||||
public function handle(): int
|
|
||||||
{
|
|
||||||
$unitName = $this->argument('unit');
|
|
||||||
|
|
||||||
$unit = Unit::where('name', $unitName)->first();
|
|
||||||
|
|
||||||
if (! $unit) {
|
|
||||||
$this->error("Unit '{$unitName}' not found.");
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$month = strtolower(now()->format('F'));
|
|
||||||
$week = (int) ceil(now()->day / 7);
|
|
||||||
|
|
||||||
if (! $this->option('force')) {
|
|
||||||
$existingReports = KJCReport::where('unit_id', $unit->id)
|
|
||||||
->where('report_type', 'weekly')
|
|
||||||
->where('report_month', $month)
|
|
||||||
->where('report_week', $week)
|
|
||||||
->exists();
|
|
||||||
|
|
||||||
if ($existingReports) {
|
|
||||||
$this->info("Reports for {$unitName} - {$month} week {$week} already exist. Skipping.");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = User::where('unit_id', $unit->id)->first();
|
|
||||||
|
|
||||||
GenerateTeamWeeklyReports::dispatch($unit->id, $month, $week, $user?->id);
|
|
||||||
|
|
||||||
$this->info("✓ Queued weekly report generation for {$unitName} - {$month} week {$week}");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use App\Jobs\CaptureKJCHistoricalDataJob;
|
|
||||||
use App\Services\KJCHistoricalDataService;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
|
|
||||||
class TriggerKJCHistoricalDataCapture extends Command
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The name and signature of the console command.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $signature = 'kjc:historical:trigger
|
|
||||||
{--month= : Month to capture (1-12)}
|
|
||||||
{--year= : Year to capture (YYYY)}
|
|
||||||
{--queue : Dispatch to queue instead of running immediately}';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'Manually trigger KJC historical data capture';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$month = $this->option('month');
|
|
||||||
$year = $this->option('year');
|
|
||||||
$useQueue = $this->option('queue');
|
|
||||||
|
|
||||||
// If no month/year provided, use previous month
|
|
||||||
if (! $month || ! $year) {
|
|
||||||
$lastMonth = Carbon::now()->subMonth();
|
|
||||||
$month = $month ?: $lastMonth->format('m');
|
|
||||||
$year = $year ?: $lastMonth->format('Y');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->info("Triggering KJC historical data capture for {$month}/{$year}...");
|
|
||||||
|
|
||||||
try {
|
|
||||||
if ($useQueue) {
|
|
||||||
// Dispatch to queue
|
|
||||||
CaptureKJCHistoricalDataJob::dispatch($month, $year);
|
|
||||||
$this->info('✓ KJC historical data capture job dispatched to queue');
|
|
||||||
$this->info('You can monitor the job in Horizon dashboard');
|
|
||||||
} else {
|
|
||||||
// Run immediately
|
|
||||||
$job = new CaptureKJCHistoricalDataJob($month, $year);
|
|
||||||
$job->handle(app(KJCHistoricalDataService::class));
|
|
||||||
$this->info('✓ KJC historical data capture completed immediately');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->error('Error: '.$e->getMessage());
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use App\Jobs\CapturePKJHistoricalDataJob;
|
|
||||||
use App\Services\PKJHistoricalDataService;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
|
|
||||||
class TriggerPKJHistoricalDataCapture extends Command
|
|
||||||
{
|
|
||||||
protected $signature = 'pkj:historical:trigger
|
|
||||||
{--month= : Month to capture (1-12)}
|
|
||||||
{--year= : Year to capture (YYYY)}
|
|
||||||
{--queue : Dispatch to queue instead of running immediately}';
|
|
||||||
|
|
||||||
protected $description = 'Manually trigger PKJ historical data capture';
|
|
||||||
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$month = $this->option('month');
|
|
||||||
$year = $this->option('year');
|
|
||||||
$useQueue = $this->option('queue');
|
|
||||||
|
|
||||||
// If no month/year provided, use previous month
|
|
||||||
if (! $month || ! $year) {
|
|
||||||
$lastMonth = Carbon::now()->subMonth();
|
|
||||||
$month = $month ?: $lastMonth->format('m');
|
|
||||||
$year = $year ?: $lastMonth->format('Y');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->info("Triggering PKJ historical data capture for {$month}/{$year}...");
|
|
||||||
|
|
||||||
try {
|
|
||||||
if ($useQueue) {
|
|
||||||
// Dispatch to queue
|
|
||||||
CapturePKJHistoricalDataJob::dispatch($month, $year);
|
|
||||||
$this->info('✓ PKJ historical data capture job dispatched to queue');
|
|
||||||
$this->info('You can monitor the job in Horizon dashboard');
|
|
||||||
} else {
|
|
||||||
// Run immediately
|
|
||||||
$job = new CapturePKJHistoricalDataJob($month, $year);
|
|
||||||
$job->handle(app(PKJHistoricalDataService::class));
|
|
||||||
$this->info('✓ PKJ historical data capture completed immediately');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->error('Error: '.$e->getMessage());
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -113,8 +113,6 @@ abstract class BaseCrudController extends Controller
|
|||||||
|
|
||||||
$item = $this->repository->create($data);
|
$item = $this->repository->create($data);
|
||||||
|
|
||||||
ActivityLogger::log("Created {$this->resourceName}: {$this->getItemName($item)}", $item);
|
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'data' => new $this->resourceClass($item),
|
'data' => new $this->resourceClass($item),
|
||||||
@@ -175,8 +173,6 @@ abstract class BaseCrudController extends Controller
|
|||||||
|
|
||||||
$item->update($data);
|
$item->update($data);
|
||||||
|
|
||||||
ActivityLogger::log("Updated {$this->resourceName}: {$this->getItemName($item)}", $item);
|
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'data' => new $this->resourceClass($item),
|
'data' => new $this->resourceClass($item),
|
||||||
@@ -221,8 +217,6 @@ abstract class BaseCrudController extends Controller
|
|||||||
|
|
||||||
$this->repository->delete($id);
|
$this->repository->delete($id);
|
||||||
|
|
||||||
ActivityLogger::log("Deleted {$this->resourceName}: {$this->getItemName($item)}", $item);
|
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => $this->getSuccessMessage('destroy'),
|
'message' => $this->getSuccessMessage('destroy'),
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use App\Services\KJCHistoricalDataService;
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class CaptureKJCHistoricalDataJob implements ShouldQueue
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
public $timeout = 300; // 5 minutes timeout
|
|
||||||
|
|
||||||
public $tries = 3; // Retry 3 times if failed
|
|
||||||
|
|
||||||
public $backoff = 60; // Wait 60 seconds between retries
|
|
||||||
|
|
||||||
protected $month;
|
|
||||||
|
|
||||||
protected $year;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new job instance.
|
|
||||||
*/
|
|
||||||
public function __construct($month = null, $year = null)
|
|
||||||
{
|
|
||||||
$this->month = $month;
|
|
||||||
$this->year = $year;
|
|
||||||
|
|
||||||
// Set queue name for Horizon monitoring
|
|
||||||
$this->onQueue('kjc-historical-data');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the job.
|
|
||||||
*/
|
|
||||||
public function handle(KJCHistoricalDataService $kjcHistoricalService)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
Log::info('Starting KJC historical data capture job', [
|
|
||||||
'month' => $this->month,
|
|
||||||
'year' => $this->year,
|
|
||||||
'job_id' => $this->job ? $this->job->getJobId() : 'manual-execution',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$success = $kjcHistoricalService->captureMonthlySnapshots($this->month, $this->year);
|
|
||||||
|
|
||||||
if ($success) {
|
|
||||||
Log::info('KJC historical data capture job completed successfully', [
|
|
||||||
'month' => $this->month,
|
|
||||||
'year' => $this->year,
|
|
||||||
'job_id' => $this->job ? $this->job->getJobId() : 'manual-execution',
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
Log::error('KJC historical data capture job failed', [
|
|
||||||
'month' => $this->month,
|
|
||||||
'year' => $this->year,
|
|
||||||
'job_id' => $this->job ? $this->job->getJobId() : 'manual-execution',
|
|
||||||
]);
|
|
||||||
|
|
||||||
throw new \Exception('KJC historical data capture failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('KJC historical data capture job exception', [
|
|
||||||
'month' => $this->month,
|
|
||||||
'year' => $this->year,
|
|
||||||
'job_id' => $this->job ? $this->job->getJobId() : 'manual-execution',
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
'trace' => $e->getTraceAsString(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle a job failure.
|
|
||||||
*/
|
|
||||||
public function failed(\Throwable $exception)
|
|
||||||
{
|
|
||||||
Log::error('KJC historical data capture job failed permanently', [
|
|
||||||
'month' => $this->month,
|
|
||||||
'year' => $this->year,
|
|
||||||
'job_id' => $this->job ? $this->job->getJobId() : 'manual-execution',
|
|
||||||
'error' => $exception->getMessage(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the tags that should be assigned to the job.
|
|
||||||
*/
|
|
||||||
public function tags()
|
|
||||||
{
|
|
||||||
return ['kjc-historical-data', "month-{$this->month}", "year-{$this->year}"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use App\Services\PKJHistoricalDataService;
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class CapturePKJHistoricalDataJob implements ShouldQueue
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
public $timeout = 300; // 5 minutes timeout
|
|
||||||
|
|
||||||
public $tries = 3; // Retry 3 times if failed
|
|
||||||
|
|
||||||
public $backoff = 60; // Wait 60 seconds between retries
|
|
||||||
|
|
||||||
protected $month;
|
|
||||||
|
|
||||||
protected $year;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new job instance.
|
|
||||||
*/
|
|
||||||
public function __construct($month = null, $year = null)
|
|
||||||
{
|
|
||||||
$this->month = $month;
|
|
||||||
$this->year = $year;
|
|
||||||
|
|
||||||
// Set queue name for Horizon monitoring
|
|
||||||
$this->onQueue('pkj-historical-data');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the job.
|
|
||||||
*/
|
|
||||||
public function handle(PKJHistoricalDataService $historicalService)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
Log::info('Starting PKJ historical data capture job', [
|
|
||||||
'month' => $this->month,
|
|
||||||
'year' => $this->year,
|
|
||||||
'job_id' => $this->job ? $this->job->getJobId() : 'manual-execution',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$success = $historicalService->captureMonthlySnapshots($this->month, $this->year);
|
|
||||||
|
|
||||||
if ($success) {
|
|
||||||
Log::info('PKJ historical data capture job completed successfully', [
|
|
||||||
'month' => $this->month,
|
|
||||||
'year' => $this->year,
|
|
||||||
'job_id' => $this->job ? $this->job->getJobId() : 'manual-execution',
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
Log::error('PKJ historical data capture job failed', [
|
|
||||||
'month' => $this->month,
|
|
||||||
'year' => $this->year,
|
|
||||||
'job_id' => $this->job ? $this->job->getJobId() : 'manual-execution',
|
|
||||||
]);
|
|
||||||
|
|
||||||
throw new \Exception('PKJ historical data capture failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('PKJ historical data capture job exception', [
|
|
||||||
'month' => $this->month,
|
|
||||||
'year' => $this->year,
|
|
||||||
'job_id' => $this->job ? $this->job->getJobId() : 'manual-execution',
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
'trace' => $e->getTraceAsString(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle a job failure.
|
|
||||||
*/
|
|
||||||
public function failed(\Throwable $exception)
|
|
||||||
{
|
|
||||||
Log::error('PKJ historical data capture job failed permanently', [
|
|
||||||
'month' => $this->month,
|
|
||||||
'year' => $this->year,
|
|
||||||
'job_id' => $this->job ? $this->job->getJobId() : 'manual-execution',
|
|
||||||
'error' => $exception->getMessage(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the tags that should be assigned to the job.
|
|
||||||
*/
|
|
||||||
public function tags()
|
|
||||||
{
|
|
||||||
return ['pkj-historical-data', "month-{$this->month}", "year-{$this->year}"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-21
@@ -3,27 +3,7 @@
|
|||||||
use Illuminate\Foundation\Inspiring;
|
use Illuminate\Foundation\Inspiring;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Illuminate\Support\Facades\Schedule;
|
use Illuminate\Support\Facades\Schedule;
|
||||||
use App\Jobs\CaptureKJCHistoricalDataJob;
|
|
||||||
use App\Jobs\CapturePKJHistoricalDataJob;
|
|
||||||
|
|
||||||
Artisan::command('inspire', function () {
|
Artisan::command('inspire', function () {
|
||||||
$this->comment(Inspiring::quote());
|
$this->comment(Inspiring::quote());
|
||||||
})->purpose('Display an inspiring quote');
|
})->purpose('Display an inspiring quote');
|
||||||
|
|
||||||
// Schedule KJC Historical Data Capture to run monthly on the 1st day at 2:00 AM
|
|
||||||
Schedule::job(new CaptureKJCHistoricalDataJob())
|
|
||||||
->monthlyOn(1, '02:00')
|
|
||||||
->withoutOverlapping()
|
|
||||||
->name('kjc-historical-data-capture');
|
|
||||||
|
|
||||||
// Schedule PKJ Historical Data Capture to run monthly on the 1st day at 2:30 AM
|
|
||||||
Schedule::job(new CapturePKJHistoricalDataJob())
|
|
||||||
->monthlyOn(1, '02:30')
|
|
||||||
->withoutOverlapping()
|
|
||||||
->name('pkj-historical-data-capture');
|
|
||||||
|
|
||||||
// Auto-generate weekly KJC reports for Jabatan Arah RAJD every Monday at 3:00 AM
|
|
||||||
Schedule::command('kjc:auto-generate-weekly-report "Jabatan Arah RAJD"')
|
|
||||||
->weeklyOn(1, '03:00') // Monday at 3:00 AM
|
|
||||||
->withoutOverlapping()
|
|
||||||
->name('kjc-auto-weekly-report');
|
|
||||||
Reference in New Issue
Block a user