From 1baaa76a0075a0211d9e47ed3e9666a7851856f5 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 26 Jun 2017 12:45:42 +0300 Subject: [PATCH] Working on recurring expenses --- .../Commands/SendRecurringInvoices.php | 58 +++++++- .../RecurringExpenseController.php | 6 +- app/Http/routes.php | 1 + app/Models/Invoice.php | 119 +--------------- app/Models/RecurringExpense.php | 2 + app/Models/Traits/HasRecurrence.php | 129 ++++++++++++++++++ .../2017_06_19_111515_update_dark_mode.php | 2 +- 7 files changed, 195 insertions(+), 122 deletions(-) create mode 100644 app/Models/Traits/HasRecurrence.php diff --git a/app/Console/Commands/SendRecurringInvoices.php b/app/Console/Commands/SendRecurringInvoices.php index 70cf6efa2ec5..f637a43145c5 100644 --- a/app/Console/Commands/SendRecurringInvoices.php +++ b/app/Console/Commands/SendRecurringInvoices.php @@ -61,13 +61,21 @@ class SendRecurringInvoices extends Command public function fire() { $this->info(date('Y-m-d H:i:s') . ' Running SendRecurringInvoices...'); - $today = new DateTime(); if ($database = $this->option('database')) { config(['database.default' => $database]); } - // check for counter resets + $this->resetCounters(); + $this->createInvoices(); + $this->billInvoices(); + $this->createExpenses(); + + $this->info(date('Y-m-d H:i:s') . ' Done'); + } + + private function resetCounters() + { $accounts = Account::where('reset_counter_frequency_id', '>', 0) ->orderBy('id', 'asc') ->get(); @@ -75,6 +83,11 @@ class SendRecurringInvoices extends Command foreach ($accounts as $account) { $account->checkCounterReset(); } + } + + private function createInvoices() + { + $today = new DateTime(); $invoices = Invoice::with('account.timezone', 'invoice_items', 'client', 'user') ->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS TRUE AND is_public IS TRUE AND frequency_id > 0 AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', [$today, $today]) @@ -102,6 +115,11 @@ class SendRecurringInvoices extends Command } Auth::logout(); } + } + + private function billInvoices() + { + $today = new DateTime(); $delayedAutoBillInvoices = Invoice::with('account.timezone', 'recurring_invoice', 'invoice_items', 'client', 'user') ->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS FALSE AND is_public IS TRUE @@ -124,8 +142,42 @@ class SendRecurringInvoices extends Command Auth::logout(); } } + } - $this->info(date('Y-m-d H:i:s') . ' Done'); + private function createExpenses() + { + $today = new DateTime(); + + $expenses = Expense::with('client') + ->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', [$today, $today]) + ->orderBy('id', 'asc') + ->get(); + $this->info(count($expenses).' recurring expenses(s) found'); + + foreach ($expenses as $expense) { + $shouldSendToday = $expense->shouldSendToday(); + + if (! $shouldSendToday) { + continue; + } + + $this->info('Processing Expense: '. $expense->id); + + $this->recurringExpenseRepo->createRecurringExpense($expense); + + /* + $account = $expense->account; + //$account->loadLocalizationSettings($recurInvoice->client); + //Auth::loginUsingId($recurInvoice->user_id); + $invoice = $this->invoiceRepo->createRecurringInvoice($recurInvoice); + + if ($invoice && ! $invoice->isPaid()) { + $this->info('Sending Invoice'); + $this->mailer->sendInvoice($invoice); + } + Auth::logout(); + */ + } } /** diff --git a/app/Http/Controllers/RecurringExpenseController.php b/app/Http/Controllers/RecurringExpenseController.php index 0f20af6e2812..2f72077466b9 100644 --- a/app/Http/Controllers/RecurringExpenseController.php +++ b/app/Http/Controllers/RecurringExpenseController.php @@ -144,6 +144,10 @@ class RecurringExpenseController extends BaseController Session::flash('message', trans('texts.updated_recurring_expense')); + if (in_array(Input::get('action'), ['archive', 'delete', 'restore'])) { + return self::bulk(); + } + return redirect()->to($recurringExpense->getRoute()); } @@ -159,6 +163,6 @@ class RecurringExpenseController extends BaseController Session::flash('message', $message); } - return redirect()->to('/recurring_expenses'); + return $this->returnBulk($this->entityType, $action, $ids); } } diff --git a/app/Http/routes.php b/app/Http/routes.php index 2af1e65f2d5d..857ef75d6375 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -185,6 +185,7 @@ Route::group(['middleware' => ['lookup:user', 'auth:user']], function () { Route::post('recurring_expenses', 'RecurringExpenseController@store'); Route::put('recurring_expenses/{recurring_expenses}', 'RecurringExpenseController@update'); Route::get('recurring_expenses/{recurring_expenses}/edit', 'RecurringExpenseController@edit'); + Route::get('recurring_expenses/{recurring_expenses}', 'RecurringExpenseController@edit'); Route::post('recurring_expenses/bulk', 'RecurringExpenseController@bulk'); Route::get('documents/{documents}/{filename?}', 'DocumentController@get'); diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index c914f933a759..6646c2772395 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -11,11 +11,11 @@ use App\Events\QuoteInvitationWasEmailed; use App\Libraries\CurlUtils; use App\Models\Activity; use App\Models\Traits\ChargesFees; +use App\Models\Traits\HasRecurrence; use DateTime; use Illuminate\Database\Eloquent\SoftDeletes; use Laracasts\Presenter\PresentableTrait; use Utils; -use Carbon; /** * Class Invoice. @@ -25,6 +25,7 @@ class Invoice extends EntityModel implements BalanceAffecting use PresentableTrait; use OwnedByClientTrait; use ChargesFees; + use HasRecurrence; use SoftDeletes { SoftDeletes::trashed as parentTrashed; } @@ -1117,122 +1118,6 @@ class Invoice extends EntityModel implements BalanceAffecting return implode('
', $dates); } - /** - * @return string - */ - private function getRecurrenceRule() - { - $rule = ''; - - switch ($this->frequency_id) { - case FREQUENCY_WEEKLY: - $rule = 'FREQ=WEEKLY;'; - break; - case FREQUENCY_TWO_WEEKS: - $rule = 'FREQ=WEEKLY;INTERVAL=2;'; - break; - case FREQUENCY_FOUR_WEEKS: - $rule = 'FREQ=WEEKLY;INTERVAL=4;'; - break; - case FREQUENCY_MONTHLY: - $rule = 'FREQ=MONTHLY;'; - break; - case FREQUENCY_TWO_MONTHS: - $rule = 'FREQ=MONTHLY;INTERVAL=2;'; - break; - case FREQUENCY_THREE_MONTHS: - $rule = 'FREQ=MONTHLY;INTERVAL=3;'; - break; - case FREQUENCY_SIX_MONTHS: - $rule = 'FREQ=MONTHLY;INTERVAL=6;'; - break; - case FREQUENCY_ANNUALLY: - $rule = 'FREQ=YEARLY;'; - break; - } - - if ($this->end_date) { - $rule .= 'UNTIL=' . $this->getOriginal('end_date'); - } - - return $rule; - } - - /* - public function shouldSendToday() - { - if (!$nextSendDate = $this->getNextSendDate()) { - return false; - } - - return $this->account->getDateTime() >= $nextSendDate; - } - */ - - /** - * @return bool - */ - public function shouldSendToday() - { - if (! $this->user->confirmed) { - return false; - } - - $account = $this->account; - $timezone = $account->getTimezone(); - - if (! $this->start_date || Carbon::parse($this->start_date, $timezone)->isFuture()) { - return false; - } - - if ($this->end_date && Carbon::parse($this->end_date, $timezone)->isPast()) { - return false; - } - - if (! $this->last_sent_date) { - return true; - } else { - $date1 = new DateTime($this->last_sent_date); - $date2 = new DateTime(); - $diff = $date2->diff($date1); - $daysSinceLastSent = $diff->format('%a'); - $monthsSinceLastSent = ($diff->format('%y') * 12) + $diff->format('%m'); - - // check we don't send a few hours early due to timezone difference - if (Carbon::now()->format('Y-m-d') != Carbon::now($timezone)->format('Y-m-d')) { - return false; - } - - // check we never send twice on one day - if ($daysSinceLastSent == 0) { - return false; - } - } - - switch ($this->frequency_id) { - case FREQUENCY_WEEKLY: - return $daysSinceLastSent >= 7; - case FREQUENCY_TWO_WEEKS: - return $daysSinceLastSent >= 14; - case FREQUENCY_FOUR_WEEKS: - return $daysSinceLastSent >= 28; - case FREQUENCY_MONTHLY: - return $monthsSinceLastSent >= 1; - case FREQUENCY_TWO_MONTHS: - return $monthsSinceLastSent >= 2; - case FREQUENCY_THREE_MONTHS: - return $monthsSinceLastSent >= 3; - case FREQUENCY_SIX_MONTHS: - return $monthsSinceLastSent >= 6; - case FREQUENCY_ANNUALLY: - return $monthsSinceLastSent >= 12; - default: - return false; - } - - return false; - } - /** * @return bool|string */ diff --git a/app/Models/RecurringExpense.php b/app/Models/RecurringExpense.php index 5c150a039590..5440d1e826cd 100644 --- a/app/Models/RecurringExpense.php +++ b/app/Models/RecurringExpense.php @@ -4,6 +4,7 @@ namespace App\Models; //use App\Events\ExpenseWasCreated; //use App\Events\ExpenseWasUpdated; +use App\Models\Traits\HasRecurrence; use Illuminate\Database\Eloquent\SoftDeletes; use Laracasts\Presenter\PresentableTrait; use Utils; @@ -16,6 +17,7 @@ class RecurringExpense extends EntityModel // Expenses use SoftDeletes; use PresentableTrait; + use HasRecurrence; /** * @var array diff --git a/app/Models/Traits/HasRecurrence.php b/app/Models/Traits/HasRecurrence.php new file mode 100644 index 000000000000..9757fa0e1758 --- /dev/null +++ b/app/Models/Traits/HasRecurrence.php @@ -0,0 +1,129 @@ +user->confirmed) { + return false; + } + + $account = $this->account; + $timezone = $account->getTimezone(); + + if (! $this->start_date || Carbon::parse($this->start_date, $timezone)->isFuture()) { + return false; + } + + if ($this->end_date && Carbon::parse($this->end_date, $timezone)->isPast()) { + return false; + } + + if (! $this->last_sent_date) { + return true; + } else { + $date1 = new DateTime($this->last_sent_date); + $date2 = new DateTime(); + $diff = $date2->diff($date1); + $daysSinceLastSent = $diff->format('%a'); + $monthsSinceLastSent = ($diff->format('%y') * 12) + $diff->format('%m'); + + // check we don't send a few hours early due to timezone difference + if (Carbon::now()->format('Y-m-d') != Carbon::now($timezone)->format('Y-m-d')) { + return false; + } + + // check we never send twice on one day + if ($daysSinceLastSent == 0) { + return false; + } + } + + switch ($this->frequency_id) { + case FREQUENCY_WEEKLY: + return $daysSinceLastSent >= 7; + case FREQUENCY_TWO_WEEKS: + return $daysSinceLastSent >= 14; + case FREQUENCY_FOUR_WEEKS: + return $daysSinceLastSent >= 28; + case FREQUENCY_MONTHLY: + return $monthsSinceLastSent >= 1; + case FREQUENCY_TWO_MONTHS: + return $monthsSinceLastSent >= 2; + case FREQUENCY_THREE_MONTHS: + return $monthsSinceLastSent >= 3; + case FREQUENCY_SIX_MONTHS: + return $monthsSinceLastSent >= 6; + case FREQUENCY_ANNUALLY: + return $monthsSinceLastSent >= 12; + default: + return false; + } + + return false; + } + + /** + * @return string + */ + private function getRecurrenceRule() + { + $rule = ''; + + switch ($this->frequency_id) { + case FREQUENCY_WEEKLY: + $rule = 'FREQ=WEEKLY;'; + break; + case FREQUENCY_TWO_WEEKS: + $rule = 'FREQ=WEEKLY;INTERVAL=2;'; + break; + case FREQUENCY_FOUR_WEEKS: + $rule = 'FREQ=WEEKLY;INTERVAL=4;'; + break; + case FREQUENCY_MONTHLY: + $rule = 'FREQ=MONTHLY;'; + break; + case FREQUENCY_TWO_MONTHS: + $rule = 'FREQ=MONTHLY;INTERVAL=2;'; + break; + case FREQUENCY_THREE_MONTHS: + $rule = 'FREQ=MONTHLY;INTERVAL=3;'; + break; + case FREQUENCY_SIX_MONTHS: + $rule = 'FREQ=MONTHLY;INTERVAL=6;'; + break; + case FREQUENCY_ANNUALLY: + $rule = 'FREQ=YEARLY;'; + break; + } + + if ($this->end_date) { + $rule .= 'UNTIL=' . $this->getOriginal('end_date'); + } + + return $rule; + } + + /* + public function shouldSendToday() + { + if (!$nextSendDate = $this->getNextSendDate()) { + return false; + } + + return $this->account->getDateTime() >= $nextSendDate; + } + */ + +} diff --git a/database/migrations/2017_06_19_111515_update_dark_mode.php b/database/migrations/2017_06_19_111515_update_dark_mode.php index 25764f72c233..6b4495a84a83 100644 --- a/database/migrations/2017_06_19_111515_update_dark_mode.php +++ b/database/migrations/2017_06_19_111515_update_dark_mode.php @@ -50,7 +50,7 @@ class UpdateDarkMode extends Migration $table->unsignedInteger('frequency_id'); $table->date('start_date')->nullable(); $table->date('end_date')->nullable(); - $table->timestamp('last_created_date')->nullable(); + $table->timestamp('last_sent_date')->nullable(); // Relations $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');