Merging changes

This commit is contained in:
Hillel Coren 2016-02-22 15:20:38 +02:00
commit f26ab6fe5b
65 changed files with 724 additions and 503 deletions

View File

@ -8,8 +8,8 @@ DB_TYPE=mysql
DB_STRICT=false
DB_HOST=localhost
DB_DATABASE=ninja
DB_USERNAME
DB_PASSWORD
DB_USERNAME=ninja
DB_PASSWORD=ninja
MAIL_DRIVER=smtp
MAIL_PORT=587
@ -23,6 +23,7 @@ MAIL_PASSWORD
PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'
LOG=single
REQUIRE_HTTPS=false
API_SECRET=password
GOOGLE_CLIENT_ID
GOOGLE_CLIENT_SECRET

View File

@ -1,12 +1,12 @@
language: php
sudo: false
sudo: true
php:
- 5.5
- 5.6
- 7.0
- hhvm
# - 5.6
# - 7.0
# - hhvm
addons:
hosts:
@ -30,12 +30,22 @@ before_install:
install:
# install Composer dependencies
- travis_retry composer update --prefer-dist;
- rm composer.lock
# these providers require referencing git commit's which cause Travis to fail
- sed -i '/mollie/d' composer.json
- sed -i '/2checkout/d' composer.json
- travis_retry composer install --prefer-dist;
before_script:
# prevent MySQL went away error
- mysql -u root -e 'SET @@GLOBAL.wait_timeout=28800;'
# copy configuration files
- cp .env.example .env
- cp tests/_bootstrap.php.default tests/_bootstrap.php
- cp tests/.env.circleci .env
- php artisan key:generate --no-interaction
- sed -i 's/APP_ENV=production/APP_ENV=development/g' .env
- sed -i 's/APP_DEBUG=false/APP_DEBUG=true/g' .env
- sed -i 's/REQUIRE_HTTPS=false/NINJA_DEV=true/g' .env
# create the database and user
- mysql -u root -e "create database IF NOT EXISTS ninja;"
- mysql -u root -e "GRANT ALL PRIVILEGES ON ninja.* To 'ninja'@'localhost' IDENTIFIED BY 'ninja'; FLUSH PRIVILEGES;"
@ -53,7 +63,29 @@ before_script:
- curl -L http://ninja.dev:8000/update
script:
- php ./vendor/codeception/codeception/codecept run --html --debug
- php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance APICest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance CheckBalanceCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance ClientCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance CreditCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceDesignCest.php
#- php ./vendor/codeception/codeception/codecept run acceptance OnlinePaymentCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance PaymentCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance TaskCest.php
#- php ./vendor/codeception/codeception/codecept run --debug acceptance TaxRatesCest.php
#- sed -i 's/NINJA_DEV=true/NINJA_PROD=true/g' .env
#- php ./vendor/codeception/codeception/codecept run acceptance GoProCest.php
after_script:
- cat .env
- mysql -u root -e 'select * from accounts;' ninja
- mysql -u root -e 'select * from account_gateways;' ninja
- mysql -u root -e 'select * from clients;' ninja
- mysql -u root -e 'select * from invoices;' ninja
- mysql -u root -e 'select * from invoice_items;' ninja
- cat storage/logs/laravel.log
notifications:
email:

View File

@ -0,0 +1,68 @@
<?php namespace app\Console\Commands;
use File;
use Illuminate\Console\Command;
class GenerateResources extends Command
{
protected $name = 'ninja:generate-resources';
protected $description = 'Generate Resouces';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the command.
*
* @return void
*/
public function fire()
{
$langs = [
'da',
'de',
'en',
'es',
'es_ES',
'fr',
'fr_CA',
'it',
'lt',
'nb_NO',
'nl',
'pt_BR',
'sv'
];
$texts = File::getRequire(base_path() . '/resources/lang/en/texts.php');
foreach ($texts as $key => $value) {
if (is_array($value)) {
echo $key;
} else {
echo "$key => $value\n";
}
}
}
protected function getArguments()
{
return array(
//array('example', InputArgument::REQUIRED, 'An example argument.'),
);
}
protected function getOptions()
{
return array(
//array('example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null),
);
}
}

View File

@ -18,6 +18,7 @@ class Kernel extends ConsoleKernel
'App\Console\Commands\SendRenewalInvoices',
'App\Console\Commands\SendReminders',
'App\Console\Commands\TestOFX',
'App\Console\Commands\GenerateResources',
];
/**

View File

@ -71,6 +71,8 @@ class AccountApiController extends BaseAPIController
'invoices' => ['invoice_items', 'user', 'client', 'payments'],
'products' => [],
'tax_rates' => [],
'expenses' => ['client', 'invoice', 'vendor'],
'payments' => ['invoice'],
];
foreach ($map as $key => $values) {

View File

@ -117,13 +117,7 @@ class AccountController extends BaseController
{
Session::put("show_trash:{$entityType}", $visible == 'true');
if ($entityType == 'user') {
return Redirect::to('settings/'.ACCOUNT_USER_MANAGEMENT);
} elseif ($entityType == 'token') {
return Redirect::to('settings/'.ACCOUNT_API_TOKENS);
} else {
return Redirect::to("{$entityType}s");
}
return RESULT_SUCCESS;
}
public function getSearchData()
@ -958,7 +952,13 @@ class AccountController extends BaseController
'text' => $reason,
];
$this->userMailer->sendTo(CONTACT_EMAIL, $email, $name, 'Invoice Ninja Feedback [Canceled Account]', 'contact', $data);
$subject = 'Invoice Ninja - Canceled Account';
if (Auth::user()->isPaidPro()) {
$subject .= ' [PRO]';
}
$this->userMailer->sendTo(CONTACT_EMAIL, $email, $name, $subject, 'contact', $data);
}
$user = Auth::user();

View File

@ -200,7 +200,7 @@ class AccountGatewayController extends BaseController
if ($gatewayId == GATEWAY_DWOLLA) {
$optional = array_merge($optional, ['key', 'secret']);
} elseif ($gatewayId == GATEWAY_STRIPE) {
if (Utils::isNinjaDev() && Input::get('23_apiKey') == env('TEST_API_KEY')) {
if (Utils::isNinjaDev()) {
// do nothing - we're unable to acceptance test with StripeJS
} else {
$rules['publishable_key'] = 'required';

View File

@ -136,11 +136,11 @@ class ClientApiController extends BaseAPIController
{
if ($request->action == ACTION_ARCHIVE) {
try {
$client = Client::scope($publicId)->withTrashed()->firstOrFail();
} catch (ModelNotFoundException $e) {
$client = Client::scope($publicId)->withTrashed()->first();
if(!$client)
return $this->errorResponse(['message'=>'Record not found'], 400);
}
$this->clientRepo->archive($client);
@ -154,7 +154,7 @@ class ClientApiController extends BaseAPIController
$client = Client::scope($publicId)->withTrashed()->first();
if(!$client)
return $this->errorResponse(['message'=>'Client not found.']);
return $this->errorResponse(['message'=>'Client not found.'], 400);
$this->clientRepo->restore($client);
@ -173,7 +173,7 @@ class ClientApiController extends BaseAPIController
->first();
if(!$client)
return $this->errorResponse(['message'=>'Client not found.']);
return $this->errorResponse(['message'=>'Client not found.'],400);
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($client, $transformer, ENTITY_CLIENT);

View File

@ -0,0 +1,64 @@
<?php namespace App\Http\Controllers;
// vendor
use App\Models\Expense;
use app\Ninja\Repositories\ExpenseRepository;
use App\Ninja\Transformers\ExpenseTransformer;
use App\Services\ExpenseService;
use Utils;
use Response;
use Input;
use Auth;
class ExpenseApiController extends BaseAPIController
{
// Expenses
protected $expenseRepo;
protected $expenseService;
public function __construct(ExpenseRepository $expenseRepo, ExpenseService $expenseService)
{
parent::__construct();
$this->expenseRepo = $expenseRepo;
$this->expenseService = $expenseService;
}
public function index()
{
$expenses = Expense::scope()
->withTrashed()
->orderBy('created_at','desc');
$expenses = $expenses->paginate();
$transformer = new ExpenseTransformer(Auth::user()->account, Input::get('serializer'));
$paginator = Expense::scope()->withTrashed()->paginate();
$data = $this->createCollection($expenses, $transformer, ENTITY_EXPENSE, $paginator);
return $this->response($data);
}
public function update()
{
//stub
}
public function store()
{
//stub
}
public function destroy()
{
//stub
}
}

View File

@ -1,6 +1,7 @@
<?php namespace App\Http\Controllers;
use Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Request;
use Utils;
use Response;
@ -19,18 +20,20 @@ use App\Http\Controllers\BaseAPIController;
use App\Ninja\Transformers\InvoiceTransformer;
use App\Http\Requests\CreateInvoiceRequest;
use App\Http\Requests\UpdateInvoiceRequest;
use App\Services\InvoiceService;
class InvoiceApiController extends BaseAPIController
{
protected $invoiceRepo;
public function __construct(InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, PaymentRepository $paymentRepo, Mailer $mailer)
public function __construct(InvoiceService $invoiceService, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, PaymentRepository $paymentRepo, Mailer $mailer)
{
parent::__construct();
$this->invoiceRepo = $invoiceRepo;
$this->clientRepo = $clientRepo;
$this->paymentRepo = $paymentRepo;
$this->invoiceService = $invoiceService;
$this->mailer = $mailer;
}
@ -84,6 +87,36 @@ class InvoiceApiController extends BaseAPIController
return $this->response($data);
}
/**
* @SWG\Get(
* path="/invoices/{invoice_id}",
* summary="Individual Invoice",
* tags={"invoice"},
* @SWG\Response(
* response=200,
* description="A single invoice",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Invoice"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function show($publicId)
{
$invoice = Invoice::scope($publicId)->withTrashed()->first();
if(!$invoice)
return $this->errorResponse(['message'=>'Invoice does not exist!'], 404);
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
return $this->response($data);
}
/**
* @SWG\Post(
@ -156,7 +189,7 @@ class InvoiceApiController extends BaseAPIController
$data = self::prepareData($data, $client);
$data['client_id'] = $client->id;
$invoice = $this->invoiceRepo->save($data);
$invoice = $this->invoiceService->save($data);
$payment = false;
// Optionally create payment with invoice
@ -168,14 +201,6 @@ class InvoiceApiController extends BaseAPIController
]);
}
if (!isset($data['id'])) {
$invitation = Invitation::createNew();
$invitation->invoice_id = $invoice->id;
$invitation->contact_id = $client->contacts[0]->id;
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
$invitation->save();
}
if (isset($data['email_invoice']) && $data['email_invoice']) {
if ($payment) {
$this->mailer->sendPaymentConfirmation($payment);
@ -249,7 +274,7 @@ class InvoiceApiController extends BaseAPIController
private function prepareItem($item)
{
// if only the product key is set we'll load the cost and notes
if ($item['product_key'] && empty($item['cost']) && empty($item['notes'])) {
if (!empty($item['product_key']) && empty($item['cost']) && empty($item['notes'])) {
$product = Product::findProductByKey($item['product_key']);
if ($product) {
if (empty($item['cost'])) {
@ -282,12 +307,17 @@ class InvoiceApiController extends BaseAPIController
$data = Input::all();
$error = null;
$invoice = Invoice::scope($data['id'])->firstOrFail();
$invoice = Invoice::scope($data['id'])->withTrashed()->first();
if(!$invoice)
return $this->errorResponse(['message'=>'Invoice does not exist.'], 400);
$this->mailer->sendInvoice($invoice, false, false);
$this->mailer->sendInvoice($invoice);
if($error) {
$response['error'] = "There was an error sending the invoice";
return $this->errorResponse(['message'=>'There was an error sending the invoice'], 400);
}
else {
$response = json_encode(RESULT_SUCCESS, JSON_PRETTY_PRINT);
@ -351,7 +381,7 @@ class InvoiceApiController extends BaseAPIController
$data = $request->input();
$data['public_id'] = $publicId;
$this->invoiceRepo->save($data);
$this->invoiceService->save($data);
$invoice = Invoice::scope($publicId)->with('client', 'invoice_items', 'invitations')->firstOrFail();
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));

View File

@ -58,8 +58,8 @@ class PaymentApiController extends BaseAPIController
$payments = $payments->orderBy('created_at', 'desc')->paginate();
$paginator = $paginator->paginate();
$transformer = new PaymentTransformer(Auth::user()->account, Input::get('serializer'));
$transformer = new PaymentTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createCollection($payments, $transformer, 'payments', $paginator);
return $this->response($data);
@ -98,11 +98,8 @@ class PaymentApiController extends BaseAPIController
$payment = Payment::scope($publicId)->withTrashed()->firstOrFail();
$this->paymentRepo->archive($payment);
$invoice = Invoice::scope($data['invoice_id'])->with('client')->with(['payments' => function($query) {
$query->withTrashed();
}])->first();
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
$transformer = new PaymentTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($payment, $transformer, 'invoice');
return $this->response($data);
}
@ -113,12 +110,17 @@ class PaymentApiController extends BaseAPIController
return $error;
}
/*
$invoice = Invoice::scope($data['invoice_id'])->with('client', 'invoice_items', 'invitations')->with(['payments' => function($query) {
$query->withTrashed();
}])->withTrashed()->first();
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
*/
$transformer = new PaymentTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($payment, $transformer, 'invoice');
return $this->response($data);
}
@ -175,13 +177,17 @@ class PaymentApiController extends BaseAPIController
$this->contactMailer->sendPaymentConfirmation($payment);
}
/*
$invoice = Invoice::scope($invoice->public_id)->with('client', 'invoice_items', 'invitations')->with(['payments' => function($query) {
$query->withTrashed();
}])->first();
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
*/
$transformer = new PaymentTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($payment, $transformer, 'invoice');
return $this->response($data);
}
/**
@ -214,12 +220,13 @@ class PaymentApiController extends BaseAPIController
$this->paymentRepo->delete($payment);
/*
$invoice = Invoice::scope($invoiceId)->with('client', 'invoice_items', 'invitations')->with(['payments' => function($query) {
$query->withTrashed();
}])->first();
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
*/
$transformer = new PaymentTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($payment, $transformer, 'invoice');
return $this->response($data);
}

View File

@ -162,6 +162,27 @@ class PublicClientController extends BaseController
return $paymentTypes;
}
public function download($invitationKey)
{
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
return response()->view('error', [
'error' => trans('texts.invoice_not_found'),
'hideHeader' => true,
]);
}
$invoice = $invitation->invoice;
$pdfString = $invoice->getPDFString();
header('Content-Type: application/pdf');
header('Content-Length: ' . strlen($pdfString));
header('Content-disposition: attachment; filename="' . $invoice->getFileName() . '"');
header('Cache-Control: public, must-revalidate, max-age=0');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
return $pdfString;
}
public function dashboard()
{
if (!$invitation = $this->getInvitation()) {

View File

@ -48,6 +48,7 @@ class VendorApiController extends BaseAPIController
{
$vendors = Vendor::scope()
->with($this->getIncluded())
->withTrashed()
->orderBy('created_at', 'desc')
->paginate();

View File

@ -1,6 +1,5 @@
<?php
/*
|--------------------------------------------------------------------------
| Application Routes
@ -37,6 +36,7 @@ Route::post('/get_started', 'AccountController@getStarted');
// Client visible pages
Route::get('view/{invitation_key}', 'PublicClientController@view');
Route::get('download/{invitation_key}', 'PublicClientController@download');
Route::get('view', 'HomeController@viewLogo');
Route::get('approve/{invitation_key}', 'QuoteController@approve');
Route::get('payment/{invitation_key}/{payment_type?}', 'PaymentController@show_payment');
@ -188,9 +188,6 @@ Route::group(['middleware' => 'auth'], function() {
Route::get('api/payments/{client_id?}', array('as'=>'api.payments', 'uses'=>'PaymentController@getDatatable'));
Route::post('payments/bulk', 'PaymentController@bulk');
Route::get('credits/{id}/edit', function() {
return View::make('header');
});
Route::resource('credits', 'CreditController');
Route::get('credits/create/{client_id?}/{invoice_id?}', 'CreditController@create');
Route::get('api/credits/{client_id?}', array('as'=>'api.credits', 'uses'=>'CreditController@getDatatable'));
@ -236,6 +233,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
Route::resource('products', 'ProductApiController');
Route::resource('tax_rates', 'TaxRateApiController');
Route::resource('users', 'UserApiController');
Route::resource('expenses','ExpenseApiController');
// Vendor
Route::resource('vendors', 'VendorApiController');
@ -245,6 +243,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
});
// Redirects for legacy links
/*
Route::get('/rocksteady', function() {
return Redirect::to(NINJA_WEB_URL, 301);
});
@ -272,6 +271,7 @@ Route::get('/compare-online-invoicing{sites?}', function() {
Route::get('/forgot_password', function() {
return Redirect::to(NINJA_APP_URL.'/forgot', 301);
});
*/
if (!defined('CONTACT_EMAIL')) {
define('CONTACT_EMAIL', Config::get('mail.from.address'));
@ -509,7 +509,7 @@ if (!defined('CONTACT_EMAIL')) {
define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG');
define('NINJA_WEB_URL', 'https://www.invoiceninja.com');
define('NINJA_APP_URL', 'https://app.invoiceninja.com');
define('NINJA_VERSION', '2.5.0.2');
define('NINJA_VERSION', '2.5.0.3');
define('NINJA_DATE', '2000-01-01');
define('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja');

View File

@ -62,17 +62,17 @@ class Utils
return true;
}
return isset($_ENV['NINJA_PROD']) && $_ENV['NINJA_PROD'] == 'true';
return env('NINJA_PROD') == 'true';
}
public static function isNinjaDev()
{
return isset($_ENV['NINJA_DEV']) && $_ENV['NINJA_DEV'] == 'true';
return env('NINJA_DEV') == 'true';
}
public static function requireHTTPS()
{
if (Request::root() === 'http://ninja.dev') {
if (Request::root() === 'http://ninja.dev:8000') {
return false;
}

View File

@ -164,6 +164,15 @@ class Account extends Eloquent
return $this->belongsTo('App\Models\TaxRate');
}
public function expenses()
{
return $this->hasMany('App\Models\Expense','account_id','id')->withTrashed();
}
public function payments()
{
return $this->hasMany('App\Models\Payment','account_id','id')->withTrashed();
}
public function setIndustryIdAttribute($value)
{

View File

@ -139,6 +139,10 @@ class Client extends EntityModel
return $this->hasMany('App\Models\Credit');
}
public function expenses()
{
return $this->hasMany('App\Models\Expense','client_id','id')->withTrashed();
}
public function addContact($data, $isPrimary = false)
{

View File

@ -12,7 +12,7 @@ class Expense extends EntityModel
use SoftDeletes;
use PresentableTrait;
protected $dates = ['deleted_at','expense_date'];
protected $dates = ['deleted_at'];
protected $presenter = 'App\Ninja\Presenters\ExpensePresenter';
protected $fillable = [
@ -76,19 +76,9 @@ class Expense extends EntityModel
return ENTITY_EXPENSE;
}
public function apply($amount)
public function isExchanged()
{
if ($amount > $this->balance) {
$applied = $this->balance;
$this->balance = 0;
} else {
$applied = $amount;
$this->balance = $this->balance - $amount;
}
$this->save();
return $applied;
return $this->invoice_currency_id != $this->expense_currency_id;
}
}

View File

@ -197,6 +197,11 @@ class Invoice extends EntityModel implements BalanceAffecting
return $this->hasMany('App\Models\Invitation')->orderBy('invitations.contact_id');
}
public function expenses()
{
return $this->hasMany('App\Models\Expense','invoice_id','id')->withTrashed();
}
public function markInvitationsSent($notify = false)
{
foreach ($this->invitations as $invitation) {
@ -728,42 +733,24 @@ class Invoice extends EntityModel implements BalanceAffecting
$invitation = $this->invitations[0];
$link = $invitation->getLink();
$key = env('PHANTOMJS_CLOUD_KEY');
$curl = curl_init();
$jsonEncodedData = json_encode([
'url' => "{$link}?phantomjs=true",
'renderType' => 'html',
'outputAsJson' => false,
'renderSettings' => [
'passThroughHeaders' => true,
],
// 'delayTime' => 1000,
]);
$opts = [
CURLOPT_URL => PHANTOMJS_CLOUD . env('PHANTOMJS_CLOUD_KEY') . '/',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $jsonEncodedData,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Content-Length: '.strlen($jsonEncodedData)
],
];
curl_setopt_array($curl, $opts);
$response = curl_exec($curl);
curl_close($curl);
$encodedString = strip_tags($response);
$pdfString = Utils::decodePDF($encodedString);
if ( ! $pdfString || strlen($pdfString) < 200) {
Utils::logError("PhantomJSCloud - failed to create pdf: {$encodedString}");
if (Utils::isNinjaDev()) {
$link = env('TEST_LINK');
}
return $pdfString;
$url = "http://api.phantomjscloud.com/api/browser/v2/{$key}/?request=%7Burl:%22{$link}?phantomjs=true%22,renderType:%22html%22%7D";
$pdfString = file_get_contents($url);
$pdfString = strip_tags($pdfString);
if ( ! $pdfString || strlen($pdfString) < 200) {
Utils::logError("PhantomJSCloud - failed to create pdf: {$pdfString}");
return false;
}
return Utils::decodePDF($pdfString);
}
}

View File

@ -107,6 +107,11 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return $this->account->isPro();
}
public function isPaidPro()
{
return $this->isPro() && ! $this->isTrial();
}
public function isTrial()
{
return $this->account->isTrial();

View File

@ -124,6 +124,11 @@ class Vendor extends EntityModel
return $this->belongsTo('App\Models\Industry');
}
public function expenses()
{
return $this->hasMany('App\Models\Expense','vendor_id','id');
}
public function addVendorContact($data, $isPrimary = false)
{
$publicId = isset($data['public_id']) ? $data['public_id'] : false;

View File

@ -41,6 +41,8 @@ class ContactMailer extends Mailer
$client = $invoice->client;
$account = $invoice->account;
$response = null;
if ($client->trashed()) {
return trans('texts.email_errors.inactive_client');
} elseif ($invoice->trashed()) {

View File

@ -64,6 +64,7 @@ class ExpenseRepository extends BaseRepository
->orWhere('contacts.is_primary', '=', null);
})
->select(
DB::raw('COALESCE(expenses.invoice_id, expenses.should_be_invoiced) expense_status_id'),
'expenses.account_id',
'expenses.amount',
'expenses.deleted_at',

View File

@ -418,8 +418,7 @@ class InvoiceRepository extends BaseRepository
$expense->save();
}
if ($item['product_key']) {
$productKey = trim($item['product_key']);
if ($productKey = trim($item['product_key'])) {
if (\Auth::user()->account->update_products && ! strtotime($productKey)) {
$product = Product::findProductByKey($productKey);
if (!$product) {

View File

@ -12,10 +12,14 @@ class AccountTransformer extends EntityTransformer
{
protected $defaultIncludes = [
'users',
// 'clients',
'invoices',
'products',
'taxRates'
'taxRates',
'payments'
];
protected $availableIncludes = [
'clients',
'invoices',
];
public function includeUsers(Account $account)
@ -48,6 +52,12 @@ class AccountTransformer extends EntityTransformer
return $this->includeCollection($account->tax_rates, $transformer, 'taxRates');
}
public function includePayments(Account $account)
{
$transformer = new PaymentTransformer($account, $this->serializer);
return $this->includeCollection($account->payments, $transformer, 'payments');
}
public function transform(Account $account)
{
return [

View File

@ -47,6 +47,7 @@ class ClientTransformer extends EntityTransformer
protected $availableIncludes = [
'invoices',
'credits',
'expenses',
];
public function includeContacts(Client $client)
@ -67,6 +68,13 @@ class ClientTransformer extends EntityTransformer
return $this->includeCollection($client->credits, $transformer, ENTITY_CREDIT);
}
public function includeExpenses(Client $client)
{
$transformer = new ExpenseTransformer($this->account, $this->serializer);
return $this->includeCollection($client->expenses, $transformer, ENTITY_EXPENSE);
}
public function transform(Client $client)
{
return [

View File

@ -0,0 +1,33 @@
<?php namespace App\Ninja\Transformers;
use App\Models\Account;
use App\Models\Expense;
use League\Fractal;
class ExpenseTransformer extends EntityTransformer
{
public function transform(Expense $expense)
{
return [
'id' => (int) $expense->public_id,
'private_notes' => $expense->private_notes,
'public_notes' => $expense->public_notes,
'should_be_invoiced' => (bool) $expense->should_be_invoiced,
'updated_at' => $this->getTimestamp($expense->updated_at),
'archived_at' => $this->getTimestamp($expense->deleted_at),
'transaction_id' => $expense->transaction_id,
'bank_id' => $expense->bank_id,
'expense_currency_id' => (int) $expense->expense_currency_id,
'account_key' => $this->account->account_key,
'amount' => (float) $expense->amount,
'expense_date' => $expense->expense_date,
'exchange_rate' => (float) $expense->exchange_rate,
'invoice_currency_id' => (int) $expense->invoice_currency_id,
'is_deleted' => (bool) $expense->is_deleted,
'client_id' => isset($expense->client->public_id) ? (int) $expense->client->public_id : null,
'invoice_id' => isset($expense->invoice->public_id) ? (int) $expense->invoice->public_id : null,
'vendor_id' => isset($expense->vendor->public_id) ? (int) $expense->vendor->public_id : null,
];
}
}

View File

@ -28,6 +28,7 @@ class InvoiceTransformer extends EntityTransformer
'invitations',
'payments',
'client',
'expenses',
];
public function includeInvoiceItems(Invoice $invoice)
@ -51,9 +52,16 @@ class InvoiceTransformer extends EntityTransformer
public function includeClient(Invoice $invoice)
{
$transformer = new ClientTransformer($this->account, $this->serializer);
return $this->includeItem($invoice->client, $transformer, 'client');
return $this->includeItem($invoice->client, $transformer, ENTITY_CLIENT);
}
public function includeExpenses(Invoice $invoice)
{
$transformer = new ExpenseTransformer($this->account, $this->serializer);
return $this->includeCollection($invoice->expenses, $transformer, ENTITY_EXPENSE);
}
public function transform(Invoice $invoice)
{
return [

View File

@ -57,6 +57,7 @@ class PaymentTransformer extends EntityTransformer
'archived_at' => $this->getTimestamp($payment->deleted_at),
'is_deleted' => (bool) $payment->is_deleted,
'payment_type_id' => (int) $payment->payment_type_id,
'invoice_id' => (int) $payment->invoice->public_id,
];
}
}

View File

@ -17,7 +17,6 @@ class VendorContactTransformer extends EntityTransformer
'archived_at' => $this->getTimestamp($contact->deleted_at),
'is_primary' => (bool) $contact->is_primary,
'phone' => $contact->phone,
'last_login' => $contact->last_login,
'account_key' => $this->account->account_key,
];
}

View File

@ -36,14 +36,15 @@ class VendorTransformer extends EntityTransformer
*/
protected $availableIncludes = [
'contacts',
'vendorContacts',
'invoices',
'expenses',
];
public function includeContacts(Vendor $vendor)
public function includeVendorContacts(Vendor $vendor)
{
$transformer = new ContactTransformer($this->account, $this->serializer);
return $this->includeCollection($vendor->contacts, $transformer, ENTITY_CONTACT);
$transformer = new VendorContactTransformer($this->account, $this->serializer);
return $this->includeCollection($vendor->vendorContacts, $transformer, ENTITY_CONTACT);
}
public function includeInvoices(Vendor $vendor)
@ -52,6 +53,12 @@ class VendorTransformer extends EntityTransformer
return $this->includeCollection($vendor->invoices, $transformer, ENTITY_INVOICE);
}
public function includeExpenses(Vendor $vendor)
{
$transformer = new ExpenseTransformer($this->account, $this->serializer);
return $this->includeCollection($vendor->expenses, $transformer, ENTITY_EXPENSE);
}
public function transform(Vendor $vendor)
{
return [

View File

@ -41,6 +41,7 @@ class DatatableService
private function createDropdown($entityType, $table, $actions)
{
$table->addColumn('dropdown', function ($model) use ($entityType, $actions) {
$hasAction = false;
$str = '<center style="min-width:100px">';
if (property_exists($model, 'is_deleted') && $model->is_deleted) {
@ -70,6 +71,7 @@ class DatatableService
if ($visible($model)) {
$str .= "<li><a href=\"{$url($model)}\">{$value}</a></li>";
$lastIsDivider = false;
$hasAction = true;
}
} elseif ( ! $lastIsDivider) {
$str .= "<li class=\"divider\"></li>";
@ -77,6 +79,10 @@ class DatatableService
}
}
if ( ! $hasAction) {
return '';
}
if ( ! $lastIsDivider) {
$str .= "<li class=\"divider\"></li>";
}

View File

@ -106,7 +106,7 @@ class ExpenseService extends BaseService
}
],
[
'invoice_id',
'expense_status_id',
function ($model) {
return self::getStatusLabel($model->invoice_id, $model->should_be_invoiced);
}

View File

@ -273,10 +273,13 @@ class PaymentService extends BaseService
// submit purchase/get response
$response = $gateway->purchase($details)->send();
$ref = $response->getTransactionReference();
// create payment record
if ($response->isSuccessful()) {
$ref = $response->getTransactionReference();
return $this->createPayment($invitation, $accountGateway, $ref);
} else {
return false;
}
}
public function getDatatable($clientPublicId, $search)

View File

@ -19,7 +19,7 @@ extensions:
modules:
config:
Db:
dsn: 'mysql:dbname=ninja;host=localhost;'
dsn: 'mysql:dbname=ninja;host=127.0.0.1;'
user: 'ninja'
password: 'ninja'
dump: tests/_data/dump.sql

View File

@ -36,7 +36,7 @@
"omnipay/bitpay": "dev-master",
"guzzlehttp/guzzle": "~6.0",
"laravelcollective/html": "~5.0",
"wildbit/laravel-postmark-provider": "dev-master",
"wildbit/laravel-postmark-provider": "2.0",
"Dwolla/omnipay-dwolla": "dev-master",
"laravel/socialite": "~2.0",
"simshaun/recurr": "dev-master",
@ -69,7 +69,7 @@
"require-dev": {
"phpunit/phpunit": "~4.0",
"phpspec/phpspec": "~2.1",
"codeception/codeception": "2.1.2",
"codeception/codeception": "*",
"codeception/c3": "~2.0",
"fzaninotto/faker": "^1.5"
},

274
composer.lock generated
View File

@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "6e219bb4f5ffaf8423177bd6fccc89f8",
"content-hash": "4778ab164bfb93c4af1bb51c4320ea4c",
"hash": "fceb9a043eac244cb01d8e8378e6d66a",
"content-hash": "f717dc8e67caa65002f0f0689d4a5478",
"packages": [
{
"name": "agmscode/omnipay-agms",
@ -435,12 +435,12 @@
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-ide-helper.git",
"reference": "aa8f772a46c35ecdcb7119f38772ac2ec952a941"
"reference": "4b8ba85b346fc9962521661aa639911535b2bb3f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/aa8f772a46c35ecdcb7119f38772ac2ec952a941",
"reference": "aa8f772a46c35ecdcb7119f38772ac2ec952a941",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/4b8ba85b346fc9962521661aa639911535b2bb3f",
"reference": "4b8ba85b346fc9962521661aa639911535b2bb3f",
"shasum": ""
},
"require": {
@ -490,7 +490,7 @@
"phpstorm",
"sublime"
],
"time": "2016-01-22 13:33:15"
"time": "2016-01-28 08:19:58"
},
{
"name": "cardgate/omnipay-cardgate",
@ -2555,12 +2555,12 @@
"source": {
"type": "git",
"url": "https://github.com/slampenny/Swaggervel.git",
"reference": "ea47fafde4984278e27a8044a1b1b0bcfd79130c"
"reference": "e026d72cacec8b2db8b2510179d73042f5e87bb9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/slampenny/Swaggervel/zipball/ea47fafde4984278e27a8044a1b1b0bcfd79130c",
"reference": "ea47fafde4984278e27a8044a1b1b0bcfd79130c",
"url": "https://api.github.com/repos/slampenny/Swaggervel/zipball/e026d72cacec8b2db8b2510179d73042f5e87bb9",
"reference": "e026d72cacec8b2db8b2510179d73042f5e87bb9",
"shasum": ""
},
"require": {
@ -2592,7 +2592,7 @@
"laravel",
"swagger"
],
"time": "2015-08-18 15:33:39"
"time": "2016-01-25 15:38:17"
},
{
"name": "jsanc623/phpbenchtime",
@ -2786,16 +2786,16 @@
},
{
"name": "laravel/framework",
"version": "v5.0.34",
"version": "v5.0.35",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "98dbaafe8e2781f86b1b858f8610be0d7318b153"
"reference": "37151cf533f468e2227605e4b9ac596154f6b92b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/98dbaafe8e2781f86b1b858f8610be0d7318b153",
"reference": "98dbaafe8e2781f86b1b858f8610be0d7318b153",
"url": "https://api.github.com/repos/laravel/framework/zipball/37151cf533f468e2227605e4b9ac596154f6b92b",
"reference": "37151cf533f468e2227605e4b9ac596154f6b92b",
"shasum": ""
},
"require": {
@ -2908,7 +2908,7 @@
"framework",
"laravel"
],
"time": "2015-12-04 23:20:49"
"time": "2016-02-02 14:55:52"
},
{
"name": "laravel/socialite",
@ -3284,12 +3284,12 @@
"source": {
"type": "git",
"url": "https://github.com/lokielse/omnipay-alipay.git",
"reference": "f39ce21a5cbfe5c7cd4108d264b398dbd42be05b"
"reference": "0b091a199f1ffce95582f5e11efcf4a8ffd90ec8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/lokielse/omnipay-alipay/zipball/f39ce21a5cbfe5c7cd4108d264b398dbd42be05b",
"reference": "f39ce21a5cbfe5c7cd4108d264b398dbd42be05b",
"url": "https://api.github.com/repos/lokielse/omnipay-alipay/zipball/0b091a199f1ffce95582f5e11efcf4a8ffd90ec8",
"reference": "0b091a199f1ffce95582f5e11efcf4a8ffd90ec8",
"shasum": ""
},
"require": {
@ -3325,7 +3325,7 @@
"payment",
"purchase"
],
"time": "2016-01-19 15:08:12"
"time": "2016-01-27 17:06:44"
},
{
"name": "maatwebsite/excel",
@ -3701,23 +3701,23 @@
},
{
"name": "mtdowling/cron-expression",
"version": "v1.0.4",
"version": "v1.1.0",
"source": {
"type": "git",
"url": "https://github.com/mtdowling/cron-expression.git",
"reference": "fd92e883195e5dfa77720b1868cf084b08be4412"
"reference": "c9ee7886f5a12902b225a1a12f36bb45f9ab89e5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mtdowling/cron-expression/zipball/fd92e883195e5dfa77720b1868cf084b08be4412",
"reference": "fd92e883195e5dfa77720b1868cf084b08be4412",
"url": "https://api.github.com/repos/mtdowling/cron-expression/zipball/c9ee7886f5a12902b225a1a12f36bb45f9ab89e5",
"reference": "c9ee7886f5a12902b225a1a12f36bb45f9ab89e5",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"phpunit/phpunit": "4.*"
"phpunit/phpunit": "~4.0|~5.0"
},
"type": "library",
"autoload": {
@ -3741,7 +3741,7 @@
"cron",
"schedule"
],
"time": "2015-01-11 23:07:46"
"time": "2016-01-26 21:23:30"
},
{
"name": "nesbot/carbon",
@ -5279,16 +5279,16 @@
},
{
"name": "omnipay/sagepay",
"version": "2.3.0",
"version": "2.3.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/omnipay-sagepay.git",
"reference": "4208d23b33b2f8a59176e44ad22d304c461ecb62"
"reference": "ca992b28a0d7ec7dbf218852dab36ec309dee07e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/omnipay-sagepay/zipball/4208d23b33b2f8a59176e44ad22d304c461ecb62",
"reference": "4208d23b33b2f8a59176e44ad22d304c461ecb62",
"url": "https://api.github.com/repos/thephpleague/omnipay-sagepay/zipball/ca992b28a0d7ec7dbf218852dab36ec309dee07e",
"reference": "ca992b28a0d7ec7dbf218852dab36ec309dee07e",
"shasum": ""
},
"require": {
@ -5334,7 +5334,7 @@
"sage pay",
"sagepay"
],
"time": "2016-01-12 12:43:31"
"time": "2016-02-07 13:25:23"
},
{
"name": "omnipay/securepay",
@ -5509,16 +5509,16 @@
},
{
"name": "omnipay/worldpay",
"version": "v2.1.1",
"version": "v2.2",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/omnipay-worldpay.git",
"reference": "5353f02b7f800b93d8aeae606d6df09afa538457"
"reference": "26ead4ca2c6ec45c9a3b3dad0be8880e0b1df083"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/omnipay-worldpay/zipball/5353f02b7f800b93d8aeae606d6df09afa538457",
"reference": "5353f02b7f800b93d8aeae606d6df09afa538457",
"url": "https://api.github.com/repos/thephpleague/omnipay-worldpay/zipball/26ead4ca2c6ec45c9a3b3dad0be8880e0b1df083",
"reference": "26ead4ca2c6ec45c9a3b3dad0be8880e0b1df083",
"shasum": ""
},
"require": {
@ -5562,20 +5562,20 @@
"payment",
"worldpay"
],
"time": "2014-09-17 00:37:18"
"time": "2016-01-28 12:55:58"
},
{
"name": "paragonie/random_compat",
"version": "1.1.5",
"version": "v1.2.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "dd8998b7c846f6909f4e7a5f67fabebfc412a4f7"
"reference": "b0e69d10852716b2ccbdff69c75c477637220790"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/dd8998b7c846f6909f4e7a5f67fabebfc412a4f7",
"reference": "dd8998b7c846f6909f4e7a5f67fabebfc412a4f7",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/b0e69d10852716b2ccbdff69c75c477637220790",
"reference": "b0e69d10852716b2ccbdff69c75c477637220790",
"shasum": ""
},
"require": {
@ -5610,7 +5610,7 @@
"pseudorandom",
"random"
],
"time": "2016-01-06 13:31:20"
"time": "2016-02-06 03:52:05"
},
{
"name": "patricktalmadge/bootstrapper",
@ -6158,23 +6158,27 @@
},
{
"name": "symfony/class-loader",
"version": "v3.0.1",
"version": "v3.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/class-loader.git",
"reference": "6294f616bb9888ba2e13c8bfdcc4d306c1c95da7"
"reference": "92e7cf1af2bc1695daabb4ac972db169606e9030"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/class-loader/zipball/6294f616bb9888ba2e13c8bfdcc4d306c1c95da7",
"reference": "6294f616bb9888ba2e13c8bfdcc4d306c1c95da7",
"url": "https://api.github.com/repos/symfony/class-loader/zipball/92e7cf1af2bc1695daabb4ac972db169606e9030",
"reference": "92e7cf1af2bc1695daabb4ac972db169606e9030",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
},
"require-dev": {
"symfony/finder": "~2.8|~3.0"
"symfony/finder": "~2.8|~3.0",
"symfony/polyfill-apcu": "~1.1"
},
"suggest": {
"symfony/polyfill-apcu": "For using ApcClassLoader on HHVM"
},
"type": "library",
"extra": {
@ -6206,7 +6210,7 @@
],
"description": "Symfony ClassLoader Component",
"homepage": "https://symfony.com",
"time": "2015-12-05 17:45:07"
"time": "2016-02-03 09:33:23"
},
{
"name": "symfony/console",
@ -6268,25 +6272,25 @@
},
{
"name": "symfony/css-selector",
"version": "v2.8.2",
"version": "v3.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
"reference": "ac06d8173bd80790536c0a4a634a7d705b91f54f"
"reference": "6605602690578496091ac20ec7a5cbd160d4dff4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/ac06d8173bd80790536c0a4a634a7d705b91f54f",
"reference": "ac06d8173bd80790536c0a4a634a7d705b91f54f",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/6605602690578496091ac20ec7a5cbd160d4dff4",
"reference": "6605602690578496091ac20ec7a5cbd160d4dff4",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
"php": ">=5.5.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.8-dev"
"dev-master": "3.0-dev"
}
},
"autoload": {
@ -6317,7 +6321,7 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
"time": "2016-01-03 15:33:41"
"time": "2016-01-27 05:14:46"
},
{
"name": "symfony/debug",
@ -6673,16 +6677,16 @@
},
{
"name": "symfony/polyfill-php56",
"version": "v1.0.1",
"version": "v1.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php56.git",
"reference": "e2e77609a9e2328eb370fbb0e0d8b2000ebb488f"
"reference": "4d891fff050101a53a4caabb03277284942d1ad9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/e2e77609a9e2328eb370fbb0e0d8b2000ebb488f",
"reference": "e2e77609a9e2328eb370fbb0e0d8b2000ebb488f",
"url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/4d891fff050101a53a4caabb03277284942d1ad9",
"reference": "4d891fff050101a53a4caabb03277284942d1ad9",
"shasum": ""
},
"require": {
@ -6692,7 +6696,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
"dev-master": "1.1-dev"
}
},
"autoload": {
@ -6725,20 +6729,20 @@
"portable",
"shim"
],
"time": "2015-12-18 15:10:25"
"time": "2016-01-20 09:13:37"
},
{
"name": "symfony/polyfill-util",
"version": "v1.0.1",
"version": "v1.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-util.git",
"reference": "4271c55cbc0a77b2641f861b978123e46b3da969"
"reference": "8de62801aa12bc4dfcf85eef5d21981ae7bb3cc4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-util/zipball/4271c55cbc0a77b2641f861b978123e46b3da969",
"reference": "4271c55cbc0a77b2641f861b978123e46b3da969",
"url": "https://api.github.com/repos/symfony/polyfill-util/zipball/8de62801aa12bc4dfcf85eef5d21981ae7bb3cc4",
"reference": "8de62801aa12bc4dfcf85eef5d21981ae7bb3cc4",
"shasum": ""
},
"require": {
@ -6747,7 +6751,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
"dev-master": "1.1-dev"
}
},
"autoload": {
@ -6777,7 +6781,7 @@
"polyfill",
"shim"
],
"time": "2015-11-04 20:28:58"
"time": "2016-01-20 09:13:37"
},
{
"name": "symfony/process",
@ -7374,7 +7378,7 @@
},
{
"name": "wildbit/laravel-postmark-provider",
"version": "dev-master",
"version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/wildbit/laravel-postmark-provider.git",
@ -7448,16 +7452,16 @@
},
{
"name": "zircote/swagger-php",
"version": "2.0.5",
"version": "2.0.6",
"source": {
"type": "git",
"url": "https://github.com/zircote/swagger-php.git",
"reference": "c19af4edcc13c00e82fabeee926335b1fe1d92e9"
"reference": "0dfc289d53bad4a2bd193adc8d4bd058029ab417"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zircote/swagger-php/zipball/c19af4edcc13c00e82fabeee926335b1fe1d92e9",
"reference": "c19af4edcc13c00e82fabeee926335b1fe1d92e9",
"url": "https://api.github.com/repos/zircote/swagger-php/zipball/0dfc289d53bad4a2bd193adc8d4bd058029ab417",
"reference": "0dfc289d53bad4a2bd193adc8d4bd058029ab417",
"shasum": ""
},
"require": {
@ -7466,6 +7470,7 @@
"symfony/finder": "*"
},
"require-dev": {
"squizlabs/php_codesniffer": "2.*",
"zendframework/zend-form": "*"
},
"bin": [
@ -7504,26 +7509,26 @@
"rest",
"service discovery"
],
"time": "2016-01-15 09:39:28"
"time": "2016-02-13 15:39:11"
}
],
"packages-dev": [
{
"name": "codeception/c3",
"version": "2.0.5",
"version": "2.0.6",
"source": {
"type": "git",
"url": "https://github.com/Codeception/c3.git",
"reference": "b866ca528474ddcf74147531cc4e50b80257a5f8"
"reference": "dc4d39b36d585c2eda58129407e78855ea67b1ca"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Codeception/c3/zipball/b866ca528474ddcf74147531cc4e50b80257a5f8",
"reference": "b866ca528474ddcf74147531cc4e50b80257a5f8",
"url": "https://api.github.com/repos/Codeception/c3/zipball/dc4d39b36d585c2eda58129407e78855ea67b1ca",
"reference": "dc4d39b36d585c2eda58129407e78855ea67b1ca",
"shasum": ""
},
"require": {
"composer-plugin-api": "1.0.0",
"composer-plugin-api": "^1.0",
"php": ">=5.4.0"
},
"type": "composer-plugin",
@ -7556,37 +7561,37 @@
"code coverage",
"codecoverage"
],
"time": "2015-11-28 10:17:10"
"time": "2016-02-09 23:31:08"
},
{
"name": "codeception/codeception",
"version": "2.1.2",
"version": "2.1.6",
"source": {
"type": "git",
"url": "https://github.com/Codeception/Codeception.git",
"reference": "521adbb2ee34e9debdd8508a2c41ab2b5c2f042b"
"reference": "b199941f5e59d1e7fd32d78296c8ab98db873d89"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/521adbb2ee34e9debdd8508a2c41ab2b5c2f042b",
"reference": "521adbb2ee34e9debdd8508a2c41ab2b5c2f042b",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/b199941f5e59d1e7fd32d78296c8ab98db873d89",
"reference": "b199941f5e59d1e7fd32d78296c8ab98db873d89",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-mbstring": "*",
"facebook/webdriver": ">=1.0.1",
"guzzlehttp/guzzle": ">=4.0|<7.0",
"guzzlehttp/guzzle": ">=4.1.4 <7.0",
"guzzlehttp/psr7": "~1.0",
"php": ">=5.4.0",
"phpunit/phpunit": "~4.8.0",
"symfony/browser-kit": "~2.4",
"symfony/console": "~2.4",
"symfony/css-selector": "~2.4",
"symfony/dom-crawler": "~2.4,!=2.4.5",
"symfony/event-dispatcher": "~2.4",
"symfony/finder": "~2.4",
"symfony/yaml": "~2.4"
"symfony/browser-kit": ">=2.4|<3.1",
"symfony/console": ">=2.4|<3.1",
"symfony/css-selector": ">=2.4|<3.1",
"symfony/dom-crawler": ">=2.4|<3.1",
"symfony/event-dispatcher": ">=2.4|<3.1",
"symfony/finder": ">=2.4|<3.1",
"symfony/yaml": ">=2.4|<3.1"
},
"require-dev": {
"codeception/specify": "~0.3",
@ -7636,7 +7641,7 @@
"functional testing",
"unit testing"
],
"time": "2015-08-09 13:48:55"
"time": "2016-02-09 22:27:48"
},
{
"name": "doctrine/instantiator",
@ -7901,22 +7906,24 @@
},
{
"name": "phpspec/prophecy",
"version": "v1.5.0",
"version": "v1.6.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7"
"reference": "3c91bdf81797d725b14cb62906f9a4ce44235972"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7",
"reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972",
"reference": "3c91bdf81797d725b14cb62906f9a4ce44235972",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
"php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "~2.0",
"sebastian/comparator": "~1.1"
"sebastian/comparator": "~1.1",
"sebastian/recursion-context": "~1.0"
},
"require-dev": {
"phpspec/phpspec": "~2.0"
@ -7924,7 +7931,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4.x-dev"
"dev-master": "1.5.x-dev"
}
},
"autoload": {
@ -7957,7 +7964,7 @@
"spy",
"stub"
],
"time": "2015-08-13 10:07:40"
"time": "2016-02-15 07:46:21"
},
{
"name": "phpunit/php-code-coverage",
@ -8201,16 +8208,16 @@
},
{
"name": "phpunit/phpunit",
"version": "4.8.21",
"version": "4.8.23",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "ea76b17bced0500a28098626b84eda12dbcf119c"
"reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea76b17bced0500a28098626b84eda12dbcf119c",
"reference": "ea76b17bced0500a28098626b84eda12dbcf119c",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e351261f9cd33daf205a131a1ba61c6d33bd483",
"reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483",
"shasum": ""
},
"require": {
@ -8269,7 +8276,7 @@
"testing",
"xunit"
],
"time": "2015-12-12 07:45:58"
"time": "2016-02-11 14:56:33"
},
{
"name": "phpunit/phpunit-mock-objects",
@ -8700,25 +8707,25 @@
},
{
"name": "symfony/browser-kit",
"version": "v2.8.2",
"version": "v3.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
"reference": "a93dffaf763182acad12a4c42c7efc372899891e"
"reference": "dde849a0485b70a24b36f826ed3fb95b904d80c3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/a93dffaf763182acad12a4c42c7efc372899891e",
"reference": "a93dffaf763182acad12a4c42c7efc372899891e",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/dde849a0485b70a24b36f826ed3fb95b904d80c3",
"reference": "dde849a0485b70a24b36f826ed3fb95b904d80c3",
"shasum": ""
},
"require": {
"php": ">=5.3.9",
"symfony/dom-crawler": "~2.0,>=2.0.5|~3.0.0"
"php": ">=5.5.9",
"symfony/dom-crawler": "~2.8|~3.0"
},
"require-dev": {
"symfony/css-selector": "~2.0,>=2.0.5|~3.0.0",
"symfony/process": "~2.3.34|~2.7,>=2.7.6|~3.0.0"
"symfony/css-selector": "~2.8|~3.0",
"symfony/process": "~2.8|~3.0"
},
"suggest": {
"symfony/process": ""
@ -8726,7 +8733,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.8-dev"
"dev-master": "3.0-dev"
}
},
"autoload": {
@ -8753,28 +8760,28 @@
],
"description": "Symfony BrowserKit Component",
"homepage": "https://symfony.com",
"time": "2016-01-12 17:46:01"
"time": "2016-01-27 11:34:55"
},
{
"name": "symfony/dom-crawler",
"version": "v2.8.2",
"version": "v3.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
"reference": "650d37aacb1fa0dcc24cced483169852b3a0594e"
"reference": "b693a9650aa004576b593ff2e91ae749dc90123d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/650d37aacb1fa0dcc24cced483169852b3a0594e",
"reference": "650d37aacb1fa0dcc24cced483169852b3a0594e",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b693a9650aa004576b593ff2e91ae749dc90123d",
"reference": "b693a9650aa004576b593ff2e91ae749dc90123d",
"shasum": ""
},
"require": {
"php": ">=5.3.9",
"php": ">=5.5.9",
"symfony/polyfill-mbstring": "~1.0"
},
"require-dev": {
"symfony/css-selector": "~2.8|~3.0.0"
"symfony/css-selector": "~2.8|~3.0"
},
"suggest": {
"symfony/css-selector": ""
@ -8782,7 +8789,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.8-dev"
"dev-master": "3.0-dev"
}
},
"autoload": {
@ -8809,20 +8816,20 @@
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
"time": "2016-01-03 15:33:41"
"time": "2016-01-25 09:56:57"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.0.1",
"version": "v1.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25"
"reference": "1289d16209491b584839022f29257ad859b8532d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/49ff736bd5d41f45240cec77b44967d76e0c3d25",
"reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/1289d16209491b584839022f29257ad859b8532d",
"reference": "1289d16209491b584839022f29257ad859b8532d",
"shasum": ""
},
"require": {
@ -8834,7 +8841,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
"dev-master": "1.1-dev"
}
},
"autoload": {
@ -8868,29 +8875,29 @@
"portable",
"shim"
],
"time": "2015-11-20 09:19:13"
"time": "2016-01-20 09:13:37"
},
{
"name": "symfony/yaml",
"version": "v2.8.2",
"version": "v3.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "34c8a4b51e751e7ea869b8262f883d008a2b81b8"
"reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/34c8a4b51e751e7ea869b8262f883d008a2b81b8",
"reference": "34c8a4b51e751e7ea869b8262f883d008a2b81b8",
"url": "https://api.github.com/repos/symfony/yaml/zipball/3cf0709d7fe936e97bee9e954382e449003f1d9a",
"reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
"php": ">=5.5.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.8-dev"
"dev-master": "3.0-dev"
}
},
"autoload": {
@ -8917,7 +8924,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2016-01-13 10:28:07"
"time": "2016-02-02 13:44:19"
}
],
"aliases": [],
@ -8935,7 +8942,6 @@
"alfaproject/omnipay-neteller": 20,
"alfaproject/omnipay-skrill": 20,
"omnipay/bitpay": 20,
"wildbit/laravel-postmark-provider": 20,
"dwolla/omnipay-dwolla": 20,
"simshaun/recurr": 20,
"meebio/omnipay-creditcall": 20,

View File

@ -126,6 +126,7 @@ class PaymentLibrariesSeeder extends Seeder
['name' => 'Bulgarian Lev', 'code' => 'BGN', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ' ', 'decimal_separator' => '.'],
['name' => 'Aruban Florin', 'code' => 'AWG', 'symbol' => 'Afl. ', 'precision' => '2', 'thousand_separator' => ' ', 'decimal_separator' => '.'],
['name' => 'Turkish Lira', 'code' => 'TRY', 'symbol' => 'TL ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['name' => 'Romanian New Leu', 'code' => 'RON', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
];
foreach ($currencies as $currency) {

View File

@ -12,4 +12,8 @@
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
# In case of running InvoiceNinja in a Subdomain like invoiceninja.example.com,
# you have to enablel the following line:
# RewriteBase /
</IfModule>

View File

@ -5,15 +5,12 @@
# Invoice Ninja
### [https://www.invoiceninja.com](https://www.invoiceninja.com)
[![Build Status](https://travis-ci.org/invoiceninja/invoiceninja.svg?branch=develop)](https://travis-ci.org/invoiceninja/invoiceninja)
[![Join the chat at https://gitter.im/hillelcoren/invoice-ninja](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hillelcoren/invoice-ninja?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
### Referral Program
* $100 per signup paid over 3 years - [Learn more](https://www.invoiceninja.com/referral-program/)
### Reseller Program
There are two options:
* 10% of revenue
* $1,000 for a site limited to 1,000 users
### Affiliates Programs
* Referral program (we pay you): $100 per signup paid over 3 years - [Learn more](https://www.invoiceninja.com/referral-program/)
* White-label reseller (you pay us): 10% of revenue with a $100 sign up fee
### Installation Options
* [Self-Host Zip](https://www.invoiceninja.com/knowledgebase/self-host/) - Free
@ -26,6 +23,10 @@ There are two options:
* MCrypt PHP Extension
* MySQL
### Recommended Providers
* [Stripe](https://stripe.com/)
* [Postmark](https://postmarkapp.com/)
### Features
* Built using Laravel 5
* Live PDF generation using [pdfmake](http://pdfmake.org/)
@ -41,10 +42,6 @@ There are two options:
* Custom email templates
* [D3.js](http://d3js.org/) visualizations
### Recommended Providers
* [Stripe](https://stripe.com/)
* [Postmark](https://postmarkapp.com/)
### Documentation
* [Ubuntu and Apache](http://blog.technerdservices.com/index.php/2015/04/techpop-how-to-install-invoice-ninja-on-ubuntu-14-04/)
* [Debian and Nginx](https://www.rosehosting.com/blog/install-invoice-ninja-on-a-debian-7-vps/)

View File

@ -1,8 +1,6 @@
<?php
return array(
// client
$LANG = array(
'organization' => 'Organization',
'name' => 'Name',
'website' => 'Website',
@ -25,8 +23,6 @@ return array(
'size_id' => 'Company Size',
'industry_id' => 'Industry',
'private_notes' => 'Private Notes',
// invoice
'invoice' => 'Invoice',
'client' => 'Client',
'invoice_date' => 'Invoice Date',
@ -50,7 +46,6 @@ return array(
'invoice_design_id' => 'Design',
'terms' => 'Terms',
'your_invoice' => 'Your Invoice',
'remove_contact' => 'Remove contact',
'add_contact' => 'Add contact',
'create_new_client' => 'Create new client',
@ -74,8 +69,6 @@ return array(
'settings' => 'Settings',
'enable_invoice_tax' => 'Enable specifying an <b>invoice tax</b>',
'enable_line_item_tax' => 'Enable specifying <b>line item taxes</b>',
// navigation
'dashboard' => 'Dashboard',
'clients' => 'Clients',
'invoices' => 'Invoices',
@ -100,8 +93,6 @@ return array(
'provide_email' => 'Please provide a valid email address',
'powered_by' => 'Powered by',
'no_items' => 'No items',
// recurring invoices
'recurring_invoices' => 'Recurring Invoices',
'recurring_help' => '<p>Automatically send clients the same invoices weekly, bi-monthly, monthly, quarterly or annually. </p>
<p>Use :MONTH, :QUARTER or :YEAR for dynamic dates. Basic math works as well, for example :MONTH-1.</p>
@ -111,8 +102,6 @@ return array(
<li>":YEAR+1 yearly subscription" => "2015 Yearly Subscription"</li>
<li>"Retainer payment for :QUARTER+1" => "Retainer payment for Q2"</li>
</ul>',
// dashboard
'in_total_revenue' => 'in total revenue',
'billed_client' => 'billed client',
'billed_clients' => 'billed clients',
@ -121,8 +110,6 @@ return array(
'invoices_past_due' => 'Invoices Past Due',
'upcoming_invoices' => 'Upcoming Invoices',
'average_invoice' => 'Average Invoice',
// list pages
'archive' => 'Archive',
'delete' => 'Delete',
'archive_client' => 'Archive Client',
@ -158,8 +145,6 @@ return array(
'select' => 'Select',
'edit_client' => 'Edit Client',
'edit_invoice' => 'Edit Invoice',
// client view page
'create_invoice' => 'Create Invoice',
'enter_credit' => 'Enter Credit',
'last_logged_in' => 'Last logged in',
@ -171,12 +156,8 @@ return array(
'message' => 'Message',
'adjustment' => 'Adjustment',
'are_you_sure' => 'Are you sure?',
// payment pages
'payment_type_id' => 'Payment Type',
'amount' => 'Amount',
// account/company pages
'work_email' => 'Email',
'language_id' => 'Language',
'timezone_id' => 'Timezone',
@ -206,13 +187,9 @@ return array(
'client_view_styling' => 'Client View Styling',
'pdf_email_attachment' => 'Attach PDFs',
'custom_css' => 'Custom CSS',
//import CSV data pages
'import_clients' => 'Import Client Data',
'csv_file' => 'CSV file',
'export_clients' => 'Export Client Data',
// application messages
'created_client' => 'Successfully created client',
'created_clients' => 'Successfully created :count client(s)',
'updated_settings' => 'Successfully updated settings',
@ -223,14 +200,12 @@ return array(
'payment_error' => 'There was an error processing your payment. Please try again later.',
'registration_required' => 'Please sign up to email an invoice',
'confirmation_required' => 'Please confirm your email address',
'updated_client' => 'Successfully updated client',
'created_client' => 'Successfully created client',
'archived_client' => 'Successfully archived client',
'archived_clients' => 'Successfully archived :count clients',
'deleted_client' => 'Successfully deleted client',
'deleted_clients' => 'Successfully deleted :count clients',
'updated_invoice' => 'Successfully updated invoice',
'created_invoice' => 'Successfully created invoice',
'cloned_invoice' => 'Successfully cloned invoice',
@ -240,7 +215,6 @@ return array(
'archived_invoices' => 'Successfully archived :count invoices',
'deleted_invoice' => 'Successfully deleted invoice',
'deleted_invoices' => 'Successfully deleted :count invoices',
'created_payment' => 'Successfully created payment',
'created_payments' => 'Successfully created :count payment(s)',
'archived_payment' => 'Successfully archived payment',
@ -248,22 +222,18 @@ return array(
'deleted_payment' => 'Successfully deleted payment',
'deleted_payments' => 'Successfully deleted :count payments',
'applied_payment' => 'Successfully applied payment',
'created_credit' => 'Successfully created credit',
'archived_credit' => 'Successfully archived credit',
'archived_credits' => 'Successfully archived :count credits',
'deleted_credit' => 'Successfully deleted credit',
'deleted_credits' => 'Successfully deleted :count credits',
'imported_file' => 'Successfully imported file',
'updated_vendor' => 'Successfully updated vendor',
'created_vendor' => 'Successfully created vendor',
'archived_vendor' => 'Successfully archived vendor',
'archived_vendors' => 'Successfully archived :count vendors',
'deleted_vendor' => 'Successfully deleted vendor',
'deleted_vendors' => 'Successfully deleted :count vendors',
// Emails
'confirmation_subject' => 'Invoice Ninja Account Confirmation',
'confirmation_header' => 'Account Confirmation',
'confirmation_message' => 'Please access the link below to confirm your account.',
@ -274,7 +244,6 @@ return array(
'email_salutation' => 'Dear :name,',
'email_signature' => 'Regards,',
'email_from' => 'The Invoice Ninja Team',
'user_email_footer' => 'To adjust your email notification settings please visit '.SITE_URL.'/settings/notifications',
'invoice_link_message' => 'To view the invoice click the link below:',
'notification_invoice_paid_subject' => 'Invoice :invoice was paid by :client',
'notification_invoice_sent_subject' => 'Invoice :invoice was sent to :client',
@ -283,32 +252,11 @@ return array(
'notification_invoice_sent' => 'The following client :client was emailed Invoice :invoice for :amount.',
'notification_invoice_viewed' => 'The following client :client viewed Invoice :invoice for :amount.',
'reset_password' => 'You can reset your account password by clicking the following button:',
'reset_password_footer' => 'If you did not request this password reset please email our support: '.CONTACT_EMAIL,
// Payment page
'secure_payment' => 'Secure Payment',
'card_number' => 'Card Number',
'expiration_month' => 'Expiration Month',
'expiration_year' => 'Expiration Year',
'cvv' => 'CVV',
// Security alerts
'security' => [
'too_many_attempts' => 'Too many attempts. Try again in few minutes.',
'wrong_credentials' => 'Incorrect email or password.',
'confirmation' => 'Your account has been confirmed!',
'wrong_confirmation' => 'Wrong confirmation code.',
'password_forgot' => 'The information regarding password reset was sent to your email.',
'password_reset' => 'Your password has been changed successfully.',
'wrong_password_reset' => 'Invalid password. Try again',
],
// Pro Plan
'pro_plan' => [
'remove_logo' => ':link to remove the Invoice Ninja logo by joining the Pro Plan',
'remove_logo_link' => 'Click here',
],
'logout' => 'Log Out',
'sign_up_to_save' => 'Sign up to save your work',
'agree_to_terms' => 'I agree to the Invoice Ninja :terms',
@ -319,7 +267,6 @@ return array(
'success_message' => 'You have successfully registered! Please visit the link in the account confirmation email to verify your email address.',
'erase_data' => 'This will permanently erase your data.',
'password' => 'Password',
'pro_plan_product' => 'Pro Plan',
'pro_plan_description' => 'One year enrollment in the Invoice Ninja Pro Plan.',
'pro_plan_success' => 'Thanks for choosing Invoice Ninja\'s Pro plan!<p/>&nbsp;<br/>
@ -329,7 +276,6 @@ return array(
for a year of Pro-level invoicing.<p/>
Can\'t find the invoice? Need further assistance? We\'re happy to help
-- email us at contact@invoiceninja.com',
'unsaved_changes' => 'You have unsaved changes',
'custom_fields' => 'Custom Fields',
'company_fields' => 'Company Fields',
@ -339,8 +285,6 @@ return array(
'edit' => 'Edit',
'set_name' => 'Set your company name',
'view_as_recipient' => 'View as recipient',
// product management
'product_library' => 'Product Library',
'product' => 'Product',
'products' => 'Product Library',
@ -355,18 +299,14 @@ return array(
'created_product' => 'Successfully created product',
'archived_product' => 'Successfully archived product',
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan',
'advanced_settings' => 'Advanced Settings',
'pro_plan_advanced_settings' => ':link to enable the advanced settings by joining the Pro Plan',
'invoice_design' => 'Invoice Design',
'specify_colors' => 'Specify colors',
'specify_colors_label' => 'Select the colors used in the invoice',
'chart_builder' => 'Chart Builder',
'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!',
'go_pro' => 'Go Pro',
// Quotes
'quote' => 'Quote',
'quotes' => 'Quotes',
'quote_number' => 'Quote Number',
@ -376,7 +316,6 @@ return array(
'your_quote' => 'Your Quote',
'total' => 'Total',
'clone' => 'Clone',
'new_quote' => 'New Quote',
'create_quote' => 'Create Quote',
'edit_quote' => 'Edit Quote',
@ -389,7 +328,6 @@ return array(
'view_invoice' => 'View Invoice',
'view_client' => 'View Client',
'view_quote' => 'View Quote',
'updated_quote' => 'Successfully updated quote',
'created_quote' => 'Successfully created quote',
'cloned_quote' => 'Successfully cloned quote',
@ -399,7 +337,6 @@ return array(
'deleted_quote' => 'Successfully deleted quote',
'deleted_quotes' => 'Successfully deleted :count quotes',
'converted_to_invoice' => 'Successfully converted quote to invoice',
'quote_subject' => 'New quote $quote from :account',
'quote_message' => 'To view your quote for :amount, click the link below.',
'quote_link_message' => 'To view your client quote click the link below:',
@ -407,16 +344,13 @@ return array(
'notification_quote_viewed_subject' => 'Quote :invoice was viewed by :client',
'notification_quote_sent' => 'The following client :client was emailed Quote :invoice for :amount.',
'notification_quote_viewed' => 'The following client :client viewed Quote :invoice for :amount.',
'session_expired' => 'Your session has expired.',
'invoice_fields' => 'Invoice Fields',
'invoice_options' => 'Invoice Options',
'hide_quantity' => 'Hide Quantity',
'hide_quantity_help' => 'If your line items quantities are always 1, then you can declutter invoices by no longer displaying this field.',
'hide_paid_to_date' => 'Hide Paid to Date',
'hide_paid_to_date_help' => 'Only display the "Paid to Date" area on your invoices once a payment has been received.',
'charge_taxes' => 'Charge taxes',
'user_management' => 'User Management',
'add_user' => 'Add User',
@ -431,21 +365,16 @@ return array(
'active' => 'Active',
'pending' => 'Pending',
'deleted_user' => 'Successfully deleted user',
'limit_users' => 'Sorry, this will exceed the limit of '.MAX_NUM_USERS.' users',
'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?',
'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?',
'cancel_account' => 'Cancel Account',
'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back',
'data_visualizations' => 'Data Visualizations',
'sample_data' => 'Sample data shown',
'hide' => 'Hide',
'new_version_available' => 'A new version of :releases_link is available. You\'re running v:user_version, the latest is v:latest_version',
'invoice_settings' => 'Invoice Settings',
'invoice_number_prefix' => 'Invoice Number Prefix',
'invoice_number_counter' => 'Invoice Number Counter',
@ -455,59 +384,48 @@ return array(
'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',
'gateway_help_1' => ':link to sign up for Authorize.net.',
'gateway_help_2' => ':link to sign up for Authorize.net.',
'gateway_help_17' => ':link to get your PayPal API signature.',
'gateway_help_27' => ':link to sign up for TwoCheckout.',
'more_designs' => 'More designs',
'more_designs_title' => 'Additional Invoice Designs',
'more_designs_cloud_header' => 'Go Pro for more invoice designs',
'more_designs_cloud_text' => '',
'more_designs_self_host_header' => 'Get 6 more invoice designs for just $'.INVOICE_DESIGNS_PRICE,
'more_designs_self_host_text' => '',
'buy' => 'Buy',
'bought_designs' => 'Successfully added additional invoice designs',
'sent' => 'sent',
'vat_number' => 'VAT Number',
'timesheets' => 'Timesheets',
'payment_title' => 'Enter Your Billing Address and Credit Card information',
'payment_cvv' => '*This is the 3-4 digit number onthe back of your card',
'payment_footer1' => '*Billing address must match address associated with credit card.',
'payment_footer2' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'id_number' => 'ID Number',
'white_label_link' => 'White label',
'white_label_header' => 'White Label',
'bought_white_label' => 'Successfully enabled white label license',
'white_labeled' => 'White labeled',
'restore' => 'Restore',
'restore_invoice' => 'Restore Invoice',
'restore_quote' => 'Restore Quote',
'restore_client' => 'Restore Client',
'restore_credit' => 'Restore Credit',
'restore_payment' => 'Restore Payment',
'restored_invoice' => 'Successfully restored invoice',
'restored_quote' => 'Successfully restored quote',
'restored_client' => 'Successfully restored client',
'restored_payment' => 'Successfully restored payment',
'restored_credit' => 'Successfully restored credit',
'reason_for_canceling' => 'Help us improve our site by telling us why you\'re leaving.',
'discount_percent' => 'Percent',
'discount_amount' => 'Amount',
'invoice_history' => 'Invoice History',
'quote_history' => 'Quote History',
'current_version' => 'Current version',
'select_versiony' => 'Select version',
'view_history' => 'View History',
'edit_payment' => 'Edit Payment',
'updated_payment' => 'Successfully updated payment',
'deleted' => 'Deleted',
@ -520,7 +438,6 @@ return array(
'quote_email' => 'Quote Email',
'reset_all' => 'Reset All',
'approve' => 'Approve',
'token_billing_type_id' => 'Token Billing',
'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.',
'token_billing_1' => 'Disabled',
@ -533,7 +450,6 @@ return array(
'edit_payment_details' => 'Edit payment details',
'token_billing' => 'Save card details',
'token_billing_secure' => 'The data is stored securely by :stripe_link',
'support' => 'Support',
'contact_information' => 'Contact Information',
'256_encryption' => '256-Bit Encryption',
@ -543,10 +459,8 @@ return array(
'order_overview' => 'Order overview',
'match_address' => '*Address must match address associated with credit card.',
'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
'invoice_footer' => 'Invoice Footer',
'save_as_default_footer' => 'Save as default footer',
'token_management' => 'Token Management',
'tokens' => 'Tokens',
'add_token' => 'Add Token',
@ -557,7 +471,6 @@ return array(
'edit_token' => 'Edit Token',
'delete_token' => 'Delete Token',
'token' => 'Token',
'add_gateway' => 'Add Gateway',
'delete_gateway' => 'Delete Gateway',
'edit_gateway' => 'Edit Gateway',
@ -566,7 +479,6 @@ return array(
'deleted_gateway' => 'Successfully deleted gateway',
'pay_with_paypal' => 'PayPal',
'pay_with_card' => 'Credit Card',
'change_password' => 'Change password',
'current_password' => 'Current password',
'new_password' => 'New password',
@ -574,7 +486,6 @@ return array(
'password_error_incorrect' => 'The current password is incorrect.',
'password_error_invalid' => 'The new password is invalid.',
'updated_password' => 'Successfully updated password',
'api_tokens' => 'API Tokens',
'users_and_tokens' => 'Users & Tokens',
'account_login' => 'Account Login',
@ -582,18 +493,15 @@ return array(
'forgot_password' => 'Forgot your password?',
'email_address' => 'Email address',
'lets_go' => 'Let\'s go',
//'lets_go' => 'Login',
'password_recovery' => 'Password Recovery',
'send_email' => 'Send email',
'set_password' => 'Set Password',
'converted' => 'Converted',
'email_approved' => 'Email me when a quote is <b>approved</b>',
'notification_quote_approved_subject' => 'Quote :invoice was approved by :client',
'notification_quote_approved' => 'The following client :client approved Quote :invoice for :amount.',
'resend_confirmation' => 'Resend confirmation email',
'confirmation_resent' => 'The confirmation email was resent',
'gateway_help_42' => ':link to sign up for BitPay.<br/>Note: use a Legacy API Key, not an API token.',
'payment_type_credit_card' => 'Credit Card',
'payment_type_paypal' => 'PayPal',
@ -601,7 +509,6 @@ return array(
'knowledge_base' => 'Knowledge Base',
'partial' => 'Partial',
'partial_remaining' => ':partial of :balance',
'more_fields' => 'More Fields',
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
@ -612,7 +519,6 @@ return array(
'view_documentation' => 'View Documentation',
'app_title' => 'Free Open-Source Online Invoicing',
'app_description' => 'Invoice Ninja is a free, open-source solution for invoicing and billing customers. With Invoice Ninja, you can easily build and send beautiful invoices from any device that has access to the web. Your clients can print your invoices, download them as pdf files, and even pay you online from within the system.',
'rows' => 'rows',
'www' => 'www',
'logo' => 'Logo',
@ -632,7 +538,6 @@ return array(
'zapier' => 'Zapier',
'recurring' => 'Recurring',
'last_invoice_sent' => 'Last invoice sent :date',
'processed_updates' => 'Successfully completed update',
'tasks' => 'Tasks',
'new_task' => 'New Task',
@ -678,12 +583,10 @@ return array(
'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',
'pro_plan_title' => 'NINJA PRO',
'pro_plan_call_to_action' => 'Upgrade Now!',
'pro_plan_feature1' => 'Create Unlimited Clients',
@ -694,14 +597,12 @@ return array(
'pro_plan_feature6' => 'Create Quotes & Pro-forma Invoices',
'pro_plan_feature7' => 'Customize Invoice Field Titles & Numbering',
'pro_plan_feature8' => 'Option to Attach PDFs to Client Emails',
'resume' => 'Resume',
'break_duration' => 'Break',
'edit_details' => 'Edit Details',
'work' => 'Work',
'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'click here',
'email_receipt' => 'Email payment receipt to the client',
'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_company' => 'Add Company',
@ -711,10 +612,8 @@ return array(
'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login',
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
@ -735,7 +634,6 @@ return array(
'primary_color' => 'Primary Color',
'secondary_color' => 'Secondary Color',
'customize_design' => 'Customize Design',
'content' => 'Content',
'styles' => 'Styles',
'defaults' => 'Defaults',
@ -749,7 +647,6 @@ return array(
'outstanding' => 'Outstanding',
'manage_companies' => 'Manage Companies',
'total_revenue' => 'Total Revenue',
'current_user' => 'Current User',
'new_recurring_invoice' => 'New Recurring Invoice',
'recurring_invoice' => 'Recurring Invoice',
@ -761,7 +658,6 @@ return array(
<p>You can access any invoice field by adding <code>Value</code> to the end. For example <code>$invoiceNumberValue</code> displays the invoice number.</p>
<p>To access a child property using dot notation. For example to show the client name you could use <code>$client.nameValue</code>.</p>
<p>If you need help figuring something out post a question to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>.</p>',
'invoice_due_date' => 'Due Date',
'quote_due_date' => 'Valid Until',
'valid_until' => 'Valid Until',
@ -774,15 +670,12 @@ return array(
'status_partial' => 'Partial',
'status_paid' => 'Paid',
'show_line_item_tax' => 'Display <b>line item taxes inline</b>',
'iframe_url' => 'Website',
'iframe_url_help1' => 'Copy the following code to a page on your site.',
'iframe_url_help2' => 'You can test the feature by clicking \'View as recipient\' for an invoice.',
'auto_bill' => 'Auto Bill',
'military_time' => '24 Hour Time',
'last_sent' => 'Last Sent',
'reminder_emails' => 'Reminder Emails',
'templates_and_reminders' => 'Templates & Reminders',
'subject' => 'Subject',
@ -794,15 +687,12 @@ return array(
'reminder_subject' => 'Reminder: Invoice :invoice from :account',
'reset' => 'Reset',
'invoice_not_found' => 'The requested invoice is not available',
'referral_program' => 'Referral Program',
'referral_code' => 'Referral URL',
'last_sent_on' => 'Sent Last: :date',
'page_expire' => 'This page will expire soon, :click_here to keep working',
'upcoming_quotes' => 'Upcoming Quotes',
'expired_quotes' => 'Expired Quotes',
'sign_up_using' => 'Sign up using',
'invalid_credentials' => 'These credentials do not match our records',
'show_all_options' => 'Show all options',
@ -811,18 +701,10 @@ return array(
'disable' => 'Disable',
'invoice_quote_number' => 'Invoice and Quote Numbers',
'invoice_charges' => 'Invoice Charges',
'invitation_status' => [
'sent' => 'Email Sent',
'opened' => 'Email Openend',
'viewed' => 'Invoice Viewed',
],
'notification_invoice_bounced' => 'We were unable to deliver Invoice :invoice to :contact.',
'notification_invoice_bounced_subject' => 'Unable to deliver Invoice :invoice',
'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.',
'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice',
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
@ -830,15 +712,12 @@ return array(
'basic_settings' => 'Basic Settings',
'pro' => 'Pro',
'gateways' => 'Payment Gateways',
'next_send_on' => 'Send Next: :date',
'no_longer_running' => 'This invoice is not scheduled to run',
'general_settings' => 'General Settings',
'customize' => 'Customize',
'oneclick_login_help' => 'Connect an account to login without a password',
'referral_code_help' => 'Earn money by sharing our app online',
'enable_with_stripe' => 'Enable | Requires Stripe',
'tax_settings' => 'Tax Settings',
'create_tax_rate' => 'Add Tax Rate',
@ -859,7 +738,6 @@ return array(
'invoice_counter' => 'Invoice Counter',
'quote_counter' => 'Quote Counter',
'type' => 'Type',
'activity_1' => ':user created client :client',
'activity_2' => ':user archived client :client',
'activity_3' => ':user deleted client :client',
@ -897,7 +775,6 @@ return array(
'activity_35' => ':user created :vendor',
'activity_36' => ':user created :vendor',
'activity_37' => ':user created :vendor',
'payment' => 'Payment',
'system' => 'System',
'signature' => 'Email Signature',
@ -908,7 +785,6 @@ return array(
'default_invoice_footer' => 'Default Invoice Footer',
'quote_footer' => 'Quote Footer',
'free' => 'Free',
'quote_is_approved' => 'This quote is approved',
'apply_credit' => 'Apply Credit',
'system_settings' => 'System Settings',
@ -926,7 +802,6 @@ return array(
'restored_recurring_invoice' => 'Successfully restored recurring invoice',
'archived' => 'Archived',
'untitled_account' => 'Untitled Company',
'before' => 'Before',
'after' => 'After',
'reset_terms_help' => 'Reset to the default account terms',
@ -935,7 +810,6 @@ return array(
'user' => 'User',
'country' => 'Country',
'include' => 'Include',
'logo_too_large' => 'Your logo is :size, for better PDF performance we suggest uploading an image file less than 200KB',
'import_freshbooks' => 'Import From FreshBooks',
'import_data' => 'Import Data',
@ -946,16 +820,6 @@ return array(
'task_file' => 'Task File',
'no_mapper' => 'No valid mapping for file',
'invalid_csv_header' => 'Invalid CSV Header',
'email_errors' => [
'inactive_client' => 'Emails can not be sent to inactive clients',
'inactive_contact' => 'Emails can not be sent to inactive contacts',
'inactive_invoice' => 'Emails can not be sent to inactive invoices',
'user_unregistered' => 'Please register your account to send emails',
'user_unconfirmed' => 'Please confirm your account to send emails',
'invalid_contact_email' => 'Invalid contact email',
],
'client_portal' => 'Client Portal',
'admin' => 'Admin',
'disabled' => 'Disabled',
@ -964,11 +828,9 @@ return array(
'invoice_will_create' => 'client will be created',
'invoices_will_create' => 'invoices will be created',
'failed_to_import' => 'The following records failed to import',
'publishable_key' => 'Publishable Key',
'secret_key' => 'Secret Key',
'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process',
'email_design' => 'Email Design',
'due_by' => 'Due by :date',
'enable_email_markup' => 'Enable Markup',
@ -980,7 +842,6 @@ return array(
'plain' => 'Plain',
'light' => 'Light',
'dark' => 'Dark',
'industry_help' => 'Used to provide comparisons against the averages of companies of similar size and industry.',
'subdomain_help' => 'Customize the invoice link subdomain or display the invoice on your own website.',
'invoice_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the invoice number.',
@ -989,7 +850,6 @@ return array(
'custom_account_fields_helps' => 'Add a label and value to the company details section of the PDF.',
'custom_invoice_fields_helps' => 'Add a text input to the invoice create/edit page and display the label and value on the PDF.',
'custom_invoice_charges_helps' => 'Add a text input to the invoice create/edit page and include the charge in the invoice subtotals.',
'token_expired' => 'Validation token was expired. Please try again.',
'invoice_link' => 'Invoice Link',
'button_confirmation_message' => 'Click to confirm your email address.',
@ -998,7 +858,6 @@ return array(
'created_invoices' => 'Successfully created :count invoice(s)',
'next_invoice_number' => 'The next invoice number is :number.',
'next_quote_number' => 'The next quote number is :number.',
'days_before' => 'days before',
'days_after' => 'days after',
'field_due_date' => 'due date',
@ -1006,11 +865,7 @@ return array(
'schedule' => 'Schedule',
'email_designs' => 'Email Designs',
'assigned_when_sent' => 'Assigned when sent',
'white_label_custom_css' => ':link for $'.WHITE_LABEL_PRICE.' to enable custom styling and help support our project.',
'white_label_purchase_link' => 'Purchase a white label license',
// Expense / vendor
'expense' => 'Expense',
'expenses' => 'Expenses',
'new_expense' => 'Enter Expense',
@ -1027,8 +882,6 @@ return array(
'archived_expense' => 'Successfully archived expense',
'deleted_expenses' => 'Successfully deleted expenses',
'archived_expenses' => 'Successfully archived expenses',
// Expenses
'expense_amount' => 'Expense Amount',
'expense_balance' => 'Expense Balance',
'expense_date' => 'Expense Date',
@ -1053,15 +906,11 @@ return array(
'expense_error_multiple_clients' => 'The expenses can\'t belong to different clients',
'expense_error_invoiced' => 'Expense has already been invoiced',
'convert_currency' => 'Convert currency',
// Payment terms
'num_days' => 'Number of days',
'create_payment_term' => 'Create Payment Term',
'edit_payment_terms' => 'Edit Payment Term',
'edit_payment_term' => 'Edit Payment Term',
'archive_payment_term' => 'Archive Payment Term',
// recurring due dates
'recurring_due_dates' => 'Recurring Invoice Due Dates',
'recurring_due_date_help' => '<p>Automatically sets a due date for the invoice.</p>
<p>Invoices on a monthly or yearly cycle set to be due on or before the day they are created will be due the next month. Invoices set to be due on the 29th or 30th in months that don\'t have that day will be due the last day of the month.</p>
@ -1089,15 +938,11 @@ return array(
'thursday' => 'Thursday',
'friday' => 'Friday',
'saturday' => 'Saturday',
// Fonts
'header_font_id' => 'Header Font',
'body_font_id' => 'Body Font',
'color_font_help' => 'Note: the primary color and fonts are also used in the client portal and custom email designs.',
'live_preview' => 'Live Preview',
'invalid_mail_config' => 'Unable to send email, please check that the mail settings are correct.',
'invoice_message_button' => 'To view your invoice for :amount, click the button below.',
'quote_message_button' => 'To view your quote for :amount, click the button below.',
'payment_message_button' => 'Thank you for your payment of :amount.',
@ -1114,7 +959,6 @@ return array(
'archived_bank_account' => 'Successfully archived bank account',
'created_bank_account' => 'Successfully created bank account',
'validate_bank_account' => 'Validate Bank Account',
'bank_accounts_help' => 'Connect a bank account to automatically import expenses and create vendors. Supports American Express and <a href="'.OFX_HOME_URL.'" target="_blank">400+ US banks.</a>',
'bank_password_help' => 'Note: your password is transmitted securely and never stored on our servers.',
'bank_password_warning' => 'Warning: your password may be transmitted in plain text, consider enabling HTTPS.',
'username' => 'Username',
@ -1128,7 +972,6 @@ return array(
'validate' => 'Validate',
'info' => 'Info',
'imported_expenses' => 'Successfully created :count_vendors vendor(s) and :count_expenses expense(s)',
'iframe_url_help3' => 'Note: if you plan on accepting credit cards details we strongly recommend enabling HTTPS on your site.',
'expense_error_multiple_currencies' => 'The expenses can\'t have different currencies.',
'expense_error_mismatch_currencies' => 'The client\'s currency does not match the expense currency.',
@ -1149,6 +992,44 @@ return array(
'trial_call_to_action' => 'Start Free Trial',
'trial_success' => 'Successfully enabled two week free pro plan trial',
'overdue' => 'Overdue',
'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.',
'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.',
'user_email_footer' => 'To adjust your email notification settings please visit '.SITE_URL.'/settings/notifications',
'reset_password_footer' => 'If you did not request this password reset please email our support: '.CONTACT_EMAIL,
'limit_users' => 'Sorry, this will exceed the limit of '.MAX_NUM_USERS.' users',
'more_designs_self_host_header' => 'Get 6 more invoice designs for just $'.INVOICE_DESIGNS_PRICE,
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'white_label_custom_css' => ':link for $'.WHITE_LABEL_PRICE.' to enable custom styling and help support our project.',
'bank_accounts_help' => 'Connect a bank account to automatically import expenses and create vendors. Supports American Express and <a href="'.OFX_HOME_URL.'" target="_blank">400+ US banks.</a>',
'security' => [
'too_many_attempts' => 'Too many attempts. Try again in few minutes.',
'wrong_credentials' => 'Incorrect email or password.',
'confirmation' => 'Your account has been confirmed!',
'wrong_confirmation' => 'Wrong confirmation code.',
'password_forgot' => 'The information regarding password reset was sent to your email.',
'password_reset' => 'Your password has been changed successfully.',
'wrong_password_reset' => 'Invalid password. Try again',
],
'pro_plan' => [
'remove_logo' => ':link to remove the Invoice Ninja logo by joining the Pro Plan',
'remove_logo_link' => 'Click here',
],
'invitation_status' => [
'sent' => 'Email Sent',
'opened' => 'Email Openend',
'viewed' => 'Invoice Viewed',
],
'email_errors' => [
'inactive_client' => 'Emails can not be sent to inactive clients',
'inactive_contact' => 'Emails can not be sent to inactive contacts',
'inactive_invoice' => 'Emails can not be sent to inactive invoices',
'user_unregistered' => 'Please register your account to send emails',
'user_unconfirmed' => 'Please confirm your account to send emails',
'invalid_contact_email' => 'Invalid contact email',
],
);
return $LANG;
?>.

View File

@ -42,7 +42,11 @@
function setTrashVisible() {
var checked = $('#trashed').is(':checked');
window.location = '{!! URL::to('view_archive/token') !!}' + (checked ? '/true' : '/false');
var url = '{{ URL::to('view_archive/token') }}' + (checked ? '/true' : '/false');
$.get(url, function(data) {
refreshDatatable();
})
}
</script>

View File

@ -39,7 +39,11 @@
function setTrashVisible() {
var checked = $('#trashed').is(':checked');
window.location = '{!! URL::to('view_archive/user') !!}' + (checked ? '/true' : '/false');
var url = '{{ URL::to('view_archive/user') }}' + (checked ? '/true' : '/false');
$.get(url, function(data) {
refreshDatatable();
})
}
</script>

View File

@ -22,7 +22,7 @@
@section('content')
<div class="row">
<div class="col-md-8">
<div class="col-md-7">
<div>
<span style="font-size:28px">{{ $client->getDisplayName() }}</span>
@if ($client->trashed())
@ -30,7 +30,7 @@
@endif
</div>
</div>
<div class="col-md-4">
<div class="col-md-5">
<div class="pull-right">
{!! Former::open('clients/bulk')->addClass('mainForm') !!}
<div style="display:none">

View File

@ -42,8 +42,12 @@
});
@endif
function refreshDatatable() {
window.dataTable.api().ajax.reload();
}
function load_{{ $class }}() {
jQuery('.{{ $class }}').dataTable({
window.dataTable = jQuery('.{{ $class }}').dataTable({
"fnRowCallback": function(row, data) {
if (data[0].indexOf('ENTITY_DELETED') > 0) {
$(row).addClass('entityDeleted');

View File

@ -59,13 +59,22 @@
->data_bind('combobox: client_id')
->addGroupClass('client-select') !!}
@if (!$expense || ($expense && !$expense->invoice_id))
@if (!$expense || ($expense && !$expense->invoice_id && !$expense->client_id))
{!! Former::checkbox('should_be_invoiced')
->text(trans('texts.should_be_invoiced'))
->data_bind('checked: should_be_invoiced() || client_id(), enable: !client_id()')
->label(' ') !!}<br/>
->label(' ') !!}
@endif
@if (!$expense || ($expense && ! $expense->isExchanged()))
{!! Former::checkbox('convert_currency')
->text(trans('texts.convert_currency'))
->data_bind('checked: convert_currency')
->label(' ') !!}
@endif
<br/>
<div style="display:none" data-bind="visible: enableExchangeRate">
<span style="display:none" data-bind="visible: !client_id()">
{!! Former::select('invoice_currency_id')->addOption('','')
->label(trans('texts.invoice_currency'))
@ -87,12 +96,13 @@
->addGroupClass('converted-amount')
->data_bind("value: convertedAmount, enable: enableExchangeRate")
->append('<span data-bind="html: invoiceCurrencyCode"></span>') !!}
</div>
</div>
<div class="col-md-6">
{!! Former::textarea('public_notes')->style('height:255px') !!}
{!! Former::textarea('private_notes')->style('height:255px') !!}
{!! Former::textarea('public_notes')->rows(8) !!}
{!! Former::textarea('private_notes')->rows(8) !!}
</div>
</div>
</div>
@ -150,7 +160,7 @@
}
$vendorSelect.combobox();
$('#expense_date').datepicker('update', new Date());
$('#expense_date').datepicker('update', '{{ $expense ? $expense->expense_date : 'new Date()' }}');
$('.expense_date .input-group-addon').click(function() {
toggleDatePicker('expense_date');
@ -194,6 +204,7 @@
self.amount = ko.observable();
self.exchange_rate = ko.observable(1);
self.should_be_invoiced = ko.observable();
self.convert_currency = ko.observable(false);
if (data) {
ko.mapping.fromJS(data, {}, this);
@ -230,9 +241,14 @@
});
self.enableExchangeRate = ko.computed(function() {
if (self.convert_currency()) {
return true;
}
var expenseCurrencyId = self.expense_currency_id() || self.account_currency_id();
var invoiceCurrencyId = self.invoice_currency_id() || self.account_currency_id();
return expenseCurrencyId != invoiceCurrencyId;
return expenseCurrencyId != invoiceCurrencyId
|| invoiceCurrencyId != self.account_currency_id()
|| expenseCurrencyId != self.account_currency_id();
})
};

View File

@ -259,8 +259,10 @@
}
function showSearch() {
$('#search-form').show();
$('#search').typeahead('setQuery', '');
$('#navbar-options').hide();
$('#search-form').show();
if (window.hasOwnProperty('searchData')) {
$('#search').focus();
} else {
@ -289,7 +291,6 @@
}
function hideSearch() {
$('#search').typeahead('setQuery', '');
$('#search-form').hide();
$('#navbar-options').show();
}
@ -299,7 +300,7 @@
$(".alert-hide").fadeOut();
}, 3000);
$('#search').blur(function(){
$('#search').blur(function(event){
hideSearch();
});
@ -331,7 +332,7 @@
showSignUp();
@endif
$('ul.navbar-settings, ul.navbar-history').hover(function () {
$('ul.navbar-settings, ul.navbar-search').hover(function () {
if ($('.user-accounts').css('display') == 'block') {
$('.user-accounts').dropdown('toggle');
}
@ -351,6 +352,14 @@
});
@endif
// Focus the search input if the user clicks forward slash
$('body').keypress(function(event) {
if (event.which == 47) {
event.preventDefault();
showSearch();
}
});
});
</script>
@ -396,7 +405,7 @@
<div class="btn-group user-dropdown">
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown">
<div id="myAccountButton" class="ellipsis" style="max-width:{{ Utils::isPro() && ! Utils::isTrial() ? '100' : '70' }}px">
<div id="myAccountButton" class="ellipsis" style="max-width:{{ Utils::isPro() && ! Utils::isTrial() ? '130' : '100' }}px">
@if (session(SESSION_USER_ACCOUNTS) && count(session(SESSION_USER_ACCOUNTS)))
{{ Auth::user()->account->getDisplayName() }}
@else
@ -466,10 +475,10 @@
</ul>
<ul class="nav navbar-nav navbar-right navbar-history">
<ul class="nav navbar-nav navbar-right navbar-search">
<li class="dropdown">
<a href="{{ Utils::getLastURL() }}" class="dropdown-toggle">
<span class="glyphicon glyphicon-time" title="{{ trans('texts.history') }}"/>
<a href="#" onclick="showSearch()">
<span class="glyphicon glyphicon-search" title="{{ trans('texts.search') }}"/>
</a>
<ul class="dropdown-menu">
@if (count(Session::get(RECENTLY_VIEWED)) == 0)
@ -484,19 +493,11 @@
</ul>
</li>
</ul>
<ul class="nav navbar-nav navbar-right navbar-settings">
<li class="dropdown">
<a href="#" onclick="showSearch()">
<span class="glyphicon glyphicon-search" title="{{ trans('texts.search') }}"/>
</a>
</li>
</ul>
</div>
<form id="search-form" class="navbar-form navbar-right" role="search" style="display:none">
<div class="form-group">
<input type="text" id="search" style="width: 300px;padding-top:0px;padding-bottom:0px"
<input type="text" id="search" style="width: 240px;padding-top:0px;padding-bottom:0px"
class="form-control" placeholder="{{ trans('texts.search') }}">
</div>
</form>

View File

@ -101,7 +101,11 @@
function setTrashVisible() {
var checked = $('#trashed').is(':checked');
window.location = '{{ URL::to('view_archive/' . $entityType) }}' + (checked ? '/true' : '/false');
var url = '{{ URL::to('view_archive/' . $entityType) }}' + (checked ? '/true' : '/false');
$.get(url, function(data) {
refreshDatatable();
})
}
$(function() {

View File

@ -127,17 +127,16 @@
//console.log(JSON.stringify(products));
var arc = d3.svg.arc()
.innerRadius(function(d) { return d.r - 2 })
.outerRadius(function(d) { return d.r - 8 })
.innerRadius(function(d) { return d.r })
.outerRadius(function(d) { return d.r - 5 })
.startAngle(0);
var fullArc = d3.svg.arc()
.innerRadius(function(d) { return d.r - 3 })
.outerRadius(function(d) { return d.r - 7 })
.innerRadius(function(d) { return d.r })
.outerRadius(function(d) { return d.r - 5 })
.startAngle(0)
.endAngle(2 * Math.PI);
var diameter = 800,
format = d3.format(",d");
//color = d3.scale.category10();
@ -233,7 +232,7 @@
.attr("class", "no-pointer-events")
.attr("class", "animate-grow")
.attr("d", arc)
.style("fill", function(d, i) { return 'grey'; });
.style("fill", function(d, i) { return '#2e9e49'; });
d3.selectAll("path.animate-grow")
.transition()
@ -245,7 +244,7 @@
.transition()
.duration(1000)
.style("fill", function(d, i) {
return d.displayAge == -1 ? 'grey' : color(d.displayAge);
return d.displayAge == -1 ? 'white' : 'red';
});
selection.exit().remove();

View File

@ -2,7 +2,10 @@
// This is global bootstrap for autoloading
use Codeception\Util\Fixtures;
Fixtures::add('url', 'http://ninja.dev:8000');
Fixtures::add('username', 'user@example.com');
Fixtures::add('password', 'password');
Fixtures::add('gateway_key', '');
Fixtures::add('api_secret', 'password');
Fixtures::add('stripe_secret_key', '');
Fixtures::add('stripe_publishable_key', '');

View File

@ -29,6 +29,7 @@ class AcceptanceTester extends \Codeception\Actor
//if ($I->loadSessionSnapshot('login')) return;
$I->amOnPage('/login');
$I->see('Login');
$I->fillField(['name' => 'email'], Fixtures::get('username'));
$I->fillField(['name' => 'password'], Fixtures::get('password'));
$I->click('Login');

View File

@ -8,16 +8,15 @@ class_name: AcceptanceTester
modules:
enabled:
- WebDriver:
url: 'http://ninja.dev/'
host: 127.0.0.1
url: 'http://ninja.dev:8000/'
window_size: 1024x768
wait: 5
browser: phantomjs
browser: firefox
capabilities:
unexpectedAlertBehaviour: 'accept'
webStorageEnabled: true
- Db:
dsn: 'mysql:dbname=ninja;host=localhost;'
dsn: 'mysql:dbname=ninja;host=127.0.0.1;'
user: 'ninja'
password: 'ninja'
dump: tests/_data/dump.sql

View File

@ -117,6 +117,8 @@ class APICest
$response = curl_exec($curl);
curl_close($curl);
//Debug::debug('Response: ' . $response);
return json_decode($response);
}
}

View File

@ -1,7 +1,6 @@
<?php
use Codeception\Util\Fixtures;
use \AcceptanceTester;
use Faker\Factory;
class CheckBalanceCest

View File

@ -1,6 +1,5 @@
<?php
use \AcceptanceTester;
use Faker\Factory;
use Codeception\Util\Fixtures;

View File

@ -1,6 +1,5 @@
<?php
use \AcceptanceTester;
use App\Models\Credit;
use Faker\Factory;
use Codeception\Util\Fixtures;

View File

@ -1,7 +1,6 @@
<?php
use Codeception\Util\Fixtures;
use \AcceptanceTester;
use Faker\Factory;
class GoProCest

View File

@ -1,6 +1,5 @@
<?php
use \AcceptanceTester;
use Faker\Factory;
class InvoiceCest

View File

@ -1,6 +1,5 @@
<?php
use \AcceptanceTester;
use Faker\Factory;
class InvoiceDesignCest

View File

@ -1,7 +1,6 @@
/<?php
use Codeception\Util\Fixtures;
use \AcceptanceTester;
use Faker\Factory;
class OnlinePaymentCest
@ -27,9 +26,9 @@ class OnlinePaymentCest
$I->amOnPage('/settings/online_payments');
if (strpos($I->grabFromCurrentUrl(), 'create') !== false) {
$I->fillField(['name' =>'23_apiKey'], Fixtures::get('secret_key'));
$I->fillField(['name' =>'23_apiKey'], env('stripe_secret_key') ?: Fixtures::get('stripe_secret_key'));
// Fails to load StripeJS causing "ReferenceError: Can't find variable: Stripe"
//$I->fillField(['name' =>'publishable_key'], Fixtures::get('publishable_key'));
//$I->fillField(['name' =>'stripe_publishable_key'], env('stripe_secret_key') ?: Fixtures::get('stripe_publishable_key'));
$I->selectOption('#token_billing_type_id', 4);
$I->click('Save');
$I->see('Successfully created gateway');

View File

@ -1,6 +1,5 @@
<?php
use \AcceptanceTester;
use App\Models\Payment;
use Faker\Factory;

View File

@ -1,6 +1,5 @@
<?php
use \AcceptanceTester;
use Faker\Factory;
class TaskCest

View File

@ -1,7 +1,6 @@
/<?php
use Codeception\Util\Fixtures;
use \AcceptanceTester;
use Faker\Factory;
class TaxRatesCest
@ -75,6 +74,7 @@ class TaxRatesCest
// check total is right before saving
$I->see("\${$total}");
$I->click('Save');
$I->wait(1);
$I->see($clientEmail);
// check total is right after saving

View File

@ -9,7 +9,7 @@ modules:
enabled:
- \Helper\Functional
- PhpBrowser:
url: 'http://ninja.dev'
url: 'http://ninja.dev:8000'
curl:
CURLOPT_RETURNTRANSFER: true
- Laravel5: