mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-10 05:02:36 +01:00
Merge pull request #7456 from CirkaN/Cirkovic/INA-5
Cirkovic/INA-5 (Ninja task scheduler)
This commit is contained in:
commit
92b5af5a39
@ -20,6 +20,7 @@ use App\Jobs\Ninja\AdjustEmailQuota;
|
||||
use App\Jobs\Ninja\CompanySizeCheck;
|
||||
use App\Jobs\Ninja\QueueSize;
|
||||
use App\Jobs\Ninja\SystemMaintenance;
|
||||
use App\Jobs\Ninja\TaskScheduler;
|
||||
use App\Jobs\Util\DiskCleanup;
|
||||
use App\Jobs\Util\ReminderJob;
|
||||
use App\Jobs\Util\SchedulerCheck;
|
||||
@ -69,19 +70,21 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
$schedule->job(new RecurringExpensesCron)->dailyAt('00:10')->withoutOverlapping();
|
||||
|
||||
$schedule->job(new AutoBillCron)->dailyAt('06:00')->withoutOverlapping();
|
||||
$schedule->job(new AutoBillCron)->dailyAt('06:00')->withoutOverlapping();
|
||||
|
||||
$schedule->job(new SchedulerCheck)->daily()->withoutOverlapping();
|
||||
|
||||
$schedule->job(new SystemMaintenance)->weekly()->withoutOverlapping();
|
||||
$schedule->job(new TaskScheduler())->daily()->withoutOverlapping();
|
||||
|
||||
$schedule->job(new SystemMaintenance)->weekly()->withoutOverlapping();
|
||||
if(Ninja::isSelfHost())
|
||||
{
|
||||
|
||||
|
||||
$schedule->call(function () {
|
||||
Account::whereNotNull('id')->update(['is_scheduler_running' => true]);
|
||||
})->everyFiveMinutes();
|
||||
|
||||
})->everyFiveMinutes();
|
||||
|
||||
}
|
||||
|
||||
/* Run hosted specific jobs */
|
||||
@ -99,12 +102,12 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
}
|
||||
|
||||
if(config('queue.default') == 'database' && Ninja::isSelfHost() && config('ninja.internal_queue_enabled') && !config('ninja.is_docker')) {
|
||||
if (config('queue.default') == 'database' && Ninja::isSelfHost() && config('ninja.internal_queue_enabled') && !config('ninja.is_docker')) {
|
||||
|
||||
$schedule->command('queue:work database --stop-when-empty')->everyMinute()->withoutOverlapping();
|
||||
|
||||
$schedule->command('queue:restart')->everyFiveMinutes()->withoutOverlapping();
|
||||
|
||||
$schedule->command('queue:restart')->everyFiveMinutes()->withoutOverlapping();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -116,7 +119,7 @@ class Kernel extends ConsoleKernel
|
||||
*/
|
||||
protected function commands()
|
||||
{
|
||||
$this->load(__DIR__.'/Commands');
|
||||
$this->load(__DIR__ . '/Commands');
|
||||
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
|
51
app/Http/Controllers/OpenAPI/TaskSchedulerSchema.php
Normal file
51
app/Http/Controllers/OpenAPI/TaskSchedulerSchema.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="TaskSchedulerSchema",
|
||||
* type="object",
|
||||
*
|
||||
*
|
||||
* @OA\Property(property="paused",type="bool",example="false",description="The scheduler paused state"),
|
||||
* @OA\Property(property="repeat_every",type="string",example="DAY",description="Accepted values (DAY,WEEK,MONTH,3MONTHS,YEAR)"),
|
||||
* @OA\Property(property="start_from",type="integer",example="1652898504",description="Timestamp when we should start the scheduler, default is today"),
|
||||
* @OA\Property(property="date_range", type="string", example="last7", description="The string representation of the date range of data to be returned"),
|
||||
* @OA\Property(property="date_key", type="string", example="created_at", description="The date column to search between."),
|
||||
* @OA\Property(property="start_date", type="string", example="2022-10-31", description="The start date to search between"),
|
||||
* @OA\Property(property="end_date", type="string", example="2022-10-31", description="The end date to search between"),
|
||||
* @OA\Property(
|
||||
* property="report_keys",
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* type="string",
|
||||
* description="Array of Keys to export",
|
||||
* example="['name','date']",
|
||||
* ),
|
||||
* ),
|
||||
*
|
||||
* )
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="UpdateTaskSchedulerSchema",
|
||||
* type="object",
|
||||
*
|
||||
* @OA\Property(property="paused",type="bool",example="false",description="The scheduler paused state"),
|
||||
* * @OA\Property(property="repeat_every",type="string",example="DAY",description="Accepted values (DAY,WEEK,MONTH,3MONTHS,YEAR)"),
|
||||
* @OA\Property(property="start_from",type="integer",example="1652898504",description="Timestamp when we should start the scheduler, default is today"),
|
||||
*
|
||||
* )
|
||||
*/
|
||||
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="UpdateJobForASchedulerSchema",
|
||||
* type="object",
|
||||
* @OA\Property(property="job",type="string",example="create_client_report",description="Set action name, action names can be found in ScheduledJob Model"),
|
||||
*
|
||||
* )
|
||||
*/
|
||||
|
@ -31,11 +31,11 @@ class CreditReportController extends BaseController
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/reports/clients",
|
||||
* operationId="getClientReport",
|
||||
* path="/api/v1/reports/credit",
|
||||
* operationId="getCreditReport",
|
||||
* tags={"reports"},
|
||||
* summary="Client reports",
|
||||
* description="Export client reports",
|
||||
* summary="Credit reports",
|
||||
* description="Export credit reports",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\RequestBody(
|
||||
|
@ -31,11 +31,11 @@ class QuoteItemReportController extends BaseController
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/reports/invoice_items",
|
||||
* path="/api/v1/reports/quote_items",
|
||||
* operationId="getQuoteItemReport",
|
||||
* tags={"reports"},
|
||||
* summary="Invoice item reports",
|
||||
* description="Export invoice item reports",
|
||||
* summary="Quote item reports",
|
||||
* description="Export Quote item reports",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\RequestBody(
|
||||
|
250
app/Http/Controllers/TaskSchedulerController.php
Normal file
250
app/Http/Controllers/TaskSchedulerController.php
Normal file
@ -0,0 +1,250 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\TaskScheduler\CreateScheduledTaskRequest;
|
||||
use App\Http\Requests\TaskScheduler\UpdateScheduledJobRequest;
|
||||
use App\Http\Requests\TaskScheduler\UpdateScheduleRequest;
|
||||
use App\Jobs\Ninja\TaskScheduler;
|
||||
use App\Jobs\Report\ProfitAndLoss;
|
||||
use App\Models\ScheduledJob;
|
||||
use App\Models\Scheduler;
|
||||
use App\Repositories\TaskSchedulerRepository;
|
||||
use App\Transformers\TaskSchedulerTransformer;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class TaskSchedulerController extends BaseController
|
||||
{
|
||||
protected $entity_type = TaskScheduler::class;
|
||||
protected $entity_transformer = TaskSchedulerTransformer::class;
|
||||
protected TaskSchedulerRepository $scheduler_repository;
|
||||
|
||||
public function __construct(TaskSchedulerRepository $scheduler_repository)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->scheduler_repository = $scheduler_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\GET(
|
||||
* path="/api/v1/task_scheduler/",
|
||||
* operationId="getTaskSchedulers",
|
||||
* tags={"task_scheduler"},
|
||||
* summary="Task Scheduler Index",
|
||||
* description="Get all schedulers with associated jobs",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="success",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
|
||||
public function index()
|
||||
{
|
||||
set_time_limit(45);
|
||||
|
||||
$schedulers = Scheduler::where('company_id', auth()->user()->company()->id);
|
||||
|
||||
return $this->listResponse($schedulers);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/task_scheduler/",
|
||||
* operationId="createTaskScheduler",
|
||||
* tags={"task_scheduler"},
|
||||
* summary="Create task scheduler with job ",
|
||||
* description="Create task scheduler with a job (action(job) request should be sent via request also. Example: We want client report to be job which will be run
|
||||
* multiple times, we should send the same parameters in the request as we would send if we wanted to get report, see example",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/TaskSchedulerSchema")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="success",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
|
||||
public function store(CreateScheduledTaskRequest $request)
|
||||
{
|
||||
$scheduler = new Scheduler();
|
||||
$scheduler->service()->store($scheduler, $request);
|
||||
return $this->itemResponse($scheduler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\GET(
|
||||
* path="/api/v1/task_scheduler/{scheduler}",
|
||||
* operationId="showTaskScheduler",
|
||||
* tags={"task_scheduler"},
|
||||
* summary="Show given scheduler",
|
||||
* description="Get scheduler with associated job",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="success",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
|
||||
public function show(Scheduler $scheduler)
|
||||
{
|
||||
return $this->itemResponse($scheduler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\PUT(
|
||||
* path="/api/v1/task_scheduler/{scheduler}",
|
||||
* operationId="updateTaskScheduler",
|
||||
* tags={"task_scheduler"},
|
||||
* summary="Update task scheduler ",
|
||||
* description="Update task scheduler",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/UpdateTaskSchedulerSchema")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="success",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function update(Scheduler $scheduler, UpdateScheduleRequest $request)
|
||||
{
|
||||
$scheduler->service()->update($scheduler, $request);
|
||||
return $this->itemResponse($scheduler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\PUT(
|
||||
* path="/api/v1/task_scheduler/{scheduler}/update_job/",
|
||||
* operationId="updateTaskSchedulerJob",
|
||||
* tags={"task_scheduler"},
|
||||
* summary="Update job for a task scheduler ",
|
||||
* description="Update job for a task scheduler | if we are changing action for a job, we should send the request for a new job same as we are creating new scheduler",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/UpdateJobForASchedulerSchema")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="success",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function updateJob(Scheduler $scheduler, UpdateScheduledJobRequest $request)
|
||||
{
|
||||
$scheduler->service()->updateJob($scheduler, $request);
|
||||
return $this->itemResponse($scheduler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\DELETE(
|
||||
* path="/api/v1/task_scheduler/{scheduler}",
|
||||
* operationId="destroyTaskScheduler",
|
||||
* tags={"task_scheduler"},
|
||||
* summary="Destroy Task Scheduler",
|
||||
* description="Destroy task scheduler and its associated job",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="success",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function destroy(Scheduler $scheduler)
|
||||
{
|
||||
$this->scheduler_repository->delete($scheduler);
|
||||
return $this->itemResponse($scheduler->fresh());
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Http\Requests\TaskScheduler;
|
||||
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class CreateScheduledTaskRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'paused' => 'sometimes|bool',
|
||||
'repeat_every' => 'required|string|in:DAY,WEEK,MONTH,3MONTHS,YEAR',
|
||||
'start_from' => 'sometimes|string',
|
||||
'job' => 'required',
|
||||
];
|
||||
}
|
||||
}
|
53
app/Http/Requests/TaskScheduler/UpdateScheduleRequest.php
Normal file
53
app/Http/Requests/TaskScheduler/UpdateScheduleRequest.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
|
||||
namespace App\Http\Requests\TaskScheduler;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\ScheduledJob;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateScheduleRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'paused' => 'sometimes|bool',
|
||||
'repeat_every' => 'sometimes|string|in:DAY,WEEK,BIWEEKLY,MONTH,3MONTHS,YEAR',
|
||||
'start_from' => 'sometimes',
|
||||
'scheduled_run'=>'sometimes'
|
||||
];
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
$request = $this->all();
|
||||
|
||||
if (isset($request['start_from'])) {
|
||||
$request['scheduled_run'] = Carbon::parse((int)$request['start_from']);
|
||||
$request['start_from'] = Carbon::parse((int)$request['start_from']);
|
||||
}
|
||||
|
||||
$this->replace($request);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Http\Requests\TaskScheduler;
|
||||
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class UpdateScheduledJobRequest extends Request
|
||||
{
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'action_name' => 'sometimes|string',
|
||||
];
|
||||
}
|
||||
}
|
129
app/Jobs/Ninja/TaskScheduler.php
Normal file
129
app/Jobs/Ninja/TaskScheduler.php
Normal file
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Jobs\Ninja;
|
||||
|
||||
|
||||
use App\Jobs\Report\SendToAdmin;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Models\ScheduledJob;
|
||||
use App\Models\Scheduler;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class TaskScheduler implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
|
||||
MultiDB::setDB($db);
|
||||
$pending_schedulers = $this->fetchJobs();
|
||||
foreach ($pending_schedulers as $scheduler) {
|
||||
$this->doJob($scheduler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function doJob(Scheduler $scheduler)
|
||||
{
|
||||
$job = $scheduler->job;
|
||||
|
||||
$company = Company::find($job->company_id);
|
||||
if (!$job || !$company) {
|
||||
return;
|
||||
}
|
||||
$parameters = $job->parameters;
|
||||
|
||||
|
||||
switch ($job->action_name) {
|
||||
case ScheduledJob::CREATE_CLIENT_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $job->action_class, 'contacts.csv');
|
||||
break;
|
||||
case ScheduledJob::CREATE_CLIENT_CONTACT_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $job->action_class, 'clients.csv');
|
||||
break;
|
||||
case ScheduledJob::CREATE_CREDIT_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $job->action_class, 'credits.csv');
|
||||
break;
|
||||
case ScheduledJob::CREATE_DOCUMENT_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $job->action_class, 'documents.csv');
|
||||
break;
|
||||
case ScheduledJob::CREATE_EXPENSE_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $job->action_class, 'expense.csv');
|
||||
break;
|
||||
case ScheduledJob::CREATE_INVOICE_ITEM_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $job->action_class, 'invoice_items.csv');
|
||||
break;
|
||||
case ScheduledJob::CREATE_INVOICE_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $job->action_class, 'invoices.csv');
|
||||
break;
|
||||
case ScheduledJob::CREATE_PAYMENT_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $job->action_class, 'payments.csv');
|
||||
break;
|
||||
case ScheduledJob::CREATE_PRODUCT_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $job->action_class, 'products.csv');
|
||||
break;
|
||||
case ScheduledJob::CREATE_PROFIT_AND_LOSS_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $job->action_class, 'profit_and_loss.csv');
|
||||
break;
|
||||
case ScheduledJob::CREATE_QUOTE_ITEM_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $job->action_class, 'quote_items.csv');
|
||||
break;
|
||||
case ScheduledJob::CREATE_QUOTE_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $job->action_class, 'quotes.csv');
|
||||
break;
|
||||
case ScheduledJob::CREATE_RECURRING_INVOICE_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $job->action_class, 'recurring_invoices.csv');
|
||||
break;
|
||||
case ScheduledJob::CREATE_TASK_REPORT:
|
||||
SendToAdmin::dispatch($company, $parameters, $job->action_class, 'tasks.csv');
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
$scheduler->scheduled_run = $scheduler->nextScheduledDate();
|
||||
$scheduler->save();
|
||||
}
|
||||
|
||||
|
||||
private function fetchJobs()
|
||||
{
|
||||
return Scheduler::where('paused', false)
|
||||
->where('is_deleted', false)
|
||||
->whereDate('scheduled_run', '<=', Carbon::now())
|
||||
->cursor();
|
||||
}
|
||||
|
||||
}
|
49
app/Models/ScheduledJob.php
Normal file
49
app/Models/ScheduledJob.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* @property mixed|string action_class
|
||||
* @property array parameters
|
||||
* @property string action_name
|
||||
* @property integer scheduler_id
|
||||
* @property integer company_id
|
||||
*/
|
||||
class ScheduledJob extends BaseModel
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
const CREATE_CLIENT_REPORT = 'create_client_report';
|
||||
const CREATE_CLIENT_CONTACT_REPORT = 'create_client_contact_report';
|
||||
const CREATE_CREDIT_REPORT = 'create_credit_report';
|
||||
const CREATE_DOCUMENT_REPORT = 'create_document_report';
|
||||
const CREATE_EXPENSE_REPORT = 'create_expense_report';
|
||||
const CREATE_INVOICE_ITEM_REPORT = 'create_invoice_item_report';
|
||||
const CREATE_INVOICE_REPORT = 'create_invoice_report';
|
||||
const CREATE_PAYMENT_REPORT = 'create_payment_report';
|
||||
const CREATE_PRODUCT_REPORT = 'create_product_report';
|
||||
const CREATE_PROFIT_AND_LOSS_REPORT = 'create_profit_and_loss_report';
|
||||
const CREATE_QUOTE_ITEM_REPORT = 'create_quote_item_report';
|
||||
const CREATE_QUOTE_REPORT = 'create_quote_report';
|
||||
const CREATE_RECURRING_INVOICE_REPORT = 'create_recurring_invoice_report';
|
||||
const CREATE_TASK_REPORT = 'create_task_report';
|
||||
|
||||
|
||||
protected $fillable = ['action_class', 'action_name', 'parameters', 'scheduler_id', 'company_id'];
|
||||
protected $casts = [
|
||||
'parameters' => 'array'
|
||||
];
|
||||
}
|
123
app/Models/Scheduler.php
Normal file
123
app/Models/Scheduler.php
Normal file
@ -0,0 +1,123 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Services\TaskScheduler\TaskSchedulerService;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* @property boolean paused
|
||||
* @property boolean is_deleted
|
||||
* @property \Carbon\Carbon|mixed start_from
|
||||
* @property string repeat_every
|
||||
* @property \Carbon\Carbon|mixed scheduled_run
|
||||
* @property mixed job
|
||||
* @property integer company_id
|
||||
* @property integer updated_at
|
||||
* @property integer created_at
|
||||
* @property integer deleted_at
|
||||
*/
|
||||
class Scheduler extends BaseModel
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'start_from',
|
||||
'paused',
|
||||
'repeat_every',
|
||||
'scheduled_run',
|
||||
'company_id'
|
||||
];
|
||||
protected $casts = [
|
||||
'start_from' => 'timestamp',
|
||||
'scheduled_run' => 'timestamp',
|
||||
'created_at' => 'timestamp',
|
||||
'updated_at' => 'timestamp',
|
||||
'deleted_at' => 'timestamp',
|
||||
'paused' => 'boolean',
|
||||
'is_deleted' => 'boolean',
|
||||
];
|
||||
protected $appends = ['linked_job'];
|
||||
|
||||
const DAILY = 'DAY';
|
||||
const WEEKLY = 'WEEK';
|
||||
const BIWEEKLY = 'BIWEEKLY';
|
||||
const MONTHLY = 'MONTH';
|
||||
const QUARTERLY = '3MONTHS';
|
||||
const ANNUALLY = 'YEAR';
|
||||
|
||||
public function getLinkedJobAttribute()
|
||||
{
|
||||
return $this->job ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Service entry points.
|
||||
*/
|
||||
public function service(): TaskSchedulerService
|
||||
{
|
||||
return new TaskSchedulerService($this);
|
||||
}
|
||||
|
||||
public function job(): \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
{
|
||||
return $this->hasOne(ScheduledJob::class, 'scheduler_id', 'id');
|
||||
}
|
||||
|
||||
public function company(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
|
||||
public function nextScheduledDate(): ?Carbon
|
||||
{
|
||||
|
||||
$offset = 0;
|
||||
|
||||
$entity_send_time = $this->company->settings->entity_send_time;
|
||||
|
||||
if ($entity_send_time != 0) {
|
||||
$timezone = $this->company->timezone();
|
||||
|
||||
$offset -= $timezone->utc_offset;
|
||||
$offset += ($entity_send_time * 3600);
|
||||
}
|
||||
|
||||
/*
|
||||
As we are firing at UTC+0 if our offset is negative it is technically firing the day before so we always need
|
||||
to add ON a day - a day = 86400 seconds
|
||||
*/
|
||||
|
||||
if ($offset < 0)
|
||||
$offset += 86400;
|
||||
|
||||
switch ($this->repeat_every) {
|
||||
case self::DAILY:
|
||||
return Carbon::parse($this->scheduled_run)->startOfDay()->addDay()->addSeconds($offset);
|
||||
case self::WEEKLY:
|
||||
return Carbon::parse($this->scheduled_run)->startOfDay()->addWeek()->addSeconds($offset);
|
||||
case self::BIWEEKLY:
|
||||
return Carbon::parse($this->scheduled_run)->startOfDay()->addWeeks(2)->addSeconds($offset);
|
||||
case self::MONTHLY:
|
||||
return Carbon::parse($this->scheduled_run)->startOfDay()->addMonthNoOverflow()->addSeconds($offset);
|
||||
case self::QUARTERLY:
|
||||
return Carbon::parse($this->scheduled_run)->startOfDay()->addMonthsNoOverflow(3)->addSeconds($offset);
|
||||
case self::ANNUALLY:
|
||||
return Carbon::parse($this->scheduled_run)->startOfDay()->addYearNoOverflow()->addSeconds($offset);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
18
app/Repositories/TaskSchedulerRepository.php
Normal file
18
app/Repositories/TaskSchedulerRepository.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
|
||||
class TaskSchedulerRepository extends BaseRepository
|
||||
{
|
||||
|
||||
}
|
209
app/Services/TaskScheduler/TaskSchedulerService.php
Normal file
209
app/Services/TaskScheduler/TaskSchedulerService.php
Normal file
@ -0,0 +1,209 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\TaskScheduler;
|
||||
|
||||
|
||||
use App\Export\CSV\ClientExport;
|
||||
use App\Export\CSV\ContactExport;
|
||||
use App\Export\CSV\CreditExport;
|
||||
use App\Export\CSV\DocumentExport;
|
||||
use App\Export\CSV\ExpenseExport;
|
||||
use App\Export\CSV\InvoiceExport;
|
||||
use App\Export\CSV\InvoiceItemExport;
|
||||
use App\Export\CSV\PaymentExport;
|
||||
use App\Export\CSV\ProductExport;
|
||||
use App\Export\CSV\QuoteExport;
|
||||
use App\Export\CSV\QuoteItemExport;
|
||||
use App\Export\CSV\RecurringInvoiceExport;
|
||||
use App\Export\CSV\TaskExport;
|
||||
use App\Http\Requests\Report\GenericReportRequest;
|
||||
use App\Http\Requests\Report\ProfitLossRequest;
|
||||
use App\Http\Requests\TaskScheduler\CreateScheduledTaskRequest;
|
||||
use App\Http\Requests\TaskScheduler\UpdateScheduledJobRequest;
|
||||
use App\Http\Requests\TaskScheduler\UpdateScheduleRequest;
|
||||
use App\Jobs\Report\ProfitAndLoss;
|
||||
use App\Models\Company;
|
||||
use App\Models\ScheduledJob;
|
||||
use App\Models\Scheduler;
|
||||
use App\Utils\Ninja;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class TaskSchedulerService
|
||||
{
|
||||
public Scheduler $scheduler;
|
||||
|
||||
public function __construct(Scheduler $scheduler)
|
||||
{
|
||||
$this->scheduler = $scheduler;
|
||||
}
|
||||
|
||||
public function store(Scheduler $scheduler, CreateScheduledTaskRequest $request)
|
||||
{
|
||||
$scheduler->paused = $request->get('paused', false);
|
||||
$scheduler->start_from = $request->get('start_from') ? Carbon::parse((int)$request->get('start_from')) : Carbon::now();
|
||||
$scheduler->repeat_every = $request->get('repeat_every');
|
||||
$scheduler->scheduled_run = $request->get('start_from') ? Carbon::parse((int)$request->get('start_from')) : Carbon::now();;
|
||||
$scheduler->company_id = auth()->user()->company()->id;
|
||||
$scheduler->save();
|
||||
$this->createJob($request, $scheduler);
|
||||
|
||||
}
|
||||
|
||||
public function update(Scheduler $scheduler, UpdateScheduleRequest $request)
|
||||
{
|
||||
|
||||
$data = $request->validated();
|
||||
|
||||
$update = $this->scheduler->update($data);
|
||||
if ($update) {
|
||||
return response(['successfully_updated_scheduler'], 200);
|
||||
}
|
||||
return response(['failed_to_update_scheduler'], 400);
|
||||
}
|
||||
|
||||
public function createJob(CreateScheduledTaskRequest $request, Scheduler $scheduler): bool
|
||||
{
|
||||
$job = new ScheduledJob();
|
||||
$job = $this->setJobParameters($job, $request);
|
||||
$job->scheduler_id = $scheduler->id;
|
||||
$job->company_id = auth()->user()->company()->id;
|
||||
return $job->save();
|
||||
|
||||
}
|
||||
|
||||
public function setJobParameters(ScheduledJob $job, $request): ScheduledJob
|
||||
{
|
||||
switch ($request->job) {
|
||||
case ScheduledJob::CREATE_CLIENT_REPORT:
|
||||
$rules = (new GenericReportRequest)->rules();
|
||||
//custom rules for example here we require date_range but in genericRequest class we don't
|
||||
$rules['date_range'] = 'string|required';
|
||||
$validatedJobData = $request->validate($rules);
|
||||
$job->action_name = ScheduledJob::CREATE_CLIENT_REPORT;
|
||||
$job->action_class = $this->getClassPath(ClientExport::class);
|
||||
$job->parameters = $validatedJobData;
|
||||
break;
|
||||
case ScheduledJob::CREATE_CLIENT_CONTACT_REPORT:
|
||||
$rules = (new GenericReportRequest)->rules();
|
||||
$validatedJobData = $request->validate($rules);
|
||||
$job->action_name = ScheduledJob::CREATE_CLIENT_CONTACT_REPORT;
|
||||
$job->action_class = $this->getClassPath(ContactExport::class);
|
||||
$job->parameters = $validatedJobData;
|
||||
break;
|
||||
case ScheduledJob::CREATE_CREDIT_REPORT:
|
||||
$rules = (new GenericReportRequest)->rules();
|
||||
$validatedJobData = $request->validate($rules);
|
||||
$job->action_name = ScheduledJob::CREATE_CREDIT_REPORT;
|
||||
$job->action_class = $this->getClassPath(CreditExport::class);
|
||||
$job->parameters = $validatedJobData;
|
||||
break;
|
||||
case ScheduledJob::CREATE_DOCUMENT_REPORT:
|
||||
$rules = (new GenericReportRequest)->rules();
|
||||
$validatedJobData = $request->validate($rules);
|
||||
$job->action_name = ScheduledJob::CREATE_DOCUMENT_REPORT;
|
||||
$job->action_class = $this->getClassPath(DocumentExport::class);
|
||||
$job->parameters = $validatedJobData;
|
||||
break;
|
||||
case ScheduledJob::CREATE_EXPENSE_REPORT:
|
||||
$rules = (new GenericReportRequest)->rules();
|
||||
$validatedJobData = $request->validate($rules);
|
||||
$job->action_name = ScheduledJob::CREATE_EXPENSE_REPORT;
|
||||
$job->action_class = $this->getClassPath(ExpenseExport::class);
|
||||
$job->parameters = $validatedJobData;
|
||||
break;
|
||||
case ScheduledJob::CREATE_INVOICE_ITEM_REPORT:
|
||||
$rules = (new GenericReportRequest)->rules();
|
||||
$validatedJobData = $request->validate($rules);
|
||||
$job->action_name = ScheduledJob::CREATE_INVOICE_ITEM_REPORT;
|
||||
$job->action_class = $this->getClassPath(InvoiceItemExport::class);
|
||||
$job->parameters = $validatedJobData;
|
||||
break;
|
||||
case ScheduledJob::CREATE_INVOICE_REPORT:
|
||||
$rules = (new GenericReportRequest)->rules();
|
||||
$validatedJobData = $request->validate($rules);
|
||||
$job->action_name = ScheduledJob::CREATE_INVOICE_REPORT;
|
||||
$job->action_class = $this->getClassPath(InvoiceExport::class);
|
||||
$job->parameters = $validatedJobData;
|
||||
break;
|
||||
case ScheduledJob::CREATE_PAYMENT_REPORT:
|
||||
$rules = (new GenericReportRequest)->rules();
|
||||
$validatedJobData = $request->validate($rules);
|
||||
$job->action_name = ScheduledJob::CREATE_PAYMENT_REPORT;
|
||||
$job->action_class = $this->getClassPath(PaymentExport::class);
|
||||
$job->parameters = $validatedJobData;
|
||||
break;
|
||||
case ScheduledJob::CREATE_PRODUCT_REPORT:
|
||||
$rules = (new GenericReportRequest)->rules();
|
||||
$validatedJobData = $request->validate($rules);
|
||||
$job->action_name = ScheduledJob::CREATE_PRODUCT_REPORT;
|
||||
$job->action_class = $this->getClassPath(ProductExport::class);
|
||||
$job->parameters = $validatedJobData;
|
||||
break;
|
||||
case ScheduledJob::CREATE_PROFIT_AND_LOSS_REPORT:
|
||||
$rules = (new ProfitLossRequest())->rules();
|
||||
$validatedJobData = $request->validate($rules);
|
||||
$job->action_name = ScheduledJob::CREATE_PROFIT_AND_LOSS_REPORT;
|
||||
$job->action_class = $this->getClassPath(ProfitAndLoss::class);
|
||||
$job->parameters = $validatedJobData;
|
||||
break;
|
||||
case ScheduledJob::CREATE_QUOTE_ITEM_REPORT:
|
||||
$rules = (new GenericReportRequest)->rules();
|
||||
$validatedJobData = $request->validate($rules);
|
||||
$job->action_name = ScheduledJob::CREATE_QUOTE_ITEM_REPORT;
|
||||
$job->action_class = $this->getClassPath(QuoteItemExport::class);
|
||||
$job->parameters = $validatedJobData;
|
||||
break;
|
||||
case ScheduledJob::CREATE_QUOTE_REPORT:
|
||||
$rules = (new GenericReportRequest)->rules();
|
||||
$validatedJobData = $request->validate($rules);
|
||||
$job->action_name = ScheduledJob::CREATE_QUOTE_REPORT;
|
||||
$job->action_class = $this->getClassPath(QuoteExport::class);
|
||||
$job->parameters = $validatedJobData;
|
||||
break;
|
||||
case ScheduledJob::CREATE_RECURRING_INVOICE_REPORT:
|
||||
$rules = (new GenericReportRequest)->rules();
|
||||
$validatedJobData = $request->validate($rules);
|
||||
$job->action_name = ScheduledJob::CREATE_RECURRING_INVOICE_REPORT;
|
||||
$job->action_class = $this->getClassPath(RecurringInvoiceExport::class);
|
||||
$job->parameters = $validatedJobData;
|
||||
break;
|
||||
case ScheduledJob::CREATE_TASK_REPORT:
|
||||
$rules = (new GenericReportRequest)->rules();
|
||||
$validatedJobData = $request->validate($rules);
|
||||
$job->action_name = ScheduledJob::CREATE_TASK_REPORT;
|
||||
$job->action_class = $this->getClassPath(TaskExport::class);
|
||||
$job->parameters = $validatedJobData;
|
||||
break;
|
||||
|
||||
}
|
||||
return $job;
|
||||
}
|
||||
|
||||
public function getClassPath($class): string
|
||||
{
|
||||
return $class = is_object($class) ? get_class($class) : $class;
|
||||
}
|
||||
|
||||
|
||||
public function updateJob(Scheduler $scheduler, UpdateScheduledJobRequest $request)
|
||||
{
|
||||
$job = $scheduler->job;
|
||||
if (!$job) {
|
||||
return abort(404);
|
||||
}
|
||||
$job = $this->setJobParameters($job, $request);
|
||||
$job->save();
|
||||
|
||||
}
|
||||
}
|
30
app/Transformers/ScheduledJobTransformer.php
Normal file
30
app/Transformers/ScheduledJobTransformer.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Transformers;
|
||||
|
||||
|
||||
use App\Models\ScheduledJob;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class ScheduledJobTransformer extends EntityTransformer
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public function transform(ScheduledJob $job)
|
||||
{
|
||||
return [
|
||||
'id' => $this->encodePrimaryKey($job->id),
|
||||
'action_name' => (string)$job->action_name,
|
||||
'parameters' => (array)$job->parameters
|
||||
];
|
||||
}
|
||||
}
|
48
app/Transformers/TaskSchedulerTransformer.php
Normal file
48
app/Transformers/TaskSchedulerTransformer.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Transformers;
|
||||
|
||||
|
||||
use App\Models\ScheduledJob;
|
||||
use App\Models\Scheduler;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class TaskSchedulerTransformer extends EntityTransformer
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
protected $defaultIncludes = [
|
||||
'job'
|
||||
];
|
||||
|
||||
public function includeJob(Scheduler $scheduler)
|
||||
{
|
||||
$transformer = new ScheduledJobTransformer($this->serializer);
|
||||
|
||||
return $this->item($scheduler->job, $transformer, ScheduledJob::class);
|
||||
}
|
||||
|
||||
public function transform(Scheduler $scheduler)
|
||||
{
|
||||
return [
|
||||
'id' => $this->encodePrimaryKey($scheduler->id),
|
||||
'paused' => (bool)$scheduler->paused,
|
||||
'repeat_every' => (string)$scheduler->repeat_every,
|
||||
'start_from' => (int)$scheduler->start_from,
|
||||
'scheduled_run' => (int)$scheduler->scheduled_run,
|
||||
'updated_at' => (int)$scheduler->updated_at,
|
||||
'created_at' => (int)$scheduler->created_at,
|
||||
'archived_at' => (int) $scheduler->deleted_at,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateScheduledJobsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('scheduled_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('action_name');
|
||||
$table->string('action_class');
|
||||
$table->json('parameters')->nullable();
|
||||
$table->foreignIdFor(\App\Models\Company::class);
|
||||
$table->foreignIdFor(\App\Models\Scheduler::class);
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('scheduled_jobs');
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateSchedulersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('schedulers', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->boolean('paused')->default(false);
|
||||
$table->boolean('is_deleted')->default(false);
|
||||
$table->string('repeat_every');
|
||||
$table->timestamp('start_from');
|
||||
$table->timestamp('scheduled_run');
|
||||
$table->foreignIdFor(\App\Models\Company::class);
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('schedulers');
|
||||
}
|
||||
}
|
@ -45,8 +45,8 @@ Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale
|
||||
|
||||
Route::post('filters/{entity}', 'FilterController@index')->name('filters');
|
||||
|
||||
Route::resource('client_gateway_tokens', 'ClientGatewayTokenController');
|
||||
|
||||
Route::resource('client_gateway_tokens', 'ClientGatewayTokenController');
|
||||
|
||||
Route::post('connected_account', 'ConnectedAccountController@index');
|
||||
Route::post('connected_account/gmail', 'ConnectedAccountController@handleGmailOauth');
|
||||
|
||||
@ -54,9 +54,9 @@ Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale
|
||||
|
||||
Route::post('companies/purge/{company}', 'MigrationController@purgeCompany')->middleware('password_protected');
|
||||
Route::post('companies/purge_save_settings/{company}', 'MigrationController@purgeCompanySaveSettings')->middleware('password_protected');
|
||||
|
||||
|
||||
Route::resource('companies', 'CompanyController'); // name = (companies. index / create / show / update / destroy / edit
|
||||
|
||||
|
||||
Route::put('companies/{company}/upload', 'CompanyController@upload');
|
||||
Route::post('companies/{company}/default', 'CompanyController@default');
|
||||
|
||||
@ -170,6 +170,10 @@ Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale
|
||||
Route::post('reports/tasks', 'Reports\TaskReportController');
|
||||
Route::post('reports/profitloss', 'Reports\ProfitAndLossController');
|
||||
|
||||
|
||||
Route::resource('task_scheduler', 'TaskSchedulerController')->except('edit')->parameters(['task_scheduler' => 'scheduler']);
|
||||
Route::put('task_scheduler/{scheduler}/update_job','TaskSchedulerController@updateJob');
|
||||
|
||||
Route::get('scheduler', 'SchedulerController@index');
|
||||
Route::post('support/messages/send', 'Support\Messages\SendingController');
|
||||
|
||||
|
163
tests/Feature/Scheduler/SchedulerTest.php
Normal file
163
tests/Feature/Scheduler/SchedulerTest.php
Normal file
@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Tests\Feature\Scheduler;
|
||||
|
||||
use App\Export\CSV\ClientExport;
|
||||
use App\Models\ScheduledJob;
|
||||
use App\Models\Scheduler;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Foundation\Testing\WithoutEvents;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Tests\MockUnitData;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SchedulerTest extends TestCase
|
||||
{
|
||||
use MakesHash;
|
||||
use MockUnitData;
|
||||
use WithoutEvents;
|
||||
use RefreshDatabase;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Session::start();
|
||||
|
||||
$this->faker = \Faker\Factory::create();
|
||||
|
||||
Model::reguard();
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class
|
||||
);
|
||||
}
|
||||
|
||||
public function testSchedulerCantBeCreatedWithWrongData()
|
||||
{
|
||||
$data = [
|
||||
'repeat_every' => Scheduler::DAILY,
|
||||
'job' => ScheduledJob::CREATE_CLIENT_REPORT,
|
||||
'date_key' => '123',
|
||||
'report_keys' => ['test'],
|
||||
// 'date_range' => 'all',
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/task_scheduler/', $data);
|
||||
|
||||
$response->assertSessionHasErrors();
|
||||
|
||||
}
|
||||
|
||||
public function testSchedulerCanBeUpdated()
|
||||
{
|
||||
$this->createScheduler();
|
||||
|
||||
|
||||
$scheduler = Scheduler::first();
|
||||
$updateData = [
|
||||
'start_from' => 1655934741
|
||||
];
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/task_scheduler/' . $this->encodePrimaryKey($scheduler->id), $updateData);
|
||||
|
||||
$responseData = $response->json();
|
||||
$this->assertEquals($updateData['start_from'], $responseData['data']['start_from']);
|
||||
}
|
||||
|
||||
public function testSchedulerCanBeSeen()
|
||||
{
|
||||
$this->createScheduler();
|
||||
|
||||
|
||||
$scheduler = Scheduler::first();
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->get('/api/v1/task_scheduler/' . $this->encodePrimaryKey($scheduler->id));
|
||||
|
||||
$arr = $response->json();
|
||||
$this->assertEquals('create_client_report', $arr['data']['job']['action_name']);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function testSchedulerCanBeDeleted()
|
||||
{
|
||||
$this->createScheduler();
|
||||
|
||||
$scheduler = Scheduler::first();
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->delete('/api/v1/task_scheduler/' . $this->encodePrimaryKey($scheduler->id));
|
||||
|
||||
$this->assertEquals(0, Scheduler::count());
|
||||
|
||||
}
|
||||
|
||||
public function testSchedulerJobCanBeUpdated()
|
||||
{
|
||||
$this->createScheduler();
|
||||
|
||||
$scheduler = Scheduler::first();
|
||||
$this->assertSame('create_client_report', $scheduler->job->action_name);
|
||||
|
||||
$updateData = [
|
||||
'job' => ScheduledJob::CREATE_CREDIT_REPORT,
|
||||
'date_range' => 'all',
|
||||
'report_keys' => ['test1']
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/task_scheduler/' . $this->encodePrimaryKey($scheduler->id) . '/update_job', $updateData);
|
||||
|
||||
$updatedSchedulerJob = Scheduler::first()->job->action_name;
|
||||
$arr = $response->json();
|
||||
$this->assertSame('create_credit_report', $arr['data']['job']['action_name']);
|
||||
}
|
||||
|
||||
public function testSchedulerCanBeCreated()
|
||||
{
|
||||
$response = $this->createScheduler();
|
||||
|
||||
$all_schedulers = Scheduler::count();
|
||||
|
||||
$this->assertSame(1, $all_schedulers);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
}
|
||||
|
||||
public function createScheduler()
|
||||
{
|
||||
$data = [
|
||||
'repeat_every' => Scheduler::DAILY,
|
||||
'job' => ScheduledJob::CREATE_CLIENT_REPORT,
|
||||
'date_key' => '123',
|
||||
'report_keys' => ['test'],
|
||||
'date_range' => 'all',
|
||||
];
|
||||
|
||||
return $response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/task_scheduler/', $data);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user