From 4b07504835e4e531821ad0df3d91a8f75bdcf72a Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 15 Oct 2015 17:14:13 +0300 Subject: [PATCH] Reworked recurrenc code using Recurr --- .../Commands/SendRecurringInvoices.php | 2 +- app/Models/Account.php | 23 +++- app/Models/Invoice.php | 114 +++++++++++++++++- app/Ninja/Mailers/Mailer.php | 2 +- composer.json | 3 +- composer.lock | 99 +++++++++++---- public/css/built.css | 2 +- public/css/style.css | 2 +- readme.md | 3 +- resources/lang/en/texts.php | 5 +- resources/views/invoices/edit.blade.php | 13 +- 11 files changed, 234 insertions(+), 34 deletions(-) diff --git a/app/Console/Commands/SendRecurringInvoices.php b/app/Console/Commands/SendRecurringInvoices.php index 81228ce5b96b..2c8d93959bd7 100644 --- a/app/Console/Commands/SendRecurringInvoices.php +++ b/app/Console/Commands/SendRecurringInvoices.php @@ -33,7 +33,7 @@ class SendRecurringInvoices extends Command $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 start_date <= ? AND (end_date IS NULL OR end_date >= ?)', array($today, $today))->get(); + ->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS TRUE AND frequency_id > 0 AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', array($today, $today))->get(); $this->info(count($invoices).' recurring invoice(s) found'); foreach ($invoices as $recurInvoice) { diff --git a/app/Models/Account.php b/app/Models/Account.php index a06ad887b882..51bb3550f80e 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -160,6 +160,25 @@ class Account extends Eloquent } } + public function getDateTime() + { + return new \DateTime('now', new \DateTimeZone($this->getTimezone())); + } + + public function getCustomDateFormat() + { + return $this->date_format ? $this->date_format->format : DEFAULT_DATE_FORMAT; + } + + public function formatDate($date) + { + if (!$date) { + return null; + } + + return $date->format($this->getCustomDateFormat()); + } + public function getGatewayByType($type = PAYMENT_TYPE_ANY) { foreach ($this->account_gateways as $gateway) { @@ -268,7 +287,9 @@ class Account extends Eloquent { $this->load('timezone', 'date_format', 'datetime_format', 'language'); - Session::put(SESSION_TIMEZONE, $this->timezone ? $this->timezone->name : DEFAULT_TIMEZONE); + $timezone = $this->timezone ? $this->timezone->name : DEFAULT_TIMEZONE; + Session::put(SESSION_TIMEZONE, $timezone); + Session::put(SESSION_DATE_FORMAT, $this->date_format ? $this->date_format->format : DEFAULT_DATE_FORMAT); Session::put(SESSION_DATE_PICKER_FORMAT, $this->date_format ? $this->date_format->picker_format : DEFAULT_DATE_PICKER_FORMAT); diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index f407cc3a972f..18bd7ebe753d 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -2,6 +2,7 @@ use Utils; use DateTime; +use Carbon; use Illuminate\Database\Eloquent\SoftDeletes; class Invoice extends EntityModel @@ -216,6 +217,115 @@ class Invoice extends EntityModel return $this; } + public function shouldSendToday() + { + if (!$nextSendDate = $this->getNextSendDate()) { + return false; + } + + return $this->account->getDateTime() >= $nextSendDate; + } + + public function getSchedule() + { + if (!$this->start_date || !$this->is_recurring || !$this->frequency_id) { + return false; + } + + $timezone = $this->account->getTimezone(); + $startDate = $this->last_sent_date ?: $this->start_date; + $startDate = new \DateTime($startDate . ' 12:00:00', new \DateTimeZone($timezone)); + $endDate = $this->end_date ? new \DateTime($this->end_date, new \DateTimeZone($timezone)) : null; + + $rule = $this->getRecurrenceRule(); + $rule = new \Recurr\Rule("{$rule}", $startDate, $endDate, $timezone); + + // Fix for months with less than 31 days + $transformerConfig = new \Recurr\Transformer\ArrayTransformerConfig(); + $transformerConfig->enableLastDayOfMonthFix(); + + $transformer = new \Recurr\Transformer\ArrayTransformer(); + $transformer->setConfig($transformerConfig); + $dates = $transformer->transform($rule); + + if (count($dates) < 2) { + return false; + } + + return $dates; + } + + public function getNextSendDate() + { + if ($this->start_date && !$this->last_sent_date) { + $timezone = $this->account->getTimezone(); + return new \DateTime($this->start_date . ' 12:00:00', new \DateTimeZone($timezone)); + } + + if (!$schedule = $this->getSchedule()) { + return null; + } + + if (count($schedule) < 2) { + return null; + } + + return $schedule[1]->getStart(); + } + + public function getPrettySchedule($min = 1, $max = 10) + { + if (!$schedule = $this->getSchedule($max)) { + return null; + } + + $dates = []; + + for ($i=$min; $iaccount->formatDate($date->getStart()); + $dates[] = $date; + } + + return implode('
', $dates); + } + + 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_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->end_date; + } + + return $rule; + } + + /* public function shouldSendToday() { if (!$this->start_date || strtotime($this->start_date) > strtotime('now')) { @@ -267,8 +377,10 @@ class Invoice extends EntityModel return false; } + */ - public function getReminder() { + public function getReminder() + { for ($i=1; $i<=3; $i++) { $field = "enable_reminder{$i}"; if (!$this->account->$field) { diff --git a/app/Ninja/Mailers/Mailer.php b/app/Ninja/Mailers/Mailer.php index c258216b33ea..db65799a3dea 100644 --- a/app/Ninja/Mailers/Mailer.php +++ b/app/Ninja/Mailers/Mailer.php @@ -63,7 +63,7 @@ class Mailer private function handleFailure($exception) { - if (isset($_ENV['POSTMARK_API_TOKEN'])) { + if (isset($_ENV['POSTMARK_API_TOKEN']) && $exception->getResponse()) { $response = $exception->getResponse()->getBody()->getContents(); $response = json_decode($response); $emailError = nl2br($response->Message); diff --git a/composer.json b/composer.json index 4427ad4a1d00..81e4aa6888f4 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,8 @@ "laravelcollective/html": "~5.0", "wildbit/laravel-postmark-provider": "dev-master", "Dwolla/omnipay-dwolla": "dev-master", - "laravel/socialite": "~2.0" + "laravel/socialite": "~2.0", + "simshaun/recurr": "dev-master" }, "require-dev": { "phpunit/phpunit": "~4.0", diff --git a/composer.lock b/composer.lock index 7c1534c5c2ea..c38eb0f70ce5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "48ed13e4113a177339d3a20f31dbc6bb", + "hash": "f76cd603e0ffaf0499cefa858ae14d07", "packages": [ { "name": "alfaproject/omnipay-neteller", @@ -501,16 +501,16 @@ }, { "name": "coatesap/omnipay-paymentsense", - "version": "v2.0.0", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/coatesap/omnipay-paymentsense.git", - "reference": "4a5a87ef140abf8e09ff27cd0e6502ac1e79e434" + "reference": "664e00a726b99b65b08381f8409263795f2986a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/coatesap/omnipay-paymentsense/zipball/4a5a87ef140abf8e09ff27cd0e6502ac1e79e434", - "reference": "4a5a87ef140abf8e09ff27cd0e6502ac1e79e434", + "url": "https://api.github.com/repos/coatesap/omnipay-paymentsense/zipball/664e00a726b99b65b08381f8409263795f2986a2", + "reference": "664e00a726b99b65b08381f8409263795f2986a2", "shasum": "" }, "require": { @@ -530,8 +530,8 @@ } }, "autoload": { - "psr-0": { - "Omnipay\\PaymentSense\\": "src/" + "psr-4": { + "Coatesap\\PaymentSense\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -545,8 +545,9 @@ } ], "description": "PaymentSense driver for the Omnipay payment processing library", - "homepage": "https://github.com/coatesap/paymentsense", + "homepage": "https://github.com/coatesap/omnipay-paymentsense", "keywords": [ + "driver", "gateway", "merchant", "omnipay", @@ -555,7 +556,7 @@ "payment sense", "paymentsense" ], - "time": "2014-03-18 17:17:57" + "time": "2015-10-13 07:08:13" }, { "name": "coatesap/omnipay-realex", @@ -2468,16 +2469,16 @@ }, { "name": "monolog/monolog", - "version": "1.17.1", + "version": "1.17.2", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "0524c87587ab85bc4c2d6f5b41253ccb930a5422" + "reference": "bee7f0dc9c3e0b69a6039697533dca1e845c8c24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/0524c87587ab85bc4c2d6f5b41253ccb930a5422", - "reference": "0524c87587ab85bc4c2d6f5b41253ccb930a5422", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bee7f0dc9c3e0b69a6039697533dca1e845c8c24", + "reference": "bee7f0dc9c3e0b69a6039697533dca1e845c8c24", "shasum": "" }, "require": { @@ -2491,10 +2492,11 @@ "aws/aws-sdk-php": "^2.4.9", "doctrine/couchdb": "~1.0@dev", "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", "php-console/php-console": "^3.1.3", "phpunit/phpunit": "~4.5", "phpunit/phpunit-mock-objects": "2.3.0", - "raven/raven": "~0.11", + "raven/raven": "^0.13", "ruflin/elastica": ">=0.90 <3.0", "swiftmailer/swiftmailer": "~5.3", "videlalvaro/php-amqplib": "~2.4" @@ -2540,7 +2542,7 @@ "logging", "psr-3" ], - "time": "2015-08-31 09:17:37" + "time": "2015-10-14 12:51:02" }, { "name": "mtdowling/cron-expression", @@ -4717,6 +4719,60 @@ "description": "A lightweight implementation of CommonJS Promises/A for PHP", "time": "2015-07-03 13:48:55" }, + { + "name": "simshaun/recurr", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/simshaun/recurr.git", + "reference": "202c067b73c6630763dcb8e3d98576d7a5fb838c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/simshaun/recurr/zipball/202c067b73c6630763dcb8e3d98576d7a5fb838c", + "reference": "202c067b73c6630763dcb8e3d98576d7a5fb838c", + "shasum": "" + }, + "require": { + "doctrine/collections": "~1.3", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Recurr": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Shaun Simmons", + "email": "shaun@envysphere.com", + "homepage": "http://envysphere.com" + } + ], + "description": "PHP library for working with recurrence rules", + "homepage": "https://github.com/simshaun/recurr", + "keywords": [ + "dates", + "events", + "recurrence", + "recurring", + "rrule" + ], + "time": "2015-10-01 06:06:14" + }, { "name": "swiftmailer/swiftmailer", "version": "v5.4.1", @@ -6439,16 +6495,16 @@ }, { "name": "phpunit/phpunit", - "version": "4.8.12", + "version": "4.8.13", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "00194eb95989190a73198390ceca081ad3441a7f" + "reference": "be067d6105286b74272facefc2697038f8807b77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/00194eb95989190a73198390ceca081ad3441a7f", - "reference": "00194eb95989190a73198390ceca081ad3441a7f", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/be067d6105286b74272facefc2697038f8807b77", + "reference": "be067d6105286b74272facefc2697038f8807b77", "shasum": "" }, "require": { @@ -6507,7 +6563,7 @@ "testing", "xunit" ], - "time": "2015-10-12 03:36:47" + "time": "2015-10-14 13:49:40" }, { "name": "phpunit/phpunit-mock-objects", @@ -7159,7 +7215,8 @@ "alfaproject/omnipay-skrill": 20, "omnipay/bitpay": 20, "wildbit/laravel-postmark-provider": 20, - "dwolla/omnipay-dwolla": 20 + "dwolla/omnipay-dwolla": 20, + "simshaun/recurr": 20 }, "prefer-stable": false, "prefer-lowest": false, diff --git a/public/css/built.css b/public/css/built.css index c38513509c69..082eae038b70 100644 --- a/public/css/built.css +++ b/public/css/built.css @@ -3362,7 +3362,7 @@ ul.user-accounts a:hover div.remove { visibility: visible; } -.tooltip-inner { +.invoice-contact .tooltip-inner { text-align:left; width: 350px; } diff --git a/public/css/style.css b/public/css/style.css index db54a785c033..b0d192373c27 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1012,7 +1012,7 @@ ul.user-accounts a:hover div.remove { visibility: visible; } -.tooltip-inner { +.invoice-contact .tooltip-inner { text-align:left; width: 350px; } diff --git a/readme.md b/readme.md index 446805cfe4ac..908c59265ea9 100644 --- a/readme.md +++ b/readme.md @@ -78,4 +78,5 @@ If you'd like to use our code to sell your own invoicing app email us for detail * [caouecs/Laravel4-long](https://github.com/caouecs/Laravel4-lang) - List of languages ​​for Laravel4 * [bgrins/spectrum](https://github.com/bgrins/spectrum) - The No Hassle JavaScript Colorpicker * [lokesh/lightbox2](https://github.com/lokesh/lightbox2/) - The original lightbox script -* [josdejong/jsoneditor](https://github.com/josdejong/jsoneditor/) - A web-based tool to view, edit and format JSON \ No newline at end of file +* [josdejong/jsoneditor](https://github.com/josdejong/jsoneditor/) - A web-based tool to view, edit and format JSON +* [simshaun/recurr](https://github.com/simshaun/recurr) - PHP library for working with recurrence rules \ No newline at end of file diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 9b1602c5b1f9..51f1da60eac3 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -789,7 +789,7 @@ return array( 'referral_program' => 'Referral Program', 'referral_code' => 'Referral Code', - 'last_sent_on' => 'Last sent on :date', + 'last_sent_on' => 'Sent last: :date', 'page_expire' => 'This page will expire soon, :click_here to keep working', 'upcoming_quotes' => 'Upcoming Quotes', @@ -822,5 +822,8 @@ return array( 'pro' => 'Pro', 'gateways' => 'Payment Gateways', + 'next_send_on' => 'Send next: :date', + + ); diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 7741ca4ce732..43ad79f3f355 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -65,7 +65,7 @@ @endif -
+