Merge pull request #8 from hillelcoren/master

Update from master
This commit is contained in:
Paul-Vincent Roll 2015-06-10 19:06:25 +02:00
commit 24f238d4a6
148 changed files with 70250 additions and 2397 deletions

View File

@ -1,8 +1,9 @@
APP_ENV=development APP_ENV=production
APP_DEBUG=true APP_DEBUG=false
APP_URL=http://ninja.dev APP_URL=http://ninja.dev
APP_CIPHER=rijndael-128 APP_CIPHER=rijndael-128
APP_KEY APP_KEY
APP_TIMEZONE
DB_TYPE=mysql DB_TYPE=mysql
DB_HOST=localhost DB_HOST=localhost
@ -15,5 +16,6 @@ MAIL_PORT=587
MAIL_ENCRYPTION=tls MAIL_ENCRYPTION=tls
MAIL_HOST MAIL_HOST
MAIL_USERNAME MAIL_USERNAME
MAIL_FROM_ADDRESS
MAIL_FROM_NAME MAIL_FROM_NAME
MAIL_PASSWORD MAIL_PASSWORD

View File

@ -56,6 +56,8 @@ module.exports = function(grunt) {
'public/vendor/accounting/accounting.min.js', 'public/vendor/accounting/accounting.min.js',
'public/vendor/spectrum/spectrum.js', 'public/vendor/spectrum/spectrum.js',
'public/vendor/jspdf/dist/jspdf.min.js', 'public/vendor/jspdf/dist/jspdf.min.js',
'public/vendor/moment/min/moment.min.js',
//'public/vendor/moment-duration-format/lib/moment-duration-format.js',
//'public/vendor/handsontable/dist/jquery.handsontable.full.min.js', //'public/vendor/handsontable/dist/jquery.handsontable.full.min.js',
//'public/vendor/pdfmake/build/pdfmake.min.js', //'public/vendor/pdfmake/build/pdfmake.min.js',
//'public/vendor/pdfmake/build/vfs_fonts.js', //'public/vendor/pdfmake/build/vfs_fonts.js',
@ -63,8 +65,7 @@ module.exports = function(grunt) {
'public/js/lightbox.min.js', 'public/js/lightbox.min.js',
'public/js/bootstrap-combobox.js', 'public/js/bootstrap-combobox.js',
'public/js/script.js', 'public/js/script.js',
'public/js/pdf.pdfmake.js', 'public/js/pdf.pdfmake.js'
], ],
dest: 'public/js/built.js', dest: 'public/js/built.js',
nonull: true nonull: true
@ -107,6 +108,7 @@ module.exports = function(grunt) {
css_public: { css_public: {
src: [ src: [
'public/vendor/bootstrap/dist/css/bootstrap.min.css', 'public/vendor/bootstrap/dist/css/bootstrap.min.css',
'public/vendor/font-awesome/css/font-awesome.min.css',
/* /*
'public/css/bootstrap.splash.css', 'public/css/bootstrap.splash.css',
'public/css/splash.css', 'public/css/splash.css',

View File

@ -49,24 +49,6 @@ class CheckData extends Command {
$today = new DateTime(); $today = new DateTime();
if (!$this->option('client_id')) { if (!$this->option('client_id')) {
// update client deletion activities with the client's current balance
$activities = DB::table('activities')
->join('clients', 'clients.id', '=', 'activities.client_id')
->where('activities.activity_type_id', '=', ACTIVITY_TYPE_DELETE_CLIENT)
->where('activities.balance', '=', 0)
->where('clients.balance', '!=', 0)
->get(['activities.id', 'clients.balance']);
$this->info(count($activities) . ' delete client activities with zero balance');
if ($this->option('fix') == 'true') {
foreach ($activities as $activity) {
DB::table('activities')
->where('id', $activity->id)
->update(['balance' => $activity->balance]);
}
}
// update client paid_to_date value // update client paid_to_date value
$clients = DB::table('clients') $clients = DB::table('clients')
->join('payments', 'payments.client_id', '=', 'clients.id') ->join('payments', 'payments.client_id', '=', 'clients.id')

View File

@ -1,335 +0,0 @@
<?php namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use PHPBenchTime\Timer;
class ImportTimesheetData extends Command {
protected $name = 'ninja:import-timesheet-data';
protected $description = 'Import timesheet data';
public function fire() {
$this->info(date('Y-m-d') . ' Running ImportTimesheetData...');
// Seems we are using the console timezone
DB::statement("SET SESSION time_zone = '+00:00'");
// Get the Unix epoch
$unix_epoch = new DateTime('1970-01-01T00:00:01', new DateTimeZone("UTC"));
// Create some initial sources we can test with
$user = User::first();
if (!$user) {
$this->error("Error: please create user account by logging in");
return;
}
// TODO: Populate with own test data until test data has been created
// Truncate the tables
/*$this->info("Truncate tables");
DB::statement('SET FOREIGN_KEY_CHECKS=0;');
DB::table('projects')->truncate();
DB::table('project_codes')->truncate();
DB::table('timesheet_event_sources')->truncate();
DB::table('timesheet_events')->truncate();
DB::statement('SET FOREIGN_KEY_CHECKS=1;'); */
if (!Project::find(1)) {
$this->info("Import old project codes");
$oldcodes = json_decode(file_get_contents("/home/tlb/git/itktime/codes.json"), true);
foreach ($oldcodes as $name => $options) {
$project = Project::createNew($user);
$project->name = $options['description'];
$project->save();
$code = ProjectCode::createNew($user);
$code->name = $name;
$project->codes()->save($code);
}
}
if (!TimesheetEventSource::find(1)) {
$this->info("Import old event sources");
$oldevent_sources = json_decode(file_get_contents("/home/tlb/git/itktime/employes.json"), true);
foreach ($oldevent_sources as $source) {
$event_source = TimesheetEventSource::createNew($user);
$event_source->name = $source['name'];
$event_source->url = $source['url'];
$event_source->owner = $source['owner'];
$event_source->type = 'ical';
//$event_source->from_date = new DateTime("2009-01-01");
$event_source->save();
}
}
// Add all URL's to Curl
$this->info("Download ICAL feeds");
$T = new Timer;
$T->start();
$T->lap("Get Event Sources");
$event_sources = TimesheetEventSource::all(); // TODO: Filter based on ical feeds
$T->lap("Get ICAL responses");
$urls = [];
$event_sources->map(function($item) use(&$urls) {
$urls[] = $item->url;
});
$icalresponses = TimesheetUtils::curlGetUrls($urls);
$T->lap("Fetch all codes so we can do a quick lookup");
$codes = array();
ProjectCode::all()->map(function($item) use(&$codes) {
$codes[$item->name] = $item;
});
$this->info("Start parsing ICAL files");
foreach ($event_sources as $i => $event_source) {
if (!is_array($icalresponses[$i])) {
$this->info("Find events in " . $event_source->name);
file_put_contents("/tmp/" . $event_source->name . ".ical", $icalresponses[$i]); // FIXME: Remove
$T->lap("Split on events for ".$event_source->name);
// Check if the file is complete
if(!preg_match("/^\s*BEGIN:VCALENDAR/", $icalresponses[$i]) || !preg_match("/END:VCALENDAR\s*$/", $icalresponses[$i])) {
$this->error("Missing start or end of ical file");
continue;
}
// Extract all events from ical file
if (preg_match_all('/BEGIN:VEVENT\r?\n(.+?)\r?\nEND:VEVENT/s', $icalresponses[$i], $icalmatches)) {
$this->info("Found ".(count($icalmatches[1])-1)." events");
$T->lap("Fetch all uids and last updated at so we can do a quick lookup to find out if the event needs to be updated in the database".$event_source->name);
$uids = [];
$org_deleted = []; // Create list of events we know are deleted on the source, but still have in the db
$event_source->events()->withTrashed()->get(['uid', 'org_updated_at', 'updated_data_at', 'org_deleted_at'])->map(function($item) use(&$uids, &$org_deleted) {
if($item->org_updated_at > $item->updated_data_at) {
$uids[$item->uid] = $item->org_updated_at;
} else {
$uids[$item->uid] = $item->updated_data_at;
}
if($item->org_deleted_at > '0000-00-00 00:00:00') {
$org_deleted[$item->uid] = $item->updated_data_at;
}
});
$deleted = $uids;
// Loop over all the found events
$T->lap("Parse events for ".$event_source->name);
foreach ($icalmatches[1] as $eventstr) {
//print "---\n";
//print $eventstr."\n";
//print "---\n";
//$this->info("Match event");
# Fix lines broken by 76 char limit
$eventstr = preg_replace('/\r?\n\s/s', '', $eventstr);
//$this->info("Parse data");
$data = TimesheetUtils::parseICALEvent($eventstr);
if ($data) {
// Extract code for summary so we only import events we use
list($codename, $tags, $title) = TimesheetUtils::parseEventSummary($data['summary']);
if ($codename != null) {
$event = TimesheetEvent::createNew($user);
// Copy data to new object
$event->uid = $data['uid'];
$event->summary = $title;
$event->org_data = $eventstr;
$event->org_code = $codename;
if(isset($data['description'])) {
$event->description = $data['description'];
}
$event->owner = $event_source->owner;
$event->timesheet_event_source_id = $event_source->id;
if (isset($codes[$codename])) {
$event->project_id = $codes[$codename]->project_id;
$event->project_code_id = $codes[$codename]->id;
}
if (isset($data['location'])) {
$event->location = $data['location'];
}
# Add RECURRENCE-ID to the UID to make sure the event is unique
if (isset($data['recurrence-id'])) {
$event->uid .= "::".$data['recurrence-id'];
}
//TODO: Add support for recurring event, make limit on number of events created : https://github.com/tplaner/When
// Bail on RRULE as we don't support that
if(isset($event['rrule'])) {
die("Recurring event not supported: {$event['summary']} - {$event['dtstart']}");
}
// Convert to DateTime objects
foreach (['dtstart', 'dtend', 'created', 'last-modified'] as $key) {
// Parse and create DataTime object from ICAL format
list($dt, $timezone) = TimesheetUtils::parseICALDate($data[$key]);
// Handle bad dates in created and last-modified
if ($dt == null || $dt < $unix_epoch) {
if ($key == 'created' || $key == 'last-modified') {
$dt = $unix_epoch; // Default to UNIX epoch
$event->import_warning = "Could not parse date for $key: '" . $data[$key] . "' so default to UNIX Epoc\n";
} else {
$event->import_error = "Could not parse date for $key: '" . $data[$key] . "' so default to UNIX Epoc\n";
// TODO: Bail on this event or write to error table
die("Could not parse date for $key: '" . $data[$key] . "'\n");
}
}
// Assign DateTime object to
switch ($key) {
case 'dtstart':
$event->start_date = $dt;
if($timezone) {
$event->org_start_date_timezone = $timezone;
}
break;
case 'dtend':
$event->end_date = $dt;
if($timezone) {
$event->org_end_date_timezone = $timezone;
}
break;
case 'created':
$event->org_created_at = $dt;
break;
case 'last-modified':
$event->org_updated_at = $dt;
break;
}
}
// Check that we are witin the range
if ($event_source->from_date != null) {
$from_date = new DateTime($event_source->from_date, new DateTimeZone('UTC'));
if ($from_date > $event->end_date) {
// Skip this event
echo "Skiped: $codename: $title\n";
continue;
}
}
// Calculate number of hours
$di = $event->end_date->diff($event->start_date);
$event->hours = $di->h + $di->i / 60;
// Check for events we already have
if (isset($uids[$event->uid])) {
// Remove from deleted list
unset($deleted[$event->uid]);
// See if the event has been updated compared to the one in the database
$db_event_org_updated_at = new DateTime($uids[$event->uid], new DateTimeZone('UTC'));
// Check if same or older version of new event then skip
if($event->org_updated_at <= $db_event_org_updated_at) {
// SKIP
// Updated version of the event
} else {
// Get the old event from the database
/* @var $db_event TimesheetEvent */
$db_event = $event_source->events()->where('uid', $event->uid)->firstOrFail();
$changes = $db_event->toChangesArray($event);
// Make sure it's more than the org_updated_at that has been changed
if (count($changes) > 1) {
// Check if we have manually changed the event in the database or used it in a timesheet
if ($db_event->manualedit || $db_event->timesheet) {
$this->info("Updated Data");
$db_event->updated_data = $event->org_data;
$db_event->updated_data_at = $event->org_updated_at;
// Update the db_event with the changes
} else {
$this->info("Updated Event");
foreach ($changes as $key => $value) {
if($value == null) {
unset($db_event->$key);
} else {
$db_event->$key = $value;
}
}
}
} else {
$this->info("Nothing Changed");
// Nothing has been changed so update the org_updated_at
$db_event->org_updated_at = $changes['org_updated_at'];
}
$db_event->save();
}
} else {
try {
$this->info("New event: " . $event->summary);
$event->save();
} catch (Exception $ex) {
echo "'" . $event->summary . "'\n";
var_dump($data);
echo $ex->getMessage();
echo $ex->getTraceAsString();
exit(0);
}
}
// Add new uid to know uids
$uids[$event->uid] = $event->org_updated_at;
}
}
}
// Delete events in database that no longer exists in the source
foreach($deleted as $uid => $lastupdated_date) {
// Skip we already marked this a deleted
if(isset($org_deleted[$uid])) {
unset($deleted[$uid]);
continue;
}
// Delete or update event in db
$db_event = $event_source->events()->where('uid', $uid)->firstOrFail();
if($db_event->timesheet_id === null && !$db_event->manualedit) {
// Hard delete if this event has not been assigned to a timesheet or have been manually edited
$db_event->forceDelete();
} else {
// Mark as deleted in source
$db_event->org_deleted_at = new DateTime('now', new DateTimeZone('UTC'));
$db_event->save();
}
}
$this->info("Deleted ".count($deleted). " events");
} else {
// TODO: Parse error
}
} else {
// TODO: Curl Error
}
}
foreach($T->end()['laps'] as $lap) {
echo number_format($lap['total'], 3)." : {$lap['name']}\n";
}
$this->info('Done');
}
protected function getArguments() {
return array(
);
}
protected function getOptions() {
return array(
);
}
}

View File

@ -51,15 +51,15 @@ class SendRecurringInvoices extends Command
$invoice = Invoice::createNew($recurInvoice); $invoice = Invoice::createNew($recurInvoice);
$invoice->client_id = $recurInvoice->client_id; $invoice->client_id = $recurInvoice->client_id;
$invoice->recurring_invoice_id = $recurInvoice->id; $invoice->recurring_invoice_id = $recurInvoice->id;
$invoice->invoice_number = 'R'.$recurInvoice->account->getNextInvoiceNumber(); $invoice->invoice_number = $recurInvoice->account->getNextInvoiceNumber(false, 'R');
$invoice->amount = $recurInvoice->amount; $invoice->amount = $recurInvoice->amount;
$invoice->balance = $recurInvoice->amount; $invoice->balance = $recurInvoice->amount;
$invoice->invoice_date = date_create()->format('Y-m-d'); $invoice->invoice_date = date_create()->format('Y-m-d');
$invoice->discount = $recurInvoice->discount; $invoice->discount = $recurInvoice->discount;
$invoice->po_number = $recurInvoice->po_number; $invoice->po_number = $recurInvoice->po_number;
$invoice->public_notes = $recurInvoice->public_notes; $invoice->public_notes = Utils::processVariables($recurInvoice->public_notes);
$invoice->terms = $recurInvoice->terms; $invoice->terms = Utils::processVariables($recurInvoice->terms);
$invoice->invoice_footer = $recurInvoice->invoice_footer; $invoice->invoice_footer = Utils::processVariables($recurInvoice->invoice_footer);
$invoice->tax_name = $recurInvoice->tax_name; $invoice->tax_name = $recurInvoice->tax_name;
$invoice->tax_rate = $recurInvoice->tax_rate; $invoice->tax_rate = $recurInvoice->tax_rate;
$invoice->invoice_design_id = $recurInvoice->invoice_design_id; $invoice->invoice_design_id = $recurInvoice->invoice_design_id;

View File

@ -31,7 +31,7 @@ class SendRenewalInvoices extends Command
$accounts = Account::whereRaw('datediff(curdate(), pro_plan_paid) = 355')->get(); $accounts = Account::whereRaw('datediff(curdate(), pro_plan_paid) = 355')->get();
$this->info(count($accounts).' accounts found'); $this->info(count($accounts).' accounts found');
dd(0);
foreach ($accounts as $account) { foreach ($accounts as $account) {
$client = $this->accountRepo->getNinjaClient($account); $client = $this->accountRepo->getNinjaClient($account);
$invitation = $this->accountRepo->createNinjaInvoice($client); $invitation = $this->accountRepo->createNinjaInvoice($client);

View File

@ -14,7 +14,6 @@ class Kernel extends ConsoleKernel {
'App\Console\Commands\SendRecurringInvoices', 'App\Console\Commands\SendRecurringInvoices',
'App\Console\Commands\CreateRandomData', 'App\Console\Commands\CreateRandomData',
'App\Console\Commands\ResetData', 'App\Console\Commands\ResetData',
'App\Console\Commands\ImportTimesheetData',
'App\Console\Commands\CheckData', 'App\Console\Commands\CheckData',
'App\Console\Commands\SendRenewalInvoices', 'App\Console\Commands\SendRenewalInvoices',
]; ];

View File

@ -209,6 +209,7 @@ class AccountController extends BaseController
$data['invoice'] = $invoice; $data['invoice'] = $invoice;
$data['invoiceDesigns'] = InvoiceDesign::availableDesigns(); $data['invoiceDesigns'] = InvoiceDesign::availableDesigns();
$data['invoiceLabels'] = json_decode($account->invoice_labels) ?: [];
} else if ($subSection == ACCOUNT_EMAIL_TEMPLATES) { } else if ($subSection == ACCOUNT_EMAIL_TEMPLATES) {
$data['invoiceEmail'] = $account->getEmailTemplate(ENTITY_INVOICE); $data['invoiceEmail'] = $account->getEmailTemplate(ENTITY_INVOICE);
$data['quoteEmail'] = $account->getEmailTemplate(ENTITY_QUOTE); $data['quoteEmail'] = $account->getEmailTemplate(ENTITY_QUOTE);
@ -331,6 +332,16 @@ class AccountController extends BaseController
$account->primary_color = Input::get('primary_color'); $account->primary_color = Input::get('primary_color');
$account->secondary_color = Input::get('secondary_color'); $account->secondary_color = Input::get('secondary_color');
$account->invoice_design_id = Input::get('invoice_design_id'); $account->invoice_design_id = Input::get('invoice_design_id');
if (Input::has('font_size')) {
$account->font_size = intval(Input::get('font_size'));
}
$labels = [];
foreach (['item', 'description', 'unit_cost', 'quantity'] as $field) {
$labels[$field] = trim(Input::get("labels_{$field}"));
}
$account->invoice_labels = json_encode($labels);
$account->save(); $account->save();
Session::flash('message', trans('texts.updated_settings')); Session::flash('message', trans('texts.updated_settings'));

View File

@ -10,7 +10,7 @@ use View;
use Validator; use Validator;
use stdClass; use stdClass;
use URL; use URL;
use Utils;
use App\Models\Gateway; use App\Models\Gateway;
use App\Models\Account; use App\Models\Account;
use App\Models\AccountGateway; use App\Models\AccountGateway;
@ -69,6 +69,7 @@ class AccountGatewayController extends BaseController
$data['method'] = 'PUT'; $data['method'] = 'PUT';
$data['title'] = trans('texts.edit_gateway') . ' - ' . $accountGateway->gateway->name; $data['title'] = trans('texts.edit_gateway') . ' - ' . $accountGateway->gateway->name;
$data['config'] = $configFields; $data['config'] = $configFields;
$data['hiddenFields'] = Gateway::$hiddenFields;
$data['paymentTypeId'] = $accountGateway->getPaymentType(); $data['paymentTypeId'] = $accountGateway->getPaymentType();
$data['selectGateways'] = Gateway::where('id', '=', $accountGateway->gateway_id)->get(); $data['selectGateways'] = Gateway::where('id', '=', $accountGateway->gateway_id)->get();
@ -97,6 +98,7 @@ class AccountGatewayController extends BaseController
$data['method'] = 'POST'; $data['method'] = 'POST';
$data['title'] = trans('texts.add_gateway'); $data['title'] = trans('texts.add_gateway');
$data['selectGateways'] = Gateway::where('payment_library_id', '=', 1)->where('id', '!=', GATEWAY_PAYPAL_EXPRESS)->where('id', '!=', GATEWAY_PAYPAL_EXPRESS)->orderBy('name')->get(); $data['selectGateways'] = Gateway::where('payment_library_id', '=', 1)->where('id', '!=', GATEWAY_PAYPAL_EXPRESS)->where('id', '!=', GATEWAY_PAYPAL_EXPRESS)->orderBy('name')->get();
$data['hiddenFields'] = Gateway::$hiddenFields;
return View::make('accounts.account_gateway', $data); return View::make('accounts.account_gateway', $data);
} }
@ -107,7 +109,7 @@ class AccountGatewayController extends BaseController
$account = Auth::user()->account; $account = Auth::user()->account;
$paymentTypes = []; $paymentTypes = [];
foreach ([PAYMENT_TYPE_CREDIT_CARD, PAYMENT_TYPE_PAYPAL, PAYMENT_TYPE_BITCOIN] as $type) { foreach (Gateway::$paymentTypes as $type) {
if ($accountGateway || !$account->getGatewayByType($type)) { if ($accountGateway || !$account->getGatewayByType($type)) {
$paymentTypes[$type] = trans('texts.'.strtolower($type)); $paymentTypes[$type] = trans('texts.'.strtolower($type));
@ -132,7 +134,9 @@ class AccountGatewayController extends BaseController
$gateways = Gateway::where('payment_library_id', '=', 1)->orderBy('name')->get(); $gateways = Gateway::where('payment_library_id', '=', 1)->orderBy('name')->get();
foreach ($gateways as $gateway) { foreach ($gateways as $gateway) {
$gateway->fields = $gateway->getFields(); $fields = $gateway->getFields();
asort($fields);
$gateway->fields = $fields;
if ($accountGateway && $accountGateway->gateway_id == $gateway->id) { if ($accountGateway && $accountGateway->gateway_id == $gateway->id) {
$accountGateway->fields = $gateway->fields; $accountGateway->fields = $gateway->fields;
} }
@ -182,6 +186,8 @@ class AccountGatewayController extends BaseController
$gatewayId = GATEWAY_PAYPAL_EXPRESS; $gatewayId = GATEWAY_PAYPAL_EXPRESS;
} elseif ($paymentType == PAYMENT_TYPE_BITCOIN) { } elseif ($paymentType == PAYMENT_TYPE_BITCOIN) {
$gatewayId = GATEWAY_BITPAY; $gatewayId = GATEWAY_BITPAY;
} elseif ($paymentType == PAYMENT_TYPE_DWOLLA) {
$gatewayId = GATEWAY_DWOLLA;
} }
if (!$gatewayId) { if (!$gatewayId) {
@ -192,9 +198,14 @@ class AccountGatewayController extends BaseController
$gateway = Gateway::findOrFail($gatewayId); $gateway = Gateway::findOrFail($gatewayId);
$fields = $gateway->getFields(); $fields = $gateway->getFields();
$optional = array_merge(Gateway::$hiddenFields, Gateway::$optionalFields);
if (Utils::isNinja() && $gatewayId == GATEWAY_DWOLLA) {
$optional = array_merge($optional, ['key', 'secret']);
}
foreach ($fields as $field => $details) { foreach ($fields as $field => $details) {
if (!in_array($field, ['testMode', 'developerMode', 'headerImageUrl', 'solutionType', 'landingPage', 'brandName', 'logoImageUrl', 'borderColor'])) { if (!in_array($field, $optional)) {
if (strtolower($gateway->name) == 'beanstream') { if (strtolower($gateway->name) == 'beanstream') {
if (in_array($field, ['merchant_id', 'passCode'])) { if (in_array($field, ['merchant_id', 'passCode'])) {
$rules[$gateway->id.'_'.$field] = 'required'; $rules[$gateway->id.'_'.$field] = 'required';

View File

@ -17,7 +17,7 @@ class ActivityController extends BaseController
->select('activities.id', 'activities.message', 'activities.created_at', 'clients.currency_id', 'activities.balance', 'activities.adjustment'); ->select('activities.id', 'activities.message', 'activities.created_at', 'clients.currency_id', 'activities.balance', 'activities.adjustment');
return Datatable::query($query) return Datatable::query($query)
->addColumn('id', function ($model) { return Utils::timestampToDateTimeString(strtotime($model->created_at)); }) ->addColumn('activities.id', function ($model) { return Utils::timestampToDateTimeString(strtotime($model->created_at)); })
->addColumn('message', function ($model) { return Utils::decodeActivity($model->message); }) ->addColumn('message', function ($model) { return Utils::decodeActivity($model->message); })
->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); }) ->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); })
->addColumn('adjustment', function ($model) { return $model->adjustment != 0 ? self::wrapAdjustment($model->adjustment, $model->currency_id) : ''; }) ->addColumn('adjustment', function ($model) { return $model->adjustment != 0 ? self::wrapAdjustment($model->adjustment, $model->currency_id) : ''; })

View File

@ -79,9 +79,8 @@ class AppController extends BaseController
return Redirect::to('/setup')->withInput(); return Redirect::to('/setup')->withInput();
} }
// == ENV Settings (Production) == // $config = "APP_ENV=production\n".
$config = "APP_ENV=development\n". "APP_DEBUG=false\n".
"APP_DEBUG=true\n".
"APP_URL={$app['url']}\n". "APP_URL={$app['url']}\n".
"APP_KEY={$app['key']}\n\n". "APP_KEY={$app['key']}\n\n".
"DB_TYPE={$dbType}\n". "DB_TYPE={$dbType}\n".
@ -116,7 +115,6 @@ class AppController extends BaseController
$user = $account->users()->first(); $user = $account->users()->first();
//Auth::login($user, true); //Auth::login($user, true);
$this->accountRepo->registerUser($user);
return Redirect::to('/login'); return Redirect::to('/login');
} }

View File

@ -20,6 +20,7 @@ use App\Models\PaymentTerm;
use App\Models\Industry; use App\Models\Industry;
use App\Models\Currency; use App\Models\Currency;
use App\Models\Country; use App\Models\Country;
use App\Models\Task;
use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\ClientRepository;
@ -58,7 +59,7 @@ class ClientController extends BaseController
->addColumn('name', function ($model) { return link_to('clients/'.$model->public_id, $model->name); }) ->addColumn('name', function ($model) { return link_to('clients/'.$model->public_id, $model->name); })
->addColumn('first_name', function ($model) { return link_to('clients/'.$model->public_id, $model->first_name.' '.$model->last_name); }) ->addColumn('first_name', function ($model) { return link_to('clients/'.$model->public_id, $model->first_name.' '.$model->last_name); })
->addColumn('email', function ($model) { return link_to('clients/'.$model->public_id, $model->email); }) ->addColumn('email', function ($model) { return link_to('clients/'.$model->public_id, $model->email); })
->addColumn('created_at', function ($model) { return Utils::timestampToDateString(strtotime($model->created_at)); }) ->addColumn('clients.created_at', function ($model) { return Utils::timestampToDateString(strtotime($model->created_at)); })
->addColumn('last_login', function ($model) { return Utils::timestampToDateString(strtotime($model->last_login)); }) ->addColumn('last_login', function ($model) { return Utils::timestampToDateString(strtotime($model->last_login)); })
->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); }) ->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); })
->addColumn('dropdown', function ($model) { ->addColumn('dropdown', function ($model) {
@ -75,9 +76,16 @@ class ClientController extends BaseController
if (!$model->deleted_at || $model->deleted_at == '0000-00-00') { if (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
$str .= '<li><a href="'.URL::to('clients/'.$model->public_id.'/edit').'">'.trans('texts.edit_client').'</a></li> $str .= '<li><a href="'.URL::to('clients/'.$model->public_id.'/edit').'">'.trans('texts.edit_client').'</a></li>
<li class="divider"></li> <li class="divider"></li>
<li><a href="'.URL::to('invoices/create/'.$model->public_id).'">'.trans('texts.new_invoice').'</a></li> <li><a href="'.URL::to('tasks/create/'.$model->public_id).'">'.trans('texts.new_task').'</a></li>
<li><a href="'.URL::to('payments/create/'.$model->public_id).'">'.trans('texts.new_payment').'</a></li> <li><a href="'.URL::to('invoices/create/'.$model->public_id).'">'.trans('texts.new_invoice').'</a></li>';
<li><a href="'.URL::to('credits/create/'.$model->public_id).'">'.trans('texts.new_credit').'</a></li>
if (Auth::user()->isPro()) {
$str .= '<li><a href="'.URL::to('quotes/create/'.$model->public_id).'">'.trans('texts.new_quote').'</a></li>';
}
$str .= '<li class="divider"></li>
<li><a href="'.URL::to('payments/create/'.$model->public_id).'">'.trans('texts.enter_payment').'</a></li>
<li><a href="'.URL::to('credits/create/'.$model->public_id).'">'.trans('texts.enter_credit').'</a></li>
<li class="divider"></li> <li class="divider"></li>
<li><a href="javascript:archiveEntity('.$model->public_id.')">'.trans('texts.archive_client').'</a></li>'; <li><a href="javascript:archiveEntity('.$model->public_id.')">'.trans('texts.archive_client').'</a></li>';
} else { } else {
@ -112,15 +120,18 @@ class ClientController extends BaseController
Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT); Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT);
$actionLinks = [ $actionLinks = [
['label' => trans('texts.create_invoice'), 'url' => URL::to('invoices/create/'.$client->public_id)], ['label' => trans('texts.new_task'), 'url' => '/tasks/create/'.$client->public_id]
['label' => trans('texts.enter_payment'), 'url' => URL::to('payments/create/'.$client->public_id)],
['label' => trans('texts.enter_credit'), 'url' => URL::to('credits/create/'.$client->public_id)],
]; ];
if (Utils::isPro()) { if (Utils::isPro()) {
array_unshift($actionLinks, ['label' => trans('texts.create_quote'), 'url' => URL::to('quotes/create/'.$client->public_id)]); array_push($actionLinks, ['label' => trans('texts.new_quote'), 'url' => '/quotes/create/'.$client->public_id]);
} }
array_push($actionLinks,
['label' => trans('texts.enter_payment'), 'url' => '/payments/create/'.$client->public_id],
['label' => trans('texts.enter_credit'), 'url' => '/credits/create/'.$client->public_id]
);
$data = array( $data = array(
'actionLinks' => $actionLinks, 'actionLinks' => $actionLinks,
'showBreadcrumbs' => false, 'showBreadcrumbs' => false,
@ -128,6 +139,8 @@ class ClientController extends BaseController
'credit' => $client->getTotalCredit(), 'credit' => $client->getTotalCredit(),
'title' => trans('texts.view_client'), 'title' => trans('texts.view_client'),
'hasRecurringInvoices' => Invoice::scope()->where('is_recurring', '=', true)->whereClientId($client->id)->count() > 0, 'hasRecurringInvoices' => Invoice::scope()->where('is_recurring', '=', true)->whereClientId($client->id)->count() > 0,
'hasQuotes' => Invoice::scope()->where('is_quote', '=', true)->whereClientId($client->id)->count() > 0,
'hasTasks' => Task::scope()->whereClientId($client->id)->count() > 0,
'gatewayLink' => $client->getGatewayLink(), 'gatewayLink' => $client->getGatewayLink(),
); );

View File

@ -54,7 +54,9 @@ class DashboardController extends BaseController
->where('activity_type_id', '>', 0) ->where('activity_type_id', '>', 0)
->orderBy('created_at', 'desc')->take(14)->get(); ->orderBy('created_at', 'desc')->take(14)->get();
$pastDue = Invoice::scope() $pastDue = Invoice::scope()->whereHas('client', function($query) {
$query->where('deleted_at', '=', null);
})
->where('due_date', '<', date('Y-m-d')) ->where('due_date', '<', date('Y-m-d'))
->where('balance', '>', 0) ->where('balance', '>', 0)
->where('is_recurring', '=', false) ->where('is_recurring', '=', false)
@ -62,7 +64,9 @@ class DashboardController extends BaseController
->where('is_deleted', '=', false) ->where('is_deleted', '=', false)
->orderBy('due_date', 'asc')->take(6)->get(); ->orderBy('due_date', 'asc')->take(6)->get();
$upcoming = Invoice::scope() $upcoming = Invoice::scope()->whereHas('client', function($query) {
$query->where('deleted_at', '=', null);
})
->where('due_date', '>=', date('Y-m-d')) ->where('due_date', '>=', date('Y-m-d'))
->where('balance', '>', 0) ->where('balance', '>', 0)
->where('is_recurring', '=', false) ->where('is_recurring', '=', false)

View File

@ -1,5 +1,7 @@
<?php namespace App\Http\Controllers; <?php namespace App\Http\Controllers;
use Response;
use Request;
use Redirect; use Redirect;
use Auth; use Auth;
use View; use View;
@ -8,6 +10,7 @@ use Session;
use App\Models\Account; use App\Models\Account;
use App\Libraries\Utils; use App\Libraries\Utils;
use App\Ninja\Mailers\Mailer; use App\Ninja\Mailers\Mailer;
use Symfony\Component\Security\Core\Util\StringUtils;
class HomeController extends BaseController class HomeController extends BaseController
{ {
@ -76,4 +79,9 @@ class HomeController extends BaseController
{ {
return Utils::logError(Input::get('error'), 'JavaScript'); return Utils::logError(Input::get('error'), 'JavaScript');
} }
public function keepAlive()
{
return RESULT_SUCCESS;
}
} }

View File

@ -60,10 +60,11 @@ class InvoiceApiController extends Controller
} }
if (isset($data['email'])) { if (isset($data['email'])) {
$contact = Contact::scope()->with('client')->whereEmail($data['email'])->first(); $client = Client::scope()->whereHas('contacts', function($query) use ($data) {
if ($contact) { $query->where('email', '=', $data['email']);
$client = $contact->client; })->first();
} else {
if (!$client) {
$clientData = ['contact' => ['email' => $data['email']]]; $clientData = ['contact' => ['email' => $data['email']]];
foreach (['name', 'private_notes'] as $field) { foreach (['name', 'private_notes'] as $field) {
if (isset($data[$field])) { if (isset($data[$field])) {

View File

@ -13,6 +13,7 @@ use URL;
use Datatable; use Datatable;
use finfo; use finfo;
use Request; use Request;
use DropdownButton;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Invitation; use App\Models\Invitation;
use App\Models\Client; use App\Models\Client;
@ -27,6 +28,7 @@ use App\Models\PaymentTerm;
use App\Models\InvoiceDesign; use App\Models\InvoiceDesign;
use App\Models\AccountGateway; use App\Models\AccountGateway;
use App\Models\Activity; use App\Models\Activity;
use App\Models\Gateway;
use App\Ninja\Mailers\ContactMailer as Mailer; use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\ClientRepository;
@ -58,10 +60,14 @@ class InvoiceController extends BaseController
'columns' => Utils::trans(['checkbox', 'invoice_number', 'client', 'invoice_date', 'invoice_total', 'balance_due', 'due_date', 'status', 'action']), 'columns' => Utils::trans(['checkbox', 'invoice_number', 'client', 'invoice_date', 'invoice_total', 'balance_due', 'due_date', 'status', 'action']),
]; ];
$recurringInvoices = Invoice::scope()->where('is_recurring', '=', true); $recurringInvoices = Invoice::scope()
->where('is_recurring', '=', true);
if (Session::get('show_trash:invoice')) { if (Session::get('show_trash:invoice')) {
$recurringInvoices->withTrashed(); $recurringInvoices->withTrashed();
} else {
$recurringInvoices->join('clients', 'clients.id', '=', 'invoices.client_id')
->where('clients.deleted_at', '=', null);
} }
if ($recurringInvoices->count() > 0) { if ($recurringInvoices->count() > 0) {
@ -221,7 +227,7 @@ class InvoiceController extends BaseController
'url' => URL::to("payment/{$invitation->invitation_key}/".PAYMENT_TYPE_TOKEN), 'label' => trans('texts.use_card_on_file') 'url' => URL::to("payment/{$invitation->invitation_key}/".PAYMENT_TYPE_TOKEN), 'label' => trans('texts.use_card_on_file')
]; ];
} }
foreach([PAYMENT_TYPE_CREDIT_CARD, PAYMENT_TYPE_PAYPAL, PAYMENT_TYPE_BITCOIN] as $type) { foreach(Gateway::$paymentTypes as $type) {
if ($account->getGatewayByType($type)) { if ($account->getGatewayByType($type)) {
$paymentTypes[] = [ $paymentTypes[] = [
'url' => URL::to("/payment/{$invitation->invitation_key}/{$type}"), 'label' => trans('texts.'.strtolower($type)) 'url' => URL::to("/payment/{$invitation->invitation_key}/{$type}"), 'label' => trans('texts.'.strtolower($type))
@ -275,6 +281,40 @@ class InvoiceController extends BaseController
$invoice->end_date = Utils::fromSqlDate($invoice->end_date); $invoice->end_date = Utils::fromSqlDate($invoice->end_date);
$invoice->is_pro = Auth::user()->isPro(); $invoice->is_pro = Auth::user()->isPro();
$actions = [
['url' => 'javascript:onCloneClick()', 'label' => trans("texts.clone_{$entityType}")],
['url' => URL::to("{$entityType}s/{$entityType}_history/{$invoice->public_id}"), 'label' => trans("texts.view_history")],
DropdownButton::DIVIDER
];
if ($invoice->invoice_status_id < INVOICE_STATUS_SENT && !$invoice->is_recurring) {
$actions[] = ['url' => 'javascript:onMarkClick()', 'label' => trans("texts.mark_sent")];
}
if ($entityType == ENTITY_QUOTE) {
if ($invoice->quote_invoice_id) {
$actions[] = ['url' => URL::to("invoices/{$invoice->quote_invoice_id}/edit"), 'label' => trans("texts.view_invoice")];
} else {
$actions[] = ['url' => 'javascript:onConvertClick()', 'label' => trans("texts.convert_to_invoice")];
}
} elseif ($entityType == ENTITY_INVOICE) {
if ($invoice->quote_id) {
$actions[] = ['url' => URL::to("quotes/{$invoice->quote_id}/edit"), 'label' => trans("texts.view_quote")];
}
if (!$invoice->is_recurring && $invoice->balance > 0) {
$actions[] = ['url' => 'javascript:onPaymentClick()', 'label' => trans('texts.enter_payment')];
}
}
if (count($actions) > 3) {
$actions[] = DropdownButton::DIVIDER;
}
$actions[] = ['url' => 'javascript:onArchiveClick()', 'label' => trans("texts.archive_{$entityType}")];
$actions[] = ['url' => 'javascript:onDeleteClick()', 'label' => trans("texts.delete_{$entityType}")];
$data = array( $data = array(
'entityType' => $entityType, 'entityType' => $entityType,
'showBreadcrumbs' => $clone, 'showBreadcrumbs' => $clone,
@ -285,7 +325,8 @@ class InvoiceController extends BaseController
'invitationContactIds' => $contactIds, 'invitationContactIds' => $contactIds,
'url' => $url, 'url' => $url,
'title' => trans("texts.edit_{$entityType}"), 'title' => trans("texts.edit_{$entityType}"),
'client' => $invoice->client, ); 'client' => $invoice->client,
'actions' => $actions);
$data = array_merge($data, self::getViewModel()); $data = array_merge($data, self::getViewModel());
// Set the invitation link on the client's contacts // Set the invitation link on the client's contacts
@ -327,7 +368,8 @@ class InvoiceController extends BaseController
'method' => 'POST', 'method' => 'POST',
'url' => 'invoices', 'url' => 'invoices',
'title' => trans('texts.new_invoice'), 'title' => trans('texts.new_invoice'),
'client' => $client, ); 'client' => $client,
'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null);
$data = array_merge($data, self::getViewModel()); $data = array_merge($data, self::getViewModel());
return View::make('invoices.edit', $data); return View::make('invoices.edit', $data);
@ -366,7 +408,9 @@ class InvoiceController extends BaseController
6 => 'Six months', 6 => 'Six months',
7 => 'Annually', 7 => 'Annually',
), ),
'recurringHelp' => $recurringHelp 'recurringHelp' => $recurringHelp,
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
]; ];
} }

View File

@ -90,7 +90,7 @@ class PaymentController extends BaseController
} }
$table->addColumn('transaction_reference', function ($model) { return $model->transaction_reference ? $model->transaction_reference : '<i>Manual entry</i>'; }) $table->addColumn('transaction_reference', function ($model) { return $model->transaction_reference ? $model->transaction_reference : '<i>Manual entry</i>'; })
->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? '<i>Online payment</i>' : ''); }); ->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? $model->gateway_name : ''); });
return $table->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); }) return $table->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); })
->addColumn('payment_date', function ($model) { return Utils::dateToString($model->payment_date); }) ->addColumn('payment_date', function ($model) { return Utils::dateToString($model->payment_date); })
@ -195,11 +195,6 @@ class PaymentController extends BaseController
$gateway = Omnipay::create($accountGateway->gateway->provider); $gateway = Omnipay::create($accountGateway->gateway->provider);
$config = json_decode($accountGateway->config); $config = json_decode($accountGateway->config);
/*
$gateway->setSolutionType("Sole");
$gateway->setLandingPage("Billing");
*/
foreach ($config as $key => $val) { foreach ($config as $key => $val) {
if (!$val) { if (!$val) {
continue; continue;
@ -209,8 +204,14 @@ class PaymentController extends BaseController
$gateway->$function($val); $gateway->$function($val);
} }
if (Utils::isNinjaDev()) { if ($accountGateway->gateway->id == GATEWAY_DWOLLA) {
$gateway->setTestMode(true); if ($gateway->getSandbox() && isset($_ENV['DWOLLA_SANDBOX_KEY']) && isset($_ENV['DWOLLA_SANSBOX_SECRET'])) {
$gateway->setKey($_ENV['DWOLLA_SANDBOX_KEY']);
$gateway->setSecret($_ENV['DWOLLA_SANSBOX_SECRET']);
} elseif (isset($_ENV['DWOLLA_KEY']) && isset($_ENV['DWOLLA_SECRET'])) {
$gateway->setKey($_ENV['DWOLLA_KEY']);
$gateway->setSecret($_ENV['DWOLLA_SECRET']);
}
} }
return $gateway; return $gateway;
@ -292,8 +293,9 @@ class PaymentController extends BaseController
$useToken = false; $useToken = false;
if (!$paymentType) { if (!$paymentType) {
$paymentType = $account->account_gateways[0]->getPaymentType(); $paymentType = Session::get('payment_type', $account->account_gateways[0]->getPaymentType());
} else if ($paymentType == PAYMENT_TYPE_TOKEN) { }
if ($paymentType == PAYMENT_TYPE_TOKEN) {
$useToken = true; $useToken = true;
$paymentType = PAYMENT_TYPE_CREDIT_CARD; $paymentType = PAYMENT_TYPE_CREDIT_CARD;
} }
@ -323,8 +325,9 @@ class PaymentController extends BaseController
'gateway' => $gateway, 'gateway' => $gateway,
'acceptedCreditCardTypes' => $acceptedCreditCardTypes, 'acceptedCreditCardTypes' => $acceptedCreditCardTypes,
'countries' => Cache::get('countries'), 'countries' => Cache::get('countries'),
'currencyId' => $client->currency_id, 'currencyId' => $client->getCurrencyId(),
'account' => $client->account, 'account' => $client->account,
'hideLogo' => $account->isWhiteLabel(),
]; ];
return View::make('payments.payment', $data); return View::make('payments.payment', $data);
@ -448,6 +451,7 @@ class PaymentController extends BaseController
'message' => $affiliate->payment_subtitle, 'message' => $affiliate->payment_subtitle,
'license' => $licenseKey, 'license' => $licenseKey,
'hideHeader' => true, 'hideHeader' => true,
'productId' => $license->product_id
]; ];
$name = "{$license->first_name} {$license->last_name}"; $name = "{$license->first_name} {$license->last_name}";
@ -473,7 +477,7 @@ class PaymentController extends BaseController
$productId = Input::get('product_id', PRODUCT_ONE_CLICK_INSTALL); $productId = Input::get('product_id', PRODUCT_ONE_CLICK_INSTALL);
$license = License::where('license_key', '=', $licenseKey) $license = License::where('license_key', '=', $licenseKey)
->where('is_claimed', '<', 3) ->where('is_claimed', '<', 5)
->where('product_id', '=', $productId) ->where('product_id', '=', $productId)
->first(); ->first();
@ -483,7 +487,7 @@ class PaymentController extends BaseController
$license->save(); $license->save();
} }
return $productId == PRODUCT_INVOICE_DESIGNS ? $_ENV['INVOICE_DESIGNS'] : 'valid'; return $productId == PRODUCT_INVOICE_DESIGNS ? file_get_contents(storage_path() . '/invoice_designs.txt') : 'valid';
} else { } else {
return 'invalid'; return 'invalid';
} }
@ -509,8 +513,10 @@ class PaymentController extends BaseController
$validator = Validator::make(Input::all(), $rules); $validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) { if ($validator->fails()) {
Utils::logError('Payment Error [invalid]');
return Redirect::to('payment/'.$invitationKey) return Redirect::to('payment/'.$invitationKey)
->withErrors($validator); ->withErrors($validator)
->withInput();
} }
} }
@ -533,7 +539,7 @@ class PaymentController extends BaseController
try { try {
$gateway = self::createGateway($accountGateway); $gateway = self::createGateway($accountGateway);
$details = self::getPaymentDetails($invitation, $useToken || !$onSite ? false : Input::all()); $details = self::getPaymentDetails($invitation, ($useToken || !$onSite) ? false : Input::all());
if ($accountGateway->gateway_id == GATEWAY_STRIPE) { if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
if ($useToken) { if ($useToken) {
@ -558,6 +564,10 @@ class PaymentController extends BaseController
$token->token = $cardReference; $token->token = $cardReference;
$token->save(); $token->save();
} else {
Session::flash('error', $tokenResponse->getMessage());
Utils::logError('Payment Error [no-token-ref]: ' . $tokenResponse->getMessage());
return Redirect::to('payment/'.$invitationKey)->withInput();
} }
} }
} }
@ -568,6 +578,7 @@ class PaymentController extends BaseController
if (!$ref) { if (!$ref) {
Session::flash('error', $response->getMessage()); Session::flash('error', $response->getMessage());
Utils::logError('Payment Error [no-ref]: ' . $response->getMessage());
if ($onSite) { if ($onSite) {
return Redirect::to('payment/'.$invitationKey)->withInput(); return Redirect::to('payment/'.$invitationKey)->withInput();
@ -580,6 +591,11 @@ class PaymentController extends BaseController
$payment = self::createPayment($invitation, $ref); $payment = self::createPayment($invitation, $ref);
Session::flash('message', trans('texts.applied_payment')); Session::flash('message', trans('texts.applied_payment'));
if ($account->account_key == NINJA_ACCOUNT_KEY) {
Session::flash('trackEventCategory', '/account');
Session::flash('trackEventAction', '/buy_pro_plan');
}
return Redirect::to('view/'.$payment->invitation->invitation_key); return Redirect::to('view/'.$payment->invitation->invitation_key);
} elseif ($response->isRedirect()) { } elseif ($response->isRedirect()) {
$invitation->transaction_reference = $ref; $invitation->transaction_reference = $ref;
@ -590,13 +606,14 @@ class PaymentController extends BaseController
$response->redirect(); $response->redirect();
} else { } else {
Session::flash('error', $response->getMessage()); Session::flash('error', $response->getMessage());
Utils::logError('Payment Error [fatal]: ' . $response->getMessage());
return Utils::fatalError('Sorry, there was an error processing your payment. Please try again later.<p>', $response->getMessage()); return Utils::fatalError('Sorry, there was an error processing your payment. Please try again later.<p>', $response->getMessage());
} }
} catch (\Exception $e) { } catch (\Exception $e) {
$errorMessage = trans('texts.payment_error'); $errorMessage = trans('texts.payment_error');
Session::flash('error', $errorMessage."<p>".$e->getMessage()); Session::flash('error', $errorMessage."<p>".$e->getMessage());
Utils::logError(Utils::getErrorString($e)); Utils::logError('Payment Error [uncaught]:' . Utils::getErrorString($e));
if ($onSite) { if ($onSite) {
return Redirect::to('payment/'.$invitationKey)->withInput(); return Redirect::to('payment/'.$invitationKey)->withInput();

View File

@ -20,6 +20,7 @@ use App\Models\Size;
use App\Models\TaxRate; use App\Models\TaxRate;
use App\Models\Invitation; use App\Models\Invitation;
use App\Models\Activity; use App\Models\Activity;
use App\Models\Invoice;
use App\Ninja\Mailers\ContactMailer as Mailer; use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\ClientRepository;

View File

@ -16,13 +16,14 @@ class ReportController extends BaseController
public function d3() public function d3()
{ {
$message = ''; $message = '';
$fileName = storage_path() . '/dataviz_sample.txt';
if (Auth::user()->account->isPro()) { if (Auth::user()->account->isPro()) {
$account = Account::where('id', '=', Auth::user()->account->id)->with(['clients.invoices.invoice_items', 'clients.contacts'])->first(); $account = Account::where('id', '=', Auth::user()->account->id)->with(['clients.invoices.invoice_items', 'clients.contacts'])->first();
$account = $account->hideFieldsForViz(); $account = $account->hideFieldsForViz();
$clients = $account->clients->toJson(); $clients = $account->clients->toJson();
} elseif (isset($_ENV['DATA_VIZ_SAMPLE'])) { } elseif (file_exists($fileName)) {
$clients = $_ENV['DATA_VIZ_SAMPLE']; $clients = file_get_contents($fileName);
$message = trans('texts.sample_data'); $message = trans('texts.sample_data');
} else { } else {
$clients = '[]'; $clients = '[]';

View File

@ -0,0 +1,261 @@
<?php namespace App\Http\Controllers;
use View;
use URL;
use Utils;
use Input;
use Datatable;
use Validator;
use Redirect;
use Session;
use App\Models\Client;
use App\Models\Task;
/*
use Auth;
use Cache;
use App\Models\Activity;
use App\Models\Contact;
use App\Models\Invoice;
use App\Models\Size;
use App\Models\PaymentTerm;
use App\Models\Industry;
use App\Models\Currency;
use App\Models\Country;
*/
use App\Ninja\Repositories\TaskRepository;
class TaskController extends BaseController
{
protected $taskRepo;
public function __construct(TaskRepository $taskRepo)
{
parent::__construct();
$this->taskRepo = $taskRepo;
}
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
return View::make('list', array(
'entityType' => ENTITY_TASK,
'title' => trans('texts.tasks'),
'sortCol' => '2',
'columns' => Utils::trans(['checkbox', 'client', 'date', 'duration', 'description', 'status', 'action']),
));
}
public function getDatatable($clientPublicId = null)
{
$tasks = $this->taskRepo->find($clientPublicId, Input::get('sSearch'));
$table = Datatable::query($tasks);
if (!$clientPublicId) {
$table->addColumn('checkbox', function ($model) { return '<input type="checkbox" name="ids[]" value="'.$model->public_id.'" '.Utils::getEntityRowClass($model).'>'; })
->addColumn('client_name', function ($model) { return $model->client_public_id ? link_to('clients/'.$model->client_public_id, Utils::getClientDisplayName($model)) : ''; });
}
return $table->addColumn('start_time', function($model) { return Utils::fromSqlDateTime($model->start_time); })
->addColumn('duration', function($model) { return gmdate('H:i:s', $model->duration == -1 ? time() - strtotime($model->start_time) : $model->duration); })
->addColumn('description', function($model) { return $model->description; })
->addColumn('invoice_number', function($model) { return self::getStatusLabel($model); })
->addColumn('dropdown', function ($model) {
$str = '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">';
if (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
$str .= '<li><a href="'.URL::to('tasks/'.$model->public_id.'/edit').'">'.trans('texts.edit_task').'</a></li>';
}
if ($model->invoice_number) {
$str .= '<li>' . link_to("/invoices/{$model->invoice_public_id}/edit", trans('texts.view_invoice')) . '</li>';
} elseif ($model->duration == -1) {
$str .= '<li><a href="javascript:stopTask('.$model->public_id.')">'.trans('texts.stop_task').'</a></li>';
} elseif (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
$str .= '<li><a href="javascript:invoiceTask('.$model->public_id.')">'.trans('texts.invoice_task').'</a></li>';
}
if (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
$str .= '<li class="divider"></li>
<li><a href="javascript:archiveEntity('.$model->public_id.')">'.trans('texts.archive_task').'</a></li>';
} else {
$str .= '<li><a href="javascript:restoreEntity('.$model->public_id.')">'.trans('texts.restore_task').'</a></li>';
}
if (!$model->is_deleted) {
$str .= '<li><a href="javascript:deleteEntity('.$model->public_id.')">'.trans('texts.delete_task').'</a></li></ul>';
}
return $str . '</div>';
})
->make();
}
private function getStatusLabel($model) {
if ($model->invoice_number) {
$class = 'success';
$label = trans('texts.invoiced');
} elseif ($model->duration == -1) {
$class = 'primary';
$label = trans('texts.running');
} else {
$class = 'default';
$label = trans('texts.logged');
}
return "<h4><div class=\"label label-{$class}\">$label</div></h4>";
}
/**
* Store a newly created resource in storage.
*
* @return Response
*/
public function store()
{
return $this->save();
}
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function create($clientPublicId = 0)
{
$data = [
'task' => null,
'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId,
'method' => 'POST',
'url' => 'tasks',
'title' => trans('texts.new_task'),
];
$data = array_merge($data, self::getViewModel());
return View::make('tasks.edit', $data);
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return Response
*/
public function edit($publicId)
{
$task = Task::scope($publicId)->with('client')->firstOrFail();
$data = [
'task' => $task,
'clientPublicId' => $task->client ? $task->client->public_id : 0,
'method' => 'PUT',
'url' => 'tasks/'.$publicId,
'title' => trans('texts.edit_task'),
'duration' => time() - strtotime($task->start_time),
];
$data = array_merge($data, self::getViewModel());
return View::make('tasks.edit', $data);
}
/**
* Update the specified resource in storage.
*
* @param int $id
* @return Response
*/
public function update($publicId)
{
return $this->save($publicId);
}
private static function getViewModel()
{
return [
'clients' => Client::scope()->with('contacts')->orderBy('name')->get()
];
}
private function save($publicId = null)
{
$task = $this->taskRepo->save($publicId, Input::all());
Session::flash('message', trans($publicId ? 'texts.updated_task' : 'texts.created_task'));
if (Input::get('action') == 'stop') {
return Redirect::to("tasks");
} else {
return Redirect::to("tasks/{$task->public_id}/edit");
}
}
public function bulk()
{
$action = Input::get('action');
$ids = Input::get('id') ? Input::get('id') : Input::get('ids');
if ($action == 'stop') {
$this->taskRepo->save($ids, ['action' => $action]);
Session::flash('message', trans('texts.stopped_task'));
return Redirect::to('tasks');
} else if ($action == 'invoice') {
$tasks = Task::scope($ids)->with('client')->get();
$clientPublicId = false;
$data = [];
foreach ($tasks as $task) {
if ($task->client) {
if (!$clientPublicId) {
$clientPublicId = $task->client->public_id;
} else if ($clientPublicId != $task->client->public_id) {
Session::flash('error', trans('texts.task_error_multiple_clients'));
return Redirect::to('tasks');
}
}
if ($task->duration == -1) {
Session::flash('error', trans('texts.task_error_running'));
return Redirect::to('tasks');
} else if ($task->invoice_id) {
Session::flash('error', trans('texts.task_error_invoiced'));
return Redirect::to('tasks');
}
$data[] = [
'publicId' => $task->public_id,
'description' => $task->description,
'startTime' => Utils::fromSqlDateTime($task->start_time),
'duration' => round($task->duration / (60 * 60), 2)
];
}
return Redirect::to("invoices/create/{$clientPublicId}")->with('tasks', $data);
} else {
$count = $this->taskRepo->bulk($ids, $action);
$message = Utils::pluralize($action.'d_task', $count);
Session::flash('message', $message);
if ($action == 'restore' && $count == 1) {
return Redirect::to('tasks/'.$ids[0].'/edit');
} else {
return Redirect::to('tasks');
}
}
}
}

View File

@ -1,93 +0,0 @@
<?php namespace App\Http\Controllers;
class TimesheetController extends BaseController {
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
$data = [
'showBreadcrumbs' => false,
'timesheet' => [
'timesheet_number' => 1
]
];
return View::make('timesheets.edit', $data);
}
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @return Response
*/
public function store()
{
//
}
/**
* Display the specified resource.
*
* @param int $id
* @return Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param int $id
* @return Response
*/
public function update($id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return Response
*/
public function destroy($id)
{
//
}
}

View File

@ -12,7 +12,7 @@ use Session;
use URL; use URL;
use Utils; use Utils;
use Validator; use Validator;
use Illuminate\Auth\Passwords\TokenRepositoryInterface;
use App\Models\User; use App\Models\User;
use App\Http\Requests; use App\Http\Requests;
use App\Ninja\Repositories\AccountRepository; use App\Ninja\Repositories\AccountRepository;
@ -263,7 +263,7 @@ class UserController extends BaseController
* *
* @param string $code * @param string $code
*/ */
public function confirm($code) public function confirm($code, TokenRepositoryInterface $tokenRepo)
{ {
$user = User::where('confirmation_code', '=', $code)->get()->first(); $user = User::where('confirmation_code', '=', $code)->get()->first();
@ -275,9 +275,10 @@ class UserController extends BaseController
$user->save(); $user->save();
if ($user->public_id) { if ($user->public_id) {
Auth::login($user); //Auth::login($user);
$token = $tokenRepo->create($user);
return Redirect::to('user/reset'); return Redirect::to("/password/reset/{$token}");
} else { } else {
if (Session::has(REQUESTED_PRO_PLAN)) { if (Session::has(REQUESTED_PRO_PLAN)) {
Session::forget(REQUESTED_PRO_PLAN); Session::forget(REQUESTED_PRO_PLAN);

View File

@ -35,7 +35,7 @@ class ApiCheck {
} }
if (!Utils::isNinja()) { if (!Utils::isNinja()) {
return null; return $next($request);
} }
if (!Utils::isPro()) { if (!Utils::isPro()) {

View File

@ -1,5 +1,6 @@
<?php namespace app\Http\Middleware; <?php namespace app\Http\Middleware;
use Request;
use Closure; use Closure;
use Utils; use Utils;
use App; use App;
@ -50,7 +51,10 @@ class StartupCheck
'countries' => 'App\Models\Country', 'countries' => 'App\Models\Country',
]; ];
foreach ($cachedTables as $name => $class) { foreach ($cachedTables as $name => $class) {
if (!Cache::has($name)) { if (Input::has('clear_cache')) {
Session::flash('message', 'Cache cleared');
}
if (Input::has('clear_cache') || !Cache::has($name)) {
if ($name == 'paymentTerms') { if ($name == 'paymentTerms') {
$orderBy = 'num_days'; $orderBy = 'num_days';
} elseif (in_array($name, ['currencies', 'sizes', 'industries', 'languages', 'countries'])) { } elseif (in_array($name, ['currencies', 'sizes', 'industries', 'languages', 'countries'])) {
@ -58,7 +62,10 @@ class StartupCheck
} else { } else {
$orderBy = 'id'; $orderBy = 'id';
} }
Cache::forever($name, $class::orderBy($orderBy)->get()); $tableData = $class::orderBy($orderBy)->get();
if (count($tableData)) {
Cache::forever($name, $tableData);
}
} }
} }
@ -137,10 +144,6 @@ class StartupCheck
$design->save(); $design->save();
} }
if (!Utils::isNinjaProd()) {
Cache::forget('invoice_designs_cache_'.Auth::user()->maxInvoiceDesignId());
}
Session::flash('message', trans('texts.bought_designs')); Session::flash('message', trans('texts.bought_designs'));
} }
} elseif ($productId == PRODUCT_WHITE_LABEL) { } elseif ($productId == PRODUCT_WHITE_LABEL) {

View File

@ -1,6 +1,5 @@
<?php <?php
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Application Routes | Application Routes
@ -32,6 +31,7 @@ Route::get('/', 'HomeController@showIndex');
Route::get('terms', 'HomeController@showTerms'); Route::get('terms', 'HomeController@showTerms');
Route::get('log_error', 'HomeController@logError'); Route::get('log_error', 'HomeController@logError');
Route::get('invoice_now', 'HomeController@invoiceNow'); Route::get('invoice_now', 'HomeController@invoiceNow');
Route::get('keep_alive', 'HomeController@keepAlive');
Route::post('get_started', 'AccountController@getStarted'); Route::post('get_started', 'AccountController@getStarted');
// Client visible pages // Client visible pages
@ -74,16 +74,6 @@ get('/password/reset/{token}', array('as' => 'forgot', 'uses' => 'Auth\PasswordC
post('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset')); post('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset'));
get('/user/confirm/{code}', 'UserController@confirm'); get('/user/confirm/{code}', 'UserController@confirm');
/*
// Confide routes
Route::get('login', 'UserController@login');
Route::post('login', 'UserController@do_login');
Route::get('forgot_password', 'UserController@forgot_password');
Route::post('forgot_password', 'UserController@do_forgot_password');
Route::get('user/reset/{token?}', 'UserController@reset_password');
Route::post('user/reset', 'UserController@do_reset_password');
Route::get('logout', 'UserController@logout');
*/
if (Utils::isNinja()) { if (Utils::isNinja()) {
Route::post('/signup/register', 'AccountController@doRegister'); Route::post('/signup/register', 'AccountController@doRegister');
@ -133,6 +123,11 @@ Route::group(['middleware' => 'auth'], function() {
Route::get('api/activities/{client_id?}', array('as'=>'api.activities', 'uses'=>'ActivityController@getDatatable')); Route::get('api/activities/{client_id?}', array('as'=>'api.activities', 'uses'=>'ActivityController@getDatatable'));
Route::post('clients/bulk', 'ClientController@bulk'); Route::post('clients/bulk', 'ClientController@bulk');
Route::resource('tasks', 'TaskController');
Route::get('api/tasks/{client_id?}', array('as'=>'api.tasks', 'uses'=>'TaskController@getDatatable'));
Route::get('tasks/create/{client_id?}', 'TaskController@create');
Route::post('tasks/bulk', 'TaskController@bulk');
Route::get('recurring_invoices', 'InvoiceController@recurringIndex'); Route::get('recurring_invoices', 'InvoiceController@recurringIndex');
Route::get('api/recurring_invoices/{client_id?}', array('as'=>'api.recurring_invoices', 'uses'=>'InvoiceController@getRecurringDatatable')); Route::get('api/recurring_invoices/{client_id?}', array('as'=>'api.recurring_invoices', 'uses'=>'InvoiceController@getRecurringDatatable'));
@ -226,6 +221,7 @@ define('ENTITY_RECURRING_INVOICE', 'recurring_invoice');
define('ENTITY_PAYMENT', 'payment'); define('ENTITY_PAYMENT', 'payment');
define('ENTITY_CREDIT', 'credit'); define('ENTITY_CREDIT', 'credit');
define('ENTITY_QUOTE', 'quote'); define('ENTITY_QUOTE', 'quote');
define('ENTITY_TASK', 'task');
define('PERSON_CONTACT', 'contact'); define('PERSON_CONTACT', 'contact');
define('PERSON_USER', 'user'); define('PERSON_USER', 'user');
@ -289,6 +285,7 @@ define('MAX_NUM_CLIENTS', 500);
define('MAX_NUM_CLIENTS_PRO', 20000); define('MAX_NUM_CLIENTS_PRO', 20000);
define('MAX_NUM_USERS', 20); define('MAX_NUM_USERS', 20);
define('MAX_SUBDOMAIN_LENGTH', 30); define('MAX_SUBDOMAIN_LENGTH', 30);
define('DEFAULT_FONT_SIZE', 9);
define('INVOICE_STATUS_DRAFT', 1); define('INVOICE_STATUS_DRAFT', 1);
define('INVOICE_STATUS_SENT', 2); define('INVOICE_STATUS_SENT', 2);
@ -342,6 +339,7 @@ define('GATEWAY_BEANSTREAM', 29);
define('GATEWAY_PSIGATE', 30); define('GATEWAY_PSIGATE', 30);
define('GATEWAY_MOOLAH', 31); define('GATEWAY_MOOLAH', 31);
define('GATEWAY_BITPAY', 42); define('GATEWAY_BITPAY', 42);
define('GATEWAY_DWOLLA', 43);
define('EVENT_CREATE_CLIENT', 1); define('EVENT_CREATE_CLIENT', 1);
define('EVENT_CREATE_INVOICE', 2); define('EVENT_CREATE_INVOICE', 2);
@ -355,7 +353,7 @@ define('NINJA_GATEWAY_ID', GATEWAY_STRIPE);
define('NINJA_GATEWAY_CONFIG', ''); define('NINJA_GATEWAY_CONFIG', '');
define('NINJA_WEB_URL', 'https://www.invoiceninja.com'); define('NINJA_WEB_URL', 'https://www.invoiceninja.com');
define('NINJA_APP_URL', 'https://app.invoiceninja.com'); define('NINJA_APP_URL', 'https://app.invoiceninja.com');
define('NINJA_VERSION', '2.0.1'); define('NINJA_VERSION', '2.2.0');
define('NINJA_DATE', '2000-01-01'); define('NINJA_DATE', '2000-01-01');
define('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com'); define('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com');
define('RELEASES_URL', 'https://github.com/hillelcoren/invoice-ninja/releases/'); define('RELEASES_URL', 'https://github.com/hillelcoren/invoice-ninja/releases/');
@ -386,6 +384,7 @@ define('TOKEN_BILLING_ALWAYS', 4);
define('PAYMENT_TYPE_PAYPAL', 'PAYMENT_TYPE_PAYPAL'); define('PAYMENT_TYPE_PAYPAL', 'PAYMENT_TYPE_PAYPAL');
define('PAYMENT_TYPE_CREDIT_CARD', 'PAYMENT_TYPE_CREDIT_CARD'); define('PAYMENT_TYPE_CREDIT_CARD', 'PAYMENT_TYPE_CREDIT_CARD');
define('PAYMENT_TYPE_BITCOIN', 'PAYMENT_TYPE_BITCOIN'); define('PAYMENT_TYPE_BITCOIN', 'PAYMENT_TYPE_BITCOIN');
define('PAYMENT_TYPE_DWOLLA', 'PAYMENT_TYPE_DWOLLA');
define('PAYMENT_TYPE_TOKEN', 'PAYMENT_TYPE_TOKEN'); define('PAYMENT_TYPE_TOKEN', 'PAYMENT_TYPE_TOKEN');
define('PAYMENT_TYPE_ANY', 'PAYMENT_TYPE_ANY'); define('PAYMENT_TYPE_ANY', 'PAYMENT_TYPE_ANY');
@ -397,11 +396,6 @@ define('GATEWAY_GOOGLE', 33);
define('GATEWAY_QUICKBOOKS', 35); define('GATEWAY_QUICKBOOKS', 35);
*/ */
/**
* TEST VALUES FOR THE CREDIT CARDS
* NUMBER IS FOR THE BINARY COUNT FOR WHICH IMAGES TO DISPLAY
* card IS FOR CARD IMAGE AND text IS FOR CARD NAME (TO ADD TO alt FOR IMAGE)
**/
$creditCards = [ $creditCards = [
1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'], 1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'],
2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'], 2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'],
@ -412,75 +406,6 @@ $creditCards = [
define('CREDIT_CARDS', serialize($creditCards)); define('CREDIT_CARDS', serialize($creditCards));
HTML::macro('nav_link', function($url, $text, $url2 = '', $extra = '') {
$class = ( Request::is($url) || Request::is($url.'/*') || Request::is($url2.'/*') ) ? ' class="active"' : '';
$title = ucwords(trans("texts.$text")) . Utils::getProLabel($text);
return '<li'.$class.'><a href="'.URL::to($url).'" '.$extra.'>'.$title.'</a></li>';
});
HTML::macro('tab_link', function($url, $text, $active = false) {
$class = $active ? ' class="active"' : '';
return '<li'.$class.'><a href="'.URL::to($url).'" data-toggle="tab">'.$text.'</a></li>';
});
HTML::macro('menu_link', function($type) {
$types = $type.'s';
$Type = ucfirst($type);
$Types = ucfirst($types);
$class = ( Request::is($types) || Request::is('*'.$type.'*')) && !Request::is('*advanced_settings*') ? ' active' : '';
return '<li class="dropdown '.$class.'">
<a href="'.URL::to($types).'" class="dropdown-toggle">'.trans("texts.$types").'</a>
<ul class="dropdown-menu" id="menu1">
<li><a href="'.URL::to($types.'/create').'">'.trans("texts.new_$type").'</a></li>
</ul>
</li>';
});
HTML::macro('image_data', function($imagePath) {
return 'data:image/jpeg;base64,' . base64_encode(file_get_contents(public_path().'/'.$imagePath));
});
HTML::macro('breadcrumbs', function() {
$str = '<ol class="breadcrumb">';
// Get the breadcrumbs by exploding the current path.
$basePath = Utils::basePath();
$parts = explode('?', $_SERVER['REQUEST_URI']);
$path = $parts[0];
if ($basePath != '/') {
$path = str_replace($basePath, '', $path);
}
$crumbs = explode('/', $path);
foreach ($crumbs as $key => $val) {
if (is_numeric($val)) {
unset($crumbs[$key]);
}
}
$crumbs = array_values($crumbs);
for ($i=0; $i<count($crumbs); $i++) {
$crumb = trim($crumbs[$i]);
if (!$crumb) {
continue;
}
if ($crumb == 'company') {
return '';
}
$name = trans("texts.$crumb");
if ($i==count($crumbs)-1) {
$str .= "<li class='active'>$name</li>";
} else {
$str .= '<li>'.link_to($crumb, $name).'</li>';
}
}
return $str . '</ol>';
});
function uctrans($text) function uctrans($text)
{ {
return ucwords(trans($text)); return ucwords(trans($text));
@ -500,21 +425,6 @@ function otrans($text)
} }
} }
Validator::extend('positive', function($attribute, $value, $parameters) {
return Utils::parseFloat($value) >= 0;
});
Validator::extend('has_credit', function($attribute, $value, $parameters) {
$publicClientId = $parameters[0];
$amount = $parameters[1];
$client = \App\Models\Client::scope($publicClientId)->firstOrFail();
$credit = $client->getTotalCredit();
return $credit >= $amount;
});
/* /*
// Log all SQL queries to laravel.log // Log all SQL queries to laravel.log
Event::listen('illuminate.query', function($query, $bindings, $time, $name) Event::listen('illuminate.query', function($query, $bindings, $time, $name)

View File

@ -233,7 +233,7 @@ class Utils
public static function formatMoney($value, $currencyId = false) public static function formatMoney($value, $currencyId = false)
{ {
if (!$currencyId) { if (!$currencyId) {
$currencyId = Session::get(SESSION_CURRENCY); $currencyId = Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY);
} }
foreach (Cache::get('currencies') as $currency) { foreach (Cache::get('currencies') as $currency) {
@ -333,7 +333,23 @@ class Utils
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE); $timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT); $format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
$dateTime = DateTime::createFromFormat('Y-m-d', $date, new DateTimeZone($timezone)); $dateTime = DateTime::createFromFormat('Y-m-d', $date);
$dateTime->setTimeZone(new DateTimeZone($timezone));
return $formatResult ? $dateTime->format($format) : $dateTime;
}
public static function fromSqlDateTime($date, $formatResult = true)
{
if (!$date || $date == '0000-00-00 00:00:00') {
return '';
}
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
$format = Session::get(SESSION_DATETIME_FORMAT, DEFAULT_DATETIME_FORMAT);
$dateTime = DateTime::createFromFormat('Y-m-d H:i:s', $date);
$dateTime->setTimeZone(new DateTimeZone($timezone));
return $formatResult ? $dateTime->format($format) : $dateTime; return $formatResult ? $dateTime->format($format) : $dateTime;
} }
@ -404,6 +420,9 @@ class Utils
if (count($matches) == 0) { if (count($matches) == 0) {
continue; continue;
} }
usort($matches, function($a, $b) {
return strlen($b) - strlen($a);
});
foreach ($matches as $match) { foreach ($matches as $match) {
$offset = 0; $offset = 0;
$addArray = explode('+', $match); $addArray = explode('+', $match);

View File

@ -1,119 +0,0 @@
<?php
class TimesheetUtils
{
public static function parseEventSummary($summary) {
if (preg_match('/^\s*([^\s:\/]+)(?:\/([^:]+))?\s*:\s*([^)].*$|$)$/s', $summary, $matches)) {
return [strtoupper($matches[1]), strtolower($matches[2]), $matches[3]];
} else {
return false;
}
}
public static function parseICALEvent($eventstr) {
if (preg_match_all('/(?:^|\r?\n)([^;:]+)[;:]([^\r\n]+)/s', $eventstr, $matches)) {
// Build ICAL event array
$data = ['summary' => ''];
foreach ($matches[1] as $i => $key) {
# Convert escaped linebreakes to linebreak
$value = preg_replace("/\r?\n\s/", "", $matches[2][$i]);
# Unescape , and ;
$value = preg_replace('/\\\\([,;])/s', '$1', $value);
$data[strtolower($key)] = $value;
}
return $data;
} else {
return false;
}
}
public static function parseICALDate($datestr) {
$dt = null;
$timezone = null;
if (preg_match('/^TZID=(.+?):([12]\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)$/', $datestr, $m)) {
$timezone = $m[1];
$dt = new DateTime("{$m[2]}-{$m[3]}-{$m[4]}T{$m[5]}:{$m[6]}:{$m[7]}", new DateTimeZone($m[1]));
} else if (preg_match('/^VALUE=DATE:([12]\d\d\d)(\d\d)(\d\d)$/', $datestr, $m)) {
$dt = new DateTime("{$m[1]}-{$m[2]}-{$m[3]}T00:00:00", new DateTimeZone("UTC"));
} else if (preg_match('/^([12]\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)Z$/', $datestr, $m)) {
$dt = new DateTime("{$m[1]}-{$m[2]}-{$m[3]}T{$m[4]}:{$m[5]}:{$m[6]}", new DateTimeZone("UTC"));
} else {
return false;
}
// Convert all to UTC
if($dt->getTimezone()->getName() != 'UTC') {
$dt->setTimezone(new DateTimeZone('UTC'));
}
return [$dt, $timezone];
}
public static function curlGetUrls($urls = [], $timeout = 30) {
// Create muxer
$results = [];
$multi = curl_multi_init();
$handles = [];
$ch2idx = [];
try {
foreach ($urls as $i => $url) {
// Create new handle and add to muxer
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_ENCODING, "gzip");
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); //timeout in seconds
curl_multi_add_handle($multi, $ch);
$handles[(int) $ch] = $ch;
$ch2idx[(int) $ch] = $i;
}
// Do initial connect
$still_running = true;
while ($still_running) {
// Do curl stuff
while (($mrc = curl_multi_exec($multi, $still_running)) === CURLM_CALL_MULTI_PERFORM);
if ($mrc !== CURLM_OK) {
break;
}
// Try to read from handles that are ready
while ($info = curl_multi_info_read($multi)) {
if ($info["result"] == CURLE_OK) {
$results[$ch2idx[(int) $info["handle"]]] = curl_multi_getcontent($info["handle"]);
} else {
if (CURLE_UNSUPPORTED_PROTOCOL == $info["result"]) {
$results[$ch2idx[(int) $info["handle"]]] = [$info["result"], "Unsupported protocol"];
} else if (CURLE_URL_MALFORMAT == $info["result"]) {
$results[$ch2idx[(int) $info["handle"]]] = [$info["result"], "Malform url"];
} else if (CURLE_COULDNT_RESOLVE_HOST == $info["result"]) {
$results[$ch2idx[(int) $info["handle"]]] = [$info["result"], "Could not resolve host"];
} else if (CURLE_OPERATION_TIMEDOUT == $info["result"]) {
$results[$ch2idx[(int) $info["handle"]]] = [$info["result"], "Timed out waiting for operations to finish"];
} else {
$results[$ch2idx[(int) $info["handle"]]] = [$info["result"], "Unknown curl error code"];
}
}
}
// Sleep until
if (($rs = curl_multi_select($multi)) === -1) {
usleep(20); // select failed for some reason, so we sleep for 20ms and run some more curl stuff
}
}
} finally {
foreach ($handles as $chi => $ch) {
curl_multi_remove_handle($multi, $ch);
}
curl_multi_close($multi);
}
return $results;
}
}

View File

@ -1,22 +1,25 @@
<?php namespace App\Listeners; <?php namespace App\Listeners;
use Utils;
use Auth; use Auth;
use Carbon; use Carbon;
use App\Events\UserLoggedIn; use App\Events\UserLoggedIn;
use App\Ninja\Repositories\AccountRepository;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldBeQueued; use Illuminate\Contracts\Queue\ShouldBeQueued;
class HandleUserLoggedIn { class HandleUserLoggedIn {
protected $accountRepo;
/** /**
* Create the event handler. * Create the event handler.
* *
* @return void * @return void
*/ */
public function __construct() public function __construct(AccountRepository $accountRepo)
{ {
// $this->accountRepo = $accountRepo;
} }
/** /**
@ -28,6 +31,11 @@ class HandleUserLoggedIn {
public function handle(UserLoggedIn $event) public function handle(UserLoggedIn $event)
{ {
$account = Auth::user()->account; $account = Auth::user()->account;
if (!Utils::isNinja() && empty($account->last_login)) {
$this->accountRepo->registerUser(Auth::user());
}
$account->last_login = Carbon::now()->toDateTimeString(); $account->last_login = Carbon::now()->toDateTimeString();
$account->save(); $account->save();

View File

@ -160,12 +160,19 @@ class Account extends Eloquent
return $height; return $height;
} }
public function getNextInvoiceNumber($isQuote = false) public function getNextInvoiceNumber($isQuote = false, $prefix = '')
{ {
$counter = $isQuote && !$this->share_counter ? $this->quote_number_counter : $this->invoice_number_counter; $counter = $isQuote && !$this->share_counter ? $this->quote_number_counter : $this->invoice_number_counter;
$prefix = $isQuote ? $this->quote_number_prefix : $this->invoice_number_prefix; $prefix .= $isQuote ? $this->quote_number_prefix : $this->invoice_number_prefix;
return $prefix.str_pad($counter, 4, "0", STR_PAD_LEFT); // confirm the invoice number isn't already taken
do {
$number = $prefix.str_pad($counter, 4, "0", STR_PAD_LEFT);
$check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first();
$counter++;
} while ($check);
return $number;
} }
public function incrementCounter($invoiceNumber, $isQuote = false, $isRecurring) public function incrementCounter($invoiceNumber, $isQuote = false, $isRecurring)
@ -212,6 +219,8 @@ class Account extends Eloquent
public function getInvoiceLabels() public function getInvoiceLabels()
{ {
$data = []; $data = [];
$custom = (array) json_decode($this->invoice_labels);
$fields = [ $fields = [
'invoice', 'invoice',
'invoice_date', 'invoice_date',
@ -238,10 +247,21 @@ class Account extends Eloquent
'quote_number', 'quote_number',
'total', 'total',
'invoice_issued_to', 'invoice_issued_to',
'date',
'rate',
'hours',
]; ];
foreach ($fields as $field) { foreach ($fields as $field) {
$data[$field] = trans("texts.$field"); if (isset($custom[$field]) && $custom[$field]) {
$data[$field] = $custom[$field];
} else {
$data[$field] = uctrans("texts.$field");
}
}
foreach (['item', 'quantity', 'unit_cost'] as $field) {
$data["{$field}_orig"] = $data[$field];
} }
return $data; return $data;

View File

@ -317,8 +317,21 @@ class Activity extends Eloquent
$invoice = $payment->invoice; $invoice = $payment->invoice;
$invoice->balance = $invoice->balance + $payment->amount; $invoice->balance = $invoice->balance + $payment->amount;
if ($invoice->isPaid() && $invoice->balance > 0) {
$invoice->invoice_status_id = ($invoice->balance == $invoice->amount ? INVOICE_STATUS_DRAFT : INVOICE_STATUS_PARTIAL);
}
$invoice->save(); $invoice->save();
// deleting a payment from credit creates a new credit
if ($payment->payment_type_id == PAYMENT_TYPE_CREDIT) {
$credit = Credit::createNew();
$credit->client_id = $client->id;
$credit->credit_date = Carbon::now()->toDateTimeString();
$credit->balance = $credit->amount = $payment->amount;
$credit->private_notes = $payment->transaction_reference;
$credit->save();
}
$activity = Activity::getBlank(); $activity = Activity::getBlank();
$activity->payment_id = $payment->id; $activity->payment_id = $payment->id;
$activity->client_id = $invoice->client_id; $activity->client_id = $invoice->client_id;
@ -393,7 +406,7 @@ class Activity extends Eloquent
public static function createCredit($credit) public static function createCredit($credit)
{ {
$activity = Activity::getBlank(); $activity = Activity::getBlank();
$activity->message = Utils::encodeActivity(Auth::user(), 'entered '.Utils::formatMoney($credit->amount, $credit->client->currency_id).' credit'); $activity->message = Utils::encodeActivity(Auth::user(), 'entered '.Utils::formatMoney($credit->amount, $credit->client->getCurrencyId()).' credit');
$activity->credit_id = $credit->id; $activity->credit_id = $credit->id;
$activity->client_id = $credit->client_id; $activity->client_id = $credit->client_id;
$activity->activity_type_id = ACTIVITY_TYPE_CREATE_CREDIT; $activity->activity_type_id = ACTIVITY_TYPE_CREATE_CREDIT;
@ -408,7 +421,7 @@ class Activity extends Eloquent
$activity->credit_id = $credit->id; $activity->credit_id = $credit->id;
$activity->client_id = $credit->client_id; $activity->client_id = $credit->client_id;
$activity->activity_type_id = ACTIVITY_TYPE_DELETE_CREDIT; $activity->activity_type_id = ACTIVITY_TYPE_DELETE_CREDIT;
$activity->message = Utils::encodeActivity(Auth::user(), 'deleted '.Utils::formatMoney($credit->balance, $credit->client->currency_id).' credit'); $activity->message = Utils::encodeActivity(Auth::user(), 'deleted '.Utils::formatMoney($credit->balance, $credit->client->getCurrencyId()).' credit');
$activity->balance = $credit->client->balance; $activity->balance = $credit->client->balance;
$activity->save(); $activity->save();
} else { } else {
@ -447,7 +460,7 @@ class Activity extends Eloquent
$activity->client_id = $credit->client_id; $activity->client_id = $credit->client_id;
$activity->credit_id = $credit->id; $activity->credit_id = $credit->id;
$activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_CREDIT; $activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_CREDIT;
$activity->message = Utils::encodeActivity(Auth::user(), 'archived '.Utils::formatMoney($credit->balance, $credit->client->currency_id).' credit'); $activity->message = Utils::encodeActivity(Auth::user(), 'archived '.Utils::formatMoney($credit->balance, $credit->client->getCurrencyId()).' credit');
$activity->balance = $credit->client->balance; $activity->balance = $credit->client->balance;
$activity->save(); $activity->save();
} }
@ -458,7 +471,7 @@ class Activity extends Eloquent
$activity->client_id = $credit->client_id; $activity->client_id = $credit->client_id;
$activity->credit_id = $credit->id; $activity->credit_id = $credit->id;
$activity->activity_type_id = ACTIVITY_TYPE_RESTORE_CREDIT; $activity->activity_type_id = ACTIVITY_TYPE_RESTORE_CREDIT;
$activity->message = Utils::encodeActivity(Auth::user(), 'restored '.Utils::formatMoney($credit->balance, $credit->client->currency_id).' credit'); $activity->message = Utils::encodeActivity(Auth::user(), 'restored '.Utils::formatMoney($credit->balance, $credit->client->getCurrencyId()).' credit');
$activity->balance = $credit->client->balance; $activity->balance = $credit->client->balance;
$activity->save(); $activity->save();
} }

View File

@ -148,6 +148,15 @@ class Client extends EntityModel
$token = $this->getGatewayToken(); $token = $this->getGatewayToken();
return $token ? "https://dashboard.stripe.com/customers/{$token}" : false; return $token ? "https://dashboard.stripe.com/customers/{$token}" : false;
} }
public function getCurrencyId()
{
if (!$this->account) {
$this->load('account');
}
return $this->currency_id ?: ($this->account->currency_id ?: DEFAULT_CURRENCY);
}
} }
/* /*

View File

@ -65,7 +65,7 @@ class EntityModel extends Eloquent
$accountId = Auth::user()->account_id; $accountId = Auth::user()->account_id;
} }
$query->whereAccountId($accountId); $query->where($this->getTable() .'.account_id', '=', $accountId);
if ($publicId) { if ($publicId) {
if (is_array($publicId)) { if (is_array($publicId)) {

View File

@ -7,6 +7,33 @@ class Gateway extends Eloquent
{ {
public $timestamps = true; public $timestamps = true;
public static $paymentTypes = [
PAYMENT_TYPE_CREDIT_CARD,
PAYMENT_TYPE_PAYPAL,
PAYMENT_TYPE_BITCOIN,
PAYMENT_TYPE_DWOLLA
];
public static $hiddenFields = [
// PayPal
'headerImageUrl',
'solutionType',
'landingPage',
'brandName',
'logoImageUrl',
'borderColor',
// Dwolla
'returnUrl',
];
public static $optionalFields = [
// PayPal
'testMode',
'developerMode',
// Dwolla
'sandbox',
];
public function getLogoUrl() public function getLogoUrl()
{ {
return '/images/gateways/logo_'.$this->provider.'.png'; return '/images/gateways/logo_'.$this->provider.'.png';
@ -24,6 +51,8 @@ class Gateway extends Eloquent
$link = 'https://www.2checkout.com/referral?r=2c37ac2298'; $link = 'https://www.2checkout.com/referral?r=2c37ac2298';
} elseif ($this->id == GATEWAY_BITPAY) { } elseif ($this->id == GATEWAY_BITPAY) {
$link = 'https://bitpay.com/dashboard/signup'; $link = 'https://bitpay.com/dashboard/signup';
} elseif ($this->id == GATEWAY_DWOLLA) {
$link = 'https://www.dwolla.com/register';
} }
$key = 'texts.gateway_help_'.$this->id; $key = 'texts.gateway_help_'.$this->id;
@ -42,6 +71,8 @@ class Gateway extends Eloquent
return PAYMENT_TYPE_PAYPAL; return PAYMENT_TYPE_PAYPAL;
} else if ($gatewayId == GATEWAY_BITPAY) { } else if ($gatewayId == GATEWAY_BITPAY) {
return PAYMENT_TYPE_BITCOIN; return PAYMENT_TYPE_BITCOIN;
} else if ($gatewayId == GATEWAY_DWOLLA) {
return PAYMENT_TYPE_DWOLLA;
} else { } else {
return PAYMENT_TYPE_CREDIT_CARD; return PAYMENT_TYPE_CREDIT_CARD;
} }

View File

@ -33,7 +33,10 @@ class Invitation extends EntityModel
$url = SITE_URL; $url = SITE_URL;
if ($this->account->subdomain) { if ($this->account->subdomain) {
$url = str_replace('://www.', "://{$this->account->subdomain}.", $url); $parsedUrl = parse_url($url);
$host = explode('.', $parsedUrl['host']);
$subdomain = $host[0];
$url = str_replace("://{$subdomain}.", "://{$this->account->subdomain}.", $url);
} }
return "{$url}/view/{$this->invitation_key}"; return "{$url}/view/{$this->invitation_key}";

View File

@ -10,6 +10,7 @@ class Invoice extends EntityModel
protected $casts = [ protected $casts = [
'is_recurring' => 'boolean', 'is_recurring' => 'boolean',
'has_tasks' => 'boolean',
]; ];
public function account() public function account()
@ -121,6 +122,7 @@ class Invoice extends EntityModel
'custom_taxes1', 'custom_taxes1',
'custom_taxes2', 'custom_taxes2',
'partial', 'partial',
'has_tasks',
]); ]);
$this->client->setVisible([ $this->client->setVisible([

View File

@ -34,7 +34,7 @@ class Payment extends EntityModel
public function getAmount() public function getAmount()
{ {
return Utils::formatMoney($this->amount, $this->client->currency_id); return Utils::formatMoney($this->amount, $this->client->getCurrencyId());
} }
public function getName() public function getName()

View File

@ -1,49 +0,0 @@
<?php namespace App\Models;
use Auth;
use Utils;
use Eloquent;
class Project extends Eloquent
{
public $timestamps = true;
protected $softDelete = true;
public function account()
{
return $this->belongsTo('App\Models\Account');
}
public function user()
{
return $this->belongsTo('App\Models\User');
}
public function client()
{
return $this->belongsTo('App\Models\Client');
}
public function codes()
{
return $this->hasMany('App\Models\ProjectCode');
}
public static function createNew($parent = false)
{
$className = get_called_class();
$entity = new $className();
if ($parent) {
$entity->user_id = $parent instanceof User ? $parent->id : $parent->user_id;
$entity->account_id = $parent->account_id;
} elseif (Auth::check()) {
$entity->user_id = Auth::user()->id;
$entity->account_id = Auth::user()->account_id;
} else {
Utils::fatalError();
}
return $entity;
}
}

View File

@ -1,51 +0,0 @@
<?php namespace App\Models;
use Auth;
use Utils;
use Eloquent;
use Illuminate\Database\Eloquent\SoftDeletes;
class ProjectCode extends Eloquent
{
public $timestamps = true;
use SoftDeletes;
protected $dates = ['deleted_at'];
public function account()
{
return $this->belongsTo('App\Models\Account');
}
public function user()
{
return $this->belongsTo('App\Models\User');
}
public function project()
{
return $this->belongsTo('App\Models\Project');
}
public function events()
{
return $this->hasMany('App\Models\TimesheetEvent');
}
public static function createNew($parent = false)
{
$className = get_called_class();
$entity = new $className();
if ($parent) {
$entity->user_id = $parent instanceof User ? $parent->id : $parent->user_id;
$entity->account_id = $parent->account_id;
} elseif (Auth::check()) {
$entity->user_id = Auth::user()->id;
$entity->account_id = Auth::user()->account_id;
} else {
Utils::fatalError();
}
return $entity;
}
}

36
app/Models/Task.php Normal file
View File

@ -0,0 +1,36 @@
<?php namespace App\Models;
use DB;
use Illuminate\Database\Eloquent\SoftDeletes;
class Task extends EntityModel
{
use SoftDeletes;
public function account()
{
return $this->belongsTo('App\Models\Account');
}
public function client()
{
return $this->belongsTo('App\Models\Client')->withTrashed();
}
}
Task::created(function ($task) {
//Activity::createTask($task);
});
Task::updating(function ($task) {
//Activity::updateTask($task);
});
Task::deleting(function ($task) {
//Activity::archiveTask($task);
});
Task::restoring(function ($task) {
//Activity::restoreTask($task);
});

View File

@ -1,26 +0,0 @@
<?php namespace App\Models;
use Eloquent;
use Illuminate\Database\Eloquent\SoftDeletes;
class Timesheet extends Eloquent
{
public $timestamps = true;
use SoftDeletes;
protected $dates = ['deleted_at'];
public function account()
{
return $this->belongsTo('App\Models\Account');
}
public function user()
{
return $this->belongsTo('App\Models\User');
}
public function timesheet_events()
{
return $this->hasMany('App\Models\TimeSheetEvent');
}
}

View File

@ -1,128 +0,0 @@
<?php namespace App\Models;
use Auth;
use Utils;
use Eloquent;
use Illuminate\Database\Eloquent\SoftDeletes;
class TimesheetEvent extends Eloquent
{
public $timestamps = true;
use SoftDeletes;
protected $dates = ['deleted_at'];
/* protected $dates = array('org_updated_at');
public function getDates() {
return array('created_at', 'updated_at', 'deleted_at');
} */
/* public function setOrgUpdatedAtAttribute($value)
{
var_dump($value);
$this->attributes['org_updated_at'] = $value->getTimestamp();
}*/
public function account()
{
return $this->belongsTo('App\Models\Account');
}
public function user()
{
return $this->belongsTo('App\Models\User');
}
public function source()
{
return $this->belongsTo('App\Models\TimesheetEventSource');
}
public function timesheet()
{
return $this->belongsTo('App\Models\Timesheet');
}
public function project()
{
return $this->belongsTo('App\Models\Project');
}
public function project_code()
{
return $this->belongsTo('App\Models\ProjectCode');
}
/**
* @return TimesheetEvent
*/
public static function createNew($parent = false)
{
$className = get_called_class();
$entity = new $className();
if ($parent) {
$entity->user_id = $parent instanceof User ? $parent->id : $parent->user_id;
$entity->account_id = $parent->account_id;
} elseif (Auth::check()) {
$entity->user_id = Auth::user()->id;
$entity->account_id = Auth::user()->account_id;
} else {
Utils::fatalError();
}
return $entity;
}
public function toChangesArray(TimesheetEvent $other)
{
$attributes_old = parent::toArray();
$attributes_new = $other->toArray();
$skip_keys = ['id' => 1, 'created_at' => 1, 'updated_at' => 1, 'deleted_at' => 1, 'org_data' => 1, 'update_data' => 1];
$zeroisempty_keys = ['discount' => 1];
$result = [];
// Find all the values that where changed or deleted
foreach ($attributes_old as $key => $value) {
// Skip null values, keys we don't care about and 0 value keys that means they are not used
if (empty($value) || isset($skip_keys[$key]) || (isset($zeroisempty_keys[$key]) && $value)) {
continue;
}
// Compare values if it exists in the new array
if (isset($attributes_new[$key]) || array_key_exists($key, $attributes_new)) {
if ($value instanceof \DateTime && $attributes_new[$key] instanceof \DateTime) {
if ($value != $attributes_new[$key]) {
$result[$key] = $attributes_new[$key]->format("Y-m-d H:i:s");
}
} elseif ($value instanceof \DateTime && is_string($attributes_new[$key])) {
if ($value->format("Y-m-d H:i:s") != $attributes_new[$key]) {
$result[$key] = $attributes_new[$key];
}
} elseif (is_string($value) && $attributes_new[$key] instanceof \DateTime) {
if ($attributes_new[$key]->format("Y-m-d H:i:s") != $value) {
$result[$key] = $attributes_new[$key]->format("Y-m-d H:i:s");
}
} elseif ($value != $attributes_new[$key]) {
$result[$key] = $attributes_new[$key];
}
} else {
$result[$key] = null;
}
}
// Find all the values that where deleted
foreach ($attributes_new as $key => $value) {
if (isset($skip_keys[$key])) {
continue;
}
if (!isset($attributes_old[$key])) {
$result[$key] = $value;
}
}
return $result;
}
}

View File

@ -1,46 +0,0 @@
<?php namespace App\Models;
use Auth;
use Utils;
use Eloquent;
use Illuminate\Database\Eloquent\SoftDeletes;
class TimesheetEventSource extends Eloquent
{
public $timestamps = true;
use SoftDeletes;
protected $dates = ['deleted_at'];
public function account()
{
return $this->belongsTo('App\Models\Account');
}
public function user()
{
return $this->belongsTo('App\Models\User');
}
public function events()
{
return $this->hasMany('App\Models\TimesheetEvent');
}
public static function createNew($parent = false)
{
$className = get_called_class();
$entity = new $className();
if ($parent) {
$entity->user_id = $parent instanceof User ? $parent->id : $parent->user_id;
$entity->account_id = $parent->account_id;
} elseif (Auth::check()) {
$entity->user_id = Auth::user()->id;
$entity->account_id = Auth::user()->account_id;
} else {
Utils::fatalError();
}
return $entity;
}
}

View File

@ -2,6 +2,7 @@
use Utils; use Utils;
use Event; use Event;
use URL;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Payment; use App\Models\Payment;
@ -19,13 +20,13 @@ class ContactMailer extends Mailer
$subject = trans("texts.{$entityType}_subject", ['invoice' => $invoice->invoice_number, 'account' => $invoice->account->getDisplayName()]); $subject = trans("texts.{$entityType}_subject", ['invoice' => $invoice->invoice_number, 'account' => $invoice->account->getDisplayName()]);
$accountName = $invoice->account->getDisplayName(); $accountName = $invoice->account->getDisplayName();
$emailTemplate = $invoice->account->getEmailTemplate($entityType); $emailTemplate = $invoice->account->getEmailTemplate($entityType);
$invoiceAmount = Utils::formatMoney($invoice->getRequestedAmount(), $invoice->client->currency_id); $invoiceAmount = Utils::formatMoney($invoice->getRequestedAmount(), $invoice->client->getCurrencyId());
foreach ($invoice->invitations as $invitation) { foreach ($invoice->invitations as $invitation) {
if (!$invitation->user || !$invitation->user->email) { if (!$invitation->user || !$invitation->user->email || $invitation->user->trashed()) {
return false; return false;
} }
if (!$invitation->contact || !$invitation->contact->email) { if (!$invitation->contact || !$invitation->contact->email || $invitation->contact->trashed()) {
return false; return false;
} }
@ -41,6 +42,18 @@ class ContactMailer extends Mailer
'$amount' => $invoiceAmount '$amount' => $invoiceAmount
]; ];
// Add variables for available payment types
foreach([PAYMENT_TYPE_CREDIT_CARD, PAYMENT_TYPE_PAYPAL, PAYMENT_TYPE_BITCOIN] as $type) {
if ($invoice->account->getGatewayByType($type)) {
// Changes "PAYMENT_TYPE_CREDIT_CARD" to "$credit_card_link"
$gateway_slug = '$'.strtolower(str_replace('PAYMENT_TYPE_', '', $type)).'_link';
$variables[$gateway_slug] = URL::to("/payment/{$invitation->invitation_key}/{$type}");
}
}
$data['body'] = str_replace(array_keys($variables), array_values($variables), $emailTemplate); $data['body'] = str_replace(array_keys($variables), array_values($variables), $emailTemplate);
$data['link'] = $invitation->getLink(); $data['link'] = $invitation->getLink();
$data['entityType'] = $entityType; $data['entityType'] = $entityType;
@ -72,7 +85,7 @@ class ContactMailer extends Mailer
'$footer' => $payment->account->getEmailFooter(), '$footer' => $payment->account->getEmailFooter(),
'$client' => $payment->client->getDisplayName(), '$client' => $payment->client->getDisplayName(),
'$account' => $accountName, '$account' => $accountName,
'$amount' => Utils::formatMoney($payment->amount, $payment->client->currency_id) '$amount' => Utils::formatMoney($payment->amount, $payment->client->getCurrencyId())
]; ];
$data = ['body' => str_replace(array_keys($variables), array_values($variables), $emailTemplate)]; $data = ['body' => str_replace(array_keys($variables), array_values($variables), $emailTemplate)];

View File

@ -39,7 +39,7 @@ class UserMailer extends Mailer
return; return;
} }
$entityType = $invoice->getEntityType(); $entityType = $notificationType == 'approved' ? ENTITY_QUOTE : ENTITY_INVOICE;
$view = "{$entityType}_{$notificationType}"; $view = "{$entityType}_{$notificationType}";
$data = [ $data = [
@ -47,13 +47,13 @@ class UserMailer extends Mailer
'clientName' => $invoice->client->getDisplayName(), 'clientName' => $invoice->client->getDisplayName(),
'accountName' => $invoice->account->getDisplayName(), 'accountName' => $invoice->account->getDisplayName(),
'userName' => $user->getDisplayName(), 'userName' => $user->getDisplayName(),
'invoiceAmount' => Utils::formatMoney($invoice->amount, $invoice->client->currency_id), 'invoiceAmount' => Utils::formatMoney($invoice->amount, $invoice->client->getCurrencyId()),
'invoiceNumber' => $invoice->invoice_number, 'invoiceNumber' => $invoice->invoice_number,
'invoiceLink' => SITE_URL."/{$entityType}s/{$invoice->public_id}", 'invoiceLink' => SITE_URL."/{$entityType}s/{$invoice->public_id}",
]; ];
if ($payment) { if ($payment) {
$data['paymentAmount'] = Utils::formatMoney($payment->amount, $invoice->client->currency_id); $data['paymentAmount'] = Utils::formatMoney($payment->amount, $invoice->client->getCurrencyId());
} }
$subject = trans("texts.notification_{$entityType}_{$notificationType}_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->getDisplayName()]); $subject = trans("texts.notification_{$entityType}_{$notificationType}_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->getDisplayName()]);

View File

@ -49,7 +49,6 @@ class ClientRepository
{ {
if (!$publicId || $publicId == "-1") { if (!$publicId || $publicId == "-1") {
$client = Client::createNew(); $client = Client::createNew();
$client->currency_id = 1;
$contact = Contact::createNew(); $contact = Contact::createNew();
$contact->is_primary = true; $contact->is_primary = true;
$contact->send_invoice = true; $contact->send_invoice = true;

View File

@ -4,6 +4,7 @@ use App\Models\Invoice;
use App\Models\InvoiceItem; use App\Models\InvoiceItem;
use App\Models\Invitation; use App\Models\Invitation;
use App\Models\Product; use App\Models\Product;
use App\Models\Task;
use Utils; use Utils;
class InvoiceRepository class InvoiceRepository
@ -51,10 +52,10 @@ class InvoiceRepository
->join('contacts', 'contacts.client_id', '=', 'clients.id') ->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('invoices.account_id', '=', $accountId) ->where('invoices.account_id', '=', $accountId)
->where('invoices.is_quote', '=', false) ->where('invoices.is_quote', '=', false)
->where('clients.deleted_at', '=', null)
->where('contacts.deleted_at', '=', null) ->where('contacts.deleted_at', '=', null)
->where('invoices.is_recurring', '=', true) ->where('invoices.is_recurring', '=', true)
->where('contacts.is_primary', '=', true) ->where('contacts.is_primary', '=', true)
->where('clients.deleted_at', '=', null)
->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', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'invoices.deleted_at', 'invoices.is_deleted'); ->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', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'invoices.deleted_at', 'invoices.is_deleted');
if ($clientPublicId) { if ($clientPublicId) {
@ -158,7 +159,7 @@ class InvoiceRepository
} }
if ($entityType == ENTITY_INVOICE) { if ($entityType == ENTITY_INVOICE) {
if ($model->invoice_status_id < INVOICE_STATUS_PAID) { if ($model->balance > 0) {
$str .= '<li><a href="'.\URL::to('payments/create/'.$model->client_public_id.'/'.$model->public_id).'">'.trans('texts.enter_payment').'</a></li>'; $str .= '<li><a href="'.\URL::to('payments/create/'.$model->client_public_id.'/'.$model->public_id).'">'.trans('texts.enter_payment').'</a></li>';
} }
@ -271,6 +272,7 @@ class InvoiceRepository
$invoice->invoice_number = trim($data['invoice_number']); $invoice->invoice_number = trim($data['invoice_number']);
$invoice->partial = round(Utils::parseFloat($data['partial']), 2); $invoice->partial = round(Utils::parseFloat($data['partial']), 2);
$invoice->invoice_date = isset($data['invoice_date_sql']) ? $data['invoice_date_sql'] : Utils::toSqlDate($data['invoice_date']); $invoice->invoice_date = isset($data['invoice_date_sql']) ? $data['invoice_date_sql'] : Utils::toSqlDate($data['invoice_date']);
$invoice->has_tasks = isset($data['has_tasks']) ? $data['has_tasks'] : false;
if (!$publicId) { if (!$publicId) {
$invoice->is_recurring = $data['is_recurring'] && !Utils::isDemo() ? true : false; $invoice->is_recurring = $data['is_recurring'] && !Utils::isDemo() ? true : false;
@ -291,6 +293,12 @@ class InvoiceRepository
$invoice->terms = trim($data['terms']) ? trim($data['terms']) : (!$publicId && $account->invoice_terms ? $account->invoice_terms : ''); $invoice->terms = trim($data['terms']) ? trim($data['terms']) : (!$publicId && $account->invoice_terms ? $account->invoice_terms : '');
$invoice->invoice_footer = trim($data['invoice_footer']) ? trim($data['invoice_footer']) : (!$publicId && $account->invoice_footer ? $account->invoice_footer : ''); $invoice->invoice_footer = trim($data['invoice_footer']) ? trim($data['invoice_footer']) : (!$publicId && $account->invoice_footer ? $account->invoice_footer : '');
$invoice->public_notes = trim($data['public_notes']); $invoice->public_notes = trim($data['public_notes']);
// process date variables
$invoice->terms = Utils::processVariables($invoice->terms);
$invoice->invoice_footer = Utils::processVariables($invoice->invoice_footer);
$invoice->public_notes = Utils::processVariables($invoice->public_notes);
$invoice->po_number = trim($data['po_number']); $invoice->po_number = trim($data['po_number']);
$invoice->invoice_design_id = $data['invoice_design_id']; $invoice->invoice_design_id = $data['invoice_design_id'];
@ -374,7 +382,12 @@ class InvoiceRepository
continue; continue;
} }
if ($item['product_key']) { if (isset($item['task_public_id']) && $item['task_public_id']) {
$task = Task::scope($item['task_public_id'])->where('invoice_id', '=', null)->firstOrFail();
$task->invoice_id = $invoice->id;
$task->client_id = $invoice->client_id;
$task->save();
} else if ($item['product_key']) {
$product = Product::findProductByKey(trim($item['product_key'])); $product = Product::findProductByKey(trim($item['product_key']));
if (!$product) { if (!$product) {
@ -423,7 +436,7 @@ class InvoiceRepository
&& $account->share_counter) { && $account->share_counter) {
$invoiceNumber = $invoice->invoice_number; $invoiceNumber = $invoice->invoice_number;
if (strpos($invoiceNumber, $account->quote_number_prefix) === 0) { if ($account->quote_number_prefix && strpos($invoiceNumber, $account->quote_number_prefix) === 0) {
$invoiceNumber = substr($invoiceNumber, strlen($account->quote_number_prefix)); $invoiceNumber = substr($invoiceNumber, strlen($account->quote_number_prefix));
} }
$clone->invoice_number = $account->invoice_number_prefix.$invoiceNumber; $clone->invoice_number = $account->invoice_number_prefix.$invoiceNumber;
@ -453,7 +466,8 @@ class InvoiceRepository
'custom_value1', 'custom_value1',
'custom_value2', 'custom_value2',
'custom_taxes1', 'custom_taxes1',
'custom_taxes2', ] as $field) { 'custom_taxes2',
'partial'] as $field) {
$clone->$field = $invoice->$field; $clone->$field = $invoice->$field;
} }

View File

@ -15,11 +15,13 @@ class PaymentRepository
->join('invoices', 'invoices.id', '=', 'payments.invoice_id') ->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
->join('contacts', 'contacts.client_id', '=', 'clients.id') ->join('contacts', 'contacts.client_id', '=', 'clients.id')
->leftJoin('payment_types', 'payment_types.id', '=', 'payments.payment_type_id') ->leftJoin('payment_types', 'payment_types.id', '=', 'payments.payment_type_id')
->leftJoin('account_gateways', 'account_gateways.id', '=', 'payments.account_gateway_id')
->leftJoin('gateways', 'gateways.id', '=', 'account_gateways.gateway_id')
->where('payments.account_id', '=', \Auth::user()->account_id) ->where('payments.account_id', '=', \Auth::user()->account_id)
->where('clients.deleted_at', '=', null) ->where('clients.deleted_at', '=', null)
->where('contacts.is_primary', '=', true) ->where('contacts.is_primary', '=', true)
->where('contacts.deleted_at', '=', null) ->where('contacts.deleted_at', '=', null)
->select('payments.public_id', 'payments.transaction_reference', 'clients.name as client_name', 'clients.public_id as client_public_id', 'payments.amount', 'payments.payment_date', 'invoices.public_id as invoice_public_id', 'invoices.invoice_number', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'payment_types.name as payment_type', 'payments.account_gateway_id', 'payments.deleted_at', 'payments.is_deleted', 'invoices.is_deleted as invoice_is_deleted'); ->select('payments.public_id', 'payments.transaction_reference', 'clients.name as client_name', 'clients.public_id as client_public_id', 'payments.amount', 'payments.payment_date', 'invoices.public_id as invoice_public_id', 'invoices.invoice_number', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'payment_types.name as payment_type', 'payments.account_gateway_id', 'payments.deleted_at', 'payments.is_deleted', 'invoices.is_deleted as invoice_is_deleted', 'gateways.name as gateway_name');
if (!\Session::get('show_trash:payment')) { if (!\Session::get('show_trash:payment')) {
$query->where('payments.deleted_at', '=', null) $query->where('payments.deleted_at', '=', null)
@ -78,6 +80,11 @@ class PaymentRepository
$rules['payment_type_id'] = 'has_credit:'.$input['client'].','.$input['amount']; $rules['payment_type_id'] = 'has_credit:'.$input['client'].','.$input['amount'];
} }
if (isset($input['invoice']) && $input['invoice']) {
$invoice = Invoice::scope($input['invoice'])->firstOrFail();
$rules['amount'] .= "|less_than:{$invoice->balance}";
}
$validator = \Validator::make($input, $rules); $validator = \Validator::make($input, $rules);
if ($validator->fails()) { if ($validator->fails()) {
@ -95,8 +102,12 @@ class PaymentRepository
$payment = Payment::createNew(); $payment = Payment::createNew();
} }
$paymentTypeId = false;
if (isset($input['payment_type_id'])) {
$paymentTypeId = $input['payment_type_id'] ? $input['payment_type_id'] : null; $paymentTypeId = $input['payment_type_id'] ? $input['payment_type_id'] : null;
$payment->payment_type_id = $paymentTypeId; $payment->payment_type_id = $paymentTypeId;
}
$payment->payment_date = Utils::toSqlDate($input['payment_date']); $payment->payment_date = Utils::toSqlDate($input['payment_date']);
$payment->transaction_reference = trim($input['transaction_reference']); $payment->transaction_reference = trim($input['transaction_reference']);

View File

@ -0,0 +1,102 @@
<?php namespace App\Ninja\Repositories;
use Auth;
use Carbon;
use Session;
use App\Models\Client;
use App\Models\Contact;
use App\Models\Activity;
use App\Models\Task;
class TaskRepository
{
public function find($clientPublicId = null, $filter = null)
{
$query = \DB::table('tasks')
->leftJoin('clients', 'tasks.client_id', '=', 'clients.id')
->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
->leftJoin('invoices', 'invoices.id', '=', 'tasks.invoice_id')
->where('tasks.account_id', '=', Auth::user()->account_id)
->where(function ($query) {
$query->where('contacts.is_primary', '=', true)
->orWhere('contacts.is_primary', '=', null);
})
->where('contacts.deleted_at', '=', null)
->where('clients.deleted_at', '=', null)
->select('tasks.public_id', 'clients.name as client_name', 'clients.public_id as client_public_id', 'contacts.first_name', 'contacts.email', 'contacts.last_name', 'invoices.invoice_status_id', 'tasks.start_time', 'tasks.description', 'tasks.duration', 'tasks.is_deleted', 'tasks.deleted_at', 'invoices.invoice_number', 'invoices.public_id as invoice_public_id');
if ($clientPublicId) {
$query->where('clients.public_id', '=', $clientPublicId);
}
if (!Session::get('show_trash:task')) {
$query->where('tasks.deleted_at', '=', null);
}
if ($filter) {
$query->where(function ($query) use ($filter) {
$query->where('clients.name', 'like', '%'.$filter.'%')
->orWhere('contacts.first_name', 'like', '%'.$filter.'%')
->orWhere('contacts.last_name', 'like', '%'.$filter.'%')
->orWhere('tasks.description', 'like', '%'.$filter.'%');
});
}
return $query;
}
public function save($publicId, $data)
{
if ($publicId) {
$task = Task::scope($publicId)->firstOrFail();
} else {
$task = Task::createNew();
}
if (isset($data['client']) && $data['client']) {
$task->client_id = Client::getPrivateId($data['client']);
}
if (isset($data['description'])) {
$task->description = trim($data['description']);
}
if ($data['action'] == 'start') {
$task->start_time = Carbon::now()->toDateTimeString();
$task->duration = -1;
} else if ($data['action'] == 'stop' && $task->duration == -1) {
$task->duration = strtotime('now') - strtotime($task->start_time);
} else if ($data['action'] == 'save' && $task->duration != -1) {
$task->start_time = $data['start_time'];
$task->duration = $data['duration'];
}
$task->duration = max($task->duration, -1);
$task->save();
return $task;
}
public function bulk($ids, $action)
{
$tasks = Task::withTrashed()->scope($ids)->get();
foreach ($tasks as $task) {
if ($action == 'restore') {
$task->restore();
$task->is_deleted = false;
$task->save();
} else {
if ($action == 'delete') {
$task->is_deleted = true;
$task->save();
}
$task->delete();
}
}
return count($tasks);
}
}

View File

@ -1,5 +1,12 @@
<?php namespace App\Providers; <?php namespace App\Providers;
use Session;
use Auth;
use Utils;
use HTML;
use URL;
use Request;
use Validator;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider { class AppServiceProvider extends ServiceProvider {
@ -11,7 +18,105 @@ class AppServiceProvider extends ServiceProvider {
*/ */
public function boot() public function boot()
{ {
// HTML::macro('nav_link', function($url, $text, $url2 = '', $extra = '') {
$class = ( Request::is($url) || Request::is($url.'/*') || Request::is($url2.'/*') ) ? ' class="active"' : '';
$title = ucwords(trans("texts.$text")) . Utils::getProLabel($text);
return '<li'.$class.'><a href="'.URL::to($url).'" '.$extra.'>'.$title.'</a></li>';
});
HTML::macro('tab_link', function($url, $text, $active = false) {
$class = $active ? ' class="active"' : '';
return '<li'.$class.'><a href="'.URL::to($url).'" data-toggle="tab">'.$text.'</a></li>';
});
HTML::macro('menu_link', function($type) {
$types = $type.'s';
$Type = ucfirst($type);
$Types = ucfirst($types);
$class = ( Request::is($types) || Request::is('*'.$type.'*')) && !Request::is('*advanced_settings*') ? ' active' : '';
$str = '<li class="dropdown '.$class.'">
<a href="'.URL::to($types).'" class="dropdown-toggle">'.trans("texts.$types").'</a>
<ul class="dropdown-menu" id="menu1">
<li><a href="'.URL::to($types.'/create').'">'.trans("texts.new_$type").'</a></li>';
if ($type == ENTITY_INVOICE && Auth::user()->isPro()) {
$str .= '<li class="divider"></li>
<li><a href="'.URL::to('quotes').'">'.trans("texts.quotes").'</a></li>
<li><a href="'.URL::to('quotes/create').'">'.trans("texts.new_quote").'</a></li>';
}
$str .= '</ul>
</li>';
return $str;
});
HTML::macro('image_data', function($imagePath) {
return 'data:image/jpeg;base64,' . base64_encode(file_get_contents(public_path().'/'.$imagePath));
});
HTML::macro('breadcrumbs', function() {
$str = '<ol class="breadcrumb">';
// Get the breadcrumbs by exploding the current path.
$basePath = Utils::basePath();
$parts = explode('?', $_SERVER['REQUEST_URI']);
$path = $parts[0];
if ($basePath != '/') {
$path = str_replace($basePath, '', $path);
}
$crumbs = explode('/', $path);
foreach ($crumbs as $key => $val) {
if (is_numeric($val)) {
unset($crumbs[$key]);
}
}
$crumbs = array_values($crumbs);
for ($i=0; $i<count($crumbs); $i++) {
$crumb = trim($crumbs[$i]);
if (!$crumb) {
continue;
}
if ($crumb == 'company') {
return '';
}
$name = trans("texts.$crumb");
if ($i==count($crumbs)-1) {
$str .= "<li class='active'>$name</li>";
} else {
$str .= '<li>'.link_to($crumb, $name).'</li>';
}
}
return $str . '</ol>';
});
Validator::extend('positive', function($attribute, $value, $parameters) {
return Utils::parseFloat($value) >= 0;
});
Validator::extend('has_credit', function($attribute, $value, $parameters) {
$publicClientId = $parameters[0];
$amount = $parameters[1];
$client = \App\Models\Client::scope($publicClientId)->firstOrFail();
$credit = $client->getTotalCredit();
return $credit >= $amount;
});
Validator::extend('less_than', function($attribute, $value, $parameters) {
return floatval($value) <= floatval($parameters[0]);
});
Validator::replacer('less_than', function($message, $attribute, $rule, $parameters) {
return str_replace(':value', $parameters[0], $message);
});
} }
/** /**

View File

@ -19,7 +19,8 @@
"spectrum": "~1.3.4", "spectrum": "~1.3.4",
"d3": "~3.4.11", "d3": "~3.4.11",
"handsontable": "*", "handsontable": "*",
"pdfmake": "*" "pdfmake": "*",
"moment": "*"
}, },
"resolutions": { "resolutions": {
"jquery": "~1.11" "jquery": "~1.11"

View File

@ -18,7 +18,7 @@
"patricktalmadge/bootstrapper": "5.5.x", "patricktalmadge/bootstrapper": "5.5.x",
"anahkiasen/former": "4.0.*@dev", "anahkiasen/former": "4.0.*@dev",
"barryvdh/laravel-debugbar": "~2.0.2", "barryvdh/laravel-debugbar": "~2.0.2",
"chumper/datatable": "dev-develop", "chumper/datatable": "dev-develop#7fa47cb",
"omnipay/omnipay": "2.3.x", "omnipay/omnipay": "2.3.x",
"intervention/image": "dev-master", "intervention/image": "dev-master",
"webpatser/laravel-countries": "dev-master", "webpatser/laravel-countries": "dev-master",
@ -33,8 +33,11 @@
"coatesap/omnipay-realex": "~2.0", "coatesap/omnipay-realex": "~2.0",
"fruitcakestudio/omnipay-sisow": "~2.0", "fruitcakestudio/omnipay-sisow": "~2.0",
"alfaproject/omnipay-skrill": "dev-master", "alfaproject/omnipay-skrill": "dev-master",
"illuminate/html": "5.*", "omnipay/bitpay": "dev-master",
"omnipay/bitpay": "dev-master" "guzzlehttp/guzzle": "~5.0",
"laravelcollective/html": "~5.0",
"wildbit/laravel-postmark-provider": "dev-master",
"Dwolla/omnipay-dwolla": "dev-master"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "~4.0", "phpunit/phpunit": "~4.0",

893
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,7 @@
<?php <?php
use App\Libraries\Utils;
return [ return [
/* /*
@ -39,7 +41,7 @@ return [
| |
*/ */
'timezone' => 'UTC', 'timezone' => env('APP_TIMEZONE', 'UTC'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -125,7 +127,7 @@ return [
'Illuminate\Filesystem\FilesystemServiceProvider', 'Illuminate\Filesystem\FilesystemServiceProvider',
'Illuminate\Foundation\Providers\FoundationServiceProvider', 'Illuminate\Foundation\Providers\FoundationServiceProvider',
'Illuminate\Hashing\HashServiceProvider', 'Illuminate\Hashing\HashServiceProvider',
'Illuminate\Mail\MailServiceProvider', (isset($_ENV['POSTMARK_API_TOKEN']) ? 'Postmark\Adapters\LaravelMailProvider' : 'Illuminate\Mail\MailServiceProvider'),
'Illuminate\Pagination\PaginationServiceProvider', 'Illuminate\Pagination\PaginationServiceProvider',
'Illuminate\Pipeline\PipelineServiceProvider', 'Illuminate\Pipeline\PipelineServiceProvider',
'Illuminate\Queue\QueueServiceProvider', 'Illuminate\Queue\QueueServiceProvider',
@ -187,7 +189,7 @@ return [
'File' => 'Illuminate\Support\Facades\File', 'File' => 'Illuminate\Support\Facades\File',
//'Form' => 'Illuminate\Support\Facades\Form', //'Form' => 'Illuminate\Support\Facades\Form',
'Hash' => 'Illuminate\Support\Facades\Hash', 'Hash' => 'Illuminate\Support\Facades\Hash',
'HTML' => 'Illuminate\Support\Facades\HTML', //'HTML' => 'Illuminate\Support\Facades\HTML',
'Input' => 'Illuminate\Support\Facades\Input', 'Input' => 'Illuminate\Support\Facades\Input',
'Lang' => 'Illuminate\Support\Facades\Lang', 'Lang' => 'Illuminate\Support\Facades\Lang',
'Log' => 'Illuminate\Support\Facades\Log', 'Log' => 'Illuminate\Support\Facades\Log',
@ -244,8 +246,8 @@ return [
// Added Class Aliases // Added Class Aliases
'Utils' => 'App\Libraries\Utils', 'Utils' => 'App\Libraries\Utils',
'Form' => 'Illuminate\Html\FormFacade', 'Form' => 'Collective\Html\FormFacade',
'HTML' => 'Illuminate\Html\HtmlFacade', 'HTML' => 'Collective\Html\HtmlFacade',
'SSH' => 'Illuminate\Support\Facades\SSH', 'SSH' => 'Illuminate\Support\Facades\SSH',
'Alert' => 'Bootstrapper\Facades\Alert', 'Alert' => 'Bootstrapper\Facades\Alert',
'Badge' => 'Bootstrapper\Facades\Badge', 'Badge' => 'Bootstrapper\Facades\Badge',
@ -255,7 +257,7 @@ return [
'ButtonToolbar' => 'Bootstrapper\Facades\ButtonToolbar', 'ButtonToolbar' => 'Bootstrapper\Facades\ButtonToolbar',
'Carousel' => 'Bootstrapper\Facades\Carousel', 'Carousel' => 'Bootstrapper\Facades\Carousel',
'DropdownButton' => 'Bootstrapper\Facades\DropdownButton', 'DropdownButton' => 'Bootstrapper\Facades\DropdownButton',
'Form' => 'Bootstrapper\Facades\Form', //'Form' => 'Bootstrapper\Facades\Form', //need to clarify this guy
'Helpers' => 'Bootstrapper\Facades\Helpers', 'Helpers' => 'Bootstrapper\Facades\Helpers',
'Icon' => 'Bootstrapper\Facades\Icon', 'Icon' => 'Bootstrapper\Facades\Icon',
//'Image' => 'Bootstrapper\Facades\Image', //'Image' => 'Bootstrapper\Facades\Image',

View File

@ -9,9 +9,6 @@
// The default form type // The default form type
'default_form_type' => 'horizontal', 'default_form_type' => 'horizontal',
// The framework to be used by Former
'framework' => 'TwitterBootstrap3',
// Validation // Validation
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
@ -42,7 +39,7 @@
'required_class' => 'required', 'required_class' => 'required',
// A facultative text to append to the labels of required fields // A facultative text to append to the labels of required fields
'required_text' => '<sup>*</sup>', 'required_text' => '',
// Translations // Translations
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
@ -63,4 +60,125 @@
'data_placeholder', 'data_placeholder',
'label', 'label',
), ),
// Framework
////////////////////////////////////////////////////////////////////
// The framework to be used by Former
'framework' => 'TwitterBootstrap3',
'TwitterBootstrap3' => array(
// Map Former-supported viewports to Bootstrap 3 equivalents
'viewports' => array(
'large' => 'lg',
'medium' => 'md',
'small' => 'sm',
'mini' => 'xs',
),
// Width of labels for horizontal forms expressed as viewport => grid columns
'labelWidths' => array(
'large' => 4,
'small' => 4,
),
// HTML markup and classes used by Bootstrap 3 for icons
'icon' => array(
'tag' => 'span',
'set' => 'glyphicon',
'prefix' => 'glyphicon',
),
),
'Nude' => array( // No-framework markup
'icon' => array(
'tag' => 'i',
'set' => null,
'prefix' => 'icon',
),
),
'TwitterBootstrap' => array( // Twitter Bootstrap version 2
'icon' => array(
'tag' => 'i',
'set' => null,
'prefix' => 'icon',
),
),
'ZurbFoundation5' => array(
// Map Former-supported viewports to Foundation 5 equivalents
'viewports' => array(
'large' => 'large',
'medium' => null,
'small' => 'small',
'mini' => null,
),
// Width of labels for horizontal forms expressed as viewport => grid columns
'labelWidths' => array(
'small' => 3,
),
// Classes to be applied to wrapped labels in horizontal forms
'wrappedLabelClasses' => array('right', 'inline'),
// HTML markup and classes used by Foundation 5 for icons
'icon' => array(
'tag' => 'i',
'set' => null,
'prefix' => 'fi',
),
// CSS for inline validation errors
'error_classes' => array('class' => 'error'),
),
'ZurbFoundation4' => array(
// Foundation 4 also has an experimental "medium" breakpoint
// explained at http://foundation.zurb.com/docs/components/grid.html
'viewports' => array(
'large' => 'large',
'medium' => null,
'small' => 'small',
'mini' => null,
),
// Width of labels for horizontal forms expressed as viewport => grid columns
'labelWidths' => array(
'small' => 3,
),
// Classes to be applied to wrapped labels in horizontal forms
'wrappedLabelClasses' => array('right', 'inline'),
// HTML markup and classes used by Foundation 4 for icons
'icon' => array(
'tag' => 'i',
'set' => 'general',
'prefix' => 'foundicon',
),
// CSS for inline validation errors
'error_classes' => array('class' => 'alert-box radius warning'),
),
'ZurbFoundation' => array( // Foundation 3
'viewports' => array(
'large' => '',
'medium' => null,
'small' => 'mobile-',
'mini' => null,
),
// Width of labels for horizontal forms expressed as viewport => grid columns
'labelWidths' => array(
'large' => 2,
'small' => 4,
),
// Classes to be applied to wrapped labels in horizontal forms
'wrappedLabelClasses' => array('right', 'inline'),
// HTML markup and classes used by Foundation 3 for icons
'icon' => array(
'tag' => 'i',
'set' => null,
'prefix' => 'fi',
),
// CSS for inline validation errors
// should work for Zurb 2 and 3
'error_classes' => array('class' => 'alert-box alert error'),
),
); );

View File

@ -1,14 +0,0 @@
<?php
return array(
// HTML markup and classes used by the "Nude" framework for icons
'icon' => array(
'tag' => 'i',
'set' => null,
'prefix' => 'icon',
),
);

View File

@ -1,14 +0,0 @@
<?php
return array(
// HTML markup and classes used by Bootstrap for icons
'icon' => array(
'tag' => 'i',
'set' => null,
'prefix' => 'icon',
),
);

View File

@ -1,26 +0,0 @@
<?php
return array(
// Map Former-supported viewports to Bootstrap 3 equivalents
'viewports' => array(
'large' => 'lg',
'medium' => 'md',
'small' => 'sm',
'mini' => 'xs',
),
// Width of labels for horizontal forms expressed as viewport => grid columns
'labelWidths' => array(
'large' => 4,
'small' => 4,
),
// HTML markup and classes used by Bootstrap 3 for icons
'icon' => array(
'tag' => 'span',
'set' => 'glyphicon',
'prefix' => 'glyphicon',
),
);

View File

@ -1,35 +0,0 @@
<?php
return array(
// Map Former-supported viewports to Foundation 3 equivalents
'viewports' => array(
'large' => '',
'medium' => null,
'small' => 'mobile-',
'mini' => null,
),
// Width of labels for horizontal forms expressed as viewport => grid columns
'labelWidths' => array(
'large' => 2,
'small' => 4,
),
// Classes to be applied to wrapped labels in horizontal forms
'wrappedLabelClasses' => array('right','inline'),
// HTML markup and classes used by Foundation 3 for icons
'icon' => array(
'tag' => 'i',
'set' => null,
'prefix' => 'fi',
),
);

View File

@ -1,36 +0,0 @@
<?php
return array(
// Map Former-supported viewports to Foundation 4 equivalents
// Foundation 4 also has an experimental "medium" breakpoint
// explained at http://foundation.zurb.com/docs/components/grid.html
'viewports' => array(
'large' => 'large',
'medium' => null,
'small' => 'small',
'mini' => null,
),
// Width of labels for horizontal forms expressed as viewport => grid columns
'labelWidths' => array(
'small' => 3,
),
// Classes to be applied to wrapped labels in horizontal forms
'wrappedLabelClasses' => array('right','inline'),
// HTML markup and classes used by Foundation 4 for icons
'icon' => array(
'tag' => 'i',
'set' => 'general',
'prefix' => 'foundicon',
),
);

View File

@ -54,7 +54,7 @@ return [
| |
*/ */
'from' => ['address' => env('MAIL_USERNAME'), 'name' => env('MAIL_FROM_NAME')], 'from' => ['address' => env('MAIL_FROM_ADDRESS', env('MAIL_USERNAME')), 'name' => env('MAIL_FROM_NAME')],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -1,14 +0,0 @@
<?php
return array(
// HTML markup and classes used by the "Nude" framework for icons
'icon' => array(
'tag' => 'i',
'set' => null,
'prefix' => 'icon',
),
);

View File

@ -1,14 +0,0 @@
<?php
return array(
// HTML markup and classes used by Bootstrap for icons
'icon' => array(
'tag' => 'i',
'set' => null,
'prefix' => 'icon',
),
);

View File

@ -1,26 +0,0 @@
<?php
return array(
// Map Former-supported viewports to Bootstrap 3 equivalents
'viewports' => array(
'large' => 'lg',
'medium' => 'md',
'small' => 'sm',
'mini' => 'xs',
),
// Width of labels for horizontal forms expressed as viewport => grid columns
'labelWidths' => array(
'large' => 4,
'small' => 4,
),
// HTML markup and classes used by Bootstrap 3 for icons
'icon' => array(
'tag' => 'span',
'set' => 'glyphicon',
'prefix' => 'glyphicon',
),
);

View File

@ -1,35 +0,0 @@
<?php
return array(
// Map Former-supported viewports to Foundation 3 equivalents
'viewports' => array(
'large' => '',
'medium' => null,
'small' => 'mobile-',
'mini' => null,
),
// Width of labels for horizontal forms expressed as viewport => grid columns
'labelWidths' => array(
'large' => 2,
'small' => 4,
),
// Classes to be applied to wrapped labels in horizontal forms
'wrappedLabelClasses' => array('right','inline'),
// HTML markup and classes used by Foundation 3 for icons
'icon' => array(
'tag' => 'i',
'set' => null,
'prefix' => 'fi',
),
);

View File

@ -1,36 +0,0 @@
<?php
return array(
// Map Former-supported viewports to Foundation 4 equivalents
// Foundation 4 also has an experimental "medium" breakpoint
// explained at http://foundation.zurb.com/docs/components/grid.html
'viewports' => array(
'large' => 'large',
'medium' => null,
'small' => 'small',
'mini' => null,
),
// Width of labels for horizontal forms expressed as viewport => grid columns
'labelWidths' => array(
'small' => 3,
),
// Classes to be applied to wrapped labels in horizontal forms
'wrappedLabelClasses' => array('right','inline'),
// HTML markup and classes used by Foundation 4 for icons
'icon' => array(
'tag' => 'i',
'set' => 'general',
'prefix' => 'foundicon',
),
);

View File

@ -1,59 +0,0 @@
<?php return array(
// Markup
////////////////////////////////////////////////////////////////////
// Whether labels should be automatically computed from name
'automatic_label' => true,
// The default form type
'default_form_type' => 'horizontal',
// The framework to be used by Former
'framework' => 'TwitterBootstrap3',
// Validation
////////////////////////////////////////////////////////////////////
// Whether Former should fetch errors from Session
'fetch_errors' => true,
// Whether Former should try to apply Validator rules as attributes
'live_validation' => true,
// Whether Former should automatically fetch error messages and
// display them next to the matching fields
'error_messages' => true,
// Checkables
////////////////////////////////////////////////////////////////////
// Whether checkboxes should always be present in the POST data,
// no matter if you checked them or not
'push_checkboxes' => false,
// The value a checkbox will have in the POST array if unchecked
'unchecked_value' => 0,
// Required fields
////////////////////////////////////////////////////////////////////
// The class to be added to required fields
'required_class' => 'required',
// A facultative text to append to the labels of required fields
'required_text' => '', //'<sup>*</sup>',
// Translations
////////////////////////////////////////////////////////////////////
// Where Former should look for translations
'translate_from' => 'texts',
// An array of attributes to automatically translate
'translatable' => array(
'help', 'inlineHelp', 'blockHelp',
'placeholder', 'data_placeholder',
'label'
),
);

View File

@ -14,6 +14,8 @@ return [
| |
*/ */
'postmark' => env('POSTMARK_API_TOKEN', ''),
'mailgun' => [ 'mailgun' => [
'domain' => '', 'domain' => '',
'secret' => '', 'secret' => '',

View File

@ -29,7 +29,7 @@ return [
| |
*/ */
'lifetime' => 360, 'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => false, 'expire_on_close' => false,

View File

@ -183,7 +183,7 @@ class ConfideSetupUsersTable extends Migration {
$t->string('username')->unique(); $t->string('username')->unique();
$t->string('email')->nullable(); $t->string('email')->nullable();
$t->string('password'); $t->string('password');
$t->string('confirmation_code'); $t->string('confirmation_code')->nullable();
$t->boolean('registered')->default(false); $t->boolean('registered')->default(false);
$t->boolean('confirmed')->default(false); $t->boolean('confirmed')->default(false);
$t->integer('theme_id')->nullable(); $t->integer('theme_id')->nullable();
@ -355,8 +355,8 @@ class ConfideSetupUsersTable extends Migration {
$t->softDeletes(); $t->softDeletes();
$t->string('transaction_reference')->nullable(); $t->string('transaction_reference')->nullable();
$t->timestamp('sent_date'); $t->timestamp('sent_date')->nullable();
$t->timestamp('viewed_date'); $t->timestamp('viewed_date')->nullable();
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');; $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
$t->foreign('contact_id')->references('id')->on('contacts')->onDelete('cascade'); $t->foreign('contact_id')->references('id')->on('contacts')->onDelete('cascade');

View File

@ -142,11 +142,11 @@ class AddTimesheets extends Migration {
*/ */
public function down() public function down()
{ {
Schema::drop('timesheet_events'); Schema::dropIfExists('timesheet_events');
Schema::drop('timesheet_event_sources'); Schema::dropIfExists('timesheet_event_sources');
Schema::drop('timesheets'); Schema::dropIfExists('timesheets');
Schema::drop('project_codes'); Schema::dropIfExists('project_codes');
Schema::drop('projects'); Schema::dropIfExists('projects');
} }
} }

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddFontSize extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function($table)
{
$table->smallInteger('font_size')->default(DEFAULT_FONT_SIZE);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accounts', function($table)
{
$table->dropColumn('font_size');
});
}
}

View File

@ -0,0 +1,55 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddTasks extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('tasks', function($table) {
$table->increments('id');
$table->unsignedInteger('user_id');
$table->unsignedInteger('account_id')->index();
$table->unsignedInteger('client_id')->nullable();
$table->unsignedInteger('invoice_id')->nullable();
$table->timestamps();
$table->softDeletes();
$table->timestamp('start_time');
$table->integer('duration')->nullable();
$table->string('description')->nullable();
$table->boolean('is_deleted')->default(false);
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
$table->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
$table->unsignedInteger('public_id')->index();
$table->unique( array('account_id','public_id') );
});
Schema::dropIfExists('timesheets');
Schema::dropIfExists('timesheet_events');
Schema::dropIfExists('timesheet_event_sources');
Schema::dropIfExists('project_codes');
Schema::dropIfExists('projects');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('tasks');
}
}

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddCustomInvoiceLabels extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function($table)
{
$table->text('invoice_labels')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accounts', function($table)
{
$table->dropColumn('invoice_labels');
});
}
}

View File

@ -0,0 +1,45 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddHasTasksToInvoices extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('invoices', function($table)
{
$table->boolean('has_tasks')->default(false);
});
$invoices = DB::table('invoices')
->join('tasks', 'tasks.invoice_id', '=', 'invoices.id')
->selectRaw('DISTINCT invoices.id')
->get();
foreach ($invoices as $invoice) {
DB::table('invoices')
->where('id', $invoice->id)
->update(['has_tasks' => true]);
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('invoices', function($table)
{
$table->dropColumn('has_tasks');
});
}
}

View File

@ -111,42 +111,6 @@ class ConstantsSeeder extends Seeder
PaymentTerm::create(array('num_days' => 60, 'name' => 'Net 60')); PaymentTerm::create(array('num_days' => 60, 'name' => 'Net 60'));
PaymentTerm::create(array('num_days' => 90, 'name' => 'Net 90')); PaymentTerm::create(array('num_days' => 90, 'name' => 'Net 90'));
Currency::create(array('name' => 'US Dollar', 'code' => 'USD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'Pound Sterling', 'code' => 'GBP', 'symbol' => '£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'Euro', 'code' => 'EUR', 'symbol' => '€', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'Rand', 'code' => 'ZAR', 'symbol' => 'R', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'Danish Krone', 'code' => 'DKK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'Israeli Shekel', 'code' => 'ILS', 'symbol' => 'NIS ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'Swedish Krona', 'code' => 'SEK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'Kenyan Shilling', 'code' => 'KES', 'symbol' => 'KSh ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'Canadian Dollar', 'code' => 'CAD', 'symbol' => 'C$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'Philippine Peso', 'code' => 'PHP', 'symbol' => 'P ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'Indian Rupee', 'code' => 'INR', 'symbol' => 'Rs. ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'Australian Dollar', 'code' => 'AUD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'Singapore Dollar', 'code' => 'SGD', 'symbol' => 'SGD ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'Norske Kroner', 'code' => 'NOK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'New Zealand Dollar', 'code' => 'NZD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'Vietnamese Dong', 'code' => 'VND', 'symbol' => 'VND ', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'Swiss Franc', 'code' => 'CHF', 'symbol' => 'CHF ', 'precision' => '2', 'thousand_separator' => '\'', 'decimal_separator' => '.'));
Currency::create(array('name' => 'Guatemalan Quetzal', 'code' => 'GTQ', 'symbol' => 'Q', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
DatetimeFormat::create(array('format' => 'd/M/Y g:i a', 'label' => '10/Mar/2013'));
DatetimeFormat::create(array('format' => 'd-M-Yk g:i a', 'label' => '10-Mar-2013'));
DatetimeFormat::create(array('format' => 'd/F/Y g:i a', 'label' => '10/March/2013'));
DatetimeFormat::create(array('format' => 'd-F-Y g:i a', 'label' => '10-March-2013'));
DatetimeFormat::create(array('format' => 'M j, Y g:i a', 'label' => 'Mar 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'));
DateFormat::create(array('format' => 'd/M/Y', 'picker_format' => 'dd/M/yyyy', 'label' => '10/Mar/2013'));
DateFormat::create(array('format' => 'd-M-Y', 'picker_format' => 'dd-M-yyyy', 'label' => '10-Mar-2013'));
DateFormat::create(array('format' => 'd/F/Y', 'picker_format' => 'dd/MM/yyyy', 'label' => '10/March/2013'));
DateFormat::create(array('format' => 'd-F-Y', 'picker_format' => 'dd-MM-yyyy', 'label' => '10-March-2013'));
DateFormat::create(array('format' => 'M j, Y', 'picker_format' => 'M d, yyyy', 'label' => 'Mar 10, 2013'));
DateFormat::create(array('format' => 'F j, Y', 'picker_format' => 'MM d, yyyy', 'label' => 'March 10, 2013'));
DateFormat::create(array('format' => 'D M j, Y', 'picker_format' => 'D MM d, yyyy', 'label' => 'Mon March 10, 2013'));
DateFormat::create(array('format' => 'Y-M-d', 'picker_format' => 'yyyy-M-dd', 'label' => '2013-03-10'));
PaymentLibrary::create(['name' => 'Omnipay']); PaymentLibrary::create(['name' => 'Omnipay']);
PaymentLibrary::create(['name' => 'PHP-Payments [Deprecated]']); PaymentLibrary::create(['name' => 'PHP-Payments [Deprecated]']);

View File

@ -2,14 +2,24 @@
use App\Models\Gateway; use App\Models\Gateway;
use App\Models\PaymentTerm; use App\Models\PaymentTerm;
use App\Models\Currency;
use App\Models\DateFormat;
use App\Models\DatetimeFormat;
class PaymentLibrariesSeeder extends Seeder class PaymentLibrariesSeeder extends Seeder
{ {
public function run() public function run()
{ {
Eloquent::unguard(); Eloquent::unguard();
$this->createGateways();
$this->createPaymentTerms();
$this->createDateFormats();
$this->createDatetimeFormats();
}
private function createGateways() {
$gateways = [ $gateways = [
['name' => 'BeanStream', 'provider' => 'BeanStream', 'payment_library_id' => 2], ['name' => 'BeanStream', 'provider' => 'BeanStream', 'payment_library_id' => 2],
['name' => 'Psigate', 'provider' => 'Psigate', 'payment_library_id' => 2], ['name' => 'Psigate', 'provider' => 'Psigate', 'payment_library_id' => 2],
@ -25,26 +35,100 @@ class PaymentLibrariesSeeder extends Seeder
['name' => 'Sisow', 'provider' => 'Sisow', 'payment_library_id' => 1], ['name' => 'Sisow', 'provider' => 'Sisow', 'payment_library_id' => 1],
['name' => 'Skrill', 'provider' => 'Skrill', 'payment_library_id' => 1], ['name' => 'Skrill', 'provider' => 'Skrill', 'payment_library_id' => 1],
['name' => 'BitPay', 'provider' => 'BitPay', 'payment_library_id' => 1], ['name' => 'BitPay', 'provider' => 'BitPay', 'payment_library_id' => 1],
['name' => 'Dwolla', 'provider' => 'Dwolla', 'payment_library_id' => 1],
]; ];
foreach ($gateways as $gateway) foreach ($gateways as $gateway) {
{ if (!DB::table('gateways')->where('name', '=', $gateway['name'])->get()) {
if (!DB::table('gateways')->where('name', '=', $gateway['name'])->get())
{
Gateway::create($gateway); Gateway::create($gateway);
} }
} }
}
private function createPaymentTerms() {
$paymentTerms = [ $paymentTerms = [
['num_days' => -1, 'name' => 'Net 0'] ['num_days' => -1, 'name' => 'Net 0'],
]; ];
foreach ($paymentTerms as $paymentTerm) foreach ($paymentTerms as $paymentTerm) {
{ if (!DB::table('payment_terms')->where('name', '=', $paymentTerm['name'])->get()) {
if (!DB::table('payment_terms')->where('name', '=', $paymentTerm['name'])->get())
{
PaymentTerm::create($paymentTerm); PaymentTerm::create($paymentTerm);
} }
} }
$currencies = [
['name' => 'US Dollar', 'code' => 'USD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Pound Sterling', 'code' => 'GBP', 'symbol' => '£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Euro', 'code' => 'EUR', 'symbol' => '€', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Rand', 'code' => 'ZAR', 'symbol' => 'R', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Danish Krone', 'code' => 'DKK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Israeli Shekel', 'code' => 'ILS', 'symbol' => 'NIS ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Swedish Krona', 'code' => 'SEK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Kenyan Shilling', 'code' => 'KES', 'symbol' => 'KSh ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Canadian Dollar', 'code' => 'CAD', 'symbol' => 'C$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Philippine Peso', 'code' => 'PHP', 'symbol' => 'P ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Indian Rupee', 'code' => 'INR', 'symbol' => 'Rs. ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Australian Dollar', 'code' => 'AUD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Singapore Dollar', 'code' => 'SGD', 'symbol' => 'SGD ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Norske Kroner', 'code' => 'NOK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'New Zealand Dollar', 'code' => 'NZD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Vietnamese Dong', 'code' => 'VND', 'symbol' => 'VND ', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Swiss Franc', 'code' => 'CHF', 'symbol' => 'CHF ', 'precision' => '2', 'thousand_separator' => '\'', 'decimal_separator' => '.'],
['name' => 'Guatemalan Quetzal', 'code' => 'GTQ', 'symbol' => 'Q', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Malaysian Ringgit', 'code' => 'MYR', 'symbol' => 'RM', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Brazilian Real', 'code' => 'BRL', 'symbol' => 'R$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Thai baht', 'code' => 'THB', 'symbol' => 'THB ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
];
foreach ($currencies as $currency) {
if (!DB::table('currencies')->whereName($currency['name'])->get()) {
Currency::create($currency);
} }
} }
}
private function createDateFormats() {
$formats = [
['format' => 'd/M/Y', 'picker_format' => 'dd/M/yyyy', 'label' => '10/Mar/2013'],
['format' => 'd-M-Y', 'picker_format' => 'dd-M-yyyy', 'label' => '10-Mar-2013'],
['format' => 'd/F/Y', 'picker_format' => 'dd/MM/yyyy', 'label' => '10/March/2013'],
['format' => 'd-F-Y', 'picker_format' => 'dd-MM-yyyy', 'label' => '10-March-2013'],
['format' => 'M j, Y', 'picker_format' => 'M d, yyyy', 'label' => 'Mar 10, 2013'],
['format' => 'F j, Y', 'picker_format' => 'MM d, yyyy', 'label' => 'March 10, 2013'],
['format' => 'D M j, Y', 'picker_format' => 'D MM d, yyyy', 'label' => 'Mon March 10, 2013'],
['format' => 'Y-M-d', 'picker_format' => 'yyyy-M-dd', 'label' => '2013-03-10'],
['format' => 'd/m/Y', 'picker_format' => 'dd/mm/yyyy', 'label' => '20/03/2013'],
];
foreach ($formats as $format) {
if (!DB::table('date_formats')->whereLabel($format['label'])->get()) {
DateFormat::create($format);
}
}
}
private function createDatetimeFormats() {
$formats = [
['format' => 'd/M/Y g:i a', 'label' => '10/Mar/2013'],
['format' => 'd-M-Yk g:i a', 'label' => '10-Mar-2013'],
['format' => 'd/F/Y g:i a', 'label' => '10/March/2013'],
['format' => 'd-F-Y g:i a', 'label' => '10-March-2013'],
['format' => 'M j, Y g:i a', 'label' => 'Mar 10, 2013 6:15 pm'],
['format' => 'F j, Y g:i a', 'label' => 'March 10, 2013 6:15 pm'],
['format' => 'D M jS, Y g:ia', 'label' => 'Mon March 10th, 2013 6:15 pm'],
['format' => 'Y-M-d g:i a', 'label' => '2013-03-10 6:15 pm'],
['format' => 'd/m/Y g:i a', 'label' => '20/03/2013 6:15 pm'],
];
foreach ($formats as $format) {
if (!DB::table('datetime_formats')->whereLabel($format['label'])->get()) {
DatetimeFormat::create($format);
}
}
}
}

27
public/css/built.css vendored
View File

@ -2449,6 +2449,11 @@ table.dataTable thead > tr > th, table.invoice-table thead > tr > th {
background-color: #e37329 !important; background-color: #e37329 !important;
color:#fff; color:#fff;
} }
/*
table.dataTable tr:hover {
background-color: #F2F5FE !important;
}
*/
th:first-child { th:first-child {
border-radius: 3px 0 0 0; border-radius: 3px 0 0 0;
border-left: none; border-left: none;
@ -2467,7 +2472,7 @@ border-bottom: 1px solid #dfe0e1;
table.dataTable.no-footer { table.dataTable.no-footer {
border-bottom: none; border-bottom: none;
} }
.table-striped>tbody>tr:nth-child(odd)>td, .table-striped>tbody>tr:nth-child(odd)>tr,
.table-striped>tbody>tr:nth-child(odd)>th { .table-striped>tbody>tr:nth-child(odd)>th {
background-color: #FDFDFD; background-color: #FDFDFD;
} }
@ -2617,7 +2622,11 @@ margin-left: 0px;
.btn-primary i{ .btn-primary i{
border-color: #0b4d78; border-color: #0b4d78;
} }
.form-actions .btn { margin-left: 10px; }
.form-actions .btn,
.form-actions div.btn-group {
margin-left: 10px;
}
.form-actions .btn.btn-success:first-child { .form-actions .btn.btn-success:first-child {
margin-left: 10px !important; margin-left: 10px !important;
@ -2863,7 +2872,7 @@ background-clip: padding-box;
.dashboard .panel-body {padding: 0;} .dashboard .panel-body {padding: 0;}
.dashboard .table-striped>tbody>tr>td, .table-striped>tbody>tr>th { background-color: #fbfbfb;} .dashboard .table-striped>tbody>tr>td, .table-striped>tbody>tr>th { background-color: #fbfbfb;}
.dashboard .table-striped>tbody>tr:nth-child(odd)>td, .table-striped>tbody>tr:nth-child(odd)>th { .dashboard .table-striped>tbody>tr:nth-child(odd)>tr, .table-striped>tbody>tr:nth-child(odd)>th {
background-color: #fff; background-color: #fff;
} }
.dashboard th { .dashboard th {
@ -3204,6 +3213,12 @@ div.checkbox > label {
border-radius: 3px; border-radius: 3px;
} }
.container input:focus,
.container textarea:focus,
.container select:focus {
background: #fdfdfd !important;
}
.container input[placeholder], .container input[placeholder],
.container textarea[placeholder], .container textarea[placeholder],
.container select[placeholder] { .container select[placeholder] {
@ -3226,11 +3241,9 @@ div.checkbox > label {
background-color: #0b4d78 !important; background-color: #0b4d78 !important;
} }
/* div.alert {
.panel-default { z-index: 1;
border-color: #e37329 !important;
} }
*/
.alert-hide { .alert-hide {
position: absolute; position: absolute;

File diff suppressed because one or more lines are too long

27
public/css/style.css vendored
View File

@ -65,6 +65,11 @@ table.dataTable thead > tr > th, table.invoice-table thead > tr > th {
background-color: #e37329 !important; background-color: #e37329 !important;
color:#fff; color:#fff;
} }
/*
table.dataTable tr:hover {
background-color: #F2F5FE !important;
}
*/
th:first-child { th:first-child {
border-radius: 3px 0 0 0; border-radius: 3px 0 0 0;
border-left: none; border-left: none;
@ -83,7 +88,7 @@ border-bottom: 1px solid #dfe0e1;
table.dataTable.no-footer { table.dataTable.no-footer {
border-bottom: none; border-bottom: none;
} }
.table-striped>tbody>tr:nth-child(odd)>td, .table-striped>tbody>tr:nth-child(odd)>tr,
.table-striped>tbody>tr:nth-child(odd)>th { .table-striped>tbody>tr:nth-child(odd)>th {
background-color: #FDFDFD; background-color: #FDFDFD;
} }
@ -233,7 +238,11 @@ margin-left: 0px;
.btn-primary i{ .btn-primary i{
border-color: #0b4d78; border-color: #0b4d78;
} }
.form-actions .btn { margin-left: 10px; }
.form-actions .btn,
.form-actions div.btn-group {
margin-left: 10px;
}
.form-actions .btn.btn-success:first-child { .form-actions .btn.btn-success:first-child {
margin-left: 10px !important; margin-left: 10px !important;
@ -479,7 +488,7 @@ background-clip: padding-box;
.dashboard .panel-body {padding: 0;} .dashboard .panel-body {padding: 0;}
.dashboard .table-striped>tbody>tr>td, .table-striped>tbody>tr>th { background-color: #fbfbfb;} .dashboard .table-striped>tbody>tr>td, .table-striped>tbody>tr>th { background-color: #fbfbfb;}
.dashboard .table-striped>tbody>tr:nth-child(odd)>td, .table-striped>tbody>tr:nth-child(odd)>th { .dashboard .table-striped>tbody>tr:nth-child(odd)>tr, .table-striped>tbody>tr:nth-child(odd)>th {
background-color: #fff; background-color: #fff;
} }
.dashboard th { .dashboard th {
@ -820,6 +829,12 @@ div.checkbox > label {
border-radius: 3px; border-radius: 3px;
} }
.container input:focus,
.container textarea:focus,
.container select:focus {
background: #fdfdfd !important;
}
.container input[placeholder], .container input[placeholder],
.container textarea[placeholder], .container textarea[placeholder],
.container select[placeholder] { .container select[placeholder] {
@ -842,11 +857,9 @@ div.checkbox > label {
background-color: #0b4d78 !important; background-color: #0b4d78 !important;
} }
/* div.alert {
.panel-default { z-index: 1;
border-color: #e37329 !important;
} }
*/
.alert-hide { .alert-hide {
position: absolute; position: absolute;

BIN
public/favicon.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,72 @@
var NINJA = NINJA || {};
function GetPdfMake(invoice, javascript, callback) { function GetPdfMake(invoice, javascript, callback) {
var account = invoice.account; var account = invoice.account;
var baseDD = {
pageMargins: [40, 40, 40, 40],
styles: {
bold: {
bold: true
},
cost: {
alignment: 'right'
},
quantity: {
alignment: 'right'
},
tax: {
alignment: 'right'
},
lineTotal: {
alignment: 'right'
},
right: {
alignment: 'right'
},
subtotals: {
alignment: 'right'
},
termsLabel: {
bold: true,
margin: [0, 10, 0, 4]
}
},
footer: function(){
f = [{ text:invoice.invoice_footer?processVariables(invoice.invoice_footer):"", margin: [40, 0]}]
if (!invoice.is_pro && logoImages.imageLogo1) {
f.push({
image: logoImages.imageLogo1,
width: 150,
margin: [40,0]
});
}
return f;
},
};
eval(javascript); eval(javascript);
dd = $.extend(true, baseDD, dd);
/* /*
var fonts = { pdfMake.fonts = {
wqy: {
normal: 'wqy.ttf',
bold: 'wqy.ttf',
italics: 'wqy.ttf',
bolditalics: 'wqy.ttf'
}
};
*/
/*
pdfMake.fonts = {
NotoSansCJKsc: {
normal: 'NotoSansCJKsc-Regular.ttf',
bold: 'NotoSansCJKsc-Medium.ttf',
italics: 'NotoSansCJKsc-Italic.ttf',
bolditalics: 'NotoSansCJKsc-Italic.ttf'
},
Roboto: { Roboto: {
normal: 'Roboto-Regular.ttf', normal: 'Roboto-Regular.ttf',
bold: 'Roboto-Medium.ttf', bold: 'Roboto-Medium.ttf',
@ -19,29 +82,34 @@ doc.save = function(fileName) {
}; };
return doc; return doc;
} }
function notesAndTerms(invoice)
NINJA.notesAndTerms = function(invoice)
{ {
var text = []; var text = [];
if (invoice.public_notes) { if (invoice.public_notes) {
text.push({text:invoice.public_notes, style:'notes'}); text.push({text:processVariables(invoice.public_notes), style:'notes'});
} }
if (invoice.terms) { if (invoice.terms) {
text.push({text:invoiceLabels.terms, style:'termsLabel'}); text.push({text:invoiceLabels.terms, style:'termsLabel'});
text.push({text:invoice.terms, style:'terms'}); text.push({text:processVariables(invoice.terms), style:'terms'});
} }
return text; return text;
} }
function invoiceLines(invoice) { NINJA.invoiceLines = function(invoice) {
var grid = var grid = [
[[{text: invoiceLabels.item, style: 'tableHeader'}, [
{text: invoiceLabels.item, style: 'tableHeader'},
{text: invoiceLabels.description, style: 'tableHeader'}, {text: invoiceLabels.description, style: 'tableHeader'},
{text: invoiceLabels.unit_cost, style: 'tableHeader'}, {text: invoiceLabels.unit_cost, style: 'tableHeader'},
{text: invoiceLabels.quantity, style: 'tableHeader'}, {text: invoiceLabels.quantity, style: 'tableHeader'},
{text: invoice.has_taxes?invoiceLabels.tax:'', style: 'tableHeader'}, {text: invoice.has_taxes?invoiceLabels.tax:'', style: 'tableHeader'},
{text: invoiceLabels.line_total, style: 'tableHeader'}]]; {text: invoiceLabels.line_total, style: 'tableHeader'}
]
];
var total = 0; var total = 0;
var shownItem = false; var shownItem = false;
var currencyId = invoice && invoice.client ? invoice.client.currency_id : 1; var currencyId = invoice && invoice.client ? invoice.client.currency_id : 1;
@ -93,10 +161,11 @@ row[5] = {style:["lineTotal", rowStyle], text:lineTotal};
grid.push(row); grid.push(row);
} }
return grid; return grid;
} }
function subtotals(invoice) NINJA.subtotals = function(invoice)
{ {
if (!invoice) { if (!invoice) {
return; return;
@ -105,6 +174,7 @@ function subtotals(invoice)
var data = [ var data = [
[invoiceLabels.subtotal, formatMoney(invoice.subtotal_amount, invoice.client.currency_id)], [invoiceLabels.subtotal, formatMoney(invoice.subtotal_amount, invoice.client.currency_id)],
]; ];
if(invoice.discount_amount != 0) { if(invoice.discount_amount != 0) {
data.push([invoiceLabels.discount, formatMoney(invoice.discount_amount, invoice.client.currency_id)]); data.push([invoiceLabels.discount, formatMoney(invoice.discount_amount, invoice.client.currency_id)]);
} }
@ -137,7 +207,7 @@ function subtotals(invoice)
return data; return data;
} }
function accountDetails(account) { NINJA.accountDetails = function(account) {
var data = []; var data = [];
if(account.name) data.push({text:account.name, style:'accountName'}); if(account.name) data.push({text:account.name, style:'accountName'});
if(account.id_number) data.push({text:account.id_number, style:'accountDetails'}); if(account.id_number) data.push({text:account.id_number, style:'accountDetails'});
@ -147,7 +217,7 @@ function accountDetails(account) {
return data; return data;
} }
function accountAddress(account) { NINJA.accountAddress = function(account) {
var address = ''; var address = '';
if (account.city || account.state || account.postal_code) { if (account.city || account.state || account.postal_code) {
address = ((account.city ? account.city + ', ' : '') + account.state + ' ' + account.postal_code).trim(); address = ((account.city ? account.city + ', ' : '') + account.state + ' ' + account.postal_code).trim();
@ -157,10 +227,12 @@ function accountAddress(account) {
if(account.address2) data.push({text:account.address2, style:'accountDetails'}); if(account.address2) data.push({text:account.address2, style:'accountDetails'});
if(address) data.push({text:address, style:'accountDetails'}); if(address) data.push({text:address, style:'accountDetails'});
if(account.country) data.push({text:account.country.name, style: 'accountDetails'}); if(account.country) data.push({text:account.country.name, style: 'accountDetails'});
if(account.custom_label1 && account.custom_value1) data.push({text:account.custom_label1 +' '+ account.custom_value1, style: 'accountDetails'});
if(account.custom_label2 && account.custom_value2) data.push({text:account.custom_label2 +' '+ account.custom_value2, style: 'accountDetails'});
return data; return data;
} }
function invoiceDetails(invoice) { NINJA.invoiceDetails = function(invoice) {
var data = [ var data = [
[ [
invoice.is_quote ? invoiceLabels.quote_number : invoiceLabels.invoice_number, invoice.is_quote ? invoiceLabels.quote_number : invoiceLabels.invoice_number,
@ -179,11 +251,12 @@ function invoiceDetails(invoice) {
return data; return data;
} }
function clientDetails(invoice) { NINJA.clientDetails = function(invoice) {
var client = invoice.client; var client = invoice.client;
if (!client) { if (!client) {
return; return;
} }
var fields = [ var fields = [
getClientDisplayName(client), getClientDisplayName(client),
client.id_number, client.id_number,
@ -202,16 +275,23 @@ function clientDetails(invoice) {
if (!field) { if (!field) {
continue; continue;
} }
data.push(field); data.push([field]);
}
if (!data.length) {
data.push(['']);
} }
return data; return data;
} }
function primaryColor( defaultColor) { NINJA.getPrimaryColor = function(defaultColor) {
return NINJA.primaryColor ? NINJA.primaryColor : defaultColor; return NINJA.primaryColor ? NINJA.primaryColor : defaultColor;
} }
function secondaryColor( defaultColor) { NINJA.getSecondaryColor = function(defaultColor) {
return NINJA.primaryColor ? NINJA.secondaryColor : defaultColor; return NINJA.primaryColor ? NINJA.secondaryColor : defaultColor;
} }
NINJA.getEntityLabel = function(invoice) {
return invoice.is_quote ? invoiceLabels.quote : invoiceLabels.invoice;
}

66368
public/js/pdfmake.js Normal file

File diff suppressed because one or more lines are too long

12
public/js/pdfmake.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -102,7 +102,7 @@ function GetPdf(invoice, javascript){
SetPdfColor(invoice.invoice_design_id == 2 || invoice.invoice_design_id == 3 ? 'White' : 'Black',doc); SetPdfColor(invoice.invoice_design_id == 2 || invoice.invoice_design_id == 3 ? 'White' : 'Black',doc);
var top = doc.internal.pageSize.height - layout.marginLeft; var top = doc.internal.pageSize.height - layout.marginLeft;
if (!invoice.is_pro) top -= 25; if (!invoice.is_pro) top -= 25;
var footer = doc.splitTextToSize(invoice.invoice_footer, 500); var footer = doc.splitTextToSize(processVariables(invoice.invoice_footer), 500);
var numLines = footer.length - 1; var numLines = footer.length - 1;
doc.text(layout.marginLeft, top - (numLines * 8), footer); doc.text(layout.marginLeft, top - (numLines * 8), footer);
} }
@ -884,13 +884,13 @@ function displayNotesAndTerms(doc, layout, invoice, y)
var origY = y; var origY = y;
if (invoice.public_notes) { if (invoice.public_notes) {
var notes = doc.splitTextToSize(invoice.public_notes, 260); var notes = doc.splitTextToSize(processVariables(invoice.public_notes), 260);
doc.text(layout.marginLeft, y, notes); doc.text(layout.marginLeft, y, notes);
y += 16 + (notes.length * doc.internal.getFontSize()); y += 16 + (notes.length * doc.internal.getFontSize());
} }
if (invoice.terms) { if (invoice.terms) {
var terms = doc.splitTextToSize(invoice.terms, 260); var terms = doc.splitTextToSize(processVariables(invoice.terms), 260);
doc.setFontType("bold"); doc.setFontType("bold");
doc.text(layout.marginLeft, y, invoiceLabels.terms); doc.text(layout.marginLeft, y, invoiceLabels.terms);
y += 16; y += 16;
@ -1540,3 +1540,30 @@ function roundToTwo(num, toString) {
function truncate(str, length) { function truncate(str, length) {
return (str && str.length > length) ? (str.substr(0, length-1) + '...') : str; return (str && str.length > length) ? (str.substr(0, length-1) + '...') : str;
} }
// http://codeaid.net/javascript/convert-seconds-to-hours-minutes-and-seconds-%28javascript%29
function secondsToTime(secs)
{
secs = Math.round(secs);
var hours = Math.floor(secs / (60 * 60));
var divisor_for_minutes = secs % (60 * 60);
var minutes = Math.floor(divisor_for_minutes / 60);
var divisor_for_seconds = divisor_for_minutes % 60;
var seconds = Math.ceil(divisor_for_seconds);
var obj = {
"h": hours,
"m": minutes,
"s": seconds
};
return obj;
}
function twoDigits(value) {
if (value < 10) {
return '0' + value;
}
return value;
}

View File

@ -1,4 +1,14 @@
//pdfmake //pdfmake
/*
var dd = {
content: 'wqy中文wqy',
defaultStyle: {
font: 'wqy'
}
};
*/
var dd = { var dd = {
content: [ content: [
{ {
@ -11,48 +21,51 @@ var dd = {
}:"" }:""
], ],
{ {
stack: accountDetails(account) stack: NINJA.accountDetails(account)
}, },
{ {
stack: accountAddress(account) stack: NINJA.accountAddress(account)
} }
] ]
}, },
{ {
text:(invoice.is_quote ? invoiceLabels.quote : invoiceLabels.invoice).toUpperCase(), text:(NINJA.getEntityLabel(invoice)).toUpperCase(),
margin: [8, 70, 8, 16], margin: [8, 70, 8, 16],
style: 'primaryColor', style: 'primaryColor',
fontSize: 11 fontSize: NINJA.fontSize + 2
}, },
{ {
style: 'tableExample',
table: { table: {
headerRows: 1, headerRows: 1,
widths: ['auto', 'auto', '*'], widths: ['auto', 'auto', '*'],
body: [[ body: [
[
{ {
table: { table: {
body: invoiceDetails(invoice), body: NINJA.invoiceDetails(invoice),
},
layout: 'noBorders',
},
{
table: {
body: NINJA.clientDetails(invoice),
}, },
layout: 'noBorders', layout: 'noBorders',
}, },
clientDetails(invoice),
'' ''
]] ]
]
}, },
layout: { layout: {
hLineWidth: function (i, node) { hLineWidth: function (i, node) {
return (i === 0 || i === node.table.body.length) ? .5 : 0; return (i === 0 || i === node.table.body.length) ? .5 : 0;
}, },
vLineWidth: function (i, node) { vLineWidth: function (i, node) {
return 0;//(i === 0 || i === node.table.widths.length) ? 2 : 1; return 0;
}, },
hLineColor: function (i, node) { hLineColor: function (i, node) {
return '#D8D8D8';//(i === 0 || i === node.table.body.length) ? 'black' : 'gray'; return '#D8D8D8';
}, },
/*vLineColor: function (i, node) {
return (i === 0 || i === node.table.widths.length) ? 'black' : 'gray';
},*/
paddingLeft: function(i, node) { return 8; }, paddingLeft: function(i, node) { return 8; },
paddingRight: function(i, node) { return 8; }, paddingRight: function(i, node) { return 8; },
paddingTop: function(i, node) { return 4; }, paddingTop: function(i, node) { return 4; },
@ -64,7 +77,7 @@ var dd = {
table: { table: {
headerRows: 1, headerRows: 1,
widths: ['15%', '*', 'auto', 'auto', 'auto', 'auto'], widths: ['15%', '*', 'auto', 'auto', 'auto', 'auto'],
body:invoiceLines(invoice), body: NINJA.invoiceLines(invoice),
}, },
layout: { layout: {
hLineWidth: function (i, node) { hLineWidth: function (i, node) {
@ -85,12 +98,12 @@ var dd = {
'\n', '\n',
{ {
columns: [ columns: [
notesAndTerms(invoice), NINJA.notesAndTerms(invoice),
{ {
style: 'subtotals', style: 'subtotals',
table: { table: {
widths: ['*', '*'], widths: ['*', '*'],
body: subtotals(invoice), body: NINJA.subtotals(invoice),
}, },
layout: { layout: {
hLineWidth: function (i, node) { hLineWidth: function (i, node) {
@ -109,84 +122,41 @@ var dd = {
}, },
], ],
footer: function(){
f = [{ text:invoice.invoice_footer?invoice.invoice_footer:"", margin: [40, 0]}]
if (!invoice.is_pro && logoImages.imageLogo1) {
f.push({
image: logoImages.imageLogo1,
width: 150,
margin: [40,0]
});
}
return f;
},
defaultStyle: { defaultStyle: {
//font: 'Roboto', //font: 'arialuni',
fontSize: 9, fontSize: NINJA.fontSize,
margin: [8, 4, 8, 4] margin: [8, 4, 8, 4]
}, },
styles: { styles: {
primaryColor:{ primaryColor:{
color: primaryColor('#299CC2') color: NINJA.getPrimaryColor('#299CC2')
}, },
accountName: { accountName: {
margin: [4, 2, 4, 2], margin: [4, 2, 4, 2],
color:primaryColor('#299CC2') color: NINJA.getPrimaryColor('#299CC2')
}, },
accountDetails: { accountDetails: {
margin: [4, 2, 4, 2], margin: [4, 2, 4, 2],
color: '#AAA9A9' color: '#AAA9A9'
}, },
bold: {
bold: true
},
even: { even: {
}, },
odd: { odd: {
fillColor:'#F4F4F4' fillColor:'#F4F4F4'
}, },
productKey: { productKey: {
color:primaryColor('#299CC2') color: NINJA.getPrimaryColor('#299CC2')
},
cost: {
alignment: 'right'
},
quantity: {
alignment: 'right'
},
tax: {
alignment: 'right'
},
lineTotal: {
alignment: 'right'
},
right: {
alignment: 'right'
},
subtotals: {
alignment: 'right'
}, },
tableHeader: { tableHeader: {
bold: true bold: true
}, },
balanceDueLabel: { balanceDueLabel: {
fontSize: 11 fontSize: NINJA.fontSize + 2
}, },
balanceDueValue: { balanceDueValue: {
fontSize: 11, fontSize: NINJA.fontSize + 2,
color:primaryColor('#299CC2') color: NINJA.getPrimaryColor('#299CC2')
}, },
notes: {
}, },
terms: { pageMargins: [40, 40, 40, 40],
},
termsLabel: {
bold: true,
fontSize: 10,
margin: [0, 10, 0, 4]
}
},
pageMargins: [40, 40, 40, 40]
}; };

View File

@ -1,6 +1,5 @@
# Invoice Ninja # Invoice Ninja
### [https://www.invoiceninja.com](https://www.invoiceninja.com) ### [https://www.invoiceninja.com](https://www.invoiceninja.com)
##### Please [click here](https://bitnami.com/stack/invoice-ninja) to vote for us to be added to Bitnami's one-click install library
If you'd like to use our code to sell your own invoicing app we have an affiliate program. Get in touch for more details. If you'd like to use our code to sell your own invoicing app we have an affiliate program. Get in touch for more details.
@ -8,7 +7,7 @@ If you'd like to use our code to sell your own invoicing app we have an affiliat
To setup the site you can either use the [zip file](https://www.invoiceninja.com/knowledgebase/self-host/) (easier to run) or checkout the code from GitHub (easier to make changes). To setup the site you can either use the [zip file](https://www.invoiceninja.com/knowledgebase/self-host/) (easier to run) or checkout the code from GitHub (easier to make changes).
For updates follow [@invoiceninja](https://twitter.com/invoiceninja) or join the [Facebook Group](https://www.facebook.com/invoiceninja). For discussion of the code please use the [Google Group](https://groups.google.com/d/forum/invoiceninja). For updates follow [@invoiceninja](https://twitter.com/invoiceninja) or join the [Facebook Group](https://www.facebook.com/invoiceninja). For discussion of the app please use our [new forum](http://www.invoiceninja.com/forums).
If you'd like to translate the site please use [caouecs/Laravel4-long](https://github.com/caouecs/Laravel4-lang) for the starter files. If you'd like to translate the site please use [caouecs/Laravel4-long](https://github.com/caouecs/Laravel4-lang) for the starter files.
@ -20,8 +19,9 @@ Developed by [@hillelcoren](https://twitter.com/hillelcoren) | Designed by [kant
* Live PDF generation * Live PDF generation
* Integrates with 30+ payment providers * Integrates with 30+ payment providers
* Recurring invoices * Recurring invoices
* Tax rates and payment terms * Tasks with time-tracking
* Multi-user support * Multi-user support
* Tax rates and payment terms
* Partial payments * Partial payments
* Custom email templates * Custom email templates
* [Zapier](https://zapier.com/) integration * [Zapier](https://zapier.com/) integration

View File

@ -627,4 +627,55 @@ return array(
'recurring' => 'Recurring', 'recurring' => 'Recurring',
'last_invoice_sent' => 'Last invoice sent :date', 'last_invoice_sent' => 'Last invoice sent :date',
'processed_updates' => 'Successfully completed update',
'tasks' => 'Tasks',
'new_task' => 'New Task',
'start_time' => 'Start Time',
'created_task' => 'Successfully created task',
'updated_task' => 'Successfully updated task',
'edit_task' => 'Edit Task',
'archive_task' => 'Archive Task',
'restore_task' => 'Restore Task',
'delete_task' => 'Delete Task',
'stop_task' => 'Stop Task',
'time' => 'Time',
'start' => 'Start',
'stop' => 'Stop',
'now' => 'Now',
'timer' => 'Timer',
'manual' => 'Manual',
'date_and_time' => 'Date & Time',
'second' => 'second',
'seconds' => 'seconds',
'minute' => 'minute',
'minutes' => 'minutes',
'hour' => 'hour',
'hours' => 'hours',
'task_details' => 'Task Details',
'duration' => 'Duration',
'end_time' => 'End Time',
'end' => 'End',
'invoiced' => 'Invoiced',
'logged' => 'Logged',
'running' => 'Running',
'task_error_multiple_clients' => 'The tasks can\'t belong to different clients',
'task_error_running' => 'Please stop running tasks first',
'task_error_invoiced' => 'Tasks have already been invoiced',
'restored_task' => 'Successfully restored task',
'archived_task' => 'Successfully archived task',
'archived_tasks' => 'Successfully archived :count tasks',
'deleted_task' => 'Successfully deleted task',
'deleted_tasks' => 'Successfully deleted :count tasks',
'create_task' => 'Create Task',
'stopped_task' => 'Successfully stopped task',
'invoice_task' => 'Invoice Task',
'invoice_labels' => 'Invoice Labels',
'prefix' => 'Prefix',
'counter' => 'Counter',
'payment_type_dwolla' => 'Dwolla',
'gateway_help_43' => ':link to sign up for Dwolla.',
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
); );

View File

@ -76,6 +76,7 @@ return array(
"positive" => "The :attribute must be greater than zero.", "positive" => "The :attribute must be greater than zero.",
"has_credit" => "The client does not have enough credit.", "has_credit" => "The client does not have enough credit.",
"notmasked" => "The values are masked", "notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -618,5 +618,55 @@ return array(
'recurring' => 'Wiederkehrend', 'recurring' => 'Wiederkehrend',
'last_invoice_sent' => 'Letzte Rechnung verschickt am :date', 'last_invoice_sent' => 'Letzte Rechnung verschickt am :date',
'processed_updates' => 'Successfully completed update',
'tasks' => 'Tasks',
'new_task' => 'New Task',
'start_time' => 'Start Time',
'created_task' => 'Successfully created task',
'updated_task' => 'Successfully updated task',
'edit_task' => 'Edit Task',
'archive_task' => 'Archive Task',
'restore_task' => 'Restore Task',
'delete_task' => 'Delete Task',
'stop_task' => 'Stop Task',
'time' => 'Time',
'start' => 'Start',
'stop' => 'Stop',
'now' => 'Now',
'timer' => 'Timer',
'manual' => 'Manual',
'date_and_time' => 'Date & Time',
'second' => 'second',
'seconds' => 'seconds',
'minute' => 'minute',
'minutes' => 'minutes',
'hour' => 'hour',
'hours' => 'hours',
'task_details' => 'Task Details',
'duration' => 'Duration',
'end_time' => 'End Time',
'end' => 'End',
'invoiced' => 'Invoiced',
'logged' => 'Logged',
'running' => 'Running',
'task_error_multiple_clients' => 'The tasks can\'t belong to different clients',
'task_error_running' => 'Please stop running tasks first',
'task_error_invoiced' => 'Tasks have already been invoiced',
'restored_task' => 'Successfully restored task',
'archived_task' => 'Successfully archived task',
'archived_tasks' => 'Successfully archived :count tasks',
'deleted_task' => 'Successfully deleted task',
'deleted_tasks' => 'Successfully deleted :count tasks',
'create_task' => 'Create Task',
'stopped_task' => 'Successfully stopped task',
'invoice_task' => 'Invoice Task',
'invoice_labels' => 'Invoice Labels',
'prefix' => 'Prefix',
'counter' => 'Counter',
'payment_type_dwolla' => 'Dwolla',
'gateway_help_43' => ':link to sign up for Dwolla.',
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
); );

View File

@ -74,6 +74,7 @@ return array(
"positive" => ":attribute muss größer als null sein.", "positive" => ":attribute muss größer als null sein.",
"has_credit" => "Der Kunde hat nicht genug Guthaben.", "has_credit" => "Der Kunde hat nicht genug Guthaben.",
"notmasked" => "The values are masked", "notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -135,8 +135,8 @@ return array(
'filter' => 'Filter', 'filter' => 'Filter',
'new_client' => 'New Client', 'new_client' => 'New Client',
'new_invoice' => 'New Invoice', 'new_invoice' => 'New Invoice',
'new_payment' => 'New Payment', 'new_payment' => 'Enter Payment',
'new_credit' => 'New Credit', 'new_credit' => 'Enter Credit',
'contact' => 'Contact', 'contact' => 'Contact',
'date_created' => 'Date Created', 'date_created' => 'Date Created',
'last_login' => 'Last Login', 'last_login' => 'Last Login',
@ -443,7 +443,7 @@ return array(
'share_invoice_counter' => 'Share invoice counter', 'share_invoice_counter' => 'Share invoice counter',
'invoice_issued_to' => 'Invoice issued to', 'invoice_issued_to' => 'Invoice issued to',
'invalid_counter' => 'To prevent a possible conflict please set either an invoice or quote number prefix', 'invalid_counter' => 'To prevent a possible conflict please set either an invoice or quote number prefix',
'mark_sent' => 'Mark sent', 'mark_sent' => 'Mark Sent',
'gateway_help_1' => ':link to sign up for Authorize.net.', 'gateway_help_1' => ':link to sign up for Authorize.net.',
'gateway_help_2' => ':link to sign up for Authorize.net.', 'gateway_help_2' => ':link to sign up for Authorize.net.',
@ -626,5 +626,54 @@ return array(
'last_invoice_sent' => 'Last invoice sent :date', 'last_invoice_sent' => 'Last invoice sent :date',
'processed_updates' => 'Successfully completed update', 'processed_updates' => 'Successfully completed update',
'tasks' => 'Tasks',
'new_task' => 'New Task',
'start_time' => 'Start Time',
'created_task' => 'Successfully created task',
'updated_task' => 'Successfully updated task',
'edit_task' => 'Edit Task',
'archive_task' => 'Archive Task',
'restore_task' => 'Restore Task',
'delete_task' => 'Delete Task',
'stop_task' => 'Stop Task',
'time' => 'Time',
'start' => 'Start',
'stop' => 'Stop',
'now' => 'Now',
'timer' => 'Timer',
'manual' => 'Manual',
'date_and_time' => 'Date & Time',
'second' => 'second',
'seconds' => 'seconds',
'minute' => 'minute',
'minutes' => 'minutes',
'hour' => 'hour',
'hours' => 'hours',
'task_details' => 'Task Details',
'duration' => 'Duration',
'end_time' => 'End Time',
'end' => 'End',
'invoiced' => 'Invoiced',
'logged' => 'Logged',
'running' => 'Running',
'task_error_multiple_clients' => 'The tasks can\'t belong to different clients',
'task_error_running' => 'Please stop running tasks first',
'task_error_invoiced' => 'Tasks have already been invoiced',
'restored_task' => 'Successfully restored task',
'archived_task' => 'Successfully archived task',
'archived_tasks' => 'Successfully archived :count tasks',
'deleted_task' => 'Successfully deleted task',
'deleted_tasks' => 'Successfully deleted :count tasks',
'create_task' => 'Create Task',
'stopped_task' => 'Successfully stopped task',
'invoice_task' => 'Invoice Task',
'invoice_labels' => 'Invoice Labels',
'prefix' => 'Prefix',
'counter' => 'Counter',
'payment_type_dwolla' => 'Dwolla',
'gateway_help_43' => ':link to sign up for Dwolla.',
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
); );

View File

@ -72,6 +72,7 @@ return array(
"positive" => "The :attribute must be greater than zero.", "positive" => "The :attribute must be greater than zero.",
"has_credit" => "The client does not have enough credit.", "has_credit" => "The client does not have enough credit.",
"notmasked" => "The values are masked", "notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -597,6 +597,55 @@ return array(
'recurring' => 'Recurring', 'recurring' => 'Recurring',
'last_invoice_sent' => 'Last invoice sent :date', 'last_invoice_sent' => 'Last invoice sent :date',
'processed_updates' => 'Successfully completed update',
'tasks' => 'Tasks',
'new_task' => 'New Task',
'start_time' => 'Start Time',
'created_task' => 'Successfully created task',
'updated_task' => 'Successfully updated task',
'edit_task' => 'Edit Task',
'archive_task' => 'Archive Task',
'restore_task' => 'Restore Task',
'delete_task' => 'Delete Task',
'stop_task' => 'Stop Task',
'time' => 'Time',
'start' => 'Start',
'stop' => 'Stop',
'now' => 'Now',
'timer' => 'Timer',
'manual' => 'Manual',
'date_and_time' => 'Date & Time',
'second' => 'second',
'seconds' => 'seconds',
'minute' => 'minute',
'minutes' => 'minutes',
'hour' => 'hour',
'hours' => 'hours',
'task_details' => 'Task Details',
'duration' => 'Duration',
'end_time' => 'End Time',
'end' => 'End',
'invoiced' => 'Invoiced',
'logged' => 'Logged',
'running' => 'Running',
'task_error_multiple_clients' => 'The tasks can\'t belong to different clients',
'task_error_running' => 'Please stop running tasks first',
'task_error_invoiced' => 'Tasks have already been invoiced',
'restored_task' => 'Successfully restored task',
'archived_task' => 'Successfully archived task',
'archived_tasks' => 'Successfully archived :count tasks',
'deleted_task' => 'Successfully deleted task',
'deleted_tasks' => 'Successfully deleted :count tasks',
'create_task' => 'Create Task',
'stopped_task' => 'Successfully stopped task',
'invoice_task' => 'Invoice Task',
'invoice_labels' => 'Invoice Labels',
'prefix' => 'Prefix',
'counter' => 'Counter',
'payment_type_dwolla' => 'Dwolla',
'gateway_help_43' => ':link to sign up for Dwolla.',
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
); );

View File

@ -73,7 +73,7 @@ return array(
"positive" => ":attribute debe ser mayor que cero.", "positive" => ":attribute debe ser mayor que cero.",
"has_credit" => "el cliente no tiene crédito suficiente.", "has_credit" => "el cliente no tiene crédito suficiente.",
"notmasked" => "The values are masked", "notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -626,5 +626,55 @@ return array(
'recurring' => 'Recurring', 'recurring' => 'Recurring',
'last_invoice_sent' => 'Last invoice sent :date', 'last_invoice_sent' => 'Last invoice sent :date',
'processed_updates' => 'Successfully completed update',
'tasks' => 'Tasks',
'new_task' => 'New Task',
'start_time' => 'Start Time',
'created_task' => 'Successfully created task',
'updated_task' => 'Successfully updated task',
'edit_task' => 'Edit Task',
'archive_task' => 'Archive Task',
'restore_task' => 'Restore Task',
'delete_task' => 'Delete Task',
'stop_task' => 'Stop Task',
'time' => 'Time',
'start' => 'Start',
'stop' => 'Stop',
'now' => 'Now',
'timer' => 'Timer',
'manual' => 'Manual',
'date_and_time' => 'Date & Time',
'second' => 'second',
'seconds' => 'seconds',
'minute' => 'minute',
'minutes' => 'minutes',
'hour' => 'hour',
'hours' => 'hours',
'task_details' => 'Task Details',
'duration' => 'Duration',
'end_time' => 'End Time',
'end' => 'End',
'invoiced' => 'Invoiced',
'logged' => 'Logged',
'running' => 'Running',
'task_error_multiple_clients' => 'The tasks can\'t belong to different clients',
'task_error_running' => 'Please stop running tasks first',
'task_error_invoiced' => 'Tasks have already been invoiced',
'restored_task' => 'Successfully restored task',
'archived_task' => 'Successfully archived task',
'archived_tasks' => 'Successfully archived :count tasks',
'deleted_task' => 'Successfully deleted task',
'deleted_tasks' => 'Successfully deleted :count tasks',
'create_task' => 'Create Task',
'stopped_task' => 'Successfully stopped task',
'invoice_task' => 'Invoice Task',
'invoice_labels' => 'Invoice Labels',
'prefix' => 'Prefix',
'counter' => 'Counter',
'payment_type_dwolla' => 'Dwolla',
'gateway_help_43' => ':link to sign up for Dwolla.',
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
); );

View File

@ -73,7 +73,7 @@ return array(
"positive" => ":attribute debe ser mayor que cero.", "positive" => ":attribute debe ser mayor que cero.",
"has_credit" => "el cliente no tiene crédito suficiente.", "has_credit" => "el cliente no tiene crédito suficiente.",
"notmasked" => "The values are masked", "notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -618,5 +618,55 @@ return array(
'recurring' => 'Recurring', 'recurring' => 'Recurring',
'last_invoice_sent' => 'Last invoice sent :date', 'last_invoice_sent' => 'Last invoice sent :date',
'processed_updates' => 'Successfully completed update',
'tasks' => 'Tasks',
'new_task' => 'New Task',
'start_time' => 'Start Time',
'created_task' => 'Successfully created task',
'updated_task' => 'Successfully updated task',
'edit_task' => 'Edit Task',
'archive_task' => 'Archive Task',
'restore_task' => 'Restore Task',
'delete_task' => 'Delete Task',
'stop_task' => 'Stop Task',
'time' => 'Time',
'start' => 'Start',
'stop' => 'Stop',
'now' => 'Now',
'timer' => 'Timer',
'manual' => 'Manual',
'date_and_time' => 'Date & Time',
'second' => 'second',
'seconds' => 'seconds',
'minute' => 'minute',
'minutes' => 'minutes',
'hour' => 'hour',
'hours' => 'hours',
'task_details' => 'Task Details',
'duration' => 'Duration',
'end_time' => 'End Time',
'end' => 'End',
'invoiced' => 'Invoiced',
'logged' => 'Logged',
'running' => 'Running',
'task_error_multiple_clients' => 'The tasks can\'t belong to different clients',
'task_error_running' => 'Please stop running tasks first',
'task_error_invoiced' => 'Tasks have already been invoiced',
'restored_task' => 'Successfully restored task',
'archived_task' => 'Successfully archived task',
'archived_tasks' => 'Successfully archived :count tasks',
'deleted_task' => 'Successfully deleted task',
'deleted_tasks' => 'Successfully deleted :count tasks',
'create_task' => 'Create Task',
'stopped_task' => 'Successfully stopped task',
'invoice_task' => 'Invoice Task',
'invoice_labels' => 'Invoice Labels',
'prefix' => 'Prefix',
'counter' => 'Counter',
'payment_type_dwolla' => 'Dwolla',
'gateway_help_43' => ':link to sign up for Dwolla.',
'partial_value' => 'Must be greater than zero and less than the total',
'more_actions' => 'More Actions',
); );

View File

@ -74,6 +74,7 @@ return array(
"positive" => "The :attribute must be greater than zero.", "positive" => "The :attribute must be greater than zero.",
"has_credit" => "The client does not have enough credit.", "has_credit" => "The client does not have enough credit.",
"notmasked" => "The values are masked", "notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

Some files were not shown because too many files have changed in this diff Show More