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

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_DEBUG=true
APP_ENV=production
APP_DEBUG=false
APP_URL=http://ninja.dev
APP_CIPHER=rijndael-128
APP_KEY
APP_TIMEZONE
DB_TYPE=mysql
DB_HOST=localhost
@ -15,5 +16,6 @@ MAIL_PORT=587
MAIL_ENCRYPTION=tls
MAIL_HOST
MAIL_USERNAME
MAIL_FROM_ADDRESS
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/spectrum/spectrum.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/pdfmake/build/pdfmake.min.js',
//'public/vendor/pdfmake/build/vfs_fonts.js',
@ -63,8 +65,7 @@ module.exports = function(grunt) {
'public/js/lightbox.min.js',
'public/js/bootstrap-combobox.js',
'public/js/script.js',
'public/js/pdf.pdfmake.js',
'public/js/pdf.pdfmake.js'
],
dest: 'public/js/built.js',
nonull: true
@ -107,6 +108,7 @@ module.exports = function(grunt) {
css_public: {
src: [
'public/vendor/bootstrap/dist/css/bootstrap.min.css',
'public/vendor/font-awesome/css/font-awesome.min.css',
/*
'public/css/bootstrap.splash.css',
'public/css/splash.css',

View File

@ -49,24 +49,6 @@ class CheckData extends Command {
$today = new DateTime();
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
$clients = DB::table('clients')
->join('payments', 'payments.client_id', '=', 'clients.id')
@ -77,7 +59,7 @@ class CheckData extends Command {
->havingRaw('clients.paid_to_date != sum(payments.amount) and clients.paid_to_date != 999999999.9999')
->get(['clients.id', 'clients.paid_to_date', DB::raw('sum(payments.amount) as amount')]);
$this->info(count($clients) . ' clients with incorrect paid to date');
if ($this->option('fix') == 'true') {
foreach ($clients as $client) {
DB::table('clients')

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->client_id = $recurInvoice->client_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->balance = $recurInvoice->amount;
$invoice->invoice_date = date_create()->format('Y-m-d');
$invoice->discount = $recurInvoice->discount;
$invoice->po_number = $recurInvoice->po_number;
$invoice->public_notes = $recurInvoice->public_notes;
$invoice->terms = $recurInvoice->terms;
$invoice->invoice_footer = $recurInvoice->invoice_footer;
$invoice->public_notes = Utils::processVariables($recurInvoice->public_notes);
$invoice->terms = Utils::processVariables($recurInvoice->terms);
$invoice->invoice_footer = Utils::processVariables($recurInvoice->invoice_footer);
$invoice->tax_name = $recurInvoice->tax_name;
$invoice->tax_rate = $recurInvoice->tax_rate;
$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();
$this->info(count($accounts).' accounts found');
dd(0);
foreach ($accounts as $account) {
$client = $this->accountRepo->getNinjaClient($account);
$invitation = $this->accountRepo->createNinjaInvoice($client);

View File

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

View File

@ -209,6 +209,7 @@ class AccountController extends BaseController
$data['invoice'] = $invoice;
$data['invoiceDesigns'] = InvoiceDesign::availableDesigns();
$data['invoiceLabels'] = json_decode($account->invoice_labels) ?: [];
} else if ($subSection == ACCOUNT_EMAIL_TEMPLATES) {
$data['invoiceEmail'] = $account->getEmailTemplate(ENTITY_INVOICE);
$data['quoteEmail'] = $account->getEmailTemplate(ENTITY_QUOTE);
@ -331,6 +332,16 @@ class AccountController extends BaseController
$account->primary_color = Input::get('primary_color');
$account->secondary_color = Input::get('secondary_color');
$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();
Session::flash('message', trans('texts.updated_settings'));

View File

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

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');
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('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) : ''; })

View File

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

View File

@ -20,6 +20,7 @@ use App\Models\PaymentTerm;
use App\Models\Industry;
use App\Models\Currency;
use App\Models\Country;
use App\Models\Task;
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('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('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('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); })
->addColumn('dropdown', function ($model) {
@ -75,9 +76,16 @@ class ClientController extends BaseController
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>
<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('payments/create/'.$model->public_id).'">'.trans('texts.new_payment').'</a></li>
<li><a href="'.URL::to('credits/create/'.$model->public_id).'">'.trans('texts.new_credit').'</a></li>
<li><a href="'.URL::to('tasks/create/'.$model->public_id).'">'.trans('texts.new_task').'</a></li>
<li><a href="'.URL::to('invoices/create/'.$model->public_id).'">'.trans('texts.new_invoice').'</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><a href="javascript:archiveEntity('.$model->public_id.')">'.trans('texts.archive_client').'</a></li>';
} else {
@ -112,15 +120,18 @@ class ClientController extends BaseController
Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT);
$actionLinks = [
['label' => trans('texts.create_invoice'), 'url' => URL::to('invoices/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)],
['label' => trans('texts.new_task'), 'url' => '/tasks/create/'.$client->public_id]
];
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(
'actionLinks' => $actionLinks,
'showBreadcrumbs' => false,
@ -128,6 +139,8 @@ class ClientController extends BaseController
'credit' => $client->getTotalCredit(),
'title' => trans('texts.view_client'),
'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(),
);

View File

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

View File

@ -1,5 +1,7 @@
<?php namespace App\Http\Controllers;
use Response;
use Request;
use Redirect;
use Auth;
use View;
@ -8,6 +10,7 @@ use Session;
use App\Models\Account;
use App\Libraries\Utils;
use App\Ninja\Mailers\Mailer;
use Symfony\Component\Security\Core\Util\StringUtils;
class HomeController extends BaseController
{
@ -76,4 +79,9 @@ class HomeController extends BaseController
{
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'])) {
$contact = Contact::scope()->with('client')->whereEmail($data['email'])->first();
if ($contact) {
$client = $contact->client;
} else {
$client = Client::scope()->whereHas('contacts', function($query) use ($data) {
$query->where('email', '=', $data['email']);
})->first();
if (!$client) {
$clientData = ['contact' => ['email' => $data['email']]];
foreach (['name', 'private_notes'] as $field) {
if (isset($data[$field])) {

View File

@ -13,6 +13,7 @@ use URL;
use Datatable;
use finfo;
use Request;
use DropdownButton;
use App\Models\Invoice;
use App\Models\Invitation;
use App\Models\Client;
@ -27,6 +28,7 @@ use App\Models\PaymentTerm;
use App\Models\InvoiceDesign;
use App\Models\AccountGateway;
use App\Models\Activity;
use App\Models\Gateway;
use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\InvoiceRepository;
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']),
];
$recurringInvoices = Invoice::scope()->where('is_recurring', '=', true);
$recurringInvoices = Invoice::scope()
->where('is_recurring', '=', true);
if (Session::get('show_trash:invoice')) {
$recurringInvoices->withTrashed();
} else {
$recurringInvoices->join('clients', 'clients.id', '=', 'invoices.client_id')
->where('clients.deleted_at', '=', null);
}
if ($recurringInvoices->count() > 0) {
@ -221,14 +227,14 @@ class InvoiceController extends BaseController
'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)) {
$paymentTypes[] = [
'url' => URL::to("/payment/{$invitation->invitation_key}/{$type}"), 'label' => trans('texts.'.strtolower($type))
];
}
}
$data = array(
'isConverted' => $invoice->quote_invoice_id ? true : false,
'showBreadcrumbs' => false,
@ -275,6 +281,40 @@ class InvoiceController extends BaseController
$invoice->end_date = Utils::fromSqlDate($invoice->end_date);
$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(
'entityType' => $entityType,
'showBreadcrumbs' => $clone,
@ -285,7 +325,8 @@ class InvoiceController extends BaseController
'invitationContactIds' => $contactIds,
'url' => $url,
'title' => trans("texts.edit_{$entityType}"),
'client' => $invoice->client, );
'client' => $invoice->client,
'actions' => $actions);
$data = array_merge($data, self::getViewModel());
// Set the invitation link on the client's contacts
@ -327,7 +368,8 @@ class InvoiceController extends BaseController
'method' => 'POST',
'url' => 'invoices',
'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());
return View::make('invoices.edit', $data);
@ -366,7 +408,9 @@ class InvoiceController extends BaseController
6 => 'Six months',
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>'; })
->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); })
->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);
$config = json_decode($accountGateway->config);
/*
$gateway->setSolutionType("Sole");
$gateway->setLandingPage("Billing");
*/
foreach ($config as $key => $val) {
if (!$val) {
continue;
@ -209,8 +204,14 @@ class PaymentController extends BaseController
$gateway->$function($val);
}
if (Utils::isNinjaDev()) {
$gateway->setTestMode(true);
if ($accountGateway->gateway->id == GATEWAY_DWOLLA) {
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;
@ -284,7 +285,7 @@ class PaymentController extends BaseController
}
public function show_payment($invitationKey, $paymentType = false)
{
{
$invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail();
$invoice = $invitation->invoice;
$client = $invoice->client;
@ -292,8 +293,9 @@ class PaymentController extends BaseController
$useToken = false;
if (!$paymentType) {
$paymentType = $account->account_gateways[0]->getPaymentType();
} else if ($paymentType == PAYMENT_TYPE_TOKEN) {
$paymentType = Session::get('payment_type', $account->account_gateways[0]->getPaymentType());
}
if ($paymentType == PAYMENT_TYPE_TOKEN) {
$useToken = true;
$paymentType = PAYMENT_TYPE_CREDIT_CARD;
}
@ -323,8 +325,9 @@ class PaymentController extends BaseController
'gateway' => $gateway,
'acceptedCreditCardTypes' => $acceptedCreditCardTypes,
'countries' => Cache::get('countries'),
'currencyId' => $client->currency_id,
'currencyId' => $client->getCurrencyId(),
'account' => $client->account,
'hideLogo' => $account->isWhiteLabel(),
];
return View::make('payments.payment', $data);
@ -448,6 +451,7 @@ class PaymentController extends BaseController
'message' => $affiliate->payment_subtitle,
'license' => $licenseKey,
'hideHeader' => true,
'productId' => $license->product_id
];
$name = "{$license->first_name} {$license->last_name}";
@ -473,7 +477,7 @@ class PaymentController extends BaseController
$productId = Input::get('product_id', PRODUCT_ONE_CLICK_INSTALL);
$license = License::where('license_key', '=', $licenseKey)
->where('is_claimed', '<', 3)
->where('is_claimed', '<', 5)
->where('product_id', '=', $productId)
->first();
@ -483,7 +487,7 @@ class PaymentController extends BaseController
$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 {
return 'invalid';
}
@ -509,8 +513,10 @@ class PaymentController extends BaseController
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
Utils::logError('Payment Error [invalid]');
return Redirect::to('payment/'.$invitationKey)
->withErrors($validator);
->withErrors($validator)
->withInput();
}
}
@ -533,7 +539,7 @@ class PaymentController extends BaseController
try {
$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 ($useToken) {
@ -558,6 +564,10 @@ class PaymentController extends BaseController
$token->token = $cardReference;
$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) {
Session::flash('error', $response->getMessage());
Utils::logError('Payment Error [no-ref]: ' . $response->getMessage());
if ($onSite) {
return Redirect::to('payment/'.$invitationKey)->withInput();
@ -580,6 +591,11 @@ class PaymentController extends BaseController
$payment = self::createPayment($invitation, $ref);
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);
} elseif ($response->isRedirect()) {
$invitation->transaction_reference = $ref;
@ -590,13 +606,14 @@ class PaymentController extends BaseController
$response->redirect();
} else {
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());
}
} catch (\Exception $e) {
$errorMessage = trans('texts.payment_error');
Session::flash('error', $errorMessage."<p>".$e->getMessage());
Utils::logError(Utils::getErrorString($e));
Utils::logError('Payment Error [uncaught]:' . Utils::getErrorString($e));
if ($onSite) {
return Redirect::to('payment/'.$invitationKey)->withInput();

View File

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

View File

@ -16,13 +16,14 @@ class ReportController extends BaseController
public function d3()
{
$message = '';
$fileName = storage_path() . '/dataviz_sample.txt';
if (Auth::user()->account->isPro()) {
$account = Account::where('id', '=', Auth::user()->account->id)->with(['clients.invoices.invoice_items', 'clients.contacts'])->first();
$account = $account->hideFieldsForViz();
$clients = $account->clients->toJson();
} elseif (isset($_ENV['DATA_VIZ_SAMPLE'])) {
$clients = $_ENV['DATA_VIZ_SAMPLE'];
} elseif (file_exists($fileName)) {
$clients = file_get_contents($fileName);
$message = trans('texts.sample_data');
} else {
$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 Utils;
use Validator;
use Illuminate\Auth\Passwords\TokenRepositoryInterface;
use App\Models\User;
use App\Http\Requests;
use App\Ninja\Repositories\AccountRepository;
@ -263,7 +263,7 @@ class UserController extends BaseController
*
* @param string $code
*/
public function confirm($code)
public function confirm($code, TokenRepositoryInterface $tokenRepo)
{
$user = User::where('confirmation_code', '=', $code)->get()->first();
@ -275,9 +275,10 @@ class UserController extends BaseController
$user->save();
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 {
if (Session::has(REQUESTED_PRO_PLAN)) {
Session::forget(REQUESTED_PRO_PLAN);

View File

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

View File

@ -1,5 +1,6 @@
<?php namespace app\Http\Middleware;
use Request;
use Closure;
use Utils;
use App;
@ -50,7 +51,10 @@ class StartupCheck
'countries' => 'App\Models\Country',
];
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') {
$orderBy = 'num_days';
} elseif (in_array($name, ['currencies', 'sizes', 'industries', 'languages', 'countries'])) {
@ -58,7 +62,10 @@ class StartupCheck
} else {
$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();
}
if (!Utils::isNinjaProd()) {
Cache::forget('invoice_designs_cache_'.Auth::user()->maxInvoiceDesignId());
}
Session::flash('message', trans('texts.bought_designs'));
}
} elseif ($productId == PRODUCT_WHITE_LABEL) {

View File

@ -1,6 +1,5 @@
<?php
/*
|--------------------------------------------------------------------------
| Application Routes
@ -32,6 +31,7 @@ Route::get('/', 'HomeController@showIndex');
Route::get('terms', 'HomeController@showTerms');
Route::get('log_error', 'HomeController@logError');
Route::get('invoice_now', 'HomeController@invoiceNow');
Route::get('keep_alive', 'HomeController@keepAlive');
Route::post('get_started', 'AccountController@getStarted');
// 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'));
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()) {
Route::post('/signup/register', 'AccountController@doRegister');
@ -96,7 +86,7 @@ Route::group(['middleware' => 'auth'], function() {
Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible');
Route::get('hide_message', 'HomeController@hideMessage');
Route::get('force_inline_pdf', 'UserController@forcePDFJS');
Route::get('api/users', array('as'=>'api.users', 'uses'=>'UserController@getDatatable'));
Route::resource('users', 'UserController');
Route::post('users/delete', 'UserController@delete');
@ -133,6 +123,11 @@ Route::group(['middleware' => 'auth'], function() {
Route::get('api/activities/{client_id?}', array('as'=>'api.activities', 'uses'=>'ActivityController@getDatatable'));
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('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_CREDIT', 'credit');
define('ENTITY_QUOTE', 'quote');
define('ENTITY_TASK', 'task');
define('PERSON_CONTACT', 'contact');
define('PERSON_USER', 'user');
@ -289,6 +285,7 @@ define('MAX_NUM_CLIENTS', 500);
define('MAX_NUM_CLIENTS_PRO', 20000);
define('MAX_NUM_USERS', 20);
define('MAX_SUBDOMAIN_LENGTH', 30);
define('DEFAULT_FONT_SIZE', 9);
define('INVOICE_STATUS_DRAFT', 1);
define('INVOICE_STATUS_SENT', 2);
@ -342,6 +339,7 @@ define('GATEWAY_BEANSTREAM', 29);
define('GATEWAY_PSIGATE', 30);
define('GATEWAY_MOOLAH', 31);
define('GATEWAY_BITPAY', 42);
define('GATEWAY_DWOLLA', 43);
define('EVENT_CREATE_CLIENT', 1);
define('EVENT_CREATE_INVOICE', 2);
@ -355,7 +353,7 @@ define('NINJA_GATEWAY_ID', GATEWAY_STRIPE);
define('NINJA_GATEWAY_CONFIG', '');
define('NINJA_WEB_URL', 'https://www.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_FROM_EMAIL', 'maildelivery@invoiceninja.com');
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_CREDIT_CARD', 'PAYMENT_TYPE_CREDIT_CARD');
define('PAYMENT_TYPE_BITCOIN', 'PAYMENT_TYPE_BITCOIN');
define('PAYMENT_TYPE_DWOLLA', 'PAYMENT_TYPE_DWOLLA');
define('PAYMENT_TYPE_TOKEN', 'PAYMENT_TYPE_TOKEN');
define('PAYMENT_TYPE_ANY', 'PAYMENT_TYPE_ANY');
@ -397,11 +396,6 @@ define('GATEWAY_GOOGLE', 33);
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 = [
1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'],
2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'],
@ -412,75 +406,6 @@ $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)
{
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
Event::listen('illuminate.query', function($query, $bindings, $time, $name)
@ -547,4 +457,4 @@ if (Auth::check() && Auth::user()->id === 1)
{
Auth::loginUsingId(1);
}
*/
*/

View File

@ -233,7 +233,7 @@ class Utils
public static function formatMoney($value, $currencyId = false)
{
if (!$currencyId) {
$currencyId = Session::get(SESSION_CURRENCY);
$currencyId = Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY);
}
foreach (Cache::get('currencies') as $currency) {
@ -333,7 +333,23 @@ class Utils
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
$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;
}
@ -404,6 +420,9 @@ class Utils
if (count($matches) == 0) {
continue;
}
usort($matches, function($a, $b) {
return strlen($b) - strlen($a);
});
foreach ($matches as $match) {
$offset = 0;
$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;
use Utils;
use Auth;
use Carbon;
use App\Events\UserLoggedIn;
use App\Ninja\Repositories\AccountRepository;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldBeQueued;
class HandleUserLoggedIn {
protected $accountRepo;
/**
* Create the event handler.
*
* @return void
*/
public function __construct()
public function __construct(AccountRepository $accountRepo)
{
//
$this->accountRepo = $accountRepo;
}
/**
@ -26,8 +29,13 @@ class HandleUserLoggedIn {
* @return void
*/
public function handle(UserLoggedIn $event)
{
{
$account = Auth::user()->account;
if (!Utils::isNinja() && empty($account->last_login)) {
$this->accountRepo->registerUser(Auth::user());
}
$account->last_login = Carbon::now()->toDateTimeString();
$account->save();

View File

@ -160,12 +160,19 @@ class Account extends Eloquent
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;
$prefix = $isQuote ? $this->quote_number_prefix : $this->invoice_number_prefix;
$prefix .= $isQuote ? $this->quote_number_prefix : $this->invoice_number_prefix;
// 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 $prefix.str_pad($counter, 4, "0", STR_PAD_LEFT);
return $number;
}
public function incrementCounter($invoiceNumber, $isQuote = false, $isRecurring)
@ -212,6 +219,8 @@ class Account extends Eloquent
public function getInvoiceLabels()
{
$data = [];
$custom = (array) json_decode($this->invoice_labels);
$fields = [
'invoice',
'invoice_date',
@ -238,10 +247,21 @@ class Account extends Eloquent
'quote_number',
'total',
'invoice_issued_to',
'date',
'rate',
'hours',
];
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;

View File

@ -317,8 +317,21 @@ class Activity extends Eloquent
$invoice = $payment->invoice;
$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();
// 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->payment_id = $payment->id;
$activity->client_id = $invoice->client_id;
@ -393,7 +406,7 @@ class Activity extends Eloquent
public static function createCredit($credit)
{
$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->client_id = $credit->client_id;
$activity->activity_type_id = ACTIVITY_TYPE_CREATE_CREDIT;
@ -408,7 +421,7 @@ class Activity extends Eloquent
$activity->credit_id = $credit->id;
$activity->client_id = $credit->client_id;
$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->save();
} else {
@ -447,7 +460,7 @@ class Activity extends Eloquent
$activity->client_id = $credit->client_id;
$activity->credit_id = $credit->id;
$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->save();
}
@ -458,7 +471,7 @@ class Activity extends Eloquent
$activity->client_id = $credit->client_id;
$activity->credit_id = $credit->id;
$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->save();
}

View File

@ -148,6 +148,15 @@ class Client extends EntityModel
$token = $this->getGatewayToken();
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;
}
$query->whereAccountId($accountId);
$query->where($this->getTable() .'.account_id', '=', $accountId);
if ($publicId) {
if (is_array($publicId)) {

View File

@ -7,6 +7,33 @@ class Gateway extends Eloquent
{
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()
{
return '/images/gateways/logo_'.$this->provider.'.png';
@ -24,6 +51,8 @@ class Gateway extends Eloquent
$link = 'https://www.2checkout.com/referral?r=2c37ac2298';
} elseif ($this->id == GATEWAY_BITPAY) {
$link = 'https://bitpay.com/dashboard/signup';
} elseif ($this->id == GATEWAY_DWOLLA) {
$link = 'https://www.dwolla.com/register';
}
$key = 'texts.gateway_help_'.$this->id;
@ -42,6 +71,8 @@ class Gateway extends Eloquent
return PAYMENT_TYPE_PAYPAL;
} else if ($gatewayId == GATEWAY_BITPAY) {
return PAYMENT_TYPE_BITCOIN;
} else if ($gatewayId == GATEWAY_DWOLLA) {
return PAYMENT_TYPE_DWOLLA;
} else {
return PAYMENT_TYPE_CREDIT_CARD;
}

View File

@ -31,9 +31,12 @@ class Invitation extends EntityModel
{
$this->load('account');
$url = SITE_URL;
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}";

View File

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

View File

@ -34,7 +34,7 @@ class Payment extends EntityModel
public function getAmount()
{
return Utils::formatMoney($this->amount, $this->client->currency_id);
return Utils::formatMoney($this->amount, $this->client->getCurrencyId());
}
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 Event;
use URL;
use App\Models\Invoice;
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()]);
$accountName = $invoice->account->getDisplayName();
$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) {
if (!$invitation->user || !$invitation->user->email) {
if (!$invitation->user || !$invitation->user->email || $invitation->user->trashed()) {
return false;
}
if (!$invitation->contact || !$invitation->contact->email) {
if (!$invitation->contact || !$invitation->contact->email || $invitation->contact->trashed()) {
return false;
}
@ -41,6 +42,18 @@ class ContactMailer extends Mailer
'$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['link'] = $invitation->getLink();
$data['entityType'] = $entityType;
@ -72,7 +85,7 @@ class ContactMailer extends Mailer
'$footer' => $payment->account->getEmailFooter(),
'$client' => $payment->client->getDisplayName(),
'$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)];

View File

@ -39,7 +39,7 @@ class UserMailer extends Mailer
return;
}
$entityType = $invoice->getEntityType();
$entityType = $notificationType == 'approved' ? ENTITY_QUOTE : ENTITY_INVOICE;
$view = "{$entityType}_{$notificationType}";
$data = [
@ -47,13 +47,13 @@ class UserMailer extends Mailer
'clientName' => $invoice->client->getDisplayName(),
'accountName' => $invoice->account->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,
'invoiceLink' => SITE_URL."/{$entityType}s/{$invoice->public_id}",
];
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()]);

View File

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

View File

@ -4,6 +4,7 @@ use App\Models\Invoice;
use App\Models\InvoiceItem;
use App\Models\Invitation;
use App\Models\Product;
use App\Models\Task;
use Utils;
class InvoiceRepository
@ -51,10 +52,10 @@ class InvoiceRepository
->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('invoices.account_id', '=', $accountId)
->where('invoices.is_quote', '=', false)
->where('clients.deleted_at', '=', null)
->where('contacts.deleted_at', '=', null)
->where('invoices.is_recurring', '=', 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');
if ($clientPublicId) {
@ -158,7 +159,7 @@ class InvoiceRepository
}
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>';
}
@ -271,7 +272,8 @@ class InvoiceRepository
$invoice->invoice_number = trim($data['invoice_number']);
$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->has_tasks = isset($data['has_tasks']) ? $data['has_tasks'] : false;
if (!$publicId) {
$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->invoice_footer = trim($data['invoice_footer']) ? trim($data['invoice_footer']) : (!$publicId && $account->invoice_footer ? $account->invoice_footer : '');
$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->invoice_design_id = $data['invoice_design_id'];
@ -374,7 +382,12 @@ class InvoiceRepository
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']));
if (!$product) {
@ -423,7 +436,7 @@ class InvoiceRepository
&& $account->share_counter) {
$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));
}
$clone->invoice_number = $account->invoice_number_prefix.$invoiceNumber;
@ -453,7 +466,8 @@ class InvoiceRepository
'custom_value1',
'custom_value2',
'custom_taxes1',
'custom_taxes2', ] as $field) {
'custom_taxes2',
'partial'] as $field) {
$clone->$field = $invoice->$field;
}

View File

@ -15,11 +15,13 @@ class PaymentRepository
->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
->join('contacts', 'contacts.client_id', '=', 'clients.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('clients.deleted_at', '=', null)
->where('contacts.is_primary', '=', true)
->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')) {
$query->where('payments.deleted_at', '=', null)
@ -78,6 +80,11 @@ class PaymentRepository
$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);
if ($validator->fails()) {
@ -95,8 +102,12 @@ class PaymentRepository
$payment = Payment::createNew();
}
$paymentTypeId = $input['payment_type_id'] ? $input['payment_type_id'] : null;
$payment->payment_type_id = $paymentTypeId;
$paymentTypeId = false;
if (isset($input['payment_type_id'])) {
$paymentTypeId = $input['payment_type_id'] ? $input['payment_type_id'] : null;
$payment->payment_type_id = $paymentTypeId;
}
$payment->payment_date = Utils::toSqlDate($input['payment_date']);
$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;
use Session;
use Auth;
use Utils;
use HTML;
use URL;
use Request;
use Validator;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider {
@ -11,7 +18,105 @@ class AppServiceProvider extends ServiceProvider {
*/
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",
"d3": "~3.4.11",
"handsontable": "*",
"pdfmake": "*"
"pdfmake": "*",
"moment": "*"
},
"resolutions": {
"jquery": "~1.11"

View File

@ -18,7 +18,7 @@
"patricktalmadge/bootstrapper": "5.5.x",
"anahkiasen/former": "4.0.*@dev",
"barryvdh/laravel-debugbar": "~2.0.2",
"chumper/datatable": "dev-develop",
"chumper/datatable": "dev-develop#7fa47cb",
"omnipay/omnipay": "2.3.x",
"intervention/image": "dev-master",
"webpatser/laravel-countries": "dev-master",
@ -33,8 +33,11 @@
"coatesap/omnipay-realex": "~2.0",
"fruitcakestudio/omnipay-sisow": "~2.0",
"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": {
"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
use App\Libraries\Utils;
return [
/*
@ -39,7 +41,7 @@ return [
|
*/
'timezone' => 'UTC',
'timezone' => env('APP_TIMEZONE', 'UTC'),
/*
|--------------------------------------------------------------------------
@ -125,8 +127,8 @@ return [
'Illuminate\Filesystem\FilesystemServiceProvider',
'Illuminate\Foundation\Providers\FoundationServiceProvider',
'Illuminate\Hashing\HashServiceProvider',
'Illuminate\Mail\MailServiceProvider',
'Illuminate\Pagination\PaginationServiceProvider',
(isset($_ENV['POSTMARK_API_TOKEN']) ? 'Postmark\Adapters\LaravelMailProvider' : 'Illuminate\Mail\MailServiceProvider'),
'Illuminate\Pagination\PaginationServiceProvider',
'Illuminate\Pipeline\PipelineServiceProvider',
'Illuminate\Queue\QueueServiceProvider',
'Illuminate\Redis\RedisServiceProvider',
@ -187,7 +189,7 @@ return [
'File' => 'Illuminate\Support\Facades\File',
//'Form' => 'Illuminate\Support\Facades\Form',
'Hash' => 'Illuminate\Support\Facades\Hash',
'HTML' => 'Illuminate\Support\Facades\HTML',
//'HTML' => 'Illuminate\Support\Facades\HTML',
'Input' => 'Illuminate\Support\Facades\Input',
'Lang' => 'Illuminate\Support\Facades\Lang',
'Log' => 'Illuminate\Support\Facades\Log',
@ -244,8 +246,8 @@ return [
// Added Class Aliases
'Utils' => 'App\Libraries\Utils',
'Form' => 'Illuminate\Html\FormFacade',
'HTML' => 'Illuminate\Html\HtmlFacade',
'Form' => 'Collective\Html\FormFacade',
'HTML' => 'Collective\Html\HtmlFacade',
'SSH' => 'Illuminate\Support\Facades\SSH',
'Alert' => 'Bootstrapper\Facades\Alert',
'Badge' => 'Bootstrapper\Facades\Badge',
@ -255,7 +257,7 @@ return [
'ButtonToolbar' => 'Bootstrapper\Facades\ButtonToolbar',
'Carousel' => 'Bootstrapper\Facades\Carousel',
'DropdownButton' => 'Bootstrapper\Facades\DropdownButton',
'Form' => 'Bootstrapper\Facades\Form',
//'Form' => 'Bootstrapper\Facades\Form', //need to clarify this guy
'Helpers' => 'Bootstrapper\Facades\Helpers',
'Icon' => 'Bootstrapper\Facades\Icon',
//'Image' => 'Bootstrapper\Facades\Image',

View File

@ -1,66 +1,184 @@
<?php return array(
// Markup
////////////////////////////////////////////////////////////////////
// Markup
////////////////////////////////////////////////////////////////////
// Whether labels should be automatically computed from name
'automatic_label' => true,
// Whether labels should be automatically computed from name
'automatic_label' => true,
// The default form type
'default_form_type' => 'horizontal',
// The default form type
'default_form_type' => 'horizontal',
// The framework to be used by Former
'framework' => 'TwitterBootstrap3',
// Validation
////////////////////////////////////////////////////////////////////
// Validation
////////////////////////////////////////////////////////////////////
// Whether Former should fetch errors from Session
'fetch_errors' => true,
// 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 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,
// Whether Former should automatically fetch error messages and
// display them next to the matching fields
'error_messages' => true,
// Checkables
////////////////////////////////////////////////////////////////////
// Checkables
////////////////////////////////////////////////////////////////////
// Whether checkboxes should always be present in the POST data,
// no matter if you checked them or not
'push_checkboxes' => false,
// 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,
// The value a checkbox will have in the POST array if unchecked
'unchecked_value' => 0,
// Required fields
////////////////////////////////////////////////////////////////////
// Required fields
////////////////////////////////////////////////////////////////////
// The class to be added to required fields
'required_class' => 'required',
// The class to be added to required fields
'required_class' => 'required',
// A facultative text to append to the labels of required fields
'required_text' => '',
// A facultative text to append to the labels of required fields
'required_text' => '<sup>*</sup>',
// Translations
////////////////////////////////////////////////////////////////////
// Translations
////////////////////////////////////////////////////////////////////
// Where Former should look for translations
'translate_from' => 'texts',
// Where Former should look for translations
'translate_from' => 'texts',
// Whether text that comes out of the translated
// should be capitalized (ex: email => Email) automatically
'capitalize_translations' => true,
// Whether text that comes out of the translated
// should be capitalized (ex: email => Email) automatically
'capitalize_translations' => true,
// An array of attributes to automatically translate
'translatable' => array(
'help',
'inlineHelp',
'blockHelp',
'placeholder',
'data_placeholder',
'label',
),
// An array of attributes to automatically translate
'translatable' => array(
'help',
'inlineHelp',
'blockHelp',
'placeholder',
'data_placeholder',
'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' => [
'domain' => '',
'secret' => '',

View File

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

View File

@ -183,7 +183,7 @@ class ConfideSetupUsersTable extends Migration {
$t->string('username')->unique();
$t->string('email')->nullable();
$t->string('password');
$t->string('confirmation_code');
$t->string('confirmation_code')->nullable();
$t->boolean('registered')->default(false);
$t->boolean('confirmed')->default(false);
$t->integer('theme_id')->nullable();
@ -355,8 +355,8 @@ class ConfideSetupUsersTable extends Migration {
$t->softDeletes();
$t->string('transaction_reference')->nullable();
$t->timestamp('sent_date');
$t->timestamp('viewed_date');
$t->timestamp('sent_date')->nullable();
$t->timestamp('viewed_date')->nullable();
$t->foreign('user_id')->references('id')->on('users')->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()
{
Schema::drop('timesheet_events');
Schema::drop('timesheet_event_sources');
Schema::drop('timesheets');
Schema::drop('project_codes');
Schema::drop('projects');
Schema::dropIfExists('timesheet_events');
Schema::dropIfExists('timesheet_event_sources');
Schema::dropIfExists('timesheets');
Schema::dropIfExists('project_codes');
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' => 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' => 'PHP-Payments [Deprecated]']);

View File

@ -2,49 +2,133 @@
use App\Models\Gateway;
use App\Models\PaymentTerm;
use App\Models\Currency;
use App\Models\DateFormat;
use App\Models\DatetimeFormat;
class PaymentLibrariesSeeder extends Seeder
{
public function run()
{
Eloquent::unguard();
public function run()
{
Eloquent::unguard();
$this->createGateways();
$this->createPaymentTerms();
$this->createDateFormats();
$this->createDatetimeFormats();
}
$gateways = [
['name' => 'BeanStream', 'provider' => 'BeanStream', 'payment_library_id' => 2],
['name' => 'Psigate', 'provider' => 'Psigate', 'payment_library_id' => 2],
['name' => 'moolah', 'provider' => 'AuthorizeNet_AIM', 'sort_order' => 1, 'recommended' => 1, 'site_url' => 'https://invoiceninja.mymoolah.com/', 'payment_library_id' => 1],
['name' => 'Alipay', 'provider' => 'Alipay_Express', 'payment_library_id' => 1],
['name' => 'Buckaroo', 'provider' => 'Buckaroo_CreditCard', 'payment_library_id' => 1],
['name' => 'Coinbase', 'provider' => 'Coinbase', 'payment_library_id' => 1],
['name' => 'DataCash', 'provider' => 'DataCash', 'payment_library_id' => 1],
['name' => 'Neteller', 'provider' => 'Neteller', 'payment_library_id' => 1],
['name' => 'Pacnet', 'provider' => 'Pacnet', 'payment_library_id' => 1],
['name' => 'PaymentSense', 'provider' => 'PaymentSense', 'payment_library_id' => 1],
['name' => 'Realex', 'provider' => 'Realex_Remote', 'payment_library_id' => 1],
['name' => 'Sisow', 'provider' => 'Sisow', 'payment_library_id' => 1],
['name' => 'Skrill', 'provider' => 'Skrill', 'payment_library_id' => 1],
private function createGateways() {
$gateways = [
['name' => 'BeanStream', 'provider' => 'BeanStream', 'payment_library_id' => 2],
['name' => 'Psigate', 'provider' => 'Psigate', 'payment_library_id' => 2],
['name' => 'moolah', 'provider' => 'AuthorizeNet_AIM', 'sort_order' => 1, 'recommended' => 1, 'site_url' => 'https://invoiceninja.mymoolah.com/', 'payment_library_id' => 1],
['name' => 'Alipay', 'provider' => 'Alipay_Express', 'payment_library_id' => 1],
['name' => 'Buckaroo', 'provider' => 'Buckaroo_CreditCard', 'payment_library_id' => 1],
['name' => 'Coinbase', 'provider' => 'Coinbase', 'payment_library_id' => 1],
['name' => 'DataCash', 'provider' => 'DataCash', 'payment_library_id' => 1],
['name' => 'Neteller', 'provider' => 'Neteller', 'payment_library_id' => 1],
['name' => 'Pacnet', 'provider' => 'Pacnet', 'payment_library_id' => 1],
['name' => 'PaymentSense', 'provider' => 'PaymentSense', 'payment_library_id' => 1],
['name' => 'Realex', 'provider' => 'Realex_Remote', 'payment_library_id' => 1],
['name' => 'Sisow', 'provider' => 'Sisow', 'payment_library_id' => 1],
['name' => 'Skrill', 'provider' => 'Skrill', 'payment_library_id' => 1],
['name' => 'BitPay', 'provider' => 'BitPay', 'payment_library_id' => 1],
];
foreach ($gateways as $gateway)
{
if (!DB::table('gateways')->where('name', '=', $gateway['name'])->get())
{
Gateway::create($gateway);
}
}
$paymentTerms = [
['num_days' => -1, 'name' => 'Net 0']
['name' => 'Dwolla', 'provider' => 'Dwolla', 'payment_library_id' => 1],
];
foreach ($paymentTerms as $paymentTerm)
{
if (!DB::table('payment_terms')->where('name', '=', $paymentTerm['name'])->get())
{
foreach ($gateways as $gateway) {
if (!DB::table('gateways')->where('name', '=', $gateway['name'])->get()) {
Gateway::create($gateway);
}
}
}
private function createPaymentTerms() {
$paymentTerms = [
['num_days' => -1, 'name' => 'Net 0'],
];
foreach ($paymentTerms as $paymentTerm) {
if (!DB::table('payment_terms')->where('name', '=', $paymentTerm['name'])->get()) {
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;
color:#fff;
}
/*
table.dataTable tr:hover {
background-color: #F2F5FE !important;
}
*/
th:first-child {
border-radius: 3px 0 0 0;
border-left: none;
@ -2467,7 +2472,7 @@ border-bottom: 1px solid #dfe0e1;
table.dataTable.no-footer {
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 {
background-color: #FDFDFD;
}
@ -2617,7 +2622,11 @@ margin-left: 0px;
.btn-primary i{
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 {
margin-left: 10px !important;
@ -2863,7 +2872,7 @@ background-clip: padding-box;
.dashboard .panel-body {padding: 0;}
.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;
}
.dashboard th {
@ -3204,6 +3213,12 @@ div.checkbox > label {
border-radius: 3px;
}
.container input:focus,
.container textarea:focus,
.container select:focus {
background: #fdfdfd !important;
}
.container input[placeholder],
.container textarea[placeholder],
.container select[placeholder] {
@ -3226,11 +3241,9 @@ div.checkbox > label {
background-color: #0b4d78 !important;
}
/*
.panel-default {
border-color: #e37329 !important;
div.alert {
z-index: 1;
}
*/
.alert-hide {
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;
color:#fff;
}
/*
table.dataTable tr:hover {
background-color: #F2F5FE !important;
}
*/
th:first-child {
border-radius: 3px 0 0 0;
border-left: none;
@ -83,7 +88,7 @@ border-bottom: 1px solid #dfe0e1;
table.dataTable.no-footer {
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 {
background-color: #FDFDFD;
}
@ -233,7 +238,11 @@ margin-left: 0px;
.btn-primary i{
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 {
margin-left: 10px !important;
@ -479,7 +488,7 @@ background-clip: padding-box;
.dashboard .panel-body {padding: 0;}
.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;
}
.dashboard th {
@ -820,6 +829,12 @@ div.checkbox > label {
border-radius: 3px;
}
.container input:focus,
.container textarea:focus,
.container select:focus {
background: #fdfdfd !important;
}
.container input[placeholder],
.container textarea[placeholder],
.container select[placeholder] {
@ -842,11 +857,9 @@ div.checkbox > label {
background-color: #0b4d78 !important;
}
/*
.panel-default {
border-color: #e37329 !important;
div.alert {
z-index: 1;
}
*/
.alert-hide {
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,47 +1,115 @@
var NINJA = NINJA || {};
function GetPdfMake(invoice, javascript, callback) {
var account = invoice.account;
eval(javascript);
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;
},
/*
var fonts = {
Roboto: {
normal: 'Roboto-Regular.ttf',
bold: 'Roboto-Medium.ttf',
italics: 'Roboto-Italic.ttf',
bolditalics: 'Roboto-Italic.ttf'
},
};
*/
};
doc = pdfMake.createPdf(dd);
doc.save = function(fileName) {
this.download(fileName);
};
return doc;
eval(javascript);
dd = $.extend(true, baseDD, dd);
/*
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: {
normal: 'Roboto-Regular.ttf',
bold: 'Roboto-Medium.ttf',
italics: 'Roboto-Italic.ttf',
bolditalics: 'Roboto-Italic.ttf'
},
};
*/
doc = pdfMake.createPdf(dd);
doc.save = function(fileName) {
this.download(fileName);
};
return doc;
}
function notesAndTerms(invoice)
NINJA.notesAndTerms = function(invoice)
{
var text = [];
if (invoice.public_notes) {
text.push({text:invoice.public_notes, style:'notes'});
text.push({text:processVariables(invoice.public_notes), style:'notes'});
}
if (invoice.terms) {
text.push({text:invoiceLabels.terms, style:'termsLabel'});
text.push({text:invoice.terms, style:'terms'});
text.push({text:processVariables(invoice.terms), style:'terms'});
}
return text;
}
function invoiceLines(invoice) {
var grid =
[[{text: invoiceLabels.item, style: 'tableHeader'},
{text: invoiceLabels.description, style: 'tableHeader'},
{text: invoiceLabels.unit_cost, style: 'tableHeader'},
{text: invoiceLabels.quantity, style: 'tableHeader'},
{text: invoice.has_taxes?invoiceLabels.tax:'', style: 'tableHeader'},
{text: invoiceLabels.line_total, style: 'tableHeader'}]];
NINJA.invoiceLines = function(invoice) {
var grid = [
[
{text: invoiceLabels.item, style: 'tableHeader'},
{text: invoiceLabels.description, style: 'tableHeader'},
{text: invoiceLabels.unit_cost, style: 'tableHeader'},
{text: invoiceLabels.quantity, style: 'tableHeader'},
{text: invoice.has_taxes?invoiceLabels.tax:'', style: 'tableHeader'},
{text: invoiceLabels.line_total, style: 'tableHeader'}
]
];
var total = 0;
var shownItem = false;
var currencyId = invoice && invoice.client ? invoice.client.currency_id : 1;
@ -61,50 +129,52 @@ function invoiceLines(invoice) {
tax = parseFloat(item.tax_rate);
}
// show at most one blank line
if (shownItem && (!cost || cost == '0.00') && !notes && !productKey) {
continue;
}
shownItem = true;
// show at most one blank line
if (shownItem && (!cost || cost == '0.00') && !notes && !productKey) {
continue;
}
shownItem = true;
// process date variables
if (invoice.is_recurring) {
notes = processVariables(notes);
productKey = processVariables(productKey);
// process date variables
if (invoice.is_recurring) {
notes = processVariables(notes);
productKey = processVariables(productKey);
}
var lineTotal = roundToTwo(NINJA.parseFloat(item.cost)) * roundToTwo(NINJA.parseFloat(item.qty));
if (tax) {
lineTotal += lineTotal * tax / 100;
}
if (lineTotal) {
total += lineTotal;
}
lineTotal = formatMoney(lineTotal, currencyId);
rowStyle = i%2===0?'odd':'even';
row[0] = {style:["productKey", rowStyle], text:productKey};
row[1] = {style:["notes", rowStyle], text:notes};
row[2] = {style:["cost", rowStyle], text:cost};
row[3] = {style:["quantity", rowStyle], text:qty};
row[4] = {style:["tax", rowStyle], text:""+tax};
row[5] = {style:["lineTotal", rowStyle], text:lineTotal};
grid.push(row);
}
return grid;
}
var lineTotal = roundToTwo(NINJA.parseFloat(item.cost)) * roundToTwo(NINJA.parseFloat(item.qty));
if (tax) {
lineTotal += lineTotal * tax / 100;
}
if (lineTotal) {
total += lineTotal;
}
lineTotal = formatMoney(lineTotal, currencyId);
rowStyle = i%2===0?'odd':'even';
row[0] = {style:["productKey", rowStyle], text:productKey};
row[1] = {style:["notes", rowStyle], text:notes};
row[2] = {style:["cost", rowStyle], text:cost};
row[3] = {style:["quantity", rowStyle], text:qty};
row[4] = {style:["tax", rowStyle], text:""+tax};
row[5] = {style:["lineTotal", rowStyle], text:lineTotal};
grid.push(row);
}
return grid;
}
function subtotals(invoice)
NINJA.subtotals = function(invoice)
{
if (!invoice) {
return;
}
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) {
data.push([invoiceLabels.discount, formatMoney(invoice.discount_amount, invoice.client.currency_id)]);
}
@ -137,7 +207,7 @@ function subtotals(invoice)
return data;
}
function accountDetails(account) {
NINJA.accountDetails = function(account) {
var data = [];
if(account.name) data.push({text:account.name, style:'accountName'});
if(account.id_number) data.push({text:account.id_number, style:'accountDetails'});
@ -147,7 +217,7 @@ function accountDetails(account) {
return data;
}
function accountAddress(account) {
NINJA.accountAddress = function(account) {
var address = '';
if (account.city || account.state || account.postal_code) {
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(address) data.push({text:address, 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;
}
function invoiceDetails(invoice) {
NINJA.invoiceDetails = function(invoice) {
var data = [
[
invoice.is_quote ? invoiceLabels.quote_number : invoiceLabels.invoice_number,
@ -179,11 +251,12 @@ function invoiceDetails(invoice) {
return data;
}
function clientDetails(invoice) {
NINJA.clientDetails = function(invoice) {
var client = invoice.client;
if (!client) {
return;
}
var fields = [
getClientDisplayName(client),
client.id_number,
@ -202,16 +275,23 @@ function clientDetails(invoice) {
if (!field) {
continue;
}
data.push(field);
}
data.push([field]);
}
if (!data.length) {
data.push(['']);
}
return data;
}
function primaryColor( defaultColor) {
return NINJA.primaryColor?NINJA.primaryColor:defaultColor;
NINJA.getPrimaryColor = function(defaultColor) {
return NINJA.primaryColor ? NINJA.primaryColor : defaultColor;
}
function secondaryColor( defaultColor) {
return NINJA.primaryColor?NINJA.secondaryColor:defaultColor;
NINJA.getSecondaryColor = function(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);
var top = doc.internal.pageSize.height - layout.marginLeft;
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;
doc.text(layout.marginLeft, top - (numLines * 8), footer);
}
@ -884,13 +884,13 @@ function displayNotesAndTerms(doc, layout, invoice, y)
var origY = y;
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);
y += 16 + (notes.length * doc.internal.getFontSize());
}
if (invoice.terms) {
var terms = doc.splitTextToSize(invoice.terms, 260);
var terms = doc.splitTextToSize(processVariables(invoice.terms), 260);
doc.setFontType("bold");
doc.text(layout.marginLeft, y, invoiceLabels.terms);
y += 16;
@ -1539,4 +1539,31 @@ function roundToTwo(num, toString) {
function truncate(str, length) {
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,192 +1,162 @@
//pdfmake
/*
var dd = {
content: 'wqy中文wqy',
defaultStyle: {
font: 'wqy'
}
};
*/
var dd = {
content: [
content: [
{
columns: [
columns: [
[
invoice.image?
{
image: invoice.image,
fit: [150, 80]
}:""
invoice.image?
{
image: invoice.image,
fit: [150, 80]
}:""
],
{
stack: accountDetails(account)
stack: NINJA.accountDetails(account)
},
{
stack: accountAddress(account)
stack: NINJA.accountAddress(account)
}
]
]
},
{
text:(invoice.is_quote ? invoiceLabels.quote : invoiceLabels.invoice).toUpperCase(),
margin: [8, 70, 8, 16],
style: 'primaryColor',
fontSize: 11
text:(NINJA.getEntityLabel(invoice)).toUpperCase(),
margin: [8, 70, 8, 16],
style: 'primaryColor',
fontSize: NINJA.fontSize + 2
},
{
style: 'tableExample',
table: {
headerRows: 1,
widths: ['auto', 'auto', '*'],
body: [[
{
table: {
body: invoiceDetails(invoice),
},
layout: 'noBorders',
},
clientDetails(invoice),
''
]]
},
layout: {
hLineWidth: function (i, node) {
return (i === 0 || i === node.table.body.length) ? .5 : 0;
table: {
headerRows: 1,
widths: ['auto', 'auto', '*'],
body: [
[
{
table: {
body: NINJA.invoiceDetails(invoice),
},
layout: 'noBorders',
},
{
table: {
body: NINJA.clientDetails(invoice),
},
layout: 'noBorders',
},
''
]
]
},
vLineWidth: function (i, node) {
return 0;//(i === 0 || i === node.table.widths.length) ? 2 : 1;
},
hLineColor: function (i, node) {
return '#D8D8D8';//(i === 0 || i === node.table.body.length) ? 'black' : 'gray';
},
/*vLineColor: function (i, node) {
return (i === 0 || i === node.table.widths.length) ? 'black' : 'gray';
},*/
paddingLeft: function(i, node) { return 8; },
paddingRight: function(i, node) { return 8; },
paddingTop: function(i, node) { return 4; },
paddingBottom: function(i, node) { return 4; }
}
},
'\n',
{
table: {
headerRows: 1,
widths: ['15%', '*', 'auto', 'auto', 'auto', 'auto'],
body:invoiceLines(invoice),
},
layout: {
hLineWidth: function (i, node) {
return i === 0 ? 0 : .5;
},
vLineWidth: function (i, node) {
return 0;
},
hLineColor: function (i, node) {
return '#D8D8D8';
},
paddingLeft: function(i, node) { return 8; },
paddingRight: function(i, node) { return 8; },
paddingTop: function(i, node) { return 8; },
paddingBottom: function(i, node) { return 8; }
},
},
'\n',
{
columns: [
notesAndTerms(invoice),
{
style: 'subtotals',
table: {
widths: ['*', '*'],
body: subtotals(invoice),
},
layout: {
layout: {
hLineWidth: function (i, node) {
return 0;
return (i === 0 || i === node.table.body.length) ? .5 : 0;
},
vLineWidth: function (i, node) {
return 0;
return 0;
},
hLineColor: function (i, node) {
return '#D8D8D8';
},
paddingLeft: function(i, node) { return 8; },
paddingRight: function(i, node) { return 8; },
paddingTop: function(i, node) { return 4; },
paddingBottom: function(i, node) { return 4; }
},
paddingBottom: function(i, node) { return 4; }
}
]
},
],
'\n',
{
table: {
headerRows: 1,
widths: ['15%', '*', 'auto', 'auto', 'auto', 'auto'],
body: NINJA.invoiceLines(invoice),
},
layout: {
hLineWidth: function (i, node) {
return i === 0 ? 0 : .5;
},
vLineWidth: function (i, node) {
return 0;
},
hLineColor: function (i, node) {
return '#D8D8D8';
},
paddingLeft: function(i, node) { return 8; },
paddingRight: function(i, node) { return 8; },
paddingTop: function(i, node) { return 8; },
paddingBottom: function(i, node) { return 8; }
},
},
'\n',
{
columns: [
NINJA.notesAndTerms(invoice),
{
style: 'subtotals',
table: {
widths: ['*', '*'],
body: NINJA.subtotals(invoice),
},
layout: {
hLineWidth: function (i, node) {
return 0;
},
vLineWidth: function (i, node) {
return 0;
},
paddingLeft: function(i, node) { return 8; },
paddingRight: function(i, node) { return 8; },
paddingTop: function(i, node) { return 4; },
paddingBottom: function(i, node) { return 4; }
},
}
]
},
],
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: {
//font: 'Roboto',
fontSize: 9,
margin: [8, 4, 8, 4]
},
styles: {
primaryColor:{
color: primaryColor('#299CC2')
defaultStyle: {
//font: 'arialuni',
fontSize: NINJA.fontSize,
margin: [8, 4, 8, 4]
},
accountName: {
margin: [4, 2, 4, 2],
color:primaryColor('#299CC2')
styles: {
primaryColor:{
color: NINJA.getPrimaryColor('#299CC2')
},
accountName: {
margin: [4, 2, 4, 2],
color: NINJA.getPrimaryColor('#299CC2')
},
accountDetails: {
margin: [4, 2, 4, 2],
color: '#AAA9A9'
},
even: {
},
odd: {
fillColor:'#F4F4F4'
},
productKey: {
color: NINJA.getPrimaryColor('#299CC2')
},
tableHeader: {
bold: true
},
balanceDueLabel: {
fontSize: NINJA.fontSize + 2
},
balanceDueValue: {
fontSize: NINJA.fontSize + 2,
color: NINJA.getPrimaryColor('#299CC2')
},
},
accountDetails: {
margin: [4, 2, 4, 2],
color: '#AAA9A9'
},
bold: {
bold: true
},
even: {
},
odd: {
fillColor:'#F4F4F4'
},
productKey: {
color:primaryColor('#299CC2')
},
cost: {
alignment: 'right'
},
quantity: {
alignment: 'right'
},
tax: {
alignment: 'right'
},
lineTotal: {
alignment: 'right'
},
right: {
alignment: 'right'
},
subtotals: {
alignment: 'right'
},
tableHeader: {
bold: true
},
balanceDueLabel: {
fontSize: 11
},
balanceDueValue: {
fontSize: 11,
color:primaryColor('#299CC2')
},
notes: {
},
terms: {
},
termsLabel: {
bold: true,
fontSize: 10,
margin: [0, 10, 0, 4]
}
},
pageMargins: [40, 40, 40, 40]
pageMargins: [40, 40, 40, 40],
};

View File

@ -1,6 +1,5 @@
# Invoice Ninja
### [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.
@ -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).
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.
@ -20,8 +19,9 @@ Developed by [@hillelcoren](https://twitter.com/hillelcoren) | Designed by [kant
* Live PDF generation
* Integrates with 30+ payment providers
* Recurring invoices
* Tax rates and payment terms
* Tasks with time-tracking
* Multi-user support
* Tax rates and payment terms
* Partial payments
* Custom email templates
* [Zapier](https://zapier.com/) integration

View File

@ -626,5 +626,56 @@ return array(
'zapier' => 'Zapier <sup>Beta</sup>',
'recurring' => 'Recurring',
'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,7 +76,8 @@ return array(
"positive" => "The :attribute must be greater than zero.",
"has_credit" => "The client does not have enough credit.",
"notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines

View File

@ -618,5 +618,55 @@ return array(
'recurring' => 'Wiederkehrend',
'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,7 +74,8 @@ return array(
"positive" => ":attribute muss größer als null sein.",
"has_credit" => "Der Kunde hat nicht genug Guthaben.",
"notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines

View File

@ -135,8 +135,8 @@ return array(
'filter' => 'Filter',
'new_client' => 'New Client',
'new_invoice' => 'New Invoice',
'new_payment' => 'New Payment',
'new_credit' => 'New Credit',
'new_payment' => 'Enter Payment',
'new_credit' => 'Enter Credit',
'contact' => 'Contact',
'date_created' => 'Date Created',
'last_login' => 'Last Login',
@ -443,7 +443,7 @@ return array(
'share_invoice_counter' => 'Share invoice counter',
'invoice_issued_to' => 'Invoice issued to',
'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_2' => ':link to sign up for Authorize.net.',
@ -626,5 +626,54 @@ return array(
'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

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

View File

@ -597,6 +597,55 @@ return array(
'recurring' => 'Recurring',
'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.",
"has_credit" => "el cliente no tiene crédito suficiente.",
"notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
/*
|--------------------------------------------------------------------------

View File

@ -626,5 +626,55 @@ return array(
'recurring' => 'Recurring',
'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.",
"has_credit" => "el cliente no tiene crédito suficiente.",
"notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
/*
|--------------------------------------------------------------------------

View File

@ -618,5 +618,55 @@ return array(
'recurring' => 'Recurring',
'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,7 +74,8 @@ return array(
"positive" => "The :attribute must be greater than zero.",
"has_credit" => "The client does not have enough credit.",
"notmasked" => "The values are masked",
"less_than" => 'The :attribute must be less than :value',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines

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