Working on additional schedulers
This commit is contained in:
parent
fcbe62f57c
commit
0edc5eeb67
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\DataMapper\Schedule;
|
||||||
|
|
||||||
|
class PaymentSchedule
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The template name
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $template = 'payment_schedule';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var array(
|
||||||
|
* 'date' => string,
|
||||||
|
* 'amount' => float,
|
||||||
|
* 'percentage' => float
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public array $schedule = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The invoice id
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $invoice_id = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to auto bill the invoice
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public bool $auto_bill = false;
|
||||||
|
}
|
||||||
|
|
@ -72,6 +72,12 @@ class StoreSchedulerRequest extends Request
|
||||||
'parameters.status' => ['bail','sometimes', 'nullable', 'string'],
|
'parameters.status' => ['bail','sometimes', 'nullable', 'string'],
|
||||||
'parameters.include_project_tasks' => ['bail','sometimes', 'boolean', 'required_if:template,invoice_outstanding_tasks'],
|
'parameters.include_project_tasks' => ['bail','sometimes', 'boolean', 'required_if:template,invoice_outstanding_tasks'],
|
||||||
'parameters.auto_send' => ['bail','sometimes', 'boolean', 'required_if:template,invoice_outstanding_tasks'],
|
'parameters.auto_send' => ['bail','sometimes', 'boolean', 'required_if:template,invoice_outstanding_tasks'],
|
||||||
|
'parameters.invoice_id' => ['bail','sometimes', 'string', 'required_if:template,payment_schedule'],
|
||||||
|
'parameters.auto_bill' => ['bail','sometimes', 'boolean', 'required_if:template,payment_schedule'],
|
||||||
|
'parameters.schedule' => ['bail','sometimes', 'array', 'required_if:template,payment_schedule'],
|
||||||
|
'parameters.schedule.*.date' => ['bail','sometimes', 'date:Y-m-d'],
|
||||||
|
'parameters.schedule.*.amount' => ['bail','sometimes', 'numeric'],
|
||||||
|
'parameters.schedule.*.percentage' => ['bail','sometimes', 'numeric'],
|
||||||
];
|
];
|
||||||
|
|
||||||
return $rules;
|
return $rules;
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,12 @@ class UpdateSchedulerRequest extends Request
|
||||||
'parameters.status' => ['bail','sometimes', 'nullable', 'string'],
|
'parameters.status' => ['bail','sometimes', 'nullable', 'string'],
|
||||||
'parameters.include_project_tasks' => ['bail','sometimes', 'boolean', 'required_if:template,invoice_outstanding_tasks'],
|
'parameters.include_project_tasks' => ['bail','sometimes', 'boolean', 'required_if:template,invoice_outstanding_tasks'],
|
||||||
'parameters.auto_send' => ['bail','sometimes', 'boolean', 'required_if:template,invoice_outstanding_tasks'],
|
'parameters.auto_send' => ['bail','sometimes', 'boolean', 'required_if:template,invoice_outstanding_tasks'],
|
||||||
|
'parameters.invoice_id' => ['bail','sometimes', 'string', 'required_if:template,payment_schedule'],
|
||||||
|
'parameters.auto_bill' => ['bail','sometimes', 'boolean', 'required_if:template,payment_schedule'],
|
||||||
|
'parameters.schedule' => ['bail','sometimes', 'array', 'required_if:template,payment_schedule','min:1'],
|
||||||
|
'parameters.schedule.*.date' => ['bail','sometimes', 'date:Y-m-d'],
|
||||||
|
'parameters.schedule.*.amount' => ['bail','sometimes', 'numeric'],
|
||||||
|
'parameters.schedule.*.percentage' => ['bail','sometimes', 'numeric'],
|
||||||
];
|
];
|
||||||
|
|
||||||
return $rules;
|
return $rules;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Services\Scheduler;
|
||||||
|
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Models\Scheduler;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class PaymentSchedule
|
||||||
|
{
|
||||||
|
use MakesHash;
|
||||||
|
|
||||||
|
public function __construct(public Scheduler $scheduler)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
$invoice = Invoice::find($this->decodePrimaryKey($this->scheduler->parameters['invoice_id']));
|
||||||
|
|
||||||
|
// Needs to be draft, partial or paid AND not deleted
|
||||||
|
if(!$invoice ||!in_array($invoice->status_id, [Invoice::STATUS_DRAFT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID]) || $invoice->is_deleted){
|
||||||
|
$this->scheduler->forceDelete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$invoice = $invoice->service()->markSent()->save();
|
||||||
|
|
||||||
|
$offset = $invoice->company->timezone_offset();
|
||||||
|
$schedule = $this->scheduler->parameters['schedule'];
|
||||||
|
$schedule_index = 0;
|
||||||
|
$next_schedule = false;
|
||||||
|
|
||||||
|
foreach($schedule as $key =>$item){
|
||||||
|
|
||||||
|
if(now()->startOfDay()->eq(Carbon::parse($item['date'])->subSeconds($offset)->startOfDay())){
|
||||||
|
$next_schedule = $item;
|
||||||
|
$schedule_index = $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$next_schedule){
|
||||||
|
$this->scheduler->forceDelete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$amount = max($next_schedule['amount'], ($next_schedule['percentage'] * $invoice->amount));
|
||||||
|
$amount += $invoice->partial;
|
||||||
|
|
||||||
|
|
||||||
|
if($amount > $invoice->balance){
|
||||||
|
$amount = $invoice->balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
$invoice->partial = $amount;
|
||||||
|
$invoice->partial_due_date = $item['date'];
|
||||||
|
$invoice->due_date = Carbon::parse($item['date'])->addDay()->format('Y-m-d');
|
||||||
|
|
||||||
|
$invoice->save();
|
||||||
|
|
||||||
|
if($this->scheduler->parameters['auto_bill']){
|
||||||
|
$invoice->service()->autoBill();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$invoice->service()->sendEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
$total_schedules = count($schedule);
|
||||||
|
|
||||||
|
if($total_schedules >= $schedule_index + 1){
|
||||||
|
$next_run = $schedule[$schedule_index + 1]['date'];
|
||||||
|
$this->scheduler->next_run_client = $next_run;
|
||||||
|
$this->scheduler->next_run = Carbon::parse($next_run)->addSeconds($offset);
|
||||||
|
$this->scheduler->save();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->scheduler->forceDelete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -61,6 +61,48 @@ class SchedulerTest extends TestCase
|
||||||
// $this->withoutExceptionHandling();
|
// $this->withoutExceptionHandling();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testPaymentSchedule()
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
[
|
||||||
|
'date' => now()->format('Y-m-d'),
|
||||||
|
'amount' => 100,
|
||||||
|
'percentage' => 100,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'date' => now()->addDays(1)->format('Y-m-d'),
|
||||||
|
'amount' => 100,
|
||||||
|
'percentage' => 100,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'date' => now()->addDays(2)->format('Y-m-d'),
|
||||||
|
'amount' => 100,
|
||||||
|
'percentage' => 100,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$offset = -3600;
|
||||||
|
|
||||||
|
$next_schedule = collect($data)->first(function ($item) use ($offset){
|
||||||
|
return now()->startOfDay()->eq(Carbon::parse($item['date'])->subSeconds($offset)->startOfDay());
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->assertNotNull($next_schedule);
|
||||||
|
|
||||||
|
$this->assertEquals($next_schedule['date'], now()->format('Y-m-d'));
|
||||||
|
|
||||||
|
$this->travelTo(now()->addDays(1));
|
||||||
|
|
||||||
|
$next_schedule = collect($data)->first(function ($item) use ($offset) {
|
||||||
|
return now()->startOfDay()->eq(Carbon::parse($item['date'])->subSeconds($offset)->startOfDay());
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->assertNotNull($next_schedule);
|
||||||
|
|
||||||
|
$this->assertEquals($next_schedule['date'], now()->format('Y-m-d'));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public function testInvoiceOutstandingTasks()
|
public function testInvoiceOutstandingTasks()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue