improved recurring
This commit is contained in:
		| @@ -12,7 +12,6 @@ use App\Models\Common\Recurring; | ||||
| use App\Models\Document\Document; | ||||
| use App\Utilities\Date; | ||||
| use Illuminate\Console\Command; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Str; | ||||
|  | ||||
| @@ -39,6 +38,8 @@ class RecurringCheck extends Command | ||||
|      */ | ||||
|     public function handle() | ||||
|     { | ||||
|         $this->info('Checking for recurring...'); | ||||
|  | ||||
|         // Bind to container | ||||
|         app()->instance(static::class, $this); | ||||
|  | ||||
| @@ -56,8 +57,6 @@ class RecurringCheck extends Command | ||||
|  | ||||
|         //$this->info('Total recurring: ' . $recurring->count()); | ||||
|  | ||||
|         $today = Date::today(); | ||||
|  | ||||
|         foreach ($recurring as $recur) { | ||||
|             if (empty($recur->company)) { | ||||
|                 $this->info('Missing company.'); | ||||
| @@ -103,7 +102,7 @@ class RecurringCheck extends Command | ||||
|  | ||||
|             company($recur->company_id)->makeCurrent(); | ||||
|  | ||||
|             if (! $model = $recur->recurable) { | ||||
|             if (! $template = $recur->recurable) { | ||||
|                 $this->info('Missing model.'); | ||||
|  | ||||
|                 $recur->delete(); | ||||
| @@ -111,41 +110,28 @@ class RecurringCheck extends Command | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             $children_count = $this->getChildrenCount($model); | ||||
|             $this->info('Template ID: ' . $template->id); | ||||
|  | ||||
|             $schedules = $recur->getRecurringSchedule(); | ||||
|             $schedule_count = $schedules->count(); | ||||
|  | ||||
|             // All recurring created, including today | ||||
|             if ($children_count > ($schedule_count - 1)) { | ||||
|                 $this->info('All recurring created.'); | ||||
|             // Check if all schedules created | ||||
|             if ($this->getChildrenCount($template) == $schedules->count()) { | ||||
|                 $this->info('All schedules created.'); | ||||
|  | ||||
|                 $recur->update(['status' => Recurring::COMPLETE_STATUS]); | ||||
|  | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             // Recur only today | ||||
|             if ($children_count == ($schedule_count - 1)) { | ||||
|                 $this->info('Recur only today.'); | ||||
|             // Recur remaining schedules, including the previously failed ones | ||||
|             $schedules = $this->getRemainingSchedules($recur, $template, $schedules); | ||||
|  | ||||
|                 $this->recur($model, $recur->recurable_type, $today); | ||||
|  | ||||
|                 $recur->update(['status' => Recurring::COMPLETE_STATUS]); | ||||
|  | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             // Don't create records for the future | ||||
|             $schedules = $schedules->endsBefore($recur->getRecurringRuleTomorrowDate()); | ||||
|  | ||||
|             // Recur all schedules, including the previously failed ones | ||||
|             foreach ($schedules as $schedule) { | ||||
|                 $this->info('Recur all schedules.'); | ||||
|  | ||||
|                 $schedule_date = Date::parse($schedule->getStart()->format('Y-m-d')); | ||||
|  | ||||
|                 $this->recur($model, $recur->recurable_type, $schedule_date); | ||||
|                 $this->info('Schedule date: ' . $schedule_date); | ||||
|  | ||||
|                 $this->recur($template, $schedule_date); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -153,27 +139,31 @@ class RecurringCheck extends Command | ||||
|  | ||||
|         // Remove from container | ||||
|         app()->forgetInstance(static::class); | ||||
|  | ||||
|         $this->info('Recurring check done!'); | ||||
|     } | ||||
|  | ||||
|     protected function recur($model, $type, $schedule_date) | ||||
|     protected function recur($template, $schedule_date) | ||||
|     { | ||||
|         DB::transaction(function () use ($model, $type, $schedule_date) { | ||||
|             /** @var Document|Transaction $clone */ | ||||
|             if (! $clone = $this->getClone($model, $schedule_date)) { | ||||
|         DB::transaction(function () use ($template, $schedule_date) { | ||||
|             /** @var Document|Transaction $model */ | ||||
|             if (! $model = $this->getModel($template, $schedule_date)) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             switch ($type) { | ||||
|                 case 'App\Models\Document\Document': | ||||
|                     event(new DocumentCreated($clone, request())); | ||||
|             $this->info('Model created: ' . $model->id); | ||||
|  | ||||
|                     event(new DocumentRecurring($clone)); | ||||
|             switch ($template::class) { | ||||
|                 case Document::class: | ||||
|                     event(new DocumentCreated($model, request())); | ||||
|  | ||||
|                     event(new DocumentRecurring($model)); | ||||
|  | ||||
|                     break; | ||||
|                 case 'App\Models\Banking\Transaction': | ||||
|                     event(new TransactionCreated($clone)); | ||||
|                 case Transaction::class: | ||||
|                     event(new TransactionCreated($model)); | ||||
|  | ||||
|                     event(new TransactionRecurring($clone)); | ||||
|                     event(new TransactionRecurring($model)); | ||||
|  | ||||
|                     break; | ||||
|             } | ||||
| @@ -181,23 +171,19 @@ class RecurringCheck extends Command | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Clone the model and return it. | ||||
|      * Clone the template and return the real model. | ||||
|      * | ||||
|      * @param  $model | ||||
|      * @param  $schedule_date | ||||
|      * | ||||
|      * @return boolean|object | ||||
|      */ | ||||
|     protected function getClone($model, $schedule_date) | ||||
|     protected function getModel($template, $schedule_date) | ||||
|     { | ||||
|         if ($this->skipThisClone($model, $schedule_date)) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         $function = ($model instanceof Transaction) ? 'getTransactionClone' : 'getDocumentClone'; | ||||
|         $function = ($template instanceof Transaction) ? 'getTransactionModel' : 'getDocumentModel'; | ||||
|  | ||||
|         try { | ||||
|             return $this->$function($model, $schedule_date); | ||||
|             return $this->$function($template, $schedule_date); | ||||
|         } catch (\Throwable $e) { | ||||
|             $this->error($e->getMessage()); | ||||
|  | ||||
| @@ -207,92 +193,79 @@ class RecurringCheck extends Command | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Clone the document and return it. | ||||
|      * | ||||
|      * @param  $model | ||||
|      * @param  $schedule_date | ||||
|      * | ||||
|      * @return boolean|object | ||||
|      */ | ||||
|     protected function getDocumentClone($model, $schedule_date) | ||||
|     protected function getDocumentModel(Document $template, $schedule_date): Document | ||||
|     { | ||||
|         $model->cloneable_relations = ['items', 'totals']; | ||||
|         $template->cloneable_relations = ['items', 'totals']; | ||||
|  | ||||
|         $clone = $model->duplicate(); | ||||
|         $model = $template->duplicate(); | ||||
|  | ||||
|         // Days between issued and due date | ||||
|         $diff_days = Date::parse($model->due_at)->diffInDays(Date::parse($model->issued_at)); | ||||
|         $diff_days = Date::parse($template->due_at)->diffInDays(Date::parse($template->issued_at)); | ||||
|  | ||||
|         $clone->type = $this->getRealType($clone->type); | ||||
|         $clone->parent_id = $model->id; | ||||
|         $clone->issued_at = $schedule_date->format('Y-m-d'); | ||||
|         $clone->due_at = $schedule_date->copy()->addDays($diff_days)->format('Y-m-d'); | ||||
|         $clone->created_from = 'core::recurring'; | ||||
|         $clone->save(); | ||||
|         $model->type = $this->getRealType($template->type); | ||||
|         $model->parent_id = $template->id; | ||||
|         $model->issued_at = $schedule_date->format('Y-m-d'); | ||||
|         $model->due_at = $schedule_date->copy()->addDays($diff_days)->format('Y-m-d'); | ||||
|         $model->created_from = 'core::recurring'; | ||||
|         $model->save(); | ||||
|  | ||||
|         return $clone; | ||||
|         return $model; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Clone the transaction and return it. | ||||
|      * | ||||
|      * @param  $model | ||||
|      * @param  $schedule_date | ||||
|      * | ||||
|      * @return boolean|object | ||||
|      */ | ||||
|     protected function getTransactionClone($model, $schedule_date) | ||||
|     protected function getTransactionModel(Transaction $template, $schedule_date): Transaction | ||||
|     { | ||||
|         $model->cloneable_relations = []; | ||||
|         $template->cloneable_relations = []; | ||||
|  | ||||
|         $clone = $model->duplicate(); | ||||
|         $model = $template->duplicate(); | ||||
|  | ||||
|         $clone->type = $this->getRealType($clone->type); | ||||
|         $clone->parent_id = $model->id; | ||||
|         $clone->paid_at = $schedule_date->format('Y-m-d'); | ||||
|         $clone->created_from = 'core::recurring'; | ||||
|         $clone->save(); | ||||
|         $model->type = $this->getRealType($template->type); | ||||
|         $model->parent_id = $template->id; | ||||
|         $model->paid_at = $schedule_date->format('Y-m-d'); | ||||
|         $model->created_from = 'core::recurring'; | ||||
|         $model->save(); | ||||
|  | ||||
|         return $clone; | ||||
|         return $model; | ||||
|     } | ||||
|  | ||||
|     protected function skipThisClone($model, $schedule_date) | ||||
|     protected function getRemainingSchedules($recur, $template, $schedules) | ||||
|     { | ||||
|         $date_field = $this->getDateField($model); | ||||
|         // Don't create schedules for the future | ||||
|         $schedules = $schedules->endsBefore($recur->getRecurringRuleTomorrowDate()); | ||||
|  | ||||
|         // 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; | ||||
|         } | ||||
|         $date_field = $this->getDateField($template); | ||||
|  | ||||
|         $already_cloned = DB::table($model->getTable()) | ||||
|                                 ->where('parent_id', $model->id) | ||||
|                                 ->whereDate($date_field, $schedule_date) | ||||
|                                 ->value('id'); | ||||
|         $created_schedules = DB::table($template->getTable()) | ||||
|                                 ->where('type', $this->getRealType($template->type)) | ||||
|                                 ->where('parent_id', $template->id) | ||||
|                                 ->get($date_field) | ||||
|                                 ->transform(function ($item, $key) use ($date_field) { | ||||
|                                     return Date::parse($item->$date_field)->format('Y-m-d'); | ||||
|                                 }) | ||||
|                                 ->toArray(); | ||||
|  | ||||
|         // Skip if already cloned | ||||
|         if ($already_cloned) { | ||||
|             return true; | ||||
|         } | ||||
|         // Skip already created schedules | ||||
|         $schedules = $schedules->filter(function ($recurrence) use ($created_schedules) { | ||||
|             return ! in_array($recurrence->getStart()->format('Y-m-d'), $created_schedules); | ||||
|         }); | ||||
|  | ||||
|         return false; | ||||
|         return $schedules; | ||||
|     } | ||||
|  | ||||
|     protected function getChildrenCount($model) | ||||
|     protected function getChildrenCount($template) | ||||
|     { | ||||
|         return DB::table($model->getTable()) | ||||
|             ->where('parent_id', $model->id) | ||||
|             ->count(); | ||||
|         return DB::table($template->getTable()) | ||||
|                     ->where('type', $this->getRealType($template->type)) | ||||
|                     ->where('parent_id', $template->id) | ||||
|                     ->count(); | ||||
|     } | ||||
|  | ||||
|     protected function getDateField($model) | ||||
|     protected function getDateField($template) | ||||
|     { | ||||
|         if ($model instanceof Transaction) { | ||||
|         if ($template instanceof Transaction) { | ||||
|             return 'paid_at'; | ||||
|         } | ||||
|  | ||||
|         if ($model instanceof Document) { | ||||
|         if ($template instanceof Document) { | ||||
|             return 'issued_at'; | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -148,8 +148,7 @@ trait Recurring | ||||
|  | ||||
|     public function getRecurringRuleCount() | ||||
|     { | ||||
|         // Fix for humans | ||||
|         return $this->limit_count + 1; | ||||
|         return $this->limit_count; | ||||
|     } | ||||
|  | ||||
|     public function getRecurringRuleFrequency() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user