1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-08 12:12:48 +01:00

Implemented notifications

This commit is contained in:
Hillel Coren 2013-12-25 16:34:42 -05:00
parent 8691d0ec26
commit c6c837f4df
29 changed files with 833 additions and 421 deletions

0
LICENSE Normal file → Executable file
View File

0
README.md Normal file → Executable file
View File

View File

@ -3,7 +3,7 @@
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; use ninja\mailers\ContactMailer as Mailer;
class SendRecurringInvoices extends Command { class SendRecurringInvoices extends Command {

View File

@ -54,7 +54,7 @@ return array(
| |
*/ */
'from' => array('address' => '', 'name' => ''), 'from' => array('address' => 'contact@invoiceninja.com', 'name' => 'Invoice Ninja'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -389,11 +389,17 @@ class AccountController extends \BaseController {
$account->date_format_id = Input::get('date_format_id') ? Input::get('date_format_id') : null; $account->date_format_id = Input::get('date_format_id') ? Input::get('date_format_id') : null;
$account->datetime_format_id = Input::get('datetime_format_id') ? Input::get('datetime_format_id') : null; $account->datetime_format_id = Input::get('datetime_format_id') ? Input::get('datetime_format_id') : null;
Event::fire('user.refresh');
$account->invoice_terms = Input::get('invoice_terms'); $account->invoice_terms = Input::get('invoice_terms');
$account->save(); $account->save();
$user = Auth::user();
$user->notify_sent = Input::get('notify_sent');
$user->notify_viewed = Input::get('notify_viewed');
$user->notify_paid = Input::get('notify_paid');
$user->save();
Event::fire('user.refresh');
if ($gatewayId) if ($gatewayId)
{ {
$accountGateway = new AccountGateway; $accountGateway = new AccountGateway;

View File

@ -1,16 +1,22 @@
<?php <?php
use Ninja\Mailers\ContactMailer as Mailer; use ninja\mailers\ContactMailer as Mailer;
use ninja\repositories\InvoiceRepository;
use ninja\repositories\ClientRepository;
class InvoiceController extends \BaseController { class InvoiceController extends \BaseController {
protected $mailer; protected $mailer;
protected $invoiceRepo;
protected $clientRepo;
public function __construct(Mailer $mailer) public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo)
{ {
parent::__construct(); parent::__construct();
$this->mailer = $mailer; $this->mailer = $mailer;
$this->invoiceRepo = $invoiceRepo;
$this->clientRepo = $clientRepo;
} }
public function index() public function index()
@ -32,30 +38,7 @@ class InvoiceController extends \BaseController {
public function getDatatable($clientPublicId = null) public function getDatatable($clientPublicId = null)
{ {
$query = DB::table('invoices') $query = $this->invoiceRepo->getInvoices(Auth::user()->account_id, $clientPublicId, Input::get('sSearch'));
->join('clients', 'clients.id', '=','invoices.client_id')
->join('invoice_statuses', 'invoice_statuses.id', '=', 'invoices.invoice_status_id')
->where('invoices.account_id', '=', Auth::user()->account_id)
->where('invoices.deleted_at', '=', null)
->where('clients.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false)
->select('clients.public_id as client_public_id', 'invoice_number', 'clients.name as client_name', 'invoices.public_id', 'amount', 'invoices.balance', 'invoice_date', 'due_date', 'invoice_statuses.name as invoice_status_name');
if ($clientPublicId) {
$query->where('clients.public_id', '=', $clientPublicId);
}
$filter = Input::get('sSearch');
if ($filter)
{
$query->where(function($query) use ($filter)
{
$query->where('clients.name', 'like', '%'.$filter.'%')
->orWhere('invoices.invoice_number', 'like', '%'.$filter.'%')
->orWhere('invoice_statuses.name', 'like', '%'.$filter.'%');
});
}
$table = Datatable::query($query); $table = Datatable::query($query);
if (!$clientPublicId) { if (!$clientPublicId) {
@ -93,28 +76,7 @@ class InvoiceController extends \BaseController {
public function getRecurringDatatable($clientPublicId = null) public function getRecurringDatatable($clientPublicId = null)
{ {
$query = DB::table('invoices') $query = $this->invoiceRepo->getRecurringInvoices(Auth::user()->account_id, $clientPublicId, Input::get('sSearch'));
->join('clients', 'clients.id', '=','invoices.client_id')
->join('frequencies', 'frequencies.id', '=', 'invoices.frequency_id')
->where('invoices.account_id', '=', Auth::user()->account_id)
->where('invoices.deleted_at', '=', null)
->where('invoices.is_recurring', '=', true)
->select('clients.public_id as client_public_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'frequencies.name as frequency', 'start_date', 'end_date');
if ($clientPublicId) {
$query->where('clients.public_id', '=', $clientPublicId);
}
$filter = Input::get('sSearch');
if ($filter)
{
$query->where(function($query) use ($filter)
{
$query->where('clients.name', 'like', '%'.$filter.'%')
->orWhere('invoices.invoice_number', 'like', '%'.$filter.'%');
});
}
$table = Datatable::query($query); $table = Datatable::query($query);
if (!$clientPublicId) { if (!$clientPublicId) {
@ -421,131 +383,40 @@ class InvoiceController extends \BaseController {
$inputClient = $input->client; $inputClient = $input->client;
$inputClient->name = trim($inputClient->name); $inputClient->name = trim($inputClient->name);
if (!$inputClient->name) { if (!$inputClient->name)
{
return Redirect::to('invoices/create') return Redirect::to('invoices/create')
->withInput(); ->withInput();
} else { }
else
{
$clientData = (array) $input->client;
$client = $this->clientRepo->save($input->client->public_id, $clientData);
$clientPublicId = $input->client->public_id; $invoiceData = (array) $input;
$invoiceData['client_id'] = $client->id;
if ($clientPublicId == "-1") $invoice = $this->invoiceRepo->save($publicId, $invoiceData);
{
$client = Client::createNew();
$contact = Contact::createNew();
$contact->is_primary = true;
}
else
{
$client = Client::scope($clientPublicId)->with('contacts')->firstOrFail();
$contact = $client->contacts()->where('is_primary', '=', true)->firstOrFail();
}
$inputClient = $input->client;
$client->name = trim($inputClient->name);
$client->work_phone = trim($inputClient->work_phone);
$client->address1 = trim($inputClient->address1);
$client->address2 = trim($inputClient->address2);
$client->city = trim($inputClient->city);
$client->state = trim($inputClient->state);
$client->postal_code = trim($inputClient->postal_code);
$client->country_id = $inputClient->country_id ? $inputClient->country_id : null;
$client->notes = trim($inputClient->notes);
$client->client_size_id = $inputClient->client_size_id ? $inputClient->client_size_id : null;
$client->client_industry_id = $inputClient->client_industry_id ? $inputClient->client_industry_id : null;
$client->website = trim($inputClient->website);
$client->save();
$isPrimary = true;
$contacts = [];
$contactIds = [];
$sendInvoiceIds = [];
foreach ($inputClient->contacts as $contact)
{
if (isset($contact->public_id) && $contact->public_id)
{
$record = Contact::scope($contact->public_id)->firstOrFail();
}
else
{
$record = Contact::createNew();
}
$record->email = trim($contact->email);
$record->first_name = trim($contact->first_name);
$record->last_name = trim($contact->last_name);
$record->phone = trim($contact->phone);
$record->is_primary = $isPrimary;
$isPrimary = false;
$client->contacts()->save($record);
$contacts[] = $record;
$contactIds[] = $record->public_id;
if ($contact->send_invoice)
{
$sendInvoiceIds[] = $record->id;
}
}
foreach ($client->contacts as $contact)
{
if (!in_array($contact->public_id, $contactIds))
{
$contact->forceDelete();
}
}
if ($publicId) {
$invoice = Invoice::scope($publicId)->firstOrFail();
$invoice->invoice_items()->forceDelete();
} else {
$invoice = Invoice::createNew();
}
$invoice->client_id = $client->id;
$invoice->discount = $input->discount;
$invoice->invoice_number = trim($input->invoice_number);
$invoice->invoice_date = Utils::toSqlDate($input->invoice_date);
$invoice->due_date = Utils::toSqlDate($input->due_date);
$invoice->is_recurring = $input->is_recurring;
$invoice->frequency_id = $input->frequency_id ? $input->frequency_id : 0;
$invoice->start_date = Utils::toSqlDate($input->start_date);
$invoice->end_date = Utils::toSqlDate($input->end_date);
$invoice->terms = $input->terms;
$invoice->po_number = $input->po_number;
$client->invoices()->save($invoice);
$items = $input->invoice_items;
$total = 0;
foreach ($items as $item)
{
if (!isset($item->cost)) {
$item->cost = 0;
}
if (!isset($item->qty)) {
$item->qty = 0;
}
$total += floatval($item->qty) * floatval($item->cost);
}
if ($action == 'email' && $invoice->invoice_status_id == INVOICE_STATUS_DRAFT) if ($action == 'email' && $invoice->invoice_status_id == INVOICE_STATUS_DRAFT)
{ {
$invoice->invoice_status_id = INVOICE_STATUS_SENT; $invoice->invoice_status_id = INVOICE_STATUS_SENT;
$client->balance = $invoice->client->balance + $invoice->amount; $client->balance = $client->balance + $invoice->amount;
$client->save(); $client->save();
} }
$invoice->amount = $total; $client->load('contacts');
$invoice->save(); $sendInvoiceIds = [];
foreach ($contacts as $contact) foreach ($client->contacts as $contact)
{
if ($contact->send_invoice)
{
$sendInvoiceIds[] = $contact->id;
}
}
foreach ($client->contacts as $contact)
{ {
$invitation = Invitation::scope()->whereContactId($contact->id)->whereInvoiceId($invoice->id)->first(); $invitation = Invitation::scope()->whereContactId($contact->id)->whereInvoiceId($invoice->id)->first();
@ -563,46 +434,14 @@ class InvoiceController extends \BaseController {
} }
} }
foreach ($items as $item) $message = '';
if ($input->client->public_id == '-1')
{ {
if (!$item->cost && !$item->qty && !$item->product_key && !$item->notes) $message = ' and created client';
{ $url = URL::to('clients/' . $client->public_id);
continue; Utils::trackViewed($client->name, ENTITY_CLIENT, $url);
}
if ($item->product_key)
{
$product = Product::findProductByKey(trim($item->product_key));
if (!$product)
{
$product = Product::createNew();
$product->product_key = trim($item->product_key);
}
/*
$product->notes = $item->notes;
$product->cost = $item->cost;
$product->qty = $item->qty;
*/
$product->save();
}
$invoiceItem = InvoiceItem::createNew();
$invoiceItem->product_id = isset($product) ? $product->id : null;
$invoiceItem->product_key = trim($item->product_key);
$invoiceItem->notes = trim($item->notes);
$invoiceItem->cost = floatval($item->cost);
$invoiceItem->qty = floatval($item->qty);
$invoice->invoice_items()->save($invoiceItem);
} }
/*
*/
$message = $clientPublicId == "-1" ? ' and created client' : '';
if ($action == 'clone') if ($action == 'clone')
{ {
return InvoiceController::cloneInvoice($publicId); return InvoiceController::cloneInvoice($publicId);
@ -610,9 +449,10 @@ class InvoiceController extends \BaseController {
else if ($action == 'email') else if ($action == 'email')
{ {
$this->mailer->sendInvoice($invoice); $this->mailer->sendInvoice($invoice);
Session::flash('message', 'Successfully emailed invoice'.$message); Session::flash('message', 'Successfully emailed invoice'.$message);
} else { }
else
{
Session::flash('message', 'Successfully saved invoice'.$message); Session::flash('message', 'Successfully saved invoice'.$message);
} }

View File

@ -69,6 +69,7 @@ class ConfideSetupUsersTable extends Migration {
{ {
$t->increments('id'); $t->increments('id');
$t->string('format'); $t->string('format');
$t->string('picker_format');
$t->string('label'); $t->string('label');
}); });
@ -149,6 +150,10 @@ class ConfideSetupUsersTable extends Migration {
$t->boolean('confirmed')->default(false); $t->boolean('confirmed')->default(false);
$t->integer('theme_id'); $t->integer('theme_id');
$t->boolean('notify_sent')->default(false);
$t->boolean('notify_viewed')->default(false);
$t->boolean('notify_paid')->default(true);
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$t->unsignedInteger('public_id'); $t->unsignedInteger('public_id');
@ -220,6 +225,7 @@ class ConfideSetupUsersTable extends Migration {
$t->softDeletes(); $t->softDeletes();
$t->boolean('is_primary'); $t->boolean('is_primary');
$t->boolean('send_invoice');
$t->string('first_name'); $t->string('first_name');
$t->string('last_name'); $t->string('last_name');
$t->string('email'); $t->string('email');

View File

@ -72,9 +72,16 @@ class ConstantsSeeder extends Seeder
DatetimeFormat::create(array('format' => 'F j, Y, g:i a', 'label' => 'March 10, 2013, 6:15 pm')); DatetimeFormat::create(array('format' => 'F j, Y, g:i a', 'label' => 'March 10, 2013, 6:15 pm'));
DatetimeFormat::create(array('format' => 'D M jS, Y g:ia', 'label' => 'Mon March 10th, 2013, 6:15 pm')); DatetimeFormat::create(array('format' => 'D M jS, Y g:ia', 'label' => 'Mon March 10th, 2013, 6:15 pm'));
DateFormat::create(array('format' => 'F j, Y', 'label' => 'March 10, 2013')); DateFormat::create(array('format' => 'F j, Y', 'picker_format' => 'MM d, yyyy', 'label' => 'March 10, 2013'));
DateFormat::create(array('format' => 'D M jS', 'label' => 'Mon March 10th, 2013')); DateFormat::create(array('format' => 'D M jS, Y', 'picker_format' => 'D MM d, yyyy', 'label' => 'Mon March 10th, 2013'));
/*
d, dd: Numeric date, no leading zero and leading zero, respectively. Eg, 5, 05.
D, DD: Abbreviated and full weekday names, respectively. Eg, Mon, Monday.
m, mm: Numeric month, no leading zero and leading zero, respectively. Eg, 7, 07.
M, MM: Abbreviated and full month names, respectively. Eg, Jan, January
yy, yyyy: 2- and 4-digit years, respectively. Eg, 12, 2012.)
*/
$gateways = [ $gateways = [
array('name'=>'Authorize.Net AIM', 'provider'=>'AuthorizeNet_AIM'), array('name'=>'Authorize.Net AIM', 'provider'=>'AuthorizeNet_AIM'),

View File

@ -0,0 +1,46 @@
<?php
use ninja\mailers\UserMailer as Mailer;
class InvoiceEventHandler
{
protected $mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function subscribe($events)
{
$events->listen('invoice.sent', 'InvoiceEventHandler@onSent');
$events->listen('invoice.viewed', 'InvoiceEventHandler@onViewed');
$events->listen('invoice.paid', 'InvoiceEventHandler@onPaid');
}
public function onSent($invoice)
{
$this->sendNotifications($invoice, 'sent');
}
public function onViewed($invoice)
{
}
public function onPaid($invoice)
{
}
private function sendNotifications($invoice, $type)
{
foreach ($invoice->account->users as $user)
{
if ($user->{'notify_' . $type})
{
$this->mailer->sendNotification($user, $invoice, $type);
}
}
}
}

View File

@ -31,6 +31,7 @@ class UserEventHandler
Session::put(SESSION_TIMEZONE, $account->timezone ? $account->timezone->name : DEFAULT_TIMEZONE); Session::put(SESSION_TIMEZONE, $account->timezone ? $account->timezone->name : DEFAULT_TIMEZONE);
Session::put(SESSION_DATE_FORMAT, $account->date_format ? $account->date_format->format : DEFAULT_DATE_FORMAT); Session::put(SESSION_DATE_FORMAT, $account->date_format ? $account->date_format->format : DEFAULT_DATE_FORMAT);
Session::put(SESSION_DATE_PICKER_FORMAT, $account->date_format ? $account->date_format->picker_format : DEFAULT_DATE_PICKER_FORMAT);
Session::put(SESSION_DATETIME_FORMAT, $account->datetime_format ? $account->datetime_format->format : DEFAULT_DATETIME_FORMAT); Session::put(SESSION_DATETIME_FORMAT, $account->datetime_format ? $account->datetime_format->format : DEFAULT_DATETIME_FORMAT);
} }
} }

0
app/libraries/entity.php Normal file → Executable file
View File

View File

@ -92,7 +92,13 @@ class Utils
return null; return null;
} }
return DateTime::createFromFormat('m/d/Y', $date); /*
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
return DateTime::createFromFormat($format, $date, new DateTimeZone($timezone));
*/
return DateTime::createFromFormat('Y-m-d', $date);
} }
public static function fromSqlDate($date) public static function fromSqlDate($date)
@ -102,12 +108,23 @@ class Utils
return ''; return '';
} }
return DateTime::createFromFormat('Y-m-d', $date)->format('m/d/Y'); /*
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
return DateTime::createFromFormat('Y-m-d', $date, new DateTimeZone($timezone))->format($format);
*/
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
return DateTime::createFromFormat('Y-m-d', $date)->format($format);
} }
public static function trackViewed($name, $type) public static function trackViewed($name, $type, $url = false)
{ {
$url = Request::url(); if (!$url)
{
$url = Request::url();
}
$viewed = Session::get(RECENTLY_VIEWED); $viewed = Session::get(RECENTLY_VIEWED);
if (!$viewed) if (!$viewed)

0
app/models/Invitation.php Normal file → Executable file
View File

View File

@ -1,4 +1,4 @@
<?php namespace Ninja\Mailers; <?php namespace ninja\mailers;
use Invoice; use Invoice;
use Contact; use Contact;
@ -27,5 +27,7 @@ class ContactMailer extends Mailer {
$invoice->invoice_status_id = INVOICE_STATUS_SENT; $invoice->invoice_status_id = INVOICE_STATUS_SENT;
$invoice->save(); $invoice->save();
} }
\Event::fire('invoice.sent', $invoice);
} }
} }

View File

@ -1,4 +1,4 @@
<?php namespace Ninja\Mailers; <?php namespace ninja\mailers;
use Mail; use Mail;
@ -11,7 +11,7 @@ abstract class Mailer {
'text' => 'emails.'.$view.'_text' 'text' => 'emails.'.$view.'_text'
]; ];
Mail::queue($views, $data, function($message) use($email, $subject) Mail::queue($views, $data, function($message) use ($email, $subject)
{ {
$message->to($email)->subject($subject); $message->to($email)->subject($subject);
}); });

View File

@ -0,0 +1,23 @@
<?php namespace ninja\mailers;
use Invoice;
use Contact;
use User;
class UserMailer extends Mailer {
public function sendNotification(User $user, Invoice $invoice, $type)
{
if (!$user->email)
{
return;
}
$view = 'invoice';
//$data = array('link' => URL::to('view') . '/' . $invoice->invoice_key);
$data = [];
$subject = 'Notification - Invoice ' . $type;
$this->sendTo($user->email, $subject, $view, $data);
}
}

View File

@ -0,0 +1,74 @@
<?php namespace ninja\repositories;
use Client;
use Contact;
class ClientRepository
{
public function save($publicId, $data)
{
if ($publicId == "-1")
{
$client = Client::createNew();
$contact = Contact::createNew();
$contact->is_primary = true;
}
else
{
$client = Client::scope($publicId)->with('contacts')->firstOrFail();
$contact = $client->contacts()->where('is_primary', '=', true)->firstOrFail();
}
$client->name = trim($data['name']);
$client->work_phone = trim($data['work_phone']);
$client->address1 = trim($data['address1']);
$client->address2 = trim($data['address2']);
$client->city = trim($data['city']);
$client->state = trim($data['state']);
$client->postal_code = trim($data['postal_code']);
$client->country_id = $data['country_id'] ? $data['country_id'] : null;
$client->notes = trim($data['notes']);
$client->client_size_id = $data['client_size_id'] ? $data['client_size_id'] : null;
$client->client_industry_id = $data['client_industry_id'] ? $data['client_industry_id'] : null;
$client->website = trim($data['website']);
$client->save();
$isPrimary = true;
$contactIds = [];
foreach ($data['contacts'] as $record)
{
$record = (array) $record;
if (isset($record['public_id']) && $record['public_id'])
{
$contact = Contact::scope($record['public_id'])->firstOrFail();
}
else
{
$contact = Contact::createNew();
}
$contact->email = trim($record['email']);
$contact->first_name = trim($record['first_name']);
$contact->last_name = trim($record['last_name']);
$contact->phone = trim($record['phone']);
$contact->is_primary = $isPrimary;
$contact->send_invoice = $record['send_invoice'];
$isPrimary = false;
$client->contacts()->save($contact);
$contactIds[] = $contact->public_id;
}
foreach ($client->contacts as $contact)
{
if (!in_array($contact->public_id, $contactIds))
{
$contact->forceDelete();
}
}
return $client;
}
}

View File

@ -0,0 +1,150 @@
<?php namespace ninja\repositories;
use Invoice;
use InvoiceItem;
use Product;
use Utils;
class InvoiceRepository
{
public function getInvoices($accountId, $clientPublicId = false, $filter = false)
{
$query = \DB::table('invoices')
->join('clients', 'clients.id', '=','invoices.client_id')
->join('invoice_statuses', 'invoice_statuses.id', '=', 'invoices.invoice_status_id')
->where('invoices.account_id', '=', $accountId)
->where('invoices.deleted_at', '=', null)
->where('clients.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false)
->select('clients.public_id as client_public_id', 'invoice_number', 'clients.name as client_name', 'invoices.public_id', 'amount', 'invoices.balance', 'invoice_date', 'due_date', 'invoice_statuses.name as invoice_status_name');
if ($clientPublicId)
{
$query->where('clients.public_id', '=', $clientPublicId);
}
if ($filter)
{
$query->where(function($query) use ($filter)
{
$query->where('clients.name', 'like', '%'.$filter.'%')
->orWhere('invoices.invoice_number', 'like', '%'.$filter.'%')
->orWhere('invoice_statuses.name', 'like', '%'.$filter.'%');
});
}
return $query;
}
public function getRecurringInvoices($accountId, $clientPublicId = false, $filter = false)
{
$query = \DB::table('invoices')
->join('clients', 'clients.id', '=','invoices.client_id')
->join('frequencies', 'frequencies.id', '=', 'invoices.frequency_id')
->where('invoices.account_id', '=', $accountId)
->where('invoices.deleted_at', '=', null)
->where('invoices.is_recurring', '=', true)
->select('clients.public_id as client_public_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'frequencies.name as frequency', 'start_date', 'end_date');
if ($clientPublicId)
{
$query->where('clients.public_id', '=', $clientPublicId);
}
if ($filter)
{
$query->where(function($query) use ($filter)
{
$query->where('clients.name', 'like', '%'.$filter.'%')
->orWhere('invoices.invoice_number', 'like', '%'.$filter.'%');
});
}
return $query;
}
public function save($publicId, $data)
{
if ($publicId)
{
$invoice = Invoice::scope($publicId)->firstOrFail();
$invoice->invoice_items()->forceDelete();
}
else
{
$invoice = Invoice::createNew();
}
$invoice->client_id = $data['client_id'];
$invoice->discount = $data['discount'];
$invoice->invoice_number = trim($data['invoice_number']);
$invoice->invoice_date = Utils::toSqlDate($data['invoice_date']);
$invoice->due_date = Utils::toSqlDate($data['due_date']);
$invoice->is_recurring = $data['is_recurring'];
$invoice->frequency_id = $data['frequency_id'] ? $data['frequency_id'] : 0;
$invoice->start_date = Utils::toSqlDate($data['start_date']);
$invoice->end_date = Utils::toSqlDate($data['end_date']);
$invoice->terms = trim($data['terms']);
$invoice->po_number = trim($data['po_number']);
$total = 0;
foreach ($data['invoice_items'] as $item)
{
if (!isset($item->cost))
{
$item->cost = 0;
}
if (!isset($item->qty))
{
$item->qty = 0;
}
$total += floatval($item->qty) * floatval($item->cost);
}
$invoice->amount = $total;
$invoice->save();
foreach ($data['invoice_items'] as $item)
{
if (!$item->cost && !$item->qty && !$item->product_key && !$item->notes)
{
continue;
}
if ($item->product_key)
{
$product = Product::findProductByKey(trim($item->product_key));
if (!$product)
{
$product = Product::createNew();
$product->product_key = trim($item->product_key);
}
/*
$product->notes = $item->notes;
$product->cost = $item->cost;
$product->qty = $item->qty;
*/
$product->save();
}
$invoiceItem = InvoiceItem::createNew();
$invoiceItem->product_id = isset($product) ? $product->id : null;
$invoiceItem->product_key = trim($item->product_key);
$invoiceItem->notes = trim($item->notes);
$invoiceItem->cost = floatval($item->cost);
$invoiceItem->qty = floatval($item->qty);
$invoice->invoice_items()->save($invoiceItem);
$total += floatval($item->qty) * floatval($item->cost);
}
return $invoice;
}
}

View File

@ -170,8 +170,10 @@ define('FREQUENCY_ANNUALLY', 7);
define('SESSION_TIMEZONE', 'timezone'); define('SESSION_TIMEZONE', 'timezone');
define('SESSION_DATE_FORMAT', 'dateFormat'); define('SESSION_DATE_FORMAT', 'dateFormat');
define('SESSION_DATE_PICKER_FORMAT', 'datePickerFormat');
define('SESSION_DATETIME_FORMAT', 'datetimeFormat'); define('SESSION_DATETIME_FORMAT', 'datetimeFormat');
define('DEFAULT_TIMEZONE', 'US/Eastern'); define('DEFAULT_TIMEZONE', 'US/Eastern');
define('DEFAULT_DATE_FORMAT', 'F j, Y'); define('DEFAULT_DATE_FORMAT', 'M j, Y');
define('DEFAULT_DATE_PICKER_FORMAT', 'yyyy-mm-dd');
define('DEFAULT_DATETIME_FORMAT', 'F j, Y, g:i a'); define('DEFAULT_DATETIME_FORMAT', 'F j, Y, g:i a');

View File

@ -74,6 +74,7 @@ App::down(function()
Event::subscribe('UserEventHandler'); Event::subscribe('UserEventHandler');
Event::subscribe('InvoiceEventHandler');
/* /*

View File

@ -5,6 +5,9 @@
{{ Former::open()->addClass('col-md-8 col-md-offset-2') }} {{ Former::open()->addClass('col-md-8 col-md-offset-2') }}
{{ Former::populate($account) }} {{ Former::populate($account) }}
{{ Former::populateField('notify_sent', Auth::user()->notify_sent) }}
{{ Former::populateField('notify_viewed', Auth::user()->notify_viewed) }}
{{ Former::populateField('notify_paid', Auth::user()->notify_paid) }}
{{ Former::legend('Payment Gateway') }} {{ Former::legend('Payment Gateway') }}
@ -44,6 +47,10 @@
{{ Former::select('datetime_format_id')->addOption('','')->label('Date/Time Format') {{ Former::select('datetime_format_id')->addOption('','')->label('Date/Time Format')
->fromQuery($datetimeFormats, 'label', 'id')->select($account->datetime_format_id) }} ->fromQuery($datetimeFormats, 'label', 'id')->select($account->datetime_format_id) }}
{{ Former::legend('Notifications') }}
{{ Former::checkbox('notify_sent')->label('&nbsp;')->text('Email me when an invoice is <b>sent</b>') }}
{{ Former::checkbox('notify_viewed')->label('&nbsp;')->text('Email me when an invoice is <b>viewed</b>') }}
{{ Former::checkbox('notify_paid')->label('&nbsp;')->text('Email me when an invoice is <b>paid</b>') }}
{{ Former::legend('Invoices') }} {{ Former::legend('Invoices') }}
{{ Former::textarea('invoice_terms') }} {{ Former::textarea('invoice_terms') }}

View File

@ -29,7 +29,7 @@
{{ Former::select('client')->fromQuery($clients, 'name', 'public_id')->select($client ? $client->public_id : '')->addOption('', '')->addGroupClass('client-select') }} {{ Former::select('client')->fromQuery($clients, 'name', 'public_id')->select($client ? $client->public_id : '')->addOption('', '')->addGroupClass('client-select') }}
{{ Former::text('amount') }} {{ Former::text('amount') }}
{{ Former::text('credit_date') }} {{ Former::text('credit_date')->data_date_format(DEFAULT_DATE_PICKER_FORMAT) }}
</div> </div>
<div class="col-md-6"> <div class="col-md-6">

View File

@ -43,15 +43,13 @@
<div class="col-md-4" id="col_2"> <div class="col-md-4" id="col_2">
<div data-bind="visible: !is_recurring()"> <div data-bind="visible: !is_recurring()">
{{ Former::text('invoice_number')->label('Invoice #')->data_bind("value: invoice_number, valueUpdate: 'afterkeydown'") }} {{ Former::text('invoice_number')->label('Invoice #')->data_bind("value: invoice_number, valueUpdate: 'afterkeydown'") }}
{{ Former::text('invoice_date')->data_bind("value: invoice_date, valueUpdate: 'afterkeydown'") }} {{ Former::text('invoice_date')->data_bind("value: invoice_date, valueUpdate: 'afterkeydown'")->data_date_format(DEFAULT_DATE_PICKER_FORMAT) }}
{{ Former::text('due_date')->data_bind("value: due_date, valueUpdate: 'afterkeydown'") }} {{ Former::text('due_date')->data_bind("value: due_date, valueUpdate: 'afterkeydown'")->data_date_format(DEFAULT_DATE_PICKER_FORMAT) }}
{{-- Former::text('invoice_date')->label('Invoice Date')->data_date_format('yyyy-mm-dd') --}}
</div> </div>
<div data-bind="visible: is_recurring"> <div data-bind="visible: is_recurring">
{{ Former::select('frequency_id')->label('How often')->options($frequencies)->data_bind("value: frequency_id") }} {{ Former::select('frequency_id')->label('How often')->options($frequencies)->data_bind("value: frequency_id") }}
{{ Former::text('start_date')->data_bind("value: start_date, valueUpdate: 'afterkeydown'") }} {{ Former::text('start_date')->data_bind("value: start_date, valueUpdate: 'afterkeydown'")->data_date_format(DEFAULT_DATE_PICKER_FORMAT) }}
{{ Former::text('end_date')->data_bind("value: end_date, valueUpdate: 'afterkeydown'") }} {{ Former::text('end_date')->data_bind("value: end_date, valueUpdate: 'afterkeydown'")->data_date_format(DEFAULT_DATE_PICKER_FORMAT) }}
</div> </div>
<div data-bind="visible: invoice_status_id() < CONSTS.INVOICE_STATUS_SENT"> <div data-bind="visible: invoice_status_id() < CONSTS.INVOICE_STATUS_SENT">
{{ Former::checkbox('recurring')->text('Enable | <a href="#" rel="tooltip" data-toggle="tooltip" title="Recurring invoices are automatically sent. Use :MONTH, :QUARTER or :YEAR for dynamic dates. Basic math works as well. ie, :MONTH-1.">Learn more</a>')->data_bind("checked: is_recurring") {{ Former::checkbox('recurring')->text('Enable | <a href="#" rel="tooltip" data-toggle="tooltip" title="Recurring invoices are automatically sent. Use :MONTH, :QUARTER or :YEAR for dynamic dates. Basic math works as well. ie, :MONTH-1.">Learn more</a>')->data_bind("checked: is_recurring")

View File

@ -30,7 +30,7 @@
{{ Former::select('client')->addOption('', '')->addGroupClass('client-select') }} {{ Former::select('client')->addOption('', '')->addGroupClass('client-select') }}
{{ Former::select('invoice')->addOption('', '')->addGroupClass('invoice-select') }} {{ Former::select('invoice')->addOption('', '')->addGroupClass('invoice-select') }}
{{ Former::text('amount') }} {{ Former::text('amount') }}
{{ Former::text('payment_date') }} {{ Former::text('payment_date')->data_date_format(DEFAULT_DATE_PICKER_FORMAT) }}
</div> </div>
<div class="col-md-6"> <div class="col-md-6">

View File

@ -23,9 +23,11 @@
"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" ],
] "psr-0" : {
"ninja" : "app/"
}
}, },
"scripts": { "scripts": {
"post-install-cmd": [ "post-install-cmd": [

0
public/css/style.css Normal file → Executable file
View File

File diff suppressed because it is too large Load Diff

View File

@ -602,6 +602,23 @@ ko.bindingHandlers.dropdown = {
} }
}; };
/*
ko.bindingHandlers.datePicker = {
init: function (element, valueAccessor, allBindingsAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (value) $(element).datepicker('update', value);
console.log("datePicker-init: %s", value);
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (value) $(element).datepicker('update', value);
console.log("datePicker-init: %s", value);
}
};
*/
function wordWrapText(value, width) function wordWrapText(value, width)
{ {
if (!width) width = 200; if (!width) width = 200;