mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Working on recurring invoices
This commit is contained in:
parent
78d2d749fb
commit
0611004e77
1
.gitignore
vendored
1
.gitignore
vendored
@ -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
|
||||||
|
@ -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(
|
||||||
|
@ -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' => '',
|
||||||
|
@ -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(
|
||||||
@ -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'));
|
||||||
@ -443,21 +450,7 @@ 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 {
|
||||||
|
@ -21,15 +21,14 @@ 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)
|
||||||
{
|
{
|
||||||
$table->increments('id');
|
$table->increments('id');
|
||||||
@ -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('user_id');
|
||||||
$t->unsignedInteger('account_id');
|
$t->unsignedInteger('account_id');
|
||||||
$t->unsignedInteger('country_id')->nullable();
|
|
||||||
$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,10 +223,14 @@ 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,6 +272,7 @@ 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');
|
||||||
|
@ -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
31
app/mailers/ContactMailer.php
Executable 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
19
app/mailers/Mailer.php
Executable 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
0
app/mailers/UserMailer.php
Executable file
@ -79,7 +79,7 @@ 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)
|
||||||
{
|
{
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -39,19 +39,49 @@ class Invoice extends EntityModel
|
|||||||
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)
|
||||||
|
@ -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;
|
||||||
|
@ -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');
|
||||||
|
@ -11,5 +11,4 @@
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//Artisan::add(new SendRecurringInvoices);
|
|
||||||
Artisan::resolve('SendRecurringInvoices');
|
Artisan::resolve('SendRecurringInvoices');
|
@ -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
|
||||||
});
|
});
|
||||||
|
@ -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')
|
||||||
|
|
||||||
));
|
));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -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": {
|
||||||
|
@ -96,6 +96,10 @@ function generatePDF(invoice) {
|
|||||||
}
|
}
|
||||||
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
8
scheduler.yml
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
SendRecurringInvoicesCron:
|
||||||
|
type: cron
|
||||||
|
script: htdocs/artisan
|
||||||
|
args:
|
||||||
|
- "ninja:send-invoices"
|
||||||
|
interval:
|
||||||
|
minute: 0
|
||||||
|
hour: *
|
Loading…
x
Reference in New Issue
Block a user