1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-18 09:04:35 +01:00

Reworked recurrenc code using Recurr

This commit is contained in:
Hillel Coren 2015-10-15 17:14:13 +03:00
parent a4960245c2
commit 4b07504835
11 changed files with 234 additions and 34 deletions

View File

@ -33,7 +33,7 @@ class SendRecurringInvoices extends Command
$today = new DateTime(); $today = new DateTime();
$invoices = Invoice::with('account.timezone', 'invoice_items', 'client', 'user') $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'); $this->info(count($invoices).' recurring invoice(s) found');
foreach ($invoices as $recurInvoice) { foreach ($invoices as $recurInvoice) {

View File

@ -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) public function getGatewayByType($type = PAYMENT_TYPE_ANY)
{ {
foreach ($this->account_gateways as $gateway) { foreach ($this->account_gateways as $gateway) {
@ -268,7 +287,9 @@ class Account extends Eloquent
{ {
$this->load('timezone', 'date_format', 'datetime_format', 'language'); $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_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); Session::put(SESSION_DATE_PICKER_FORMAT, $this->date_format ? $this->date_format->picker_format : DEFAULT_DATE_PICKER_FORMAT);

View File

@ -2,6 +2,7 @@
use Utils; use Utils;
use DateTime; use DateTime;
use Carbon;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
class Invoice extends EntityModel class Invoice extends EntityModel
@ -216,6 +217,115 @@ class Invoice extends EntityModel
return $this; 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; $i<min($max, count($schedule)); $i++) {
$date = $schedule[$i];
$date = $this->account->formatDate($date->getStart());
$dates[] = $date;
}
return implode('<br/>', $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() public function shouldSendToday()
{ {
if (!$this->start_date || strtotime($this->start_date) > strtotime('now')) { if (!$this->start_date || strtotime($this->start_date) > strtotime('now')) {
@ -267,8 +377,10 @@ class Invoice extends EntityModel
return false; return false;
} }
*/
public function getReminder() { public function getReminder()
{
for ($i=1; $i<=3; $i++) { for ($i=1; $i<=3; $i++) {
$field = "enable_reminder{$i}"; $field = "enable_reminder{$i}";
if (!$this->account->$field) { if (!$this->account->$field) {

View File

@ -63,7 +63,7 @@ class Mailer
private function handleFailure($exception) private function handleFailure($exception)
{ {
if (isset($_ENV['POSTMARK_API_TOKEN'])) { if (isset($_ENV['POSTMARK_API_TOKEN']) && $exception->getResponse()) {
$response = $exception->getResponse()->getBody()->getContents(); $response = $exception->getResponse()->getBody()->getContents();
$response = json_decode($response); $response = json_decode($response);
$emailError = nl2br($response->Message); $emailError = nl2br($response->Message);

View File

@ -38,7 +38,8 @@
"laravelcollective/html": "~5.0", "laravelcollective/html": "~5.0",
"wildbit/laravel-postmark-provider": "dev-master", "wildbit/laravel-postmark-provider": "dev-master",
"Dwolla/omnipay-dwolla": "dev-master", "Dwolla/omnipay-dwolla": "dev-master",
"laravel/socialite": "~2.0" "laravel/socialite": "~2.0",
"simshaun/recurr": "dev-master"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "~4.0", "phpunit/phpunit": "~4.0",

99
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"hash": "48ed13e4113a177339d3a20f31dbc6bb", "hash": "f76cd603e0ffaf0499cefa858ae14d07",
"packages": [ "packages": [
{ {
"name": "alfaproject/omnipay-neteller", "name": "alfaproject/omnipay-neteller",
@ -501,16 +501,16 @@
}, },
{ {
"name": "coatesap/omnipay-paymentsense", "name": "coatesap/omnipay-paymentsense",
"version": "v2.0.0", "version": "v2.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/coatesap/omnipay-paymentsense.git", "url": "https://github.com/coatesap/omnipay-paymentsense.git",
"reference": "4a5a87ef140abf8e09ff27cd0e6502ac1e79e434" "reference": "664e00a726b99b65b08381f8409263795f2986a2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/coatesap/omnipay-paymentsense/zipball/4a5a87ef140abf8e09ff27cd0e6502ac1e79e434", "url": "https://api.github.com/repos/coatesap/omnipay-paymentsense/zipball/664e00a726b99b65b08381f8409263795f2986a2",
"reference": "4a5a87ef140abf8e09ff27cd0e6502ac1e79e434", "reference": "664e00a726b99b65b08381f8409263795f2986a2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -530,8 +530,8 @@
} }
}, },
"autoload": { "autoload": {
"psr-0": { "psr-4": {
"Omnipay\\PaymentSense\\": "src/" "Coatesap\\PaymentSense\\": "src/"
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
@ -545,8 +545,9 @@
} }
], ],
"description": "PaymentSense driver for the Omnipay payment processing library", "description": "PaymentSense driver for the Omnipay payment processing library",
"homepage": "https://github.com/coatesap/paymentsense", "homepage": "https://github.com/coatesap/omnipay-paymentsense",
"keywords": [ "keywords": [
"driver",
"gateway", "gateway",
"merchant", "merchant",
"omnipay", "omnipay",
@ -555,7 +556,7 @@
"payment sense", "payment sense",
"paymentsense" "paymentsense"
], ],
"time": "2014-03-18 17:17:57" "time": "2015-10-13 07:08:13"
}, },
{ {
"name": "coatesap/omnipay-realex", "name": "coatesap/omnipay-realex",
@ -2468,16 +2469,16 @@
}, },
{ {
"name": "monolog/monolog", "name": "monolog/monolog",
"version": "1.17.1", "version": "1.17.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Seldaek/monolog.git", "url": "https://github.com/Seldaek/monolog.git",
"reference": "0524c87587ab85bc4c2d6f5b41253ccb930a5422" "reference": "bee7f0dc9c3e0b69a6039697533dca1e845c8c24"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/0524c87587ab85bc4c2d6f5b41253ccb930a5422", "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bee7f0dc9c3e0b69a6039697533dca1e845c8c24",
"reference": "0524c87587ab85bc4c2d6f5b41253ccb930a5422", "reference": "bee7f0dc9c3e0b69a6039697533dca1e845c8c24",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2491,10 +2492,11 @@
"aws/aws-sdk-php": "^2.4.9", "aws/aws-sdk-php": "^2.4.9",
"doctrine/couchdb": "~1.0@dev", "doctrine/couchdb": "~1.0@dev",
"graylog2/gelf-php": "~1.0", "graylog2/gelf-php": "~1.0",
"jakub-onderka/php-parallel-lint": "0.9",
"php-console/php-console": "^3.1.3", "php-console/php-console": "^3.1.3",
"phpunit/phpunit": "~4.5", "phpunit/phpunit": "~4.5",
"phpunit/phpunit-mock-objects": "2.3.0", "phpunit/phpunit-mock-objects": "2.3.0",
"raven/raven": "~0.11", "raven/raven": "^0.13",
"ruflin/elastica": ">=0.90 <3.0", "ruflin/elastica": ">=0.90 <3.0",
"swiftmailer/swiftmailer": "~5.3", "swiftmailer/swiftmailer": "~5.3",
"videlalvaro/php-amqplib": "~2.4" "videlalvaro/php-amqplib": "~2.4"
@ -2540,7 +2542,7 @@
"logging", "logging",
"psr-3" "psr-3"
], ],
"time": "2015-08-31 09:17:37" "time": "2015-10-14 12:51:02"
}, },
{ {
"name": "mtdowling/cron-expression", "name": "mtdowling/cron-expression",
@ -4717,6 +4719,60 @@
"description": "A lightweight implementation of CommonJS Promises/A for PHP", "description": "A lightweight implementation of CommonJS Promises/A for PHP",
"time": "2015-07-03 13:48:55" "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", "name": "swiftmailer/swiftmailer",
"version": "v5.4.1", "version": "v5.4.1",
@ -6439,16 +6495,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "4.8.12", "version": "4.8.13",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "00194eb95989190a73198390ceca081ad3441a7f" "reference": "be067d6105286b74272facefc2697038f8807b77"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/00194eb95989190a73198390ceca081ad3441a7f", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/be067d6105286b74272facefc2697038f8807b77",
"reference": "00194eb95989190a73198390ceca081ad3441a7f", "reference": "be067d6105286b74272facefc2697038f8807b77",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -6507,7 +6563,7 @@
"testing", "testing",
"xunit" "xunit"
], ],
"time": "2015-10-12 03:36:47" "time": "2015-10-14 13:49:40"
}, },
{ {
"name": "phpunit/phpunit-mock-objects", "name": "phpunit/phpunit-mock-objects",
@ -7159,7 +7215,8 @@
"alfaproject/omnipay-skrill": 20, "alfaproject/omnipay-skrill": 20,
"omnipay/bitpay": 20, "omnipay/bitpay": 20,
"wildbit/laravel-postmark-provider": 20, "wildbit/laravel-postmark-provider": 20,
"dwolla/omnipay-dwolla": 20 "dwolla/omnipay-dwolla": 20,
"simshaun/recurr": 20
}, },
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,

View File

@ -3362,7 +3362,7 @@ ul.user-accounts a:hover div.remove {
visibility: visible; visibility: visible;
} }
.tooltip-inner { .invoice-contact .tooltip-inner {
text-align:left; text-align:left;
width: 350px; width: 350px;
} }

View File

@ -1012,7 +1012,7 @@ ul.user-accounts a:hover div.remove {
visibility: visible; visibility: visible;
} }
.tooltip-inner { .invoice-contact .tooltip-inner {
text-align:left; text-align:left;
width: 350px; width: 350px;
} }

View File

@ -79,3 +79,4 @@ If you'd like to use our code to sell your own invoicing app email us for detail
* [bgrins/spectrum](https://github.com/bgrins/spectrum) - The No Hassle JavaScript Colorpicker * [bgrins/spectrum](https://github.com/bgrins/spectrum) - The No Hassle JavaScript Colorpicker
* [lokesh/lightbox2](https://github.com/lokesh/lightbox2/) - The original lightbox script * [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 * [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

View File

@ -789,7 +789,7 @@ return array(
'referral_program' => 'Referral Program', 'referral_program' => 'Referral Program',
'referral_code' => 'Referral Code', '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', 'page_expire' => 'This page will expire soon, :click_here to keep working',
'upcoming_quotes' => 'Upcoming Quotes', 'upcoming_quotes' => 'Upcoming Quotes',
@ -822,5 +822,8 @@ return array(
'pro' => 'Pro', 'pro' => 'Pro',
'gateways' => 'Payment Gateways', 'gateways' => 'Payment Gateways',
'next_send_on' => 'Send next: :date',
); );

View File

@ -65,7 +65,7 @@
</div> </div>
@endif @endif
<div data-bind="with: client"> <div data-bind="with: client" class="invoice-contact">
<div style="display:none" class="form-group" data-bind="visible: contacts().length > 0 &amp;&amp; (contacts()[0].email() || contacts()[0].first_name()), foreach: contacts"> <div style="display:none" class="form-group" data-bind="visible: contacts().length > 0 &amp;&amp; (contacts()[0].email() || contacts()[0].first_name()), foreach: contacts">
<div class="col-lg-8 col-lg-offset-4"> <div class="col-lg-8 col-lg-offset-4">
<label class="checkbox" data-bind="attr: {for: $index() + '_check'}" onclick="refreshPDF(true)"> <label class="checkbox" data-bind="attr: {for: $index() + '_check'}" onclick="refreshPDF(true)">
@ -113,9 +113,14 @@
<div class="pull-right" style="padding-top: 6px"> <div class="pull-right" style="padding-top: 6px">
{!! trans('texts.created_by_invoice', ['invoice' => link_to('/invoices/'.$invoice->recurring_invoice->public_id, trans('texts.recurring_invoice'))]) !!} {!! trans('texts.created_by_invoice', ['invoice' => link_to('/invoices/'.$invoice->recurring_invoice->public_id, trans('texts.recurring_invoice'))]) !!}
</div> </div>
@elseif ($invoice && isset($lastSent) && $lastSent) @elseif ($invoice)
<div class="pull-right" style="padding-top: 6px"> <div class="pull-right" style="padding-top: 6px">
{!! trans('texts.last_sent_on', ['date' => link_to('/invoices/'.$lastSent->public_id, $invoice->last_sent_date, ['id' => 'lastSent'])]) !!} @if (isset($lastSent) && $lastSent)
{!! trans('texts.last_sent_on', ['date' => link_to('/invoices/'.$lastSent->public_id, $invoice->last_sent_date, ['id' => 'lastSent'])]) !!} &nbsp;
@endif
@if ($invoice->is_recurring && $invoice->getNextSendDate())
{!! trans('texts.next_send_on', ['date' => '<span data-bind="tooltip: {title: \''.$invoice->getPrettySchedule().'\', html: true}">'.$account->formatDate($invoice->getNextSendDate()).'</span>']) !!}
@endif
</div> </div>
@endif @endif
@endif @endif
@ -827,7 +832,7 @@
} }
function onSaveClick() { function onSaveClick() {
if (model.invoice().is_recurring()) { if (model.invoice().is_recurring() && {{ $invoice ? 'false' : 'true' }}) {
if (confirm("{!! trans("texts.confirm_recurring_email_$entityType") !!}" + '\n\n' + getSendToEmails() + '\n' + "{!! trans("texts.confirm_recurring_timing") !!}")) { if (confirm("{!! trans("texts.confirm_recurring_email_$entityType") !!}" + '\n\n' + getSendToEmails() + '\n' + "{!! trans("texts.confirm_recurring_timing") !!}")) {
submitAction(''); submitAction('');
} }