diff --git a/app/Console/Commands/RecurringCheck.php b/app/Console/Commands/RecurringCheck.php index e69ef3c74..f2a5d2ba4 100644 --- a/app/Console/Commands/RecurringCheck.php +++ b/app/Console/Commands/RecurringCheck.php @@ -2,21 +2,21 @@ namespace App\Console\Commands; +use App\Events\Banking\TransactionCreated; +use App\Events\Banking\TransactionRecurring; use App\Events\Purchase\BillCreated; use App\Events\Purchase\BillRecurring; use App\Events\Sale\InvoiceCreated; use App\Events\Sale\InvoiceRecurring; +use App\Models\Banking\Transaction; use App\Models\Common\Company; -use App\Traits\Sales; +use App\Models\Sale\Invoice; use App\Utilities\Overrider; -use Carbon\Carbon; use Date; use Illuminate\Console\Command; class RecurringCheck extends Command { - use Sales; - /** * The name and signature of the console command. * @@ -34,7 +34,7 @@ class RecurringCheck extends Command /** * The current day. * - * @var Carbon + * @var \Carbon\Carbon */ protected $today; @@ -64,8 +64,16 @@ class RecurringCheck extends Command $this->today = Date::today(); foreach ($company->recurring as $recurring) { - foreach ($recurring->schedule() as $schedule) { - $this->recur($recurring, $schedule); + if (!$model = $recurring->recurable) { + continue; + } + + foreach ($recurring->getRecurringSchedule() as $schedule) { + $schedule_date = Date::parse($schedule->getStart()->format('Y-m-d')); + + \DB::transaction(function () use ($model, $recurring, $schedule_date) { + $this->recur($model, $recurring->recurable_type, $schedule_date); + }); } } } @@ -75,97 +83,161 @@ class RecurringCheck extends Command setting()->forgetAll(); } - protected function recur($recurring, $schedule) + protected function recur($model, $type, $schedule_date) { - $schedule_date = Date::parse($schedule->getStart()->format('Y-m-d')); - - // Check if should recur today - if ($this->today->ne($schedule_date)) { + // Don't recur the future + if ($schedule_date->greaterThan($this->today)) { return; } - if (!$model = $recurring->recurable) { + if (!$clone = $this->getClone($model, $schedule_date)) { return; } - switch ($recurring->recurable_type) { + switch ($type) { case 'App\Models\Purchase\Bill': - if (!$clone = $this->getDocumentClone($model, 'billed_at')) { - break; - } - event(new BillCreated($clone)); event(new BillRecurring($clone)); break; case 'App\Models\Sale\Invoice': - if (!$clone = $this->getDocumentClone($model, 'invoiced_at')) { - break; - } - event(new InvoiceCreated($clone)); event(new InvoiceRecurring($clone)); break; case 'App\Models\Banking\Transaction': - // Skip model created on the same day, but scheduler hasn't run yet - if ($this->today->eq(Date::parse($model->paid_at->format('Y-m-d')))) { - break; - } + event(new TransactionCreated($clone)); - $model->cloneable_relations = []; - - // Create new record - $clone = $model->duplicate(); - - $clone->parent_id = $model->id; - $clone->paid_at = $this->today->format('Y-m-d'); - $clone->save(); + event(new TransactionRecurring($clone)); break; } } + /** + * Clone the model and return it. + * + * @param $model + * @param $schedule_date + * + * @return boolean|object + */ + protected function getClone($model, $schedule_date) + { + if ($this->skipThisClone($model, $schedule_date)) { + return false; + } + + $function = ($model instanceof Transaction) ? 'getTransactionClone' : 'getDocumentClone'; + + try { + return $this->$function($model, $schedule_date); + } catch (\Exception | \Throwable | \Swift_RfcComplianceException| \Swift_TransportException | \Illuminate\Database\QueryException $e) { + $this->error($e->getMessage()); + + logger('Recurring check:: ' . $e->getMessage()); + + return false; + } + } + /** * Clone the document and return it. * * @param $model - * @param $date_field + * @param $schedule_date * * @return boolean|object */ - protected function getDocumentClone($model, $date_field) + protected function getDocumentClone($model, $schedule_date) { - // Skip model created on the same day, but scheduler hasn't run yet - if ($this->today->eq(Date::parse($model->$date_field->format('Y-m-d')))) { - return false; - } - $model->cloneable_relations = ['items', 'totals']; - try { - $clone = $model->duplicate(); - } catch (\Exception | \Throwable | \Swift_RfcComplianceException | \Illuminate\Database\QueryException $e) { - $this->error($e->getMessage()); + $clone = $model->duplicate(); - logger('Recurring check:: ' . $e->getMessage()); - - return false; - } - - // Set original model id - $clone->parent_id = $model->id; + $date_field = $this->getDateField($model); // Days between issued and due date $diff_days = Date::parse($clone->due_at)->diffInDays(Date::parse($clone->$date_field)); - // Update dates - $clone->$date_field = $this->today->format('Y-m-d'); - $clone->due_at = $this->today->copy()->addDays($diff_days)->format('Y-m-d'); + $clone->parent_id = $model->id; + $clone->$date_field = $schedule_date->format('Y-m-d'); + $clone->due_at = $schedule_date->copy()->addDays($diff_days)->format('Y-m-d'); $clone->save(); return $clone; } -} + + /** + * Clone the transaction and return it. + * + * @param $model + * @param $schedule_date + * + * @return boolean|object + */ + protected function getTransactionClone($model, $schedule_date) + { + $model->cloneable_relations = []; + + $clone = $model->duplicate(); + + $clone->parent_id = $model->id; + $clone->paid_at = $schedule_date->format('Y-m-d'); + $clone->save(); + + return $clone; + } + + protected function skipThisClone($model, $schedule_date) + { + $date_field = $this->getDateField($model); + + // Skip model created on the same day, but scheduler hasn't run yet + if ($schedule_date->equalTo(Date::parse($model->$date_field->format('Y-m-d')))) { + return true; + } + + $table = $this->getTable($model); + + $already_cloned = \DB::table($table) + ->where('parent_id', $model->id) + ->whereDate($date_field, $schedule_date) + ->value('id'); + + // Skip if already cloned + if ($already_cloned) { + return true; + } + + return false; + } + + protected function getDateField($model) + { + if ($model instanceof Transaction) { + return 'paid_at'; + } + + if ($model instanceof Invoice) { + return 'invoiced_at'; + } + + return 'billed_at'; + } + + protected function getTable($model) + { + if ($model instanceof Transaction) { + return 'transactions'; + } + + if ($model instanceof Invoice) { + return 'invoices'; + } + + return 'bills'; + } +} \ No newline at end of file diff --git a/app/Events/Banking/TransactionRecurring.php b/app/Events/Banking/TransactionRecurring.php new file mode 100644 index 000000000..2622a5617 --- /dev/null +++ b/app/Events/Banking/TransactionRecurring.php @@ -0,0 +1,22 @@ +transaction = $transaction; + } +} \ No newline at end of file diff --git a/app/Traits/Recurring.php b/app/Traits/Recurring.php index bbbe671b5..271271f2c 100644 --- a/app/Traits/Recurring.php +++ b/app/Traits/Recurring.php @@ -59,18 +59,71 @@ trait Recurring ]); } - public function current() + public function getRecurringSchedule() { - if (!$schedule = $this->schedule()) { + $config = new ArrayTransformerConfig(); + $config->enableLastDayOfMonthFix(); + + $transformer = new ArrayTransformer(); + $transformer->setConfig($config); + + return $transformer->transform($this->getRecurringRule()); + } + + public function getRecurringRule() + { + $rule = (new Rule()) + ->setStartDate($this->getRecurringRuleStartDate()) + ->setTimezone($this->getRecurringRuleTimeZone()) + ->setFreq($this->getRecurringRuleFrequency()) + ->setInterval($this->getRecurringRuleInterval()); + + // 0 means infinite + if ($this->count != 0) { + $rule->setCount($this->getRecurringRuleCount()); + } + + return $rule; + } + + public function getRecurringRuleStartDate() + { + return new \DateTime($this->started_at, new \DateTimeZone($this->getRecurringRuleTimeZone())); + } + + public function getRecurringRuleTimeZone() + { + return setting('localisation.timezone'); + } + + public function getRecurringRuleCount() + { + // Fix for humans + return $this->count + 1; + } + + public function getRecurringRuleFrequency() + { + return strtoupper($this->frequency); + } + + public function getRecurringRuleInterval() + { + return $this->interval; + } + + public function getCurrentRecurring() + { + if (!$schedule = $this->getRecurringSchedule()) { return false; } return $schedule->current()->getStart(); } - public function next() + public function getNextRecurring() { - if (!$schedule = $this->schedule()) { + if (!$schedule = $this->getRecurringSchedule()) { return false; } @@ -81,69 +134,21 @@ trait Recurring return $next->getStart(); } - public function first() + public function getFirstRecurring() { - if (!$schedule = $this->schedule()) { + if (!$schedule = $this->getRecurringSchedule()) { return false; } return $schedule->first()->getStart(); } - public function last() + public function getLastRecurring() { - if (!$schedule = $this->schedule()) { + if (!$schedule = $this->getRecurringSchedule()) { return false; } return $schedule->last()->getStart(); } - - public function schedule() - { - $config = new ArrayTransformerConfig(); - $config->enableLastDayOfMonthFix(); - - $transformer = new ArrayTransformer(); - $transformer->setConfig($config); - - return $transformer->transform($this->getRule()); - } - - public function getRule() - { - $rule = (new Rule()) - ->setStartDate($this->getRuleStartDate()) - ->setTimezone($this->getRuleTimeZone()) - ->setFreq($this->getRuleFrequency()) - ->setInterval($this->interval); - - // 0 means infinite - if ($this->count != 0) { - $rule->setCount($this->getRuleCount()); - } - - return $rule; - } - - public function getRuleStartDate() - { - return new \DateTime($this->started_at, new \DateTimeZone($this->getRuleTimeZone())); - } - - public function getRuleTimeZone() - { - return setting('localisation.timezone'); - } - - public function getRuleCount() - { - // Fix for humans - return $this->count + 1; - } - - public function getRuleFrequency() - { - return strtoupper($this->frequency); - } -} +} \ No newline at end of file diff --git a/app/Utilities/Recurring.php b/app/Utilities/Recurring.php index ee915ec46..2f2562cf4 100644 --- a/app/Utilities/Recurring.php +++ b/app/Utilities/Recurring.php @@ -16,9 +16,9 @@ class Recurring continue; } - foreach ($item->recurring->schedule() as $recurr) { + foreach ($item->recurring->getRecurringSchedule() as $schedule) { $issued = Date::parse($item->$issued_date_field); - $start = $recurr->getStart(); + $start = $schedule->getStart(); if ($issued->format('Y') != $start->format('Y')) { continue; diff --git a/resources/views/purchases/bills/show.blade.php b/resources/views/purchases/bills/show.blade.php index ed597f760..7f42610c7 100644 --- a/resources/views/purchases/bills/show.blade.php +++ b/resources/views/purchases/bills/show.blade.php @@ -4,7 +4,7 @@ @section('content') @stack('recurring_message_start') - @if (($recurring = $bill->recurring) && ($next = $recurring->next())) + @if (($recurring = $bill->recurring) && ($next = $recurring->getNextRecurring()))
diff --git a/resources/views/purchases/payments/edit.blade.php b/resources/views/purchases/payments/edit.blade.php index ad19b9955..5b5799fe7 100644 --- a/resources/views/purchases/payments/edit.blade.php +++ b/resources/views/purchases/payments/edit.blade.php @@ -3,7 +3,7 @@ @section('title', trans('general.title.edit', ['type' => trans_choice('general.payments', 1)])) @section('content') - @if (($recurring = $payment->recurring) && ($next = $recurring->next())) + @if (($recurring = $payment->recurring) && ($next = $recurring->getNextRecurring()))
diff --git a/resources/views/sales/invoices/show.blade.php b/resources/views/sales/invoices/show.blade.php index a57843b54..0411052bc 100644 --- a/resources/views/sales/invoices/show.blade.php +++ b/resources/views/sales/invoices/show.blade.php @@ -4,7 +4,7 @@ @section('content') @stack('recurring_message_start') - @if (($recurring = $invoice->recurring) && ($next = $recurring->next())) + @if (($recurring = $invoice->recurring) && ($next = $recurring->getNextRecurring()))
diff --git a/resources/views/sales/revenues/edit.blade.php b/resources/views/sales/revenues/edit.blade.php index 8d547b717..e11c41f02 100644 --- a/resources/views/sales/revenues/edit.blade.php +++ b/resources/views/sales/revenues/edit.blade.php @@ -3,7 +3,7 @@ @section('title', trans('general.title.edit', ['type' => trans_choice('general.revenues', 1)])) @section('content') - @if (($recurring = $revenue->recurring) && ($next = $recurring->next())) + @if (($recurring = $revenue->recurring) && ($next = $recurring->getNextRecurring()))