diff --git a/app/DataMapper/Schedule/ScheduleInvoice.php b/app/DataMapper/Schedule/ScheduleEntity.php similarity index 80% rename from app/DataMapper/Schedule/ScheduleInvoice.php rename to app/DataMapper/Schedule/ScheduleEntity.php index cf22aaa7e224..649360f4354c 100644 --- a/app/DataMapper/Schedule/ScheduleInvoice.php +++ b/app/DataMapper/Schedule/ScheduleEntity.php @@ -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 diff --git a/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php b/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php index aea0fee893a4..632cd8cb80b3 100644 --- a/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php +++ b/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php @@ -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; diff --git a/app/Jobs/PurchaseOrder/PurchaseOrderEmail.php b/app/Jobs/PurchaseOrder/PurchaseOrderEmail.php index ace474991c4e..b00622effc98 100644 --- a/app/Jobs/PurchaseOrder/PurchaseOrderEmail.php +++ b/app/Jobs/PurchaseOrder/PurchaseOrderEmail.php @@ -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 */ diff --git a/app/Models/Scheduler.php b/app/Models/Scheduler.php index f657dfee6a99..83506410843c 100644 --- a/app/Models/Scheduler.php +++ b/app/Models/Scheduler.php @@ -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(); + } + + } diff --git a/app/Services/Invoice/SendEmail.php b/app/Services/Invoice/SendEmail.php index 833d777c533e..b71677457307 100644 --- a/app/Services/Invoice/SendEmail.php +++ b/app/Services/Invoice/SendEmail.php @@ -35,7 +35,6 @@ class SendEmail extends AbstractService /** * Builds the correct template to send. - * @return void */ public function run() { diff --git a/app/Services/PurchaseOrder/PurchaseOrderService.php b/app/Services/PurchaseOrder/PurchaseOrderService.php index d1ab465d262c..f9bc5d7c75e1 100644 --- a/app/Services/PurchaseOrder/PurchaseOrderService.php +++ b/app/Services/PurchaseOrder/PurchaseOrderService.php @@ -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 diff --git a/app/Services/PurchaseOrder/SendEmail.php b/app/Services/PurchaseOrder/SendEmail.php new file mode 100644 index 000000000000..900d5380bc6a --- /dev/null +++ b/app/Services/PurchaseOrder/SendEmail.php @@ -0,0 +1,116 @@ +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))); + } + + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/Services/Scheduler/EmailStatementService.php b/app/Services/Scheduler/EmailStatementService.php new file mode 100644 index 000000000000..c1c93ba941c5 --- /dev/null +++ b/app/Services/Scheduler/EmailStatementService.php @@ -0,0 +1,95 @@ +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')], + }; + } + +} diff --git a/app/Services/Scheduler/ScheduleEntity.php b/app/Services/Scheduler/ScheduleEntity.php new file mode 100644 index 000000000000..84229c16863f --- /dev/null +++ b/app/Services/Scheduler/ScheduleEntity.php @@ -0,0 +1,29 @@ +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. }