Working on recurring invoices

This commit is contained in:
Hillel Coren 2013-12-10 19:18:35 +02:00
parent 78d2d749fb
commit 0611004e77
21 changed files with 381 additions and 103 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
/app/config/staging /app/config/staging
/app/config/development
/public/logo /public/logo
/bootstrap/compiled.php /bootstrap/compiled.php
/vendor /vendor

View File

@ -3,50 +3,68 @@
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Ninja\Mailers\ContactMailer as Mailer;
class SendRecurringInvoices extends Command { class SendRecurringInvoices extends Command {
/**
* The console command name.
*
* @var string
*/
protected $name = 'ninja:send-invoices'; protected $name = 'ninja:send-invoices';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send recurring invoices'; protected $description = 'Send recurring invoices';
protected $mailer;
/** public function __construct(Mailer $mailer)
* Create a new command instance.
*
* @return void
*/
public function __construct()
{ {
parent::__construct(); parent::__construct();
$this->mailer = $mailer;
} }
/**
* Execute the console command.
*
* @return void
*/
public function fire() public function fire()
{ {
$this->info('Running SendRecurringInvoices...'); $this->info(date('Y-m-d') . ' Running SendRecurringInvoices...');
$today = date('Y-m-d');
$invoices = Invoice::with('account', 'invoice_items')->whereRaw('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)
{
$this->info('Processing Invoice ' . $recurInvoice->id . ' - Should send ' . ($recurInvoice->shouldSendToday() ? 'YES' : 'NO'));
if (!$recurInvoice->shouldSendToday())
{
continue;
}
$invoice = Invoice::createNew($recurInvoice);
$invoice->client_id = $recurInvoice->client_id;
$invoice->parent_id = $recurInvoice->id;
$invoice->invoice_number = $recurInvoice->account->getNextInvoiceNumber();
$invoice->total = $recurInvoice->total;
$invoice->invoice_date = new DateTime();
$invoice->due_date = new DateTime();
$invoice->save();
foreach ($recurInvoice->invoice_items as $recurItem)
{
$item = InvoiceItem::createNew($recurItem);
$item->product_id = $recurItem->product_id;
$item->qty = $recurItem->qty;
$item->cost = $recurItem->cost;
$item->notes = Utils::processVariables($recurItem->notes);
$item->product_key = Utils::processVariables($recurItem->product_key);
$invoice->invoice_items()->save($item);
}
$recurInvoice->last_sent_date = new DateTime();
$recurInvoice->save();
$this->mailer->sendInvoice($invoice, $invoice->client->contacts()->first());
}
$this->info('Done'); $this->info('Done');
} }
/**
* Get the console command arguments.
*
* @return array
*/
protected function getArguments() protected function getArguments()
{ {
return array( return array(
@ -54,11 +72,6 @@ class SendRecurringInvoices extends Command {
); );
} }
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions() protected function getOptions()
{ {
return array( return array(

View File

@ -54,10 +54,10 @@ return array(
'mysql' => array( 'mysql' => array(
'driver' => 'mysql', 'driver' => 'mysql',
'host' => 'localhost', 'host' => getenv('DB_HOST'),
'database' => '', 'database' => getenv('DB_NAME'),
'username' => '', 'username' => getenv('DB_USER'),
'password' => '', 'password' => getenv('DB_PASS'),
'charset' => 'utf8', 'charset' => 'utf8',
'collation' => 'utf8_unicode_ci', 'collation' => 'utf8_unicode_ci',
'prefix' => '', 'prefix' => '',

View File

@ -1,12 +1,18 @@
<?php <?php
use Ninja\Mailers\ContactMailer as Mailer;
class InvoiceController extends \BaseController { class InvoiceController extends \BaseController {
/** protected $mailer;
* Display a listing of the resource.
* public function __construct(Mailer $mailer)
* @return Response {
*/ parent::__construct();
$this->mailer = $mailer;
}
public function index() public function index()
{ {
return View::make('list', array( return View::make('list', array(
@ -41,7 +47,7 @@ class InvoiceController extends \BaseController {
$table->addColumn('client', function($model) { return link_to('clients/' . $model->client_public_id, $model->client_name); }); $table->addColumn('client', function($model) { return link_to('clients/' . $model->client_public_id, $model->client_name); });
} }
return $table->addColumn('total', function($model){ return '$' . money_format('%i', $model->total); }) return $table->addColumn('total', function($model) { return '$' . money_format('%i', $model->total); })
->addColumn('balance', function($model) { return '$' . money_format('%i', $model->balance); }) ->addColumn('balance', function($model) { return '$' . money_format('%i', $model->balance); })
->addColumn('invoice_date', function($model) { return Utils::fromSqlDate($model->invoice_date); }) ->addColumn('invoice_date', function($model) { return Utils::fromSqlDate($model->invoice_date); })
->addColumn('due_date', function($model) { return Utils::fromSqlDate($model->due_date); }) ->addColumn('due_date', function($model) { return Utils::fromSqlDate($model->due_date); })
@ -364,6 +370,7 @@ class InvoiceController extends \BaseController {
$invoice = Invoice::createNew(); $invoice = Invoice::createNew();
} }
$invoice->client_id = $client->id;
$invoice->invoice_number = trim(Input::get('invoice_number')); $invoice->invoice_number = trim(Input::get('invoice_number'));
$invoice->discount = 0; $invoice->discount = 0;
$invoice->invoice_date = Utils::toSqlDate(Input::get('invoice_date')); $invoice->invoice_date = Utils::toSqlDate(Input::get('invoice_date'));
@ -442,23 +449,9 @@ class InvoiceController extends \BaseController {
*/ */
if ($action == 'email') if ($action == 'email')
{ {
$data = array('link' => URL::to('view') . '/' . $invoice->invoice_key); $this->mailer->sendInvoice($invoice, $contact);
/*
Mail::send(array('html'=>'emails.invoice_html','text'=>'emails.invoice_text'), $data, function($message) use ($contact)
{
$message->from('hillelcoren@gmail.com', 'Hillel Coren');
$message->to($contact->email);
});
*/
$invitation = Invitation::createNew();
$invitation->invoice_id = $invoice->id;
$invitation->user_id = Auth::user()->id;
$invitation->contact_id = $contact->id;
$invitation->invitation_key = str_random(20);
$invitation->save();
Session::flash('message', 'Successfully emailed invoice'); Session::flash('message', 'Successfully emailed invoice');
} else { } else {
Session::flash('message', 'Successfully saved invoice'); Session::flash('message', 'Successfully saved invoice');

View File

@ -21,14 +21,13 @@ class ConfideSetupUsersTable extends Migration {
Schema::dropIfExists('products'); Schema::dropIfExists('products');
Schema::dropIfExists('contacts'); Schema::dropIfExists('contacts');
Schema::dropIfExists('invoices'); Schema::dropIfExists('invoices');
Schema::dropIfExists('users');
Schema::dropIfExists('password_reminders'); Schema::dropIfExists('password_reminders');
Schema::dropIfExists('clients'); Schema::dropIfExists('clients');
Schema::dropIfExists('users');
Schema::dropIfExists('accounts'); Schema::dropIfExists('accounts');
Schema::dropIfExists('invoice_statuses'); Schema::dropIfExists('invoice_statuses');
Schema::dropIfExists('countries'); Schema::dropIfExists('countries');
Schema::dropIfExists('timezones'); Schema::dropIfExists('timezones');
Schema::create('countries', function($table) Schema::create('countries', function($table)
{ {
@ -147,8 +146,8 @@ class ConfideSetupUsersTable extends Migration {
Schema::create('clients', function($t) Schema::create('clients', function($t)
{ {
$t->increments('id'); $t->increments('id');
$t->unsignedInteger('account_id'); $t->unsignedInteger('user_id');
$t->unsignedInteger('country_id')->nullable(); $t->unsignedInteger('account_id');
$t->timestamps(); $t->timestamps();
$t->softDeletes(); $t->softDeletes();
@ -158,12 +157,14 @@ class ConfideSetupUsersTable extends Migration {
$t->string('city'); $t->string('city');
$t->string('state'); $t->string('state');
$t->string('postal_code'); $t->string('postal_code');
$t->unsignedInteger('country_id')->nullable();
$t->string('work_phone'); $t->string('work_phone');
$t->text('notes'); $t->text('notes');
$t->decimal('balance', 10, 2); $t->decimal('balance', 10, 2);
$t->timestamp('last_login'); $t->timestamp('last_login');
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users');
$t->foreign('country_id')->references('id')->on('countries'); $t->foreign('country_id')->references('id')->on('countries');
$t->unsignedInteger('public_id'); $t->unsignedInteger('public_id');
@ -174,6 +175,7 @@ class ConfideSetupUsersTable extends Migration {
{ {
$t->increments('id'); $t->increments('id');
$t->unsignedInteger('account_id'); $t->unsignedInteger('account_id');
$t->unsignedInteger('user_id');
$t->unsignedInteger('client_id'); $t->unsignedInteger('client_id');
$t->timestamps(); $t->timestamps();
$t->softDeletes(); $t->softDeletes();
@ -186,6 +188,7 @@ class ConfideSetupUsersTable extends Migration {
$t->timestamp('last_login'); $t->timestamp('last_login');
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); $t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users');
$t->unsignedInteger('public_id'); $t->unsignedInteger('public_id');
$t->unique( array('account_id','public_id') ); $t->unique( array('account_id','public_id') );
@ -202,6 +205,7 @@ class ConfideSetupUsersTable extends Migration {
{ {
$t->increments('id'); $t->increments('id');
$t->unsignedInteger('client_id'); $t->unsignedInteger('client_id');
$t->unsignedInteger('user_id');
$t->unsignedInteger('account_id'); $t->unsignedInteger('account_id');
$t->unsignedInteger('invoice_status_id')->default(1); $t->unsignedInteger('invoice_status_id')->default(1);
$t->timestamps(); $t->timestamps();
@ -219,11 +223,15 @@ class ConfideSetupUsersTable extends Migration {
$t->integer('how_often'); $t->integer('how_often');
$t->date('start_date')->nullable(); $t->date('start_date')->nullable();
$t->date('end_date')->nullable(); $t->date('end_date')->nullable();
$t->date('last_sent_date')->nullable();
$t->unsignedInteger('parent_id')->nullable();
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); $t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
$t->foreign('account_id')->references('id')->on('accounts'); $t->foreign('account_id')->references('id')->on('accounts');
$t->foreign('user_id')->references('id')->on('users');
$t->foreign('invoice_status_id')->references('id')->on('invoice_statuses'); $t->foreign('invoice_status_id')->references('id')->on('invoice_statuses');
$t->foreign('parent_id')->references('id')->on('invoices');
$t->unsignedInteger('public_id'); $t->unsignedInteger('public_id');
$t->unique( array('account_id','public_id') ); $t->unique( array('account_id','public_id') );
}); });
@ -254,6 +262,7 @@ class ConfideSetupUsersTable extends Migration {
{ {
$t->increments('id'); $t->increments('id');
$t->unsignedInteger('account_id'); $t->unsignedInteger('account_id');
$t->unsignedInteger('user_id');
$t->timestamps(); $t->timestamps();
$t->softDeletes(); $t->softDeletes();
@ -263,7 +272,8 @@ class ConfideSetupUsersTable extends Migration {
$t->decimal('qty', 10, 2); $t->decimal('qty', 10, 2);
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users');
$t->unsignedInteger('public_id'); $t->unsignedInteger('public_id');
$t->unique( array('account_id','public_id') ); $t->unique( array('account_id','public_id') );
}); });
@ -273,6 +283,7 @@ class ConfideSetupUsersTable extends Migration {
{ {
$t->increments('id'); $t->increments('id');
$t->unsignedInteger('account_id'); $t->unsignedInteger('account_id');
$t->unsignedInteger('user_id');
$t->unsignedInteger('invoice_id'); $t->unsignedInteger('invoice_id');
$t->unsignedInteger('product_id')->nullable(); $t->unsignedInteger('product_id')->nullable();
$t->timestamps(); $t->timestamps();
@ -285,6 +296,7 @@ class ConfideSetupUsersTable extends Migration {
$t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade'); $t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
$t->foreign('product_id')->references('id')->on('products'); $t->foreign('product_id')->references('id')->on('products');
$t->foreign('user_id')->references('id')->on('users');
$t->unsignedInteger('public_id'); $t->unsignedInteger('public_id');
$t->unique( array('account_id','public_id') ); $t->unique( array('account_id','public_id') );
@ -321,6 +333,7 @@ class ConfideSetupUsersTable extends Migration {
{ {
$t->increments('id'); $t->increments('id');
$t->unsignedInteger('account_id'); $t->unsignedInteger('account_id');
$t->unsignedInteger('user_id');
$t->unsignedInteger('client_id')->nullable(); $t->unsignedInteger('client_id')->nullable();
$t->unsignedInteger('contact_id')->nullable(); $t->unsignedInteger('contact_id')->nullable();
$t->timestamps(); $t->timestamps();
@ -333,6 +346,7 @@ class ConfideSetupUsersTable extends Migration {
$t->foreign('account_id')->references('id')->on('accounts'); $t->foreign('account_id')->references('id')->on('accounts');
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); $t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
$t->foreign('contact_id')->references('id')->on('contacts'); $t->foreign('contact_id')->references('id')->on('contacts');
$t->foreign('user_id')->references('id')->on('users');
$t->unsignedInteger('public_id'); $t->unsignedInteger('public_id');
$t->unique( array('account_id','public_id') ); $t->unique( array('account_id','public_id') );
@ -380,9 +394,9 @@ class ConfideSetupUsersTable extends Migration {
Schema::dropIfExists('products'); Schema::dropIfExists('products');
Schema::dropIfExists('contacts'); Schema::dropIfExists('contacts');
Schema::dropIfExists('invoices'); Schema::dropIfExists('invoices');
Schema::dropIfExists('users');
Schema::dropIfExists('password_reminders'); Schema::dropIfExists('password_reminders');
Schema::dropIfExists('clients'); Schema::dropIfExists('clients');
Schema::dropIfExists('users');
Schema::dropIfExists('accounts'); Schema::dropIfExists('accounts');
Schema::dropIfExists('invoice_statuses'); Schema::dropIfExists('invoice_statuses');
Schema::dropIfExists('countries'); Schema::dropIfExists('countries');

View File

@ -133,5 +133,78 @@ class Utils
Session::put(RECENTLY_VIEWED, $viewed); Session::put(RECENTLY_VIEWED, $viewed);
} }
public static function processVariables($str)
{
if (!$str) {
return '';
}
$variables = ['MONTH', 'QUARTER', 'YEAR'];
for ($i=0; $i<count($variables); $i++)
{
$variable = $variables[$i];
$regExp = '/\[' . $variable . '[+-]?[\d]*\]/';
preg_match_all($regExp, $str, $matches);
$matches = $matches[0];
if (count($matches) == 0) {
continue;
}
foreach ($matches as $match) {
$offset = 0;
$addArray = explode('+', $match);
$minArray = explode('-', $match);
if (count($addArray) > 1) {
$offset = intval($addArray[1]);
} else if (count($minArray) > 1) {
$offset = intval($minArray[1]) * -1;
}
$val = Utils::getDatePart($variable, $offset);
$str = str_replace($match, $val, $str);
}
}
return $str;
}
private static function getDatePart($part, $offset)
{
$offset = intval($offset);
if ($part == 'MONTH') {
return Utils::getMonth($offset);
} else if ($part == 'QUARTER') {
return Utils::getQuarter($offset);
} else if ($part == 'YEAR') {
return Utils::getYear($offset);
}
}
private static function getMonth($offset)
{
$months = [ "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December" ];
$month = intval(date('n')) - 1;
$month += $offset;
$month = $month % 12;
return $months[$month];
}
private static function getQuarter($offset)
{
$month = intval(date('n')) - 1;
$quarter = floor(($month + 3) / 3);
$quarter += $offset;
$quarter = $quarter % 4;
if ($quarter == 0) {
$quarter = 4;
}
return 'Q' . $quarter;
}
private static function getYear($offset)
{
$year = intval(date('Y'));
return $year + $offset;
}
} }

31
app/mailers/ContactMailer.php Executable file
View File

@ -0,0 +1,31 @@
<?php namespace Ninja\Mailers;
use Invoice;
use Contact;
use Invitation;
use URL;
use Auth;
class ContactMailer extends Mailer {
public function sendInvoice(Invoice $invoice, Contact $contact)
{
$view = 'invoice';
$data = array('link' => URL::to('view') . '/' . $invoice->invoice_key);
$subject = '';
if (Auth::check()) {
$invitation = Invitation::createNew();
} else {
$invitation = Invitation::createNew($invoice);
}
$invitation->invoice_id = $invoice->id;
$invitation->user_id = Auth::check() ? Auth::user()->id : $invoice->user_id;
$invitation->contact_id = $contact->id;
$invitation->invitation_key = str_random(20);
$invitation->save();
return $this->sendTo($contact->email, $subject, $view, $data);
}
}

19
app/mailers/Mailer.php Executable file
View File

@ -0,0 +1,19 @@
<?php namespace Ninja\Mailers;
use Mail;
abstract class Mailer {
public function sendTo($email, $subject, $view, $data = [])
{
$views = [
'html' => 'emails.'.$view.'_html',
'text' => 'emails.'.$view.'_text'
];
Mail::queue($views, $data, function($message) use($email, $subject)
{
$message->to($email)->subject($subject);
});
}
}

0
app/mailers/UserMailer.php Executable file
View File

View File

@ -78,8 +78,8 @@ class Account extends Eloquent
} }
public function getNextInvoiceNumber() public function getNextInvoiceNumber()
{ {
$order = Invoice::withTrashed()->scope()->orderBy('invoice_number', 'DESC')->first(); $order = Invoice::withTrashed()->scope(false, $this->id)->orderBy('invoice_number', 'DESC')->first();
if ($order) if ($order)
{ {

View File

@ -25,12 +25,19 @@ class Activity extends Eloquent
return $query->whereAccountId(Auth::user()->account_id); return $query->whereAccountId(Auth::user()->account_id);
} }
private static function getBlank() private static function getBlank($entity = false)
{ {
$user = Auth::user();
$activity = new Activity; $activity = new Activity;
$activity->user_id = $user->id;
$activity->account_id = $user->account_id; if (Auth::check()) {
$activity->user_id = Auth::user()->id;
$activity->account_id = Auth::user()->account_id;
} else if ($entity) {
$activity->user_id = $entity->user_id;
$activity->account_id = $entity->account_id;
} else {
exit; // TODO_FIX log error
}
return $activity; return $activity;
} }
@ -56,11 +63,12 @@ class Activity extends Eloquent
public static function createInvoice($invoice) public static function createInvoice($invoice)
{ {
$activity = Activity::getBlank(); $userName = Auth::check() ? Auth::user()->getFullName() : '<i>System</i>';
$activity = Activity::getBlank($invoice);
$activity->invoice_id = $invoice->id; $activity->invoice_id = $invoice->id;
$activity->client_id = $invoice->client_id; $activity->client_id = $invoice->client_id;
$activity->activity_type_id = ACTIVITY_TYPE_CREATE_INVOICE; $activity->activity_type_id = ACTIVITY_TYPE_CREATE_INVOICE;
$activity->message = Auth::user()->getFullName() . ' created invoice ' . link_to('invoices/'.$invoice->public_id, $invoice->invoice_number); $activity->message = $userName . ' created invoice ' . link_to('invoices/'.$invoice->public_id, $invoice->invoice_number);
$activity->save(); $activity->save();
} }
@ -76,12 +84,13 @@ class Activity extends Eloquent
public static function emailInvoice($invitation) public static function emailInvoice($invitation)
{ {
$activity = Activity::getBlank(); $userName = Auth::check() ? Auth::user()->getFullName() : '<i>System</i>';
$activity = Activity::getBlank($invitation);
$activity->client_id = $invitation->invoice->client_id; $activity->client_id = $invitation->invoice->client_id;
$activity->invoice_id = $invitation->invoice_id; $activity->invoice_id = $invitation->invoice_id;
$activity->contact_id = $invitation->contact_id; $activity->contact_id = $invitation->contact_id;
$activity->activity_type_id = ACTIVITY_TYPE_EMAIL_INVOICE; $activity->activity_type_id = ACTIVITY_TYPE_EMAIL_INVOICE;
$activity->message = Auth::user()->getFullName() . ' emailed invoice ' . link_to('invoices/'.$invitation->invoice->public_id, $invitation->invoice->invoice_number) . ' to ' . $invitation->contact->getFullName(); $activity->message = $userName . ' emailed invoice ' . link_to('invoices/'.$invitation->invoice->public_id, $invitation->invoice->invoice_number) . ' to ' . $invitation->contact->getFullName();
$activity->save(); $activity->save();
} }

View File

@ -5,13 +5,22 @@ class EntityModel extends Eloquent
protected $softDelete = true; protected $softDelete = true;
protected $hidden = array('id', 'created_at', 'updated_at', 'deleted_at'); protected $hidden = array('id', 'created_at', 'updated_at', 'deleted_at');
public static function createNew() public static function createNew($parent = false)
{ {
$className = get_called_class(); $className = get_called_class();
$entity = new $className(); $entity = new $className();
$entity->account_id = Auth::user()->account_id;
$lastEntity = $className::scope()->orderBy('public_id', 'DESC')->first(); if (Auth::check()) {
$entity->user_id = Auth::user()->id;
$entity->account_id = Auth::user()->account_id;
} else if ($parent) {
$entity->user_id = $parent->user_id;
$entity->account_id = $parent->account_id;
} else {
exit; // TODO_FIX
}
$lastEntity = $className::scope(false, $entity->account_id)->orderBy('public_id', 'DESC')->first();
if ($lastEntity) if ($lastEntity)
{ {
@ -21,7 +30,7 @@ class EntityModel extends Eloquent
{ {
$entity->public_id = 1; $entity->public_id = 1;
} }
return $entity; return $entity;
} }
@ -36,9 +45,12 @@ class EntityModel extends Eloquent
return ''; return '';
} }
public function scopeScope($query, $publicId = false) public function scopeScope($query, $publicId = false, $accountId = false)
{ {
$query->whereAccountId(Auth::user()->account_id); if (!$accountId) {
$accountId = Auth::user()->account_id;
}
$query->whereAccountId($accountId);
if ($publicId) if ($publicId)
{ {

View File

@ -36,22 +36,52 @@ class Invoice extends EntityModel
public function isRecurring() public function isRecurring()
{ {
return $this->how_often || $this->start_date || $this->end_date; return $this->how_often || $this->start_date || $this->end_date;
} }
/* public function shouldSendToday()
public function getTotal()
{ {
$total = 0; $dayOfWeekToday = date('w');
$dayOfWeekStart = date('w', strtotime($this->start_date));
foreach ($this->invoice_items as $invoiceItem) $dayOfMonthToday = date('j');
{ $dayOfMonthStart = date('j', strtotime($this->start_date));
$total += $invoiceItem->qty * $invoiceItem->cost;
if (!$this->last_sent_date) {
$daysSinceLastSent = 0;
$monthsSinceLastSent = 0;
} 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');
if ($daysSinceLastSent == 0) {
return false;
}
} }
return $total; switch ($this->how_often)
{
case FREQUENCY_WEEKLY:
return $dayOfWeekStart == $dayOfWeekToday;
case FREQUENCY_TWO_WEEKS:
return $dayOfWeekStart == $dayOfWeekToday && (!$daysSinceLastSent || $daysSinceLastSent == 14);
case FREQUENCY_FOUR_WEEKS:
return $dayOfWeekStart == $dayOfWeekToday && (!$daysSinceLastSent || $daysSinceLastSent == 28);
case FREQUENCY_MONTHLY:
return $dayOfMonthStart == $dayOfMonthToday || $daysSinceLastSent > 31;
case FREQUENCY_THREE_MONTHS:
return ($dayOfMonthStart == $dayOfMonthToday && (!$daysSinceLastSent || $monthsSinceLastSent == 3)) || $daysSinceLastSent > (3 * 31);
case FREQUENCY_SIX_MONTHS:
return ($dayOfMonthStart == $dayOfMonthToday && (!$daysSinceLastSent || $monthsSinceLastSent == 6)) || $daysSinceLastSent > (6 * 31);
case FREQUENCY_ANNUALLY:
return ($dayOfMonthStart == $dayOfMonthToday && (!$daysSinceLastSent || $monthsSinceLastSent == 12)) || $daysSinceLastSent > (12 *31);
}
return false;
} }
*/
} }
Invoice::created(function($invoice) Invoice::created(function($invoice)

View File

@ -1,6 +1,6 @@
<?php <?php
class Theme extends EntityModel class Theme extends Eloquent
{ {
public $timestamps = false; public $timestamps = false;
protected $softDelete = false; protected $softDelete = false;

View File

@ -13,6 +13,7 @@
//dd(DB::getQueryLog()); //dd(DB::getQueryLog());
//dd(Client::getPrivateId(1)); //dd(Client::getPrivateId(1));
//dd(new DateTime());
Route::get('/', 'HomeController@showWelcome'); Route::get('/', 'HomeController@showWelcome');
Route::post('get_started', 'AccountController@getStarted'); Route::post('get_started', 'AccountController@getStarted');
@ -142,4 +143,4 @@ define('FREQUENCY_FOUR_WEEKS', 3);
define('FREQUENCY_MONTHLY', 4); define('FREQUENCY_MONTHLY', 4);
define('FREQUENCY_THREE_MONTHS', 5); define('FREQUENCY_THREE_MONTHS', 5);
define('FREQUENCY_SIX_MONTHS', 6); define('FREQUENCY_SIX_MONTHS', 6);
define('FREQUENCY_ANNUALLY', 7); define('FREQUENCY_ANNUALLY', 7);

View File

@ -11,5 +11,4 @@
| |
*/ */
//Artisan::add(new SendRecurringInvoices); Artisan::resolve('SendRecurringInvoices');
Artisan::resolve('SendRecurringInvoices');

View File

@ -256,7 +256,7 @@
refreshPDF(); refreshPDF();
}); });
$('#due_date,#start_date').datepicker({ $('#due_date, #start_date, #end_date').datepicker({
autoclose: true, autoclose: true,
todayHighlight: true todayHighlight: true
}); });

View File

@ -28,9 +28,8 @@ $app->redirectIfTrailingSlash();
$env = $app->detectEnvironment(array( $env = $app->detectEnvironment(array(
'local' => array('precise64'), 'development' => array('precise64'),
'staging' => array('host107.hostmonster.com') 'staging' => array('host107.hostmonster.com')
)); ));
/* /*

View File

@ -23,7 +23,8 @@
"app/database/migrations", "app/database/migrations",
"app/database/seeds", "app/database/seeds",
"app/tests/TestCase.php", "app/tests/TestCase.php",
"app/libraries" "app/libraries",
"app/mailers"
] ]
}, },
"scripts": { "scripts": {

View File

@ -93,9 +93,13 @@ function generatePDF(invoice) {
// show at most one blank line // show at most one blank line
if (shownItem && !cost && !qty && !notes && !productKey) { if (shownItem && !cost && !qty && !notes && !productKey) {
continue; continue;
} }
shownItem = true; shownItem = true;
// process date variables
notes = processVariables(notes);
productKey = processVariables(productKey);
var lineTotal = item.cost * item.qty; var lineTotal = item.cost * item.qty;
if (lineTotal) total += lineTotal; if (lineTotal) total += lineTotal;
lineTotal = formatNumber(lineTotal); lineTotal = formatNumber(lineTotal);
@ -200,6 +204,77 @@ function formatNumber(num) {
}, "") + "." + p[1]; }, "") + "." + p[1];
} }
/* Handle converting variables in the invoices (ie, MONTH+1) */
function processVariables(str) {
if (!str) return '';
var variables = ['MONTH','QUARTER','YEAR'];
for (var i=0; i<variables.length; i++) {
var variable = variables[i];
var regexp = new RegExp('\\[' + variable + '[+-]?[\\d]*\\]', 'g');
var matches = str.match(regexp);
if (!matches) {
continue;
}
for (var j=0; j<matches.length; j++) {
var match = matches[j];
var offset = 0;
if (match.split('+').length > 1) {
offset = match.split('+')[1];
} else if (match.split('-').length > 1) {
offset = parseInt(match.split('-')[1]) * -1;
}
str = str.replace(match, getDatePart(variable, offset));
}
}
return str;
}
function getDatePart(part, offset) {
offset = parseInt(offset);
if (!offset) {
offset = 0;
}
if (part == 'MONTH') {
return getMonth(offset);
} else if (part == 'QUARTER') {
return getQuarter(offset);
} else if (part == 'YEAR') {
return getYear(offset);
}
}
function getMonth(offset) {
var today = new Date();
var months = [ "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December" ];
var month = today.getMonth();
month = parseInt(month) + offset;
month = month % 12;
return months[month];
}
function getYear(offset) {
var today = new Date();
var year = today.getFullYear();
return parseInt(year) + offset;
}
function getQuarter(offset) {
var today = new Date();
var quarter = Math.floor((today.getMonth() + 3) / 3);
quarter += offset;
quarter = quarter % 4;
if (quarter == 0) {
quarter = 4;
}
return 'Q' + quarter;
}
function formatMoney(num) { function formatMoney(num) {
num = parseFloat(num); num = parseFloat(num);
if (!num) return '$0.00'; if (!num) return '$0.00';

8
scheduler.yml Executable file
View File

@ -0,0 +1,8 @@
SendRecurringInvoicesCron:
type: cron
script: htdocs/artisan
args:
- "ninja:send-invoices"
interval:
minute: 0
hour: *