Schedule Entity

This commit is contained in:
David Bomba 2023-03-18 19:06:32 +11:00
parent 5a42b89d55
commit 222806eeeb
10 changed files with 323 additions and 120 deletions

View File

@ -11,21 +11,21 @@
namespace App\DataMapper\Schedule;
class ScheduleInvoice
class ScheduleEntity
{
/**
* Defines the template name
*
* @var string
*/
public string $template = 'schedule_invoice';
public string $template = 'schedule_entity';
/**
* Defines the template name
*
* @var string
*/
public string $entity = '';
public string $entity = ''; // invoice, credit, quote, purchase_order
/**
* Defines the template name

View File

@ -43,6 +43,8 @@ class StoreSchedulerRequest extends Request
'parameters.date_range' => 'bail|sometimes|string|in:last7_days,last30_days,last365_days,this_month,last_month,this_quarter,last_quarter,this_year,last_year,custom',
'parameters.start_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom'],
'parameters.end_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom', 'after_or_equal:parameters.start_date'],
'parameters.entity' => ['bail', 'sometimes', 'string', 'in:invoice,credit,quote,purchase_order'],
'parameters.entity_id' => ['bail', 'sometimes', 'string'],
];
return $rules;

View File

@ -57,6 +57,7 @@ class PurchaseOrderEmail implements ShouldQueue
MultiDB::setDb($this->company->db);
$this->purchase_order->last_sent_date = now();
$this->purchase_order->save();
$this->purchase_order->invitations->load('contact.vendor.country', 'purchase_order.vendor.country', 'purchase_order.company')->each(function ($invitation) {
/* Don't fire emails if the company is disabled */

View File

@ -11,6 +11,9 @@
namespace App\Models;
use App\Models\Company;
use App\Models\BaseModel;
use App\Models\RecurringInvoice;
use App\Services\Scheduler\SchedulerService;
use Illuminate\Database\Eloquent\SoftDeletes;
@ -134,4 +137,61 @@ class Scheduler extends BaseModel
return $this->remaining_cycles - 1;
}
}
public function calculateNextRun()
{
if (! $this->next_run) {
return null;
}
$offset = $this->company->timezone_offset();
switch ($this->frequency_id) {
case RecurringInvoice::FREQUENCY_DAILY:
$next_run = now()->startOfDay()->addDay();
break;
case RecurringInvoice::FREQUENCY_WEEKLY:
$next_run = now()->startOfDay()->addWeek();
break;
case RecurringInvoice::FREQUENCY_TWO_WEEKS:
$next_run = now()->startOfDay()->addWeeks(2);
break;
case RecurringInvoice::FREQUENCY_FOUR_WEEKS:
$next_run = now()->startOfDay()->addWeeks(4);
break;
case RecurringInvoice::FREQUENCY_MONTHLY:
$next_run = now()->startOfDay()->addMonthNoOverflow();
break;
case RecurringInvoice::FREQUENCY_TWO_MONTHS:
$next_run = now()->startOfDay()->addMonthsNoOverflow(2);
break;
case RecurringInvoice::FREQUENCY_THREE_MONTHS:
$next_run = now()->startOfDay()->addMonthsNoOverflow(3);
break;
case RecurringInvoice::FREQUENCY_FOUR_MONTHS:
$next_run = now()->startOfDay()->addMonthsNoOverflow(4);
break;
case RecurringInvoice::FREQUENCY_SIX_MONTHS:
$next_run = now()->startOfDay()->addMonthsNoOverflow(6);
break;
case RecurringInvoice::FREQUENCY_ANNUALLY:
$next_run = now()->startOfDay()->addYear();
break;
case RecurringInvoice::FREQUENCY_TWO_YEARS:
$next_run = now()->startOfDay()->addYears(2);
break;
case RecurringInvoice::FREQUENCY_THREE_YEARS:
$next_run = now()->startOfDay()->addYears(3);
break;
default:
$next_run = null;
}
$this->next_run_client = $next_run ?: null;
$this->next_run = $next_run ? $next_run->copy()->addSeconds($offset) : null;
$this->save();
}
}

View File

@ -35,7 +35,6 @@ class SendEmail extends AbstractService
/**
* Builds the correct template to send.
* @return void
*/
public function run()
{

View File

@ -11,9 +11,10 @@
namespace App\Services\PurchaseOrder;
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
use App\Models\PurchaseOrder;
use App\Utils\Traits\MakesHash;
use App\Services\PurchaseOrder\SendEmail;
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
class PurchaseOrderService
{
@ -145,6 +146,14 @@ class PurchaseOrderService
return $expense;
}
public function sendEmail($contact = null)
{
$send_email = new SendEmail($this->purchase_order, null, $contact);
return $send_email->run();
}
/**
* Saves the purchase order.
* @return \App\Models\PurchaseOrder object

View File

@ -0,0 +1,116 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\PurchaseOrder;
use App\Utils\Ninja;
use App\Models\PurchaseOrder;
use App\Models\VendorContact;
use App\Jobs\Mail\NinjaMailerJob;
use App\Mail\VendorTemplateEmail;
use App\Services\AbstractService;
use Illuminate\Support\Facades\App;
use App\Jobs\Mail\NinjaMailerObject;
use App\Mail\Engine\PurchaseOrderEmailEngine;
use App\Events\PurchaseOrder\PurchaseOrderWasEmailed;
class SendEmail extends AbstractService
{
public function __construct(protected PurchaseOrder $purchase_order, protected ?string $reminder_template = null, protected ?VendorContact $contact = null)
{
}
/**
* Builds the correct template to send.
*/
public function run()
{
$this->purchase_order->last_sent_date = now();
$this->purchase_order->invitations->load('contact.vendor.country', 'purchase_order.vendor.country', 'purchase_order.company')->each(function ($invitation) {
App::forgetInstance('translator');
$t = app('translator');
App::setLocale($invitation->contact->preferredLocale());
$t->replace(Ninja::transformTranslations($this->purchase_order->company->settings));
/* Mark entity sent */
$invitation->purchase_order->service()->markSent()->save();
$template = 'purchase_order';
$email_builder = (new PurchaseOrderEmailEngine($invitation, $template, null))->build();
$nmo = new NinjaMailerObject;
$nmo->mailable = new VendorTemplateEmail($email_builder, $invitation->contact, $invitation);
$nmo->company = $this->purchase_order->company;
$nmo->settings = $this->purchase_order->company->settings;
$nmo->to_user = $invitation->contact;
$nmo->entity_string = 'purchase_order';
$nmo->invitation = $invitation;
$nmo->reminder_template = 'email_template_purchase_order';
$nmo->entity = $invitation->purchase_order;
NinjaMailerJob::dispatch($nmo);
});
if ($this->purchase_order->invitations->count() >= 1) {
event(new PurchaseOrderWasEmailed($this->purchase_order->invitations->first(), $this->purchase_order->invitations->first()->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
}
}
}

View File

@ -0,0 +1,95 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\Scheduler;
use App\Models\Client;
use App\Models\Scheduler;
use App\Utils\Traits\MakesHash;
use App\DataMapper\Schedule\EmailStatement;
class EmailStatementService
{
use MakesHash;
private Client $client;
public function __construct(public Scheduler $scheduler)
{
}
public function run()
{
$query = Client::query()
->where('company_id', $this->scheduler->company_id)
->where('is_deleted', 0);
//Email only the selected clients
if (count($this->scheduler->parameters['clients']) >= 1) {
$query->whereIn('id', $this->transformKeys($this->scheduler->parameters['clients']));
}
$query->cursor()
->each(function ($_client) {
$this->client = $_client;
//work out the date range
$statement_properties = $this->calculateStatementProperties();
$_client->service()->statement($statement_properties, true);
});
//calculate next run dates;
$this->scheduler->calculateNextRun();
}
/**
* Hydrates the array needed to generate the statement
*
* @return array The statement options array
*/
private function calculateStatementProperties(): array
{
$start_end = $this->calculateStartAndEndDates();
return [
'start_date' =>$start_end[0],
'end_date' =>$start_end[1],
'show_payments_table' => $this->scheduler->parameters['show_payments_table'],
'show_aging_table' => $this->scheduler->parameters['show_aging_table'],
'status' => $this->scheduler->parameters['status']
];
}
/**
* Start and end date of the statement
*
* @return array [$start_date, $end_date];
*/
private function calculateStartAndEndDates(): array
{
return match ($this->scheduler->parameters['date_range']) {
EmailStatement::LAST7 => [now()->startOfDay()->subDays(7)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')],
EmailStatement::LAST30 => [now()->startOfDay()->subDays(30)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')],
EmailStatement::LAST365 => [now()->startOfDay()->subDays(365)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')],
EmailStatement::THIS_MONTH => [now()->startOfDay()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->lastOfMonth()->format('Y-m-d')],
EmailStatement::LAST_MONTH => [now()->startOfDay()->subMonthNoOverflow()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->subMonthNoOverflow()->lastOfMonth()->format('Y-m-d')],
EmailStatement::THIS_QUARTER => [now()->startOfDay()->firstOfQuarter()->format('Y-m-d'), now()->startOfDay()->lastOfQuarter()->format('Y-m-d')],
EmailStatement::LAST_QUARTER => [now()->startOfDay()->subQuarterNoOverflow()->firstOfQuarter()->format('Y-m-d'), now()->startOfDay()->subQuarterNoOverflow()->lastOfQuarter()->format('Y-m-d')],
EmailStatement::THIS_YEAR => [now()->startOfDay()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->lastOfYear()->format('Y-m-d')],
EmailStatement::LAST_YEAR => [now()->startOfDay()->subYearNoOverflow()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->subYearNoOverflow()->lastOfYear()->format('Y-m-d')],
EmailStatement::CUSTOM_RANGE => [$this->scheduler->parameters['start_date'], $this->scheduler->parameters['end_date']],
default => [now()->startOfDay()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->lastOfMonth()->format('Y-m-d')],
};
}
}

View File

@ -0,0 +1,29 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\Scheduler;
use App\Models\Scheduler;
use App\Utils\Traits\MakesHash;
class ScheduleEntity
{
use MakesHash;
public function __construct(public Scheduler $scheduler)
{
}
public function run()
{
}
}

View File

@ -25,8 +25,6 @@ class SchedulerService
private string $method;
private Client $client;
public function __construct(public Scheduler $scheduler)
{
}
@ -43,129 +41,23 @@ class SchedulerService
}
}
private function schedule_entity()
{
(new ScheduleEntity($this->scheduler))->run();
}
private function email_statement()
{
$query = Client::query()
->where('company_id', $this->scheduler->company_id)
->where('is_deleted', 0);
//Email only the selected clients
if (count($this->scheduler->parameters['clients']) >= 1) {
$query->whereIn('id', $this->transformKeys($this->scheduler->parameters['clients']));
}
$query->cursor()
->each(function ($_client) {
$this->client = $_client;
//work out the date range
$statement_properties = $this->calculateStatementProperties();
$_client->service()->statement($statement_properties, true);
});
//calculate next run dates;
$this->calculateNextRun();
(new EmailStatementService($this->scheduler))->run();
}
/**
* Hydrates the array needed to generate the statement
*
* @return array The statement options array
*/
private function calculateStatementProperties(): array
{
$start_end = $this->calculateStartAndEndDates();
return [
'start_date' =>$start_end[0],
'end_date' =>$start_end[1],
'show_payments_table' => $this->scheduler->parameters['show_payments_table'],
'show_aging_table' => $this->scheduler->parameters['show_aging_table'],
'status' => $this->scheduler->parameters['status']
];
}
/**
* Start and end date of the statement
*
* @return array [$start_date, $end_date];
*/
private function calculateStartAndEndDates(): array
{
return match ($this->scheduler->parameters['date_range']) {
EmailStatement::LAST7 => [now()->startOfDay()->subDays(7)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')],
EmailStatement::LAST30 => [now()->startOfDay()->subDays(30)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')],
EmailStatement::LAST365 => [now()->startOfDay()->subDays(365)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')],
EmailStatement::THIS_MONTH => [now()->startOfDay()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->lastOfMonth()->format('Y-m-d')],
EmailStatement::LAST_MONTH => [now()->startOfDay()->subMonthNoOverflow()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->subMonthNoOverflow()->lastOfMonth()->format('Y-m-d')],
EmailStatement::THIS_QUARTER => [now()->startOfDay()->firstOfQuarter()->format('Y-m-d'), now()->startOfDay()->lastOfQuarter()->format('Y-m-d')],
EmailStatement::LAST_QUARTER => [now()->startOfDay()->subQuarterNoOverflow()->firstOfQuarter()->format('Y-m-d'), now()->startOfDay()->subQuarterNoOverflow()->lastOfQuarter()->format('Y-m-d')],
EmailStatement::THIS_YEAR => [now()->startOfDay()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->lastOfYear()->format('Y-m-d')],
EmailStatement::LAST_YEAR => [now()->startOfDay()->subYearNoOverflow()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->subYearNoOverflow()->lastOfYear()->format('Y-m-d')],
EmailStatement::CUSTOM_RANGE => [$this->scheduler->parameters['start_date'], $this->scheduler->parameters['end_date']],
default => [now()->startOfDay()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->lastOfMonth()->format('Y-m-d')],
};
}
/**
* Sets the next run date of the scheduled task
*
*/
private function calculateNextRun()
{
if (! $this->scheduler->next_run) {
return null;
}
$offset = $this->scheduler->company->timezone_offset();
switch ($this->scheduler->frequency_id) {
case RecurringInvoice::FREQUENCY_DAILY:
$next_run = now()->startOfDay()->addDay();
break;
case RecurringInvoice::FREQUENCY_WEEKLY:
$next_run = now()->startOfDay()->addWeek();
break;
case RecurringInvoice::FREQUENCY_TWO_WEEKS:
$next_run = now()->startOfDay()->addWeeks(2);
break;
case RecurringInvoice::FREQUENCY_FOUR_WEEKS:
$next_run = now()->startOfDay()->addWeeks(4);
break;
case RecurringInvoice::FREQUENCY_MONTHLY:
$next_run = now()->startOfDay()->addMonthNoOverflow();
break;
case RecurringInvoice::FREQUENCY_TWO_MONTHS:
$next_run = now()->startOfDay()->addMonthsNoOverflow(2);
break;
case RecurringInvoice::FREQUENCY_THREE_MONTHS:
$next_run = now()->startOfDay()->addMonthsNoOverflow(3);
break;
case RecurringInvoice::FREQUENCY_FOUR_MONTHS:
$next_run = now()->startOfDay()->addMonthsNoOverflow(4);
break;
case RecurringInvoice::FREQUENCY_SIX_MONTHS:
$next_run = now()->startOfDay()->addMonthsNoOverflow(6);
break;
case RecurringInvoice::FREQUENCY_ANNUALLY:
$next_run = now()->startOfDay()->addYear();
break;
case RecurringInvoice::FREQUENCY_TWO_YEARS:
$next_run = now()->startOfDay()->addYears(2);
break;
case RecurringInvoice::FREQUENCY_THREE_YEARS:
$next_run = now()->startOfDay()->addYears(3);
break;
default:
$next_run = null;
}
$this->scheduler->next_run_client = $next_run ?: null;
$this->scheduler->next_run = $next_run ? $next_run->copy()->addSeconds($offset) : null;
$this->scheduler->save();
}
//handle when the scheduler has been paused.
}