mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-09 20:52:56 +01:00
Merge branch 'release-4.1.0'
This commit is contained in:
commit
3cbf29afad
@ -1,6 +1,6 @@
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
APP_URL=http://ninja.dev
|
||||
APP_URL=http://www.ninja.test
|
||||
APP_KEY=SomeRandomStringSomeRandomString
|
||||
APP_CIPHER=AES-256-CBC
|
||||
APP_LOCALE=en
|
||||
@ -48,7 +48,7 @@ API_SECRET=password
|
||||
|
||||
#GOOGLE_CLIENT_ID=
|
||||
#GOOGLE_CLIENT_SECRET=
|
||||
#GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google
|
||||
#GOOGLE_OAUTH_REDIRECT=http://ninja.test/auth/google
|
||||
|
||||
GOOGLE_MAPS_ENABLED=true
|
||||
#GOOGLE_MAPS_API_KEY=
|
||||
@ -104,4 +104,4 @@ CLOUDFLARE_DNS_ENABLED=false
|
||||
CLOUDFLARE_API_KEY=
|
||||
CLOUDFLARE_EMAIL=
|
||||
CLOUDFLARE_TARGET_IP_ADDRESS=
|
||||
CLOUDFLARE_ZONE_IDS={}
|
||||
CLOUDFLARE_ZONE_IDS={}
|
||||
|
@ -1,6 +1,6 @@
|
||||
APP_ENV=development
|
||||
APP_DEBUG=false
|
||||
APP_URL=http://ninja.dev
|
||||
APP_URL=http://ninja.test
|
||||
APP_KEY=SomeRandomStringSomeRandomString
|
||||
APP_CIPHER=AES-256-CBC
|
||||
APP_LOCALE=en
|
||||
|
14
.travis.yml
14
.travis.yml
@ -11,11 +11,12 @@ php:
|
||||
# - 5.6
|
||||
# - 7.0
|
||||
- 7.1
|
||||
# - 7.2
|
||||
# - hhvm
|
||||
|
||||
addons:
|
||||
hosts:
|
||||
- ninja.dev
|
||||
- www.ninja.test
|
||||
|
||||
cache:
|
||||
directories:
|
||||
@ -62,14 +63,14 @@ before_script:
|
||||
- php artisan migrate --database=db-ninja-0 --seed --no-interaction
|
||||
- php artisan migrate --database=db-ninja-1 --seed --no-interaction
|
||||
- php artisan migrate --database=db-ninja-2 --seed --no-interaction
|
||||
# Start webserver on ninja.dev:8000
|
||||
- php artisan serve --host=ninja.dev --port=8000 & # '&' allows to run in background
|
||||
# Start webserver on ninja.test:8000
|
||||
- php artisan serve --host=www.ninja.test --port=8000 & # '&' allows to run in background
|
||||
# Start PhantomJS
|
||||
- phantomjs --webdriver=4444 & # '&' allows to run in background
|
||||
# Give it some time to start
|
||||
- sleep 5
|
||||
# Make sure the app is up-to-date
|
||||
- curl -L http://ninja.dev:8000/update
|
||||
- curl -L http://www.ninja.test:8000/update
|
||||
- php artisan ninja:create-test-data 4 true
|
||||
- php artisan db:seed --no-interaction --class=UserTableSeeder # development seed
|
||||
- sed -i 's/DB_TYPE=db-ninja-1/DB_TYPE=db-ninja-2/g' .env
|
||||
@ -90,6 +91,7 @@ script:
|
||||
- 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 GatewayFeesCest.php
|
||||
- php ./vendor/codeception/codeception/codecept run --debug acceptance DiscountCest.php
|
||||
- php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php
|
||||
|
||||
#- sed -i 's/NINJA_DEV=true/NINJA_PROD=true/g' .env
|
||||
@ -100,6 +102,8 @@ after_script:
|
||||
- php artisan ninja:check-data --no-interaction --database='db-ninja-2'
|
||||
- php artisan ninja:init-lookup --validate=true --database='db-ninja-1'
|
||||
- php artisan ninja:init-lookup --validate=true --database='db-ninja-2'
|
||||
- cat storage/logs/laravel-error.log
|
||||
- cat storage/logs/laravel-info.log
|
||||
- cat .env
|
||||
- mysql -u root -e 'select * from lookup_companies;' ninja0
|
||||
- mysql -u root -e 'select * from lookup_accounts;' ninja0
|
||||
@ -119,8 +123,6 @@ after_script:
|
||||
- mysql -u root -e 'select * from accounts;' ninja
|
||||
- mysql -u root -e 'select * from fonts;' ninja
|
||||
- mysql -u root -e 'select * from banks;' ninja
|
||||
- cat storage/logs/laravel-error.log
|
||||
- cat storage/logs/laravel-info.log
|
||||
- FILES=$(find tests/_output -type f -name '*.png' | sort -nr)
|
||||
- for i in $FILES; do echo $i; base64 "$i"; break; done
|
||||
|
||||
|
2
LICENSE
2
LICENSE
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2017 by Hillel Coren
|
||||
Copyright (c) 2018 by Hillel Coren
|
||||
Invoice Ninja * https://www.invoiceninja.com
|
||||
"CREATE. SEND. GET PAID"
|
||||
|
||||
|
@ -6,6 +6,7 @@ use Illuminate\Console\Command;
|
||||
use App\Models\DbServer;
|
||||
use App\Models\User;
|
||||
use App\Models\Company;
|
||||
use App\Libraries\CurlUtils;
|
||||
|
||||
class CalculatePayouts extends Command
|
||||
{
|
||||
@ -14,14 +15,14 @@ class CalculatePayouts extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'ninja:calculate-payouts';
|
||||
protected $signature = 'ninja:calculate-payouts {--type=} {--url=} {--password=}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Calculate referral payouts';
|
||||
protected $description = 'Calculate payouts';
|
||||
|
||||
|
||||
/**
|
||||
@ -42,7 +43,20 @@ class CalculatePayouts extends Command
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Running CalculatePayouts...');
|
||||
$type = strtolower($this->option('type'));
|
||||
|
||||
switch ($type) {
|
||||
case 'referral':
|
||||
$this->referralPayouts();
|
||||
break;
|
||||
case 'reseller':
|
||||
$this->resellerPayouts();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function referralPayouts()
|
||||
{
|
||||
$servers = DbServer::orderBy('id')->get(['name']);
|
||||
$userMap = [];
|
||||
|
||||
@ -81,10 +95,22 @@ class CalculatePayouts extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function resellerPayouts()
|
||||
{
|
||||
$response = CurlUtils::post($this->option('url') . '/reseller_stats', [
|
||||
'password' => $this->option('password')
|
||||
]);
|
||||
|
||||
$this->info('Response:');
|
||||
$this->info($response);
|
||||
}
|
||||
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
|
||||
['type', null, InputOption::VALUE_OPTIONAL, 'Type', null],
|
||||
['url', null, InputOption::VALUE_OPTIONAL, 'Url', null],
|
||||
['password', null, InputOption::VALUE_OPTIONAL, 'Password', null],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ class CheckData extends Command
|
||||
$this->checkDraftSentInvoices();
|
||||
}
|
||||
|
||||
$this->checkInvoices();
|
||||
//$this->checkInvoices();
|
||||
$this->checkInvoiceBalances();
|
||||
$this->checkClientBalances();
|
||||
$this->checkContacts();
|
||||
|
@ -2,9 +2,12 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Libraries\CurlUtils;
|
||||
use Carbon;
|
||||
use Str;
|
||||
use Cache;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Currency;
|
||||
use App\Ninja\Mailers\ContactMailer as Mailer;
|
||||
use App\Ninja\Mailers\UserMailer;
|
||||
use App\Ninja\Repositories\AccountRepository;
|
||||
@ -71,8 +74,9 @@ class SendReminders extends Command
|
||||
}
|
||||
|
||||
$this->chargeLateFees();
|
||||
$this->setReminderEmails();
|
||||
$this->sendReminderEmails();
|
||||
$this->sendScheduledReports();
|
||||
$this->loadExchangeRates();
|
||||
|
||||
$this->info('Done');
|
||||
|
||||
@ -112,7 +116,7 @@ class SendReminders extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function setReminderEmails()
|
||||
private function sendReminderEmails()
|
||||
{
|
||||
$accounts = $this->accountRepo->findWithReminders();
|
||||
$this->info(count($accounts) . ' accounts found with reminders');
|
||||
@ -122,15 +126,31 @@ class SendReminders extends Command
|
||||
continue;
|
||||
}
|
||||
|
||||
// standard reminders
|
||||
$invoices = $this->invoiceRepo->findNeedingReminding($account);
|
||||
$this->info($account->name . ': ' . count($invoices) . ' invoices found');
|
||||
|
||||
foreach ($invoices as $invoice) {
|
||||
if ($reminder = $account->getInvoiceReminder($invoice)) {
|
||||
if ($invoice->last_sent_date == date('Y-m-d')) {
|
||||
continue;
|
||||
}
|
||||
$this->info('Send email: ' . $invoice->id);
|
||||
$this->mailer->sendInvoice($invoice, $reminder);
|
||||
}
|
||||
}
|
||||
|
||||
// endless reminders
|
||||
$invoices = $this->invoiceRepo->findNeedingEndlessReminding($account);
|
||||
$this->info($account->name . ': ' . count($invoices) . ' endless invoices found');
|
||||
|
||||
foreach ($invoices as $invoice) {
|
||||
if ($invoice->last_sent_date == date('Y-m-d')) {
|
||||
continue;
|
||||
}
|
||||
$this->info('Send email: ' . $invoice->id);
|
||||
$this->mailer->sendInvoice($invoice, 'reminder4');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,6 +183,22 @@ class SendReminders extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function loadExchangeRates()
|
||||
{
|
||||
$this->info('Loading latest exchange rates...');
|
||||
|
||||
$data = CurlUtils::get(config('ninja.exchange_rates_url'));
|
||||
$data = json_decode($data);
|
||||
|
||||
Currency::whereCode('EUR')->update(['exchange_rate' => 1]);
|
||||
|
||||
foreach ($data->rates as $code => $rate) {
|
||||
Currency::whereCode($code)->update(['exchange_rate' => $rate]);
|
||||
}
|
||||
|
||||
Cache::forget('currencies');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
|
@ -23,6 +23,7 @@ if (! defined('APP_NAME')) {
|
||||
define('ENTITY_CREDIT', 'credit');
|
||||
define('ENTITY_QUOTE', 'quote');
|
||||
define('ENTITY_TASK', 'task');
|
||||
define('ENTITY_TASK_STATUS', 'task_status');
|
||||
define('ENTITY_ACCOUNT_GATEWAY', 'account_gateway');
|
||||
define('ENTITY_USER', 'user');
|
||||
define('ENTITY_TOKEN', 'token');
|
||||
@ -204,10 +205,10 @@ if (! defined('APP_NAME')) {
|
||||
define('PAYMENT_STATUS_PARTIALLY_REFUNDED', 5);
|
||||
define('PAYMENT_STATUS_REFUNDED', 6);
|
||||
|
||||
define('TASK_STATUS_LOGGED', 1);
|
||||
define('TASK_STATUS_RUNNING', 2);
|
||||
define('TASK_STATUS_INVOICED', 3);
|
||||
define('TASK_STATUS_PAID', 4);
|
||||
define('TASK_STATUS_LOGGED', -1);
|
||||
define('TASK_STATUS_RUNNING', -2);
|
||||
define('TASK_STATUS_INVOICED', -3);
|
||||
define('TASK_STATUS_PAID', -4);
|
||||
|
||||
define('EXPENSE_STATUS_LOGGED', 1);
|
||||
define('EXPENSE_STATUS_PENDING', 2);
|
||||
@ -229,6 +230,7 @@ if (! defined('APP_NAME')) {
|
||||
define('FREQUENCY_FOUR_MONTHS', 7);
|
||||
define('FREQUENCY_SIX_MONTHS', 8);
|
||||
define('FREQUENCY_ANNUALLY', 9);
|
||||
define('FREQUENCY_TWO_YEARS', 10);
|
||||
|
||||
define('REPORT_FREQUENCY_DAILY', 'daily');
|
||||
define('REPORT_FREQUENCY_WEEKLY', 'weekly');
|
||||
@ -306,6 +308,18 @@ if (! defined('APP_NAME')) {
|
||||
define('EVENT_DELETE_QUOTE', 7);
|
||||
define('EVENT_UPDATE_INVOICE', 8);
|
||||
define('EVENT_DELETE_INVOICE', 9);
|
||||
define('EVENT_UPDATE_CLIENT', 10);
|
||||
define('EVENT_DELETE_CLIENT', 11);
|
||||
define('EVENT_DELETE_PAYMENT', 12);
|
||||
define('EVENT_UPDATE_VENDOR', 13);
|
||||
define('EVENT_DELETE_VENDOR', 14);
|
||||
define('EVENT_CREATE_EXPENSE', 15);
|
||||
define('EVENT_UPDATE_EXPENSE', 16);
|
||||
define('EVENT_DELETE_EXPENSE', 17);
|
||||
define('EVENT_CREATE_TASK', 18);
|
||||
define('EVENT_UPDATE_TASK', 19);
|
||||
define('EVENT_DELETE_TASK', 20);
|
||||
define('EVENT_APPROVE_QUOTE', 21);
|
||||
|
||||
define('REQUESTED_PRO_PLAN', 'REQUESTED_PRO_PLAN');
|
||||
define('NINJA_ACCOUNT_KEY', env('NINJA_ACCOUNT_KEY', 'zg4ylmzDkdkPOT8yoKQw9LTWaoZJx79h'));
|
||||
@ -317,7 +331,7 @@ if (! defined('APP_NAME')) {
|
||||
define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com'));
|
||||
define('NINJA_DOCS_URL', env('NINJA_DOCS_URL', 'http://docs.invoiceninja.com/en/latest'));
|
||||
define('NINJA_DATE', '2000-01-01');
|
||||
define('NINJA_VERSION', '4.0.1' . env('NINJA_VERSION_SUFFIX'));
|
||||
define('NINJA_VERSION', '4.1.0' . env('NINJA_VERSION_SUFFIX'));
|
||||
|
||||
define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'));
|
||||
define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja'));
|
||||
@ -443,6 +457,7 @@ if (! defined('APP_NAME')) {
|
||||
define('TEMPLATE_REMINDER1', 'reminder1');
|
||||
define('TEMPLATE_REMINDER2', 'reminder2');
|
||||
define('TEMPLATE_REMINDER3', 'reminder3');
|
||||
define('TEMPLATE_REMINDER4', 'reminder4');
|
||||
|
||||
define('RESET_FREQUENCY_DAILY', 1);
|
||||
define('RESET_FREQUENCY_WEEKLY', 2);
|
||||
|
@ -30,9 +30,10 @@ class Handler extends ExceptionHandler
|
||||
protected $dontReport = [
|
||||
TokenMismatchException::class,
|
||||
ModelNotFoundException::class,
|
||||
ValidationException::class,
|
||||
\Illuminate\Validation\ValidationException::class,
|
||||
//AuthorizationException::class,
|
||||
//HttpException::class,
|
||||
//ValidationException::class,
|
||||
];
|
||||
|
||||
/**
|
||||
@ -50,6 +51,10 @@ class Handler extends ExceptionHandler
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! class_exists('Utils')) {
|
||||
return parent::report($e);
|
||||
}
|
||||
|
||||
if (Crawler::isCrawler()) {
|
||||
return false;
|
||||
}
|
||||
@ -93,6 +98,10 @@ class Handler extends ExceptionHandler
|
||||
return Redirect::to('/');
|
||||
}
|
||||
|
||||
if (! class_exists('Utils')) {
|
||||
return parent::render($request, $e);
|
||||
}
|
||||
|
||||
if ($e instanceof TokenMismatchException) {
|
||||
if (! in_array($request->path(), ['get_started', 'save_sidebar_state'])) {
|
||||
// https://gist.github.com/jrmadsen67/bd0f9ad0ef1ed6bb594e
|
||||
|
@ -536,6 +536,8 @@ class AccountController extends BaseController
|
||||
$client->work_phone = '(212) 555-0000';
|
||||
$client->work_email = 'sample@example.com';
|
||||
$client->balance = 100;
|
||||
$client->vat_number = $account->vat_number ? '1234567890' : '';
|
||||
$client->id_number = $account->id_number ? '1234567890' : '';
|
||||
|
||||
$invoice->invoice_number = '0000';
|
||||
$invoice->invoice_date = Utils::fromSqlDate(date('Y-m-d'));
|
||||
@ -843,6 +845,9 @@ class AccountController extends BaseController
|
||||
$account->account_email_settings->{"late_fee{$number}_percent"} = Input::get("late_fee{$number}_percent");
|
||||
}
|
||||
|
||||
$account->enable_reminder4 = Input::get('enable_reminder4') ? true : false;
|
||||
$account->account_email_settings->frequency_id_reminder4 = Input::get('frequency_id_reminder4');
|
||||
|
||||
$account->save();
|
||||
$account->account_email_settings->save();
|
||||
|
||||
@ -875,6 +880,7 @@ class AccountController extends BaseController
|
||||
|
||||
$account->fill_products = Input::get('fill_products') ? true : false;
|
||||
$account->update_products = Input::get('update_products') ? true : false;
|
||||
$account->convert_products = Input::get('convert_products') ? true : false;
|
||||
$account->save();
|
||||
|
||||
Session::flash('message', trans('texts.updated_settings'));
|
||||
|
@ -391,7 +391,7 @@ class AppController extends BaseController
|
||||
|
||||
public function stats()
|
||||
{
|
||||
if (! hash_equals(Input::get('password'), env('RESELLER_PASSWORD'))) {
|
||||
if (! hash_equals(Input::get('password') ?: '', env('RESELLER_PASSWORD'))) {
|
||||
sleep(3);
|
||||
|
||||
return '';
|
||||
|
@ -11,6 +11,8 @@ use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Event;
|
||||
use Cache;
|
||||
use Lang;
|
||||
use Str;
|
||||
use Cookie;
|
||||
use App\Events\UserLoggedIn;
|
||||
use App\Http\Requests\ValidateTwoFactorRequest;
|
||||
|
||||
@ -139,9 +141,18 @@ class LoginController extends Controller
|
||||
private function authenticated(Request $request, Authenticatable $user)
|
||||
{
|
||||
if ($user->google_2fa_secret) {
|
||||
auth()->logout();
|
||||
session()->put('2fa:user:id', $user->id);
|
||||
return redirect('/validate_two_factor/' . $user->account->account_key);
|
||||
$cookie = false;
|
||||
if ($user->remember_2fa_token) {
|
||||
$cookie = Cookie::get('remember_2fa_' . sha1($user->id));
|
||||
}
|
||||
|
||||
if ($cookie && hash_equals($user->remember_2fa_token, $cookie)) {
|
||||
// do nothing
|
||||
} else {
|
||||
auth()->logout();
|
||||
session()->put('2fa:user:id', $user->id);
|
||||
return redirect('/validate_two_factor/' . $user->account->account_key);
|
||||
}
|
||||
}
|
||||
|
||||
Event::fire(new UserLoggedIn());
|
||||
@ -180,6 +191,16 @@ class LoginController extends Controller
|
||||
auth()->loginUsingId($userId);
|
||||
Event::fire(new UserLoggedIn());
|
||||
|
||||
if ($trust = request()->trust) {
|
||||
$user = auth()->user();
|
||||
if (! $user->remember_2fa_token) {
|
||||
$user->remember_2fa_token = Str::random(60);
|
||||
$user->save();
|
||||
}
|
||||
$minutes = $trust == 30 ? 60 * 27 * 30 : 2628000;
|
||||
cookie()->queue('remember_2fa_' . sha1($user->id), $user->remember_2fa_token, $minutes);
|
||||
}
|
||||
|
||||
return redirect()->intended($this->redirectTo);
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ use Utils;
|
||||
/**
|
||||
* @SWG\Swagger(
|
||||
* schemes={"http","https"},
|
||||
* host="ninja.dev",
|
||||
* host="ninja.test",
|
||||
* basePath="/api/v1",
|
||||
* produces={"application/json"},
|
||||
* @SWG\Info(
|
||||
@ -155,7 +155,10 @@ class BaseAPIController extends Controller
|
||||
}
|
||||
|
||||
if (is_a($query, "Illuminate\Database\Eloquent\Builder")) {
|
||||
$limit = min(MAX_API_PAGE_SIZE, Input::get('per_page', DEFAULT_API_PAGE_SIZE));
|
||||
$limit = Input::get('per_page', DEFAULT_API_PAGE_SIZE);
|
||||
if (Utils::isNinja()) {
|
||||
$limit = min(MAX_API_PAGE_SIZE, $limit);
|
||||
}
|
||||
$paginator = $query->paginate($limit);
|
||||
$query = $paginator->getCollection();
|
||||
$resource = new Collection($query, $transformer, $entityType);
|
||||
|
@ -39,7 +39,7 @@ class BaseController extends Controller
|
||||
if ($action == 'restore' && count($ids) == 1) {
|
||||
return redirect("{$entityTypes}/" . $ids[0]);
|
||||
// when viewing from a datatable list
|
||||
} elseif (strpos($referer, '/clients/')) {
|
||||
} elseif (strpos($referer, '/clients/') || strpos($referer, '/projects/')) {
|
||||
return redirect($referer);
|
||||
} elseif ($isDatatable || ($action == 'archive' || $action == 'delete')) {
|
||||
return redirect("{$entityTypes}");
|
||||
|
@ -39,7 +39,7 @@ class LoginController extends Controller
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('guest:client', ['except' => 'logout']);
|
||||
$this->middleware('guest:client', ['except' => 'getLogoutWrapper']);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -170,4 +170,16 @@ class LoginController extends Controller
|
||||
return view('clientauth.sessionexpired')->with(['clientauth' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function getLogoutWrapper(Request $request)
|
||||
{
|
||||
$contactKey = session('contact_key');
|
||||
|
||||
self::logout($request);
|
||||
|
||||
return redirect('/client/dashboard/' . $contactKey);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -146,19 +146,24 @@ class HomeController extends BaseController
|
||||
}
|
||||
|
||||
Mail::raw($message, function ($message) {
|
||||
$subject = 'Customer Message: ';
|
||||
$subject = 'Customer Message [';
|
||||
if (Utils::isNinjaProd()) {
|
||||
$subject .= str_replace('db-', '', config('database.default'));
|
||||
$subject .= str_replace('db-ninja-', '', config('database.default'));
|
||||
$account = Auth::user()->account;
|
||||
if ($account->isEnterprise()) {
|
||||
if ($account->isTrial()) {
|
||||
$subject .= 'T';
|
||||
} elseif ($account->isEnterprise()) {
|
||||
$subject .= 'E';
|
||||
} elseif ($account->isPro()) {
|
||||
$subject .= 'P';
|
||||
} else {
|
||||
$subject .= 'H';
|
||||
}
|
||||
$subject .= '] ';
|
||||
} else {
|
||||
$subject .= 'Self-Host';
|
||||
$subject .= 'Self-Host | ';
|
||||
}
|
||||
$subject .= ' | ' . date('M jS, h:ia');
|
||||
$subject .= date('M jS, g:ia');
|
||||
$message->to(env('CONTACT_EMAIL', 'contact@invoiceninja.com'))
|
||||
->from(CONTACT_EMAIL, Auth::user()->present()->fullName)
|
||||
->replyTo(Auth::user()->email, Auth::user()->present()->fullName)
|
||||
|
@ -278,6 +278,16 @@ class InvoiceApiController extends BaseAPIController
|
||||
unset($data['invoice_items'][0]['tax_rate2']);
|
||||
} else {
|
||||
foreach ($data['invoice_items'] as $index => $item) {
|
||||
// check for multiple products
|
||||
if ($productKey = array_get($item, 'product_key')) {
|
||||
$parts = explode(',', $productKey);
|
||||
if (count($parts) > 1 && Product::findProductByKey($parts[0])) {
|
||||
foreach ($parts as $index => $productKey) {
|
||||
$data['invoice_items'][$index] = self::prepareItem(['product_key' => $productKey]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
$data['invoice_items'][$index] = self::prepareItem($item);
|
||||
}
|
||||
}
|
||||
|
@ -603,6 +603,12 @@ class InvoiceController extends BaseController
|
||||
];
|
||||
$invoice->invoice_type_id = intval($invoice->invoice_type_id);
|
||||
|
||||
if ($invoice->client->shipping_address1) {
|
||||
foreach (['address1', 'address2', 'city', 'state', 'postal_code', 'country_id'] as $field) {
|
||||
$invoice->client->$field = $invoice->client->{'shipping_' . $field};
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'invoice' => $invoice,
|
||||
'invoiceDesigns' => InvoiceDesign::getDesigns(),
|
||||
|
@ -258,12 +258,7 @@ class NinjaController extends BaseController
|
||||
if ($productId == PRODUCT_INVOICE_DESIGNS) {
|
||||
return file_get_contents(storage_path() . '/invoice_designs.txt');
|
||||
} else {
|
||||
// temporary fix to enable previous version to work
|
||||
if (Input::get('get_date')) {
|
||||
return $license->created_at->format('Y-m-d');
|
||||
} else {
|
||||
return 'valid';
|
||||
}
|
||||
return $license->created_at->format('Y-m-d');
|
||||
}
|
||||
} else {
|
||||
return RESULT_FAILURE;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Jobs\GenerateProjectChartData;
|
||||
use App\Http\Requests\CreateProjectRequest;
|
||||
use App\Http\Requests\ProjectRequest;
|
||||
use App\Http\Requests\UpdateProjectRequest;
|
||||
@ -49,6 +50,23 @@ class ProjectController extends BaseController
|
||||
return $this->projectService->getDatatable($search, $userId);
|
||||
}
|
||||
|
||||
public function show(ProjectRequest $request)
|
||||
{
|
||||
$account = auth()->user()->account;
|
||||
$project = $request->entity();
|
||||
$chartData = dispatch(new GenerateProjectChartData($project));
|
||||
|
||||
$data = [
|
||||
'account' => auth()->user()->account,
|
||||
'project' => $project,
|
||||
'title' => trans('texts.view_project'),
|
||||
'showBreadcrumbs' => false,
|
||||
'chartData' => $chartData,
|
||||
];
|
||||
|
||||
return View::make('projects.show', $data);
|
||||
}
|
||||
|
||||
public function create(ProjectRequest $request)
|
||||
{
|
||||
$data = [
|
||||
|
@ -156,9 +156,11 @@ class QuoteController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
$invitationKey = $this->invoiceService->approveQuote($invoice, $invitation);
|
||||
Session::flash('message', trans('texts.quote_is_approved'));
|
||||
|
||||
return Redirect::to("view/{$invitationKey}");
|
||||
if ($invoiceInvitationKey = $this->invoiceService->approveQuote($invoice, $invitation)) {
|
||||
Session::flash('message', trans('texts.quote_is_approved'));
|
||||
return Redirect::to("view/{$invoiceInvitationKey}");
|
||||
} else {
|
||||
return Redirect::to("view/{$invitationKey}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ class ReportController extends BaseController
|
||||
|
||||
if (Auth::user()->account->hasFeature(FEATURE_REPORTS)) {
|
||||
$account = Account::where('id', '=', Auth::user()->account->id)
|
||||
->with(['clients.invoices.invoice_items', 'clients.contacts'])
|
||||
->with(['clients.invoices.invoice_items', 'clients.contacts', 'clients.currency'])
|
||||
->first();
|
||||
$account = $account->hideFieldsForViz();
|
||||
$clients = $account->clients;
|
||||
|
@ -149,9 +149,6 @@ class TaskApiController extends BaseAPIController
|
||||
*/
|
||||
public function update(UpdateTaskRequest $request)
|
||||
{
|
||||
if ($request->action) {
|
||||
return $this->handleAction($request);
|
||||
}
|
||||
|
||||
$task = $request->entity();
|
||||
|
||||
|
@ -82,9 +82,9 @@ class TaskController extends BaseController
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function getDatatable($clientPublicId = null)
|
||||
public function getDatatable($clientPublicId = null, $projectPublicId = null)
|
||||
{
|
||||
return $this->taskService->getDatatable($clientPublicId, Input::get('sSearch'));
|
||||
return $this->taskService->getDatatable($clientPublicId, $projectPublicId, Input::get('sSearch'));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -148,9 +148,12 @@ class TaskController extends BaseController
|
||||
public function edit(TaskRequest $request)
|
||||
{
|
||||
$this->checkTimezone();
|
||||
|
||||
$task = $request->entity();
|
||||
|
||||
if (! $task) {
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
$actions = [];
|
||||
if ($task->invoice) {
|
||||
$actions[] = ['url' => URL::to("invoices/{$task->invoice->public_id}/edit"), 'label' => trans('texts.view_invoice')];
|
||||
@ -260,7 +263,8 @@ class TaskController extends BaseController
|
||||
|
||||
if (in_array($action, ['resume', 'stop'])) {
|
||||
$this->taskRepo->save($ids, ['action' => $action]);
|
||||
return Redirect::to('tasks')->withMessage(trans($action == 'stop' ? 'texts.stopped_task' : 'texts.resumed_task'));
|
||||
Session::flash('message', trans($action == 'stop' ? 'texts.stopped_task' : 'texts.resumed_task'));
|
||||
return $this->returnBulk($this->entityType, $action, $ids);
|
||||
} elseif ($action == 'invoice' || $action == 'add_to_invoice') {
|
||||
$tasks = Task::scope($ids)->with('account', 'client', 'project')->orderBy('project_id', 'id')->get();
|
||||
$clientPublicId = false;
|
||||
|
198
app/Http/Controllers/TaskKanbanController.php
Normal file
198
app/Http/Controllers/TaskKanbanController.php
Normal file
@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Task;
|
||||
use App\Models\TaskStatus;
|
||||
use App\Models\Project;
|
||||
use App\Models\Client;
|
||||
|
||||
class TaskKanbanController extends BaseController
|
||||
{
|
||||
/**
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function index($clientPublicId = false, $projectPublicId = false)
|
||||
{
|
||||
$tasks = Task::scope()
|
||||
->with(['project', 'client', 'task_status'])
|
||||
->whereNull('invoice_id')
|
||||
->orderBy('task_status_sort_order')
|
||||
->orderBy('id')
|
||||
->get();
|
||||
|
||||
$statuses = TaskStatus::scope()
|
||||
->orderBy('sort_order')
|
||||
->orderBy('id')
|
||||
->get();
|
||||
|
||||
$projects = Project::scope()->get();
|
||||
$clients = Client::scope()->with(['contacts'])->get();
|
||||
|
||||
// check initial statuses exist
|
||||
if (! $statuses->count()) {
|
||||
$statuses = collect([]);
|
||||
$firstStatus = false;
|
||||
$defaults = [
|
||||
'backlog',
|
||||
'ready_to_do',
|
||||
'in_progress',
|
||||
'done',
|
||||
];
|
||||
for ($i=0; $i<count($defaults); $i++) {
|
||||
$status = TaskStatus::createNew();
|
||||
$status->name = trans('texts.' . $defaults[$i]);
|
||||
$status->sort_order = $i;
|
||||
$status->save();
|
||||
$statuses[] = $status;
|
||||
if (! $firstStatus) {
|
||||
$firstStatus = $status;
|
||||
}
|
||||
}
|
||||
$i = 0;
|
||||
foreach ($tasks as $task) {
|
||||
$task->task_status_id = $firstStatus->id;
|
||||
$task->task_status_sort_order = $i++;
|
||||
$task->save();
|
||||
}
|
||||
// otherwise, check that the tasks orders are correct
|
||||
} else {
|
||||
$firstStatus = $statuses[0];
|
||||
$adjustment = 0;
|
||||
$counts = [];
|
||||
foreach ($tasks as $task) {
|
||||
if (! $task->task_status || $task->task_status->trashed()) {
|
||||
$task->task_status_id = $firstStatus->id;
|
||||
$task->setRelation('task_status', $firstStatus);
|
||||
}
|
||||
if (! isset($counts[$task->task_status_id])) {
|
||||
$counts[$task->task_status_id] = 0;
|
||||
}
|
||||
if ($task->task_status_sort_order != $counts[$task->task_status_id]) {
|
||||
$task->task_status_sort_order = $counts[$task->task_status_id];
|
||||
}
|
||||
$counts[$task->task_status_id]++;
|
||||
if ($task->isDirty()) {
|
||||
$task->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'title' => trans('texts.kanban'),
|
||||
'statuses' => $statuses,
|
||||
'tasks' => $tasks,
|
||||
'clients' => $clients,
|
||||
'projects' => $projects,
|
||||
'clientPublicId' => $clientPublicId,
|
||||
'client' => $clientPublicId ? Client::scope($clientPublicId)->first() : null,
|
||||
'projectPublicId' => $projectPublicId,
|
||||
'project' => $projectPublicId ? Project::scope($projectPublicId)->first() : null,
|
||||
];
|
||||
|
||||
return view('tasks.kanban', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function storeStatus()
|
||||
{
|
||||
$status = TaskStatus::createNew();
|
||||
$status->fill(request()->all());
|
||||
$status->save();
|
||||
|
||||
return response()->json($status);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $publicId
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function updateStatus($publicId)
|
||||
{
|
||||
$status = TaskStatus::scope($publicId)->firstOrFail();
|
||||
|
||||
$origSortOrder = $status->sort_order;
|
||||
$newSortOrder = request('sort_order');
|
||||
|
||||
if ($newSortOrder && $newSortOrder != $origSortOrder) {
|
||||
TaskStatus::scope()
|
||||
->where('sort_order', '>', $origSortOrder)
|
||||
->decrement('sort_order');
|
||||
|
||||
TaskStatus::scope()
|
||||
->where('sort_order', '>=', $newSortOrder)
|
||||
->increment('sort_order');
|
||||
}
|
||||
|
||||
$status->fill(request()->all());
|
||||
$status->save();
|
||||
|
||||
return response()->json($status);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function deleteStatus($publicId)
|
||||
{
|
||||
$status = TaskStatus::scope($publicId)->firstOrFail();
|
||||
$status->delete();
|
||||
|
||||
TaskStatus::scope()
|
||||
->where('sort_order', '>', $status->sort_order)
|
||||
->decrement('sort_order');
|
||||
|
||||
$firstStatus = TaskStatus::scope()
|
||||
->orderBy('sort_order')
|
||||
->first();
|
||||
|
||||
// Move linked tasks to the end of the first status
|
||||
if ($firstStatus) {
|
||||
$firstCount = $firstStatus->tasks->count();
|
||||
Task::scope()
|
||||
->where('task_status_id', '=', $status->id)
|
||||
->increment('task_status_sort_order', $firstCount, [
|
||||
'task_status_id' => $firstStatus->id
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json(['message' => RESULT_SUCCESS]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $publicId
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function updateTask($publicId)
|
||||
{
|
||||
$task = Task::scope($publicId)->firstOrFail();
|
||||
|
||||
$origStatusId = $task->task_status_id;
|
||||
$origSortOrder = $task->task_status_sort_order;
|
||||
|
||||
$newStatusId = TaskStatus::getPrivateId(request('task_status_id'));
|
||||
$newSortOrder = request('task_status_sort_order');
|
||||
|
||||
Task::scope()
|
||||
->where('task_status_id', '=', $origStatusId)
|
||||
->where('task_status_sort_order', '>', $origSortOrder)
|
||||
->decrement('task_status_sort_order');
|
||||
|
||||
Task::scope()
|
||||
->where('task_status_id', '=', $newStatusId)
|
||||
->where('task_status_sort_order', '>=', $newSortOrder)
|
||||
->increment('task_status_sort_order');
|
||||
|
||||
$task->task_status_id = $newStatusId;
|
||||
$task->task_status_sort_order = $newSortOrder;
|
||||
$task->save();
|
||||
|
||||
return response()->json($task);
|
||||
}
|
||||
|
||||
}
|
@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Requests;
|
||||
use App\Models\Task;
|
||||
use App\Models\TaskStatus;
|
||||
use App\Models\Client;
|
||||
use App\Models\Project;
|
||||
|
||||
@ -21,9 +22,10 @@ class TimeTrackerController extends Controller
|
||||
|
||||
$data = [
|
||||
'title' => trans('texts.time_tracker'),
|
||||
'tasks' => Task::scope()->with('project', 'client.contacts')->whereNull('invoice_id')->get(),
|
||||
'tasks' => Task::scope()->with('project', 'client.contacts', 'task_status')->whereNull('invoice_id')->get(),
|
||||
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
|
||||
'projects' => Project::scope()->with('client.contacts')->orderBy('name')->get(),
|
||||
'statuses' => TaskStatus::scope()->orderBy('sort_order')->get(),
|
||||
'account' => $account,
|
||||
];
|
||||
|
||||
|
@ -16,9 +16,13 @@ class TwoFactorController extends Controller
|
||||
}
|
||||
|
||||
$google2fa = new Google2FA();
|
||||
$secret = $google2fa->generateSecretKey();
|
||||
|
||||
session(['2fa:secret' => $secret]);
|
||||
if ($secret = session('2fa:secret')) {
|
||||
// do nothing
|
||||
} else {
|
||||
$secret = $google2fa->generateSecretKey();
|
||||
session(['2fa:secret' => $secret]);
|
||||
}
|
||||
|
||||
$qrCode = $google2fa->getQRCodeGoogleUrl(
|
||||
APP_NAME,
|
||||
@ -37,15 +41,16 @@ class TwoFactorController extends Controller
|
||||
public function enableTwoFactor()
|
||||
{
|
||||
$user = auth()->user();
|
||||
$secret = session()->pull('2fa:secret');
|
||||
$secret = session('2fa:secret');
|
||||
$oneTimePassword = request('one_time_password');
|
||||
|
||||
if (! $secret || ! \Google2FA::verifyKey($secret, $oneTimePassword)) {
|
||||
return redirect('settings/enable_two_factor')->withMessage(trans('texts.invalid_one_time_password'));
|
||||
return redirect('settings/enable_two_factor')->withError(trans('texts.invalid_one_time_password'));
|
||||
} elseif (! $user->google_2fa_secret && $user->phone && $user->confirmed) {
|
||||
$user->google_2fa_secret = Crypt::encrypt($secret);
|
||||
$user->save();
|
||||
|
||||
session()->forget('2fa:secret');
|
||||
session()->flash('message', trans('texts.enabled_two_factor'));
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ class DuplicateSubmissionCheck
|
||||
$lastPage = session(SESSION_LAST_REQUEST_PAGE);
|
||||
$lastTime = session(SESSION_LAST_REQUEST_TIME);
|
||||
|
||||
if ($lastPage == $path && (microtime(true) - $lastTime <= 1)) {
|
||||
if ($lastPage == $path && (microtime(true) - $lastTime <= 1.5)) {
|
||||
return redirect('/')->with('warning', trans('texts.duplicate_post'));
|
||||
}
|
||||
|
||||
|
@ -171,14 +171,18 @@ class StartupCheck
|
||||
if ($data == RESULT_FAILURE) {
|
||||
Session::flash('error', trans('texts.invalid_white_label_license'));
|
||||
} elseif ($data) {
|
||||
$company->plan_term = PLAN_TERM_YEARLY;
|
||||
$company->plan_paid = $data;
|
||||
$date = max(date_create($data), date_create($company->plan_expires));
|
||||
$company->plan_expires = $date->modify('+1 year')->format('Y-m-d');
|
||||
$company->plan = PLAN_WHITE_LABEL;
|
||||
$company->save();
|
||||
$date = date_create($data)->modify('+1 year');
|
||||
if ($date < date_create()) {
|
||||
Session::flash('message', trans('texts.expired_white_label'));
|
||||
} else {
|
||||
$company->plan_term = PLAN_TERM_YEARLY;
|
||||
$company->plan_paid = $data;
|
||||
$company->plan_expires = $date->format('Y-m-d');
|
||||
$company->plan = PLAN_WHITE_LABEL;
|
||||
$company->save();
|
||||
|
||||
Session::flash('message', trans('texts.bought_white_label'));
|
||||
Session::flash('message', trans('texts.bought_white_label'));
|
||||
}
|
||||
} else {
|
||||
Session::flash('error', trans('texts.white_label_license_error'));
|
||||
}
|
||||
@ -206,7 +210,7 @@ class StartupCheck
|
||||
$orderBy = 'id';
|
||||
}
|
||||
$tableData = $class::orderBy($orderBy)->get();
|
||||
if (count($tableData)) {
|
||||
if ($tableData->count()) {
|
||||
Cache::forever($name, $tableData);
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Expense;
|
||||
use App\Models\Task;
|
||||
use App\Models\Project;
|
||||
|
||||
class GenerateCalendarEvents extends Job
|
||||
{
|
||||
@ -26,6 +27,7 @@ class GenerateCalendarEvents extends Job
|
||||
ENTITY_TASK => Task::scope()->with(['project']),
|
||||
ENTITY_PAYMENT => Payment::scope()->with(['invoice']),
|
||||
ENTITY_EXPENSE => Expense::scope()->with(['expense_category']),
|
||||
ENTITY_PROJECT => Project::scope(),
|
||||
];
|
||||
|
||||
foreach ($data as $type => $source) {
|
||||
|
100
app/Jobs/GenerateProjectChartData.php
Normal file
100
app/Jobs/GenerateProjectChartData.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use DateInterval;
|
||||
use DatePeriod;
|
||||
use stdClass;
|
||||
use App\Jobs\Job;
|
||||
use App\Models\Task;
|
||||
use App\Models\Project;
|
||||
|
||||
class GenerateProjectChartData extends Job
|
||||
{
|
||||
public function __construct($project)
|
||||
{
|
||||
$this->project = $project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$project = $this->project;
|
||||
$account = $project->account;
|
||||
$taskMap = [];
|
||||
$startTimestamp = time();
|
||||
$endTimestamp = max(time(), strtotime($project->due_date));
|
||||
$count = 0;
|
||||
$duration = 0;
|
||||
|
||||
foreach ($project->tasks as $task) {
|
||||
$parts = json_decode($task->time_log) ?: [];
|
||||
|
||||
if (! count($parts)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$count++;
|
||||
|
||||
foreach ($parts as $part) {
|
||||
$start = $part[0];
|
||||
$end = (count($part) > 1 && $part[1]) ? $part[1] : time();
|
||||
|
||||
$date = $account->getDateTime();
|
||||
$date->setTimestamp($part[0]);
|
||||
$sqlDate = $date->format('Y-m-d');
|
||||
|
||||
if (! isset($taskMap[$sqlDate])) {
|
||||
$taskMap[$sqlDate] = 0;
|
||||
}
|
||||
|
||||
$taskMap[$sqlDate] += $end - $start;
|
||||
$duration += $end - $start;
|
||||
$startTimestamp = min($startTimestamp, $start);
|
||||
$endTimestamp = max($endTimestamp, $end);
|
||||
}
|
||||
}
|
||||
|
||||
$labels = [];
|
||||
$records = [];
|
||||
$startDate = $account->getDateTime()->setTimestamp($startTimestamp);
|
||||
$endDate = $account->getDateTime()->setTimestamp($endTimestamp);
|
||||
|
||||
$interval = new DateInterval('P1D');
|
||||
$period = new DatePeriod($startDate, $interval, $endDate);
|
||||
$data = [];
|
||||
$total = 0;
|
||||
$color = '51,122,183';
|
||||
|
||||
foreach ($period as $date) {
|
||||
$labels[] = $date->format('m/d/Y');
|
||||
$sqlDate = $date->format('Y-m-d');
|
||||
|
||||
if (isset($taskMap[$sqlDate])) {
|
||||
$total += $taskMap[$sqlDate] / 60 / 60;
|
||||
}
|
||||
|
||||
$records[] = $total;
|
||||
}
|
||||
|
||||
$dataset = new stdClass();
|
||||
$dataset->data = $records;
|
||||
$dataset->label = trans("texts.tasks");
|
||||
$dataset->lineTension = 0;
|
||||
$dataset->borderWidth = 4;
|
||||
$dataset->borderColor = "rgba({$color}, 1)";
|
||||
$dataset->backgroundColor = "rgba({$color}, 0.1)";
|
||||
|
||||
$data = new stdClass();
|
||||
$data->labels = $labels;
|
||||
$data->datasets = [$dataset];
|
||||
$data->count = $count;
|
||||
$data->duration = $duration;
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
@ -96,7 +96,7 @@ class Utils
|
||||
|
||||
public static function requireHTTPS()
|
||||
{
|
||||
if (in_array(Request::root(), ['http://ninja.dev', 'http://ninja.dev:8000', 'http://www.ninja.test'])) {
|
||||
if (in_array(Request::root(), ['http://www.ninja.test', 'http://www.ninja.test:8000'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -911,32 +911,6 @@ class Utils
|
||||
}
|
||||
}
|
||||
|
||||
public static function notifyZapier($subscription, $data)
|
||||
{
|
||||
$curl = curl_init();
|
||||
$jsonEncodedData = json_encode($data);
|
||||
|
||||
$opts = [
|
||||
CURLOPT_URL => $subscription->target_url,
|
||||
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);
|
||||
|
||||
$result = curl_exec($curl);
|
||||
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
|
||||
curl_close($curl);
|
||||
|
||||
if ($status == 410) {
|
||||
$subscription->delete();
|
||||
}
|
||||
}
|
||||
|
||||
public static function getApiHeaders($count = 0)
|
||||
{
|
||||
return [
|
||||
|
@ -6,6 +6,7 @@ use Illuminate\Queue\Events\JobExceptionOccurred;
|
||||
use App\Events\InvoiceInvitationWasViewed;
|
||||
use App\Events\InvoiceWasCreated;
|
||||
use App\Events\InvoiceWasUpdated;
|
||||
use App\Events\InvoiceWasEmailed;
|
||||
use App\Events\PaymentFailed;
|
||||
use App\Events\PaymentWasCreated;
|
||||
use App\Events\PaymentWasDeleted;
|
||||
@ -60,6 +61,16 @@ class InvoiceListener
|
||||
$invitation->markViewed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceWasEmailed $event
|
||||
*/
|
||||
public function emailedInvoice(InvoiceWasEmailed $event)
|
||||
{
|
||||
$invoice = $event->invoice;
|
||||
$invoice->last_sent_date = date('Y-m-d');
|
||||
$invoice->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PaymentWasCreated $event
|
||||
*/
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\QuoteInvitationWasViewed;
|
||||
use App\Events\QuoteWasEmailed;
|
||||
|
||||
/**
|
||||
* Class QuoteListener.
|
||||
@ -17,4 +18,15 @@ class QuoteListener
|
||||
$invitation = $event->invitation;
|
||||
$invitation->markViewed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceWasEmailed $event
|
||||
*/
|
||||
public function emailedQuote(QuoteWasEmailed $event)
|
||||
{
|
||||
$quote = $event->quote;
|
||||
$quote->last_sent_date = date('Y-m-d');
|
||||
$quote->save();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,21 +3,34 @@
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\ClientWasCreated;
|
||||
use App\Events\CreditWasCreated;
|
||||
use App\Events\ClientWasUpdated;
|
||||
use App\Events\ClientWasDeleted;
|
||||
use App\Events\ExpenseWasCreated;
|
||||
use App\Events\ExpenseWasUpdated;
|
||||
use App\Events\ExpenseWasDeleted;
|
||||
use App\Events\QuoteItemsWereCreated;
|
||||
use App\Events\QuoteItemsWereUpdated;
|
||||
use App\Events\InvoiceWasDeleted;
|
||||
use App\Events\QuoteWasDeleted;
|
||||
use App\Events\QuoteInvitationWasApproved;
|
||||
use App\Events\PaymentWasCreated;
|
||||
use App\Events\PaymentWasDeleted;
|
||||
use App\Events\InvoiceItemsWereCreated;
|
||||
use App\Events\InvoiceItemsWereUpdated;
|
||||
use App\Events\QuoteWasDeleted;
|
||||
use App\Events\InvoiceWasDeleted;
|
||||
use App\Events\VendorWasCreated;
|
||||
use App\Events\VendorWasUpdated;
|
||||
use App\Events\VendorWasDeleted;
|
||||
use App\Events\TaskWasCreated;
|
||||
use App\Events\TaskWasUpdated;
|
||||
use App\Events\TaskWasDeleted;
|
||||
use App\Models\EntityModel;
|
||||
use App\Ninja\Serializers\ArraySerializer;
|
||||
use App\Ninja\Transformers\ClientTransformer;
|
||||
use App\Ninja\Transformers\InvoiceTransformer;
|
||||
use App\Ninja\Transformers\PaymentTransformer;
|
||||
use App\Ninja\Transformers\VendorTransformer;
|
||||
use App\Ninja\Transformers\ExpenseTransformer;
|
||||
use App\Ninja\Transformers\TaskTransformer;
|
||||
use League\Fractal\Manager;
|
||||
use League\Fractal\Resource\Item;
|
||||
use Utils;
|
||||
@ -36,6 +49,25 @@ class SubscriptionListener
|
||||
$this->checkSubscriptions(EVENT_CREATE_CLIENT, $event->client, $transformer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClientWasUpdated $event
|
||||
*/
|
||||
public function updatedClient(ClientWasUpdated $event)
|
||||
{
|
||||
$transformer = new ClientTransformer($event->client->account);
|
||||
$this->checkSubscriptions(EVENT_UPDATE_CLIENT, $event->client, $transformer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClientWasDeleted $event
|
||||
*/
|
||||
public function deletedClient(ClientWasDeleted $event)
|
||||
{
|
||||
$transformer = new ClientTransformer($event->client->account);
|
||||
$this->checkSubscriptions(EVENT_DELETE_CLIENT, $event->client, $transformer);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param PaymentWasCreated $event
|
||||
*/
|
||||
@ -46,25 +78,14 @@ class SubscriptionListener
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CreditWasCreated $event
|
||||
* @param PaymentWasDeleted $event
|
||||
*/
|
||||
public function createdCredit(CreditWasCreated $event)
|
||||
public function deletedPayment(PaymentWasDeleted $event)
|
||||
{
|
||||
$transformer = new PaymentTransformer($event->payment->account);
|
||||
$this->checkSubscriptions(EVENT_DELETE_PAYMENT, $event->payment, $transformer, [ENTITY_CLIENT, ENTITY_INVOICE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VendorWasCreated $event
|
||||
*/
|
||||
public function createdVendor(VendorWasCreated $event)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ExpenseWasCreated $event
|
||||
*/
|
||||
public function createdExpense(ExpenseWasCreated $event)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceWasCreated $event
|
||||
@ -84,6 +105,16 @@ class SubscriptionListener
|
||||
$this->checkSubscriptions(EVENT_UPDATE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceWasDeleted $event
|
||||
*/
|
||||
public function deletedInvoice(InvoiceWasDeleted $event)
|
||||
{
|
||||
$transformer = new InvoiceTransformer($event->invoice->account);
|
||||
$this->checkSubscriptions(EVENT_DELETE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param QuoteWasCreated $event
|
||||
*/
|
||||
@ -103,12 +134,12 @@ class SubscriptionListener
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceWasDeleted $event
|
||||
* @param QuoteInvitationWasApproved $event
|
||||
*/
|
||||
public function deletedInvoice(InvoiceWasDeleted $event)
|
||||
public function approvedQuote(QuoteInvitationWasApproved $event)
|
||||
{
|
||||
$transformer = new InvoiceTransformer($event->invoice->account);
|
||||
$this->checkSubscriptions(EVENT_DELETE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT);
|
||||
$transformer = new InvoiceTransformer($event->quote->account);
|
||||
$this->checkSubscriptions(EVENT_APPROVE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -120,6 +151,91 @@ class SubscriptionListener
|
||||
$this->checkSubscriptions(EVENT_DELETE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param VendorWasCreated $event
|
||||
*/
|
||||
public function createdVendor(VendorWasCreated $event)
|
||||
{
|
||||
$transformer = new VendorTransformer($event->vendor->account);
|
||||
$this->checkSubscriptions(EVENT_CREATE_VENDOR, $event->vendor, $transformer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VendorWasUpdated $event
|
||||
*/
|
||||
public function updatedVendor(VendorWasUpdated $event)
|
||||
{
|
||||
$transformer = new VendorTransformer($event->vendor->account);
|
||||
$this->checkSubscriptions(EVENT_UPDATE_VENDOR, $event->vendor, $transformer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param VendorWasDeleted $event
|
||||
*/
|
||||
public function deletedVendor(VendorWasDeleted $event)
|
||||
{
|
||||
$transformer = new VendorTransformer($event->vendor->account);
|
||||
$this->checkSubscriptions(EVENT_DELETE_VENDOR, $event->vendor, $transformer);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param ExpenseWasCreated $event
|
||||
*/
|
||||
public function createdExpense(ExpenseWasCreated $event)
|
||||
{
|
||||
$transformer = new ExpenseTransformer($event->expense->account);
|
||||
$this->checkSubscriptions(EVENT_CREATE_EXPENSE, $event->expense, $transformer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ExpenseWasUpdated $event
|
||||
*/
|
||||
public function updatedExpense(ExpenseWasUpdated $event)
|
||||
{
|
||||
$transformer = new ExpenseTransformer($event->expense->account);
|
||||
$this->checkSubscriptions(EVENT_UPDATE_EXPENSE, $event->expense, $transformer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ExpenseWasDeleted $event
|
||||
*/
|
||||
public function deletedExpense(ExpenseWasDeleted $event)
|
||||
{
|
||||
$transformer = new ExpenseTransformer($event->expense->account);
|
||||
$this->checkSubscriptions(EVENT_DELETE_EXPENSE, $event->expense, $transformer);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param TaskWasCreated $event
|
||||
*/
|
||||
public function createdTask(TaskWasCreated $event)
|
||||
{
|
||||
$transformer = new TaskTransformer($event->task->account);
|
||||
$this->checkSubscriptions(EVENT_CREATE_TASK, $event->task, $transformer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TaskWasUpdated $event
|
||||
*/
|
||||
public function updatedTask(TaskWasUpdated $event)
|
||||
{
|
||||
$transformer = new TaskTransformer($event->task->account);
|
||||
$this->checkSubscriptions(EVENT_UPDATE_TASK, $event->task, $transformer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TaskWasDeleted $event
|
||||
*/
|
||||
public function deletedTask(TaskWasDeleted $event)
|
||||
{
|
||||
$transformer = new TaskTransformer($event->task->account);
|
||||
$this->checkSubscriptions(EVENT_DELETE_TASK, $event->task, $transformer);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $eventId
|
||||
* @param $entity
|
||||
@ -151,7 +267,39 @@ class SubscriptionListener
|
||||
}
|
||||
|
||||
foreach ($subscriptions as $subscription) {
|
||||
Utils::notifyZapier($subscription, $data);
|
||||
self::notifySubscription($subscription, $data);
|
||||
}
|
||||
}
|
||||
|
||||
private static function notifySubscription($subscription, $data)
|
||||
{
|
||||
$curl = curl_init();
|
||||
$jsonEncodedData = json_encode($data);
|
||||
$url = $subscription->target_url;
|
||||
|
||||
if (! Utils::isNinja() && $secret = env('SUBSCRIPTION_SECRET')) {
|
||||
$url .= '?secret=' . $secret;
|
||||
}
|
||||
|
||||
$opts = [
|
||||
CURLOPT_URL => $url,
|
||||
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);
|
||||
|
||||
$result = curl_exec($curl);
|
||||
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
|
||||
curl_close($curl);
|
||||
|
||||
if ($status == 410) {
|
||||
$subscription->delete();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -108,6 +108,7 @@ class Account extends Eloquent
|
||||
'enable_reminder1',
|
||||
'enable_reminder2',
|
||||
'enable_reminder3',
|
||||
'enable_reminder4',
|
||||
'num_days_reminder1',
|
||||
'num_days_reminder2',
|
||||
'num_days_reminder3',
|
||||
@ -178,6 +179,8 @@ class Account extends Eloquent
|
||||
'credit_number_pattern',
|
||||
'task_rate',
|
||||
'inclusive_taxes',
|
||||
'convert_products',
|
||||
'signature_on_pdf',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -1016,7 +1019,7 @@ class Account extends Eloquent
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->enable_reminder1 || $this->enable_reminder2 || $this->enable_reminder3;
|
||||
return $this->enable_reminder1 || $this->enable_reminder2 || $this->enable_reminder3 || $this->enable_reminder4;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1320,6 +1323,8 @@ class Account extends Eloquent
|
||||
'paid_to_date',
|
||||
'invoices',
|
||||
'contacts',
|
||||
'currency_id',
|
||||
'currency',
|
||||
]);
|
||||
|
||||
foreach ($client->invoices as $invoice) {
|
||||
@ -1333,6 +1338,8 @@ class Account extends Eloquent
|
||||
'created_at',
|
||||
'is_recurring',
|
||||
'invoice_type_id',
|
||||
'is_public',
|
||||
'due_date',
|
||||
]);
|
||||
|
||||
foreach ($invoice->invoice_items as $invoiceItem) {
|
||||
@ -1340,6 +1347,7 @@ class Account extends Eloquent
|
||||
'product_key',
|
||||
'cost',
|
||||
'qty',
|
||||
'discount',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -1413,7 +1421,7 @@ class Account extends Eloquent
|
||||
*/
|
||||
public function getSiteUrl()
|
||||
{
|
||||
$url = SITE_URL;
|
||||
$url = trim(SITE_URL, '/');
|
||||
$iframe_url = $this->iframe_url;
|
||||
|
||||
if ($iframe_url) {
|
||||
|
@ -43,6 +43,7 @@ class AccountEmailSettings extends Eloquent
|
||||
TEMPLATE_REMINDER1,
|
||||
TEMPLATE_REMINDER2,
|
||||
TEMPLATE_REMINDER3,
|
||||
TEMPLATE_REMINDER4,
|
||||
];
|
||||
|
||||
}
|
||||
|
@ -105,6 +105,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
{
|
||||
return [
|
||||
'name',
|
||||
'email',
|
||||
'invoice_number',
|
||||
'po_number',
|
||||
'invoice_date',
|
||||
@ -130,6 +131,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
return [
|
||||
'number^po' => 'invoice_number',
|
||||
'client|organization' => 'name',
|
||||
'email' => 'email',
|
||||
'paid^date' => 'paid',
|
||||
'invoice date|create date' => 'invoice_date',
|
||||
'po number' => 'po_number',
|
||||
@ -140,7 +142,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
'description' => 'item_notes',
|
||||
'quantity|qty' => 'item_quantity',
|
||||
'amount|cost' => 'item_cost',
|
||||
'product|item' => 'item_product',
|
||||
'product' => 'item_product',
|
||||
'tax' => 'item_tax1',
|
||||
];
|
||||
}
|
||||
@ -906,6 +908,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
'documents',
|
||||
'expenses',
|
||||
'client',
|
||||
'invitations',
|
||||
'tax_name1',
|
||||
'tax_rate1',
|
||||
'tax_name2',
|
||||
@ -988,8 +991,19 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
'invoice_fields',
|
||||
'show_currency_code',
|
||||
'inclusive_taxes',
|
||||
'date_format',
|
||||
'datetime_format',
|
||||
'timezone',
|
||||
'signature_on_pdf',
|
||||
]);
|
||||
|
||||
foreach ($this->invitations as $invitation) {
|
||||
$invitation->setVisible([
|
||||
'signature_base64',
|
||||
'signature_date',
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($this->invoice_items as $invoiceItem) {
|
||||
$invoiceItem->setVisible([
|
||||
'product_key',
|
||||
@ -1003,6 +1017,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
'tax_name2',
|
||||
'tax_rate2',
|
||||
'invoice_item_type_id',
|
||||
'discount',
|
||||
]);
|
||||
}
|
||||
|
||||
@ -1300,7 +1315,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
{
|
||||
$total = $invoiceItem->qty * $invoiceItem->cost;
|
||||
|
||||
if ($this->discount > 0) {
|
||||
if ($this->discount != 0) {
|
||||
if ($this->is_amount_discount) {
|
||||
$total -= $invoiceTotal ? ($total / ($invoiceTotal + $this->discount) * $this->discount) : 0;
|
||||
} else {
|
||||
@ -1308,6 +1323,14 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
}
|
||||
}
|
||||
|
||||
if ($invoiceItem->discount != 0) {
|
||||
if ($this->is_amount_discount) {
|
||||
$total -= $invoiceItem->discount;
|
||||
} else {
|
||||
$total -= $total * $invoiceItem->discount / 100;
|
||||
}
|
||||
}
|
||||
|
||||
return round($total, 2);
|
||||
}
|
||||
|
||||
@ -1319,7 +1342,17 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
$total = 0;
|
||||
|
||||
foreach ($this->invoice_items as $invoiceItem) {
|
||||
$total += $invoiceItem->qty * $invoiceItem->cost;
|
||||
$lineTotal = $invoiceItem->qty * $invoiceItem->cost;
|
||||
|
||||
if ($invoiceItem->discount != 0) {
|
||||
if ($this->is_amount_discount) {
|
||||
$lineTotal -= $invoiceItem->discount;
|
||||
} else {
|
||||
$lineTotal -= $lineTotal * $invoiceItem->discount / 100;
|
||||
}
|
||||
}
|
||||
|
||||
$total += $lineTotal;
|
||||
}
|
||||
|
||||
if ($this->discount > 0) {
|
||||
|
@ -40,6 +40,7 @@ class InvoiceItem extends EntityModel
|
||||
'tax_name2',
|
||||
'tax_rate2',
|
||||
'invoice_item_type_id',
|
||||
'discount',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -77,6 +78,15 @@ class InvoiceItem extends EntityModel
|
||||
public function amount()
|
||||
{
|
||||
$amount = $this->cost * $this->qty;
|
||||
|
||||
if ($this->discount != 0) {
|
||||
if ($this->invoice->is_amount_discount) {
|
||||
$amount -= $this->discount;
|
||||
} else {
|
||||
$amount -= $amount * $this->discount / 100;
|
||||
}
|
||||
}
|
||||
|
||||
$preTaxAmount = $amount;
|
||||
|
||||
if ($this->tax_rate1) {
|
||||
|
@ -25,12 +25,15 @@ class Project extends EntityModel
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'task_rate',
|
||||
'private_notes',
|
||||
'due_date',
|
||||
'budgeted_hours',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $presenter = 'App\Ninja\Presenters\EntityPresenter';
|
||||
protected $presenter = 'App\Ninja\Presenters\ProjectPresenter';
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
@ -45,7 +48,15 @@ class Project extends EntityModel
|
||||
*/
|
||||
public function getRoute()
|
||||
{
|
||||
return "/projects/{$this->public_id}/edit";
|
||||
return "/projects/{$this->public_id}";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('App\Models\Account');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,6 +74,18 @@ class Project extends EntityModel
|
||||
{
|
||||
return $this->hasMany('App\Models\Task');
|
||||
}
|
||||
|
||||
public function scopeDateRange($query, $startDate, $endDate)
|
||||
{
|
||||
return $query->where(function ($query) use ($startDate, $endDate) {
|
||||
$query->whereBetween('due_date', [$startDate, $endDate]);
|
||||
});
|
||||
}
|
||||
|
||||
public function getDisplayName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
|
||||
Project::creating(function ($project) {
|
||||
|
@ -79,6 +79,14 @@ class Task extends EntityModel
|
||||
return $this->belongsTo('App\Models\Project')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function task_status()
|
||||
{
|
||||
return $this->belongsTo('App\Models\TaskStatus')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $task
|
||||
*
|
||||
@ -231,7 +239,17 @@ class Task extends EntityModel
|
||||
public static function getStatuses($entityType = false)
|
||||
{
|
||||
$statuses = [];
|
||||
$statuses[TASK_STATUS_LOGGED] = trans('texts.logged');
|
||||
|
||||
$taskStatues = TaskStatus::scope()->orderBy('sort_order')->get();
|
||||
|
||||
foreach ($taskStatues as $status) {
|
||||
$statuses[$status->public_id] = $status->name;
|
||||
}
|
||||
|
||||
if (! $taskStatues->count()) {
|
||||
$statuses[TASK_STATUS_LOGGED] = trans('texts.logged');
|
||||
}
|
||||
|
||||
$statuses[TASK_STATUS_RUNNING] = trans('texts.running');
|
||||
$statuses[TASK_STATUS_INVOICED] = trans('texts.invoiced');
|
||||
$statuses[TASK_STATUS_PAID] = trans('texts.paid');
|
||||
@ -239,21 +257,25 @@ class Task extends EntityModel
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
public static function calcStatusLabel($isRunning, $balance, $invoiceNumber)
|
||||
public static function calcStatusLabel($isRunning, $balance, $invoiceNumber, $taskStatus)
|
||||
{
|
||||
if ($invoiceNumber) {
|
||||
if (floatval($balance) > 0) {
|
||||
$label = 'invoiced';
|
||||
$label = trans('texts.invoiced');
|
||||
} else {
|
||||
$label = 'paid';
|
||||
$label = trans('texts.paid');
|
||||
}
|
||||
} elseif ($isRunning) {
|
||||
$label = 'running';
|
||||
} elseif ($taskStatus) {
|
||||
$label = $taskStatus;
|
||||
} else {
|
||||
$label = 'logged';
|
||||
$label = trans('texts.logged');
|
||||
}
|
||||
|
||||
return trans("texts.{$label}");
|
||||
if ($isRunning) {
|
||||
$label .= ' | ' . trans('texts.running');
|
||||
}
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
public static function calcStatusClass($isRunning, $balance, $invoiceNumber)
|
||||
@ -267,7 +289,7 @@ class Task extends EntityModel
|
||||
} elseif ($isRunning) {
|
||||
return 'primary';
|
||||
} else {
|
||||
return 'warning';
|
||||
return 'info';
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,7 +316,9 @@ class Task extends EntityModel
|
||||
$invoiceNumber = false;
|
||||
}
|
||||
|
||||
return static::calcStatusLabel($this->is_running, $balance, $invoiceNumber);
|
||||
$taskStatus = $this->task_status ? $this->task_status->name : false;
|
||||
|
||||
return static::calcStatusLabel($this->is_running, $balance, $invoiceNumber, $taskStatus);
|
||||
}
|
||||
}
|
||||
|
||||
|
49
app/Models/TaskStatus.php
Normal file
49
app/Models/TaskStatus.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* Class PaymentTerm.
|
||||
*/
|
||||
class TaskStatus extends EntityModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = ['deleted_at'];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getEntityType()
|
||||
{
|
||||
return ENTITY_TASK_STATUS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function tasks()
|
||||
{
|
||||
return $this->hasMany('App\Models\Task')->orderBy('task_status_sort_order');
|
||||
}
|
||||
|
||||
}
|
@ -358,6 +358,9 @@ trait GeneratesNumbers
|
||||
case FREQUENCY_ANNUALLY:
|
||||
$resetDate->addYear();
|
||||
break;
|
||||
case FREQUENCY_TWO_YEARS:
|
||||
$resetDate->addYears(2);
|
||||
break;
|
||||
}
|
||||
|
||||
$this->reset_counter_date = $resetDate->format('Y-m-d');
|
||||
|
@ -70,6 +70,8 @@ trait HasRecurrence
|
||||
return $monthsSinceLastSent >= 6;
|
||||
case FREQUENCY_ANNUALLY:
|
||||
return $monthsSinceLastSent >= 12;
|
||||
case FREQUENCY_TWO_YEARS:
|
||||
return $monthsSinceLastSent >= 24;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -112,6 +114,9 @@ trait HasRecurrence
|
||||
case FREQUENCY_ANNUALLY:
|
||||
$rule = 'FREQ=YEARLY;';
|
||||
break;
|
||||
case FREQUENCY_TWO_YEARS:
|
||||
$rule = 'FREQ=YEARLY;INTERVAL=2;';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->end_date) {
|
||||
|
@ -186,6 +186,7 @@ trait PresentsInvoice
|
||||
'product.custom_value2',
|
||||
'product.unit_cost',
|
||||
'product.quantity',
|
||||
'product.discount',
|
||||
'product.tax',
|
||||
'product.line_total',
|
||||
],
|
||||
@ -196,6 +197,7 @@ trait PresentsInvoice
|
||||
'product.custom_value2',
|
||||
'product.rate',
|
||||
'product.hours',
|
||||
'product.discount',
|
||||
'product.tax',
|
||||
'product.line_total',
|
||||
],
|
||||
@ -378,9 +380,9 @@ trait PresentsInvoice
|
||||
return null;
|
||||
}
|
||||
|
||||
public function hideQuantity() {
|
||||
public function hasInvoiceField($type, $field) {
|
||||
$fields = $this->getInvoiceFields();
|
||||
|
||||
return ! isset($fields['product_fields']['product.quantity']);
|
||||
return isset($fields[$type . '_fields'][$field]);
|
||||
}
|
||||
}
|
||||
|
@ -69,6 +69,7 @@ class User extends Authenticatable
|
||||
'oauth_provider_id',
|
||||
'google_2fa_secret',
|
||||
'google_2fa_phone',
|
||||
'remember_2fa_token',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -30,7 +30,7 @@ class ProductDatatable extends EntityDatatable
|
||||
[
|
||||
'cost',
|
||||
function ($model) {
|
||||
return Utils::roundSignificant($model->cost);
|
||||
return Utils::formatMoney($model->cost);
|
||||
},
|
||||
],
|
||||
[
|
||||
|
@ -21,7 +21,8 @@ class ProjectDatatable extends EntityDatatable
|
||||
return $model->project;
|
||||
}
|
||||
|
||||
return link_to("projects/{$model->public_id}/edit", $model->project)->toHtml();
|
||||
$str = link_to("projects/{$model->public_id}", $model->project)->toHtml();
|
||||
return $this->addNote($str, $model->private_notes);
|
||||
},
|
||||
],
|
||||
[
|
||||
@ -32,16 +33,28 @@ class ProjectDatatable extends EntityDatatable
|
||||
return Utils::getClientDisplayName($model);
|
||||
}
|
||||
|
||||
return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml();
|
||||
return link_to("clients/{$model->client_public_id}", $model->client_name)->toHtml();
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
],
|
||||
[
|
||||
'due_date',
|
||||
function ($model) {
|
||||
return Utils::fromSqlDate($model->due_date);
|
||||
},
|
||||
],
|
||||
[
|
||||
'budgeted_hours',
|
||||
function ($model) {
|
||||
return $model->budgeted_hours ?: '';
|
||||
},
|
||||
],
|
||||
[
|
||||
'task_rate',
|
||||
function ($model) {
|
||||
return floatval($model->task_rate) ? Utils::roundSignificant($model->task_rate) : '';
|
||||
return floatval($model->task_rate) ? Utils::formatMoney($model->task_rate) : '';
|
||||
}
|
||||
],
|
||||
];
|
||||
|
15
app/Ninja/Datatables/ProjectTaskDatatable.php
Normal file
15
app/Ninja/Datatables/ProjectTaskDatatable.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Ninja\Datatables;
|
||||
|
||||
class ProjectTaskDatatable extends TaskDatatable
|
||||
{
|
||||
public function columns()
|
||||
{
|
||||
$columns = parent::columns();
|
||||
|
||||
unset($columns[1]);
|
||||
|
||||
return $columns;
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ class SubscriptionDatatable extends EntityDatatable
|
||||
[
|
||||
'target',
|
||||
function ($model) {
|
||||
return $model->target;
|
||||
return e(substr($model->target, 0, 40) . (strlen($model->target) > 40 ? '...' : ''));
|
||||
},
|
||||
],
|
||||
];
|
||||
|
@ -33,7 +33,7 @@ class TaskDatatable extends EntityDatatable
|
||||
return $model->project;
|
||||
}
|
||||
|
||||
return $model->project_public_id ? link_to("projects/{$model->project_public_id}/edit", $model->project)->toHtml() : '';
|
||||
return $model->project_public_id ? link_to("projects/{$model->project_public_id}", $model->project)->toHtml() : '';
|
||||
},
|
||||
],
|
||||
[
|
||||
@ -49,13 +49,17 @@ class TaskDatatable extends EntityDatatable
|
||||
[
|
||||
'duration',
|
||||
function ($model) {
|
||||
return Utils::formatTime(Task::calcDuration($model));
|
||||
if (! Auth::user()->can('viewByOwner', [ENTITY_EXPENSE, $model->user_id])) {
|
||||
return Utils::formatTime(Task::calcDuration($model));
|
||||
}
|
||||
|
||||
return link_to("tasks/{$model->public_id}/edit", Utils::formatTime(Task::calcDuration($model)))->toHtml();
|
||||
},
|
||||
],
|
||||
[
|
||||
'description',
|
||||
function ($model) {
|
||||
return e($model->description);
|
||||
return e(substr($model->description, 0, 80) . (strlen($model->description) > 80 ? '...' : ''));
|
||||
},
|
||||
],
|
||||
[
|
||||
@ -120,7 +124,7 @@ class TaskDatatable extends EntityDatatable
|
||||
|
||||
private function getStatusLabel($model)
|
||||
{
|
||||
$label = Task::calcStatusLabel($model->is_running, $model->balance, $model->invoice_number);
|
||||
$label = Task::calcStatusLabel($model->is_running, $model->balance, $model->invoice_number, $model->task_status);
|
||||
$class = Task::calcStatusClass($model->is_running, $model->balance, $model->invoice_number);
|
||||
|
||||
return "<h4><div class=\"label label-{$class}\">$label</div></h4>";
|
||||
|
@ -17,7 +17,9 @@ class InvoiceTransformer extends BaseTransformer
|
||||
*/
|
||||
public function transform($data)
|
||||
{
|
||||
if (! $this->getClientId($data->name)) {
|
||||
$clientId = $this->getClientId($data->email) ?: $this->getClientId($data->name);
|
||||
|
||||
if (! $clientId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -25,9 +27,9 @@ class InvoiceTransformer extends BaseTransformer
|
||||
return false;
|
||||
}
|
||||
|
||||
return new Item($data, function ($data) {
|
||||
return new Item($data, function ($data) use ($clientId) {
|
||||
return [
|
||||
'client_id' => $this->getClientId($data->name),
|
||||
'client_id' => $clientId,
|
||||
'invoice_number' => isset($data->invoice_number) ? $this->getInvoiceNumber($data->invoice_number) : null,
|
||||
'paid' => $this->getFloat($data, 'paid'),
|
||||
'po_number' => $this->getString($data, 'po_number'),
|
||||
|
@ -374,6 +374,7 @@ class BasePaymentDriver
|
||||
]);
|
||||
|
||||
$items[] = $item;
|
||||
|
||||
$total += $invoiceItem->cost * $invoiceItem->qty;
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ class AccountPresenter extends Presenter
|
||||
*/
|
||||
public function taskRate()
|
||||
{
|
||||
if ($this->entity->task_rate) {
|
||||
if (floatval($this->entity->task_rate)) {
|
||||
return Utils::roundSignificant($this->entity->task_rate);
|
||||
} else {
|
||||
return '';
|
||||
@ -247,9 +247,9 @@ class AccountPresenter extends Presenter
|
||||
$url .= $account->subdomain ?: 'app';
|
||||
$url .= '.' . Domain::getDomainFromId($account->domain_id);
|
||||
} else {
|
||||
$url = SITE_URL;
|
||||
$url = trim(SITE_URL, '/');
|
||||
}
|
||||
|
||||
|
||||
$url .= '/client/login';
|
||||
|
||||
if (Utils::isNinja()) {
|
||||
|
@ -108,10 +108,23 @@ class ClientPresenter extends EntityPresenter
|
||||
*/
|
||||
public function taskRate()
|
||||
{
|
||||
if ($this->entity->task_rate) {
|
||||
if (floatval($this->entity->task_rate)) {
|
||||
return Utils::roundSignificant($this->entity->task_rate);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function defaultTaskRate()
|
||||
{
|
||||
if ($rate = $this->taskRate()) {
|
||||
return $rate;
|
||||
} else {
|
||||
return $this->entity->account->present()->taskRate;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
50
app/Ninja/Presenters/ProjectPresenter.php
Normal file
50
app/Ninja/Presenters/ProjectPresenter.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Ninja\Presenters;
|
||||
|
||||
use Utils;
|
||||
|
||||
class ProjectPresenter extends EntityPresenter
|
||||
{
|
||||
public function calendarEvent($subColors = false)
|
||||
{
|
||||
$data = parent::calendarEvent();
|
||||
$project = $this->entity;
|
||||
|
||||
$data->title = trans('texts.project') . ': ' . $project->name;
|
||||
$data->start = $project->due_date;
|
||||
|
||||
if ($subColors) {
|
||||
$data->borderColor = $data->backgroundColor = Utils::brewerColor($project->public_id);
|
||||
} else {
|
||||
$data->borderColor = $data->backgroundColor = '#676767';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function taskRate()
|
||||
{
|
||||
if (floatval($this->entity->task_rate)) {
|
||||
return Utils::roundSignificant($this->entity->task_rate);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function defaultTaskRate()
|
||||
{
|
||||
if ($rate = $this->taskRate()) {
|
||||
return $rate;
|
||||
} else {
|
||||
return $this->entity->client->present()->defaultTaskRate;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -50,7 +50,7 @@ class DocumentReport extends AbstractReport
|
||||
$zip = Archive::instance_by_useragent(date('Y-m-d') . '_' . str_replace(' ', '_', trans('texts.documents')));
|
||||
foreach ($records as $record) {
|
||||
foreach ($record->documents as $document) {
|
||||
$name = sprintf('%s_%s_%s', date('Y-m-d'), $record->present()->titledName, $document->name);
|
||||
$name = sprintf('%s_%s_%s', $document->created_at->format('Y-m-d'), $record->present()->titledName, $document->name);
|
||||
$name = str_replace(' ', '_', $name);
|
||||
$name = str_replace('#', '', $name);
|
||||
$zip->add_file($name, $document->getRaw());
|
||||
|
@ -39,7 +39,7 @@ class ExpenseReport extends AbstractReport
|
||||
foreach ($expenses->get() as $expense) {
|
||||
foreach ($expense->documents as $document) {
|
||||
$expenseId = str_pad($expense->public_id, $account->invoice_number_padding, '0', STR_PAD_LEFT);
|
||||
$name = sprintf('%s_%s_%s_%s', date('Y-m-d'), trans('texts.expense'), $expenseId, $document->name);
|
||||
$name = sprintf('%s_%s_%s_%s', $expense->expense_date ?: date('Y-m-d'), trans('texts.expense'), $expenseId, $document->name);
|
||||
$name = str_replace(' ', '_', $name);
|
||||
$zip->add_file($name, $document->getRaw());
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ class InvoiceReport extends AbstractReport
|
||||
foreach ($clients->get() as $client) {
|
||||
foreach ($client->invoices as $invoice) {
|
||||
foreach ($invoice->documents as $document) {
|
||||
$name = sprintf('%s_%s_%s', date('Y-m-d'), $invoice->present()->titledName, $document->name);
|
||||
$name = sprintf('%s_%s_%s', $invoice->invoice_date ?: date('Y-m-d'), $invoice->present()->titledName, $document->name);
|
||||
$zip->add_file($name, $document->getRaw());
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ class QuoteReport extends AbstractReport
|
||||
foreach ($clients->get() as $client) {
|
||||
foreach ($client->invoices as $invoice) {
|
||||
foreach ($invoice->documents as $document) {
|
||||
$name = sprintf('%s_%s_%s', date('Y-m-d'), $invoice->present()->titledName, $document->name);
|
||||
$name = sprintf('%s_%s_%s', $invoice->invoice_date ?: date('Y-m-d'), $invoice->present()->titledName, $document->name);
|
||||
$name = str_replace(' ', '_', $name);
|
||||
$zip->add_file($name, $document->getRaw());
|
||||
}
|
||||
|
@ -242,6 +242,7 @@ class AccountRepository
|
||||
['dashboard', '/dashboard'],
|
||||
['reports', '/reports'],
|
||||
['calendar', '/calendar'],
|
||||
['kanban', '/tasks/kanban'],
|
||||
['customize_design', '/settings/customize_design'],
|
||||
['new_tax_rate', '/tax_rates/create'],
|
||||
['new_product', '/products/create'],
|
||||
@ -510,7 +511,7 @@ class AccountRepository
|
||||
|
||||
public function registerNinjaUser($user)
|
||||
{
|
||||
if ($user->email == TEST_USERNAME) {
|
||||
if (! $user || $user->email == TEST_USERNAME) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -696,7 +697,7 @@ class AccountRepository
|
||||
|
||||
public function findWithReminders()
|
||||
{
|
||||
return Account::whereRaw('enable_reminder1 = 1 OR enable_reminder2 = 1 OR enable_reminder3 = 1')->get();
|
||||
return Account::whereRaw('enable_reminder1 = 1 OR enable_reminder2 = 1 OR enable_reminder3 = 1 OR enable_reminder4 = 1')->get();
|
||||
}
|
||||
|
||||
public function findWithFees()
|
||||
|
@ -38,7 +38,7 @@ class ClientRepository extends BaseRepository
|
||||
->select(
|
||||
DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'),
|
||||
DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'),
|
||||
DB::raw("CONCAT(contacts.first_name, ' ', contacts.last_name) contact"),
|
||||
DB::raw("CONCAT(COALESCE(contacts.first_name, ''), ' ', COALESCE(contacts.last_name, '')) contact"),
|
||||
'clients.public_id',
|
||||
'clients.name',
|
||||
'clients.private_notes',
|
||||
|
@ -92,7 +92,7 @@ class DashboardRepository
|
||||
$record->lineTension = 0;
|
||||
$record->borderWidth = 4;
|
||||
$record->borderColor = "rgba({$color}, 1)";
|
||||
$record->backgroundColor = "rgba({$color}, 0.05)";
|
||||
$record->backgroundColor = "rgba({$color}, 0.1)";
|
||||
$datasets[] = $record;
|
||||
|
||||
if ($entityType == ENTITY_INVOICE) {
|
||||
|
@ -541,8 +541,18 @@ class InvoiceRepository extends BaseRepository
|
||||
|
||||
$invoiceItemCost = Utils::roundSignificant(Utils::parseFloat($item['cost']));
|
||||
$invoiceItemQty = Utils::roundSignificant(Utils::parseFloat($item['qty']));
|
||||
$discount = empty($item['discount']) ? 0 : round(Utils::parseFloat($item['discount']), 2);
|
||||
|
||||
$lineTotal = $invoiceItemCost * $invoiceItemQty;
|
||||
|
||||
if ($discount) {
|
||||
if ($invoice->is_amount_discount) {
|
||||
$lineTotal -= $discount;
|
||||
} else {
|
||||
$lineTotal -= $lineTotal * $discount / 100;
|
||||
}
|
||||
}
|
||||
|
||||
$total += round($lineTotal, 2);
|
||||
}
|
||||
|
||||
@ -550,8 +560,17 @@ class InvoiceRepository extends BaseRepository
|
||||
$item = (array) $item;
|
||||
$invoiceItemCost = Utils::roundSignificant(Utils::parseFloat($item['cost']));
|
||||
$invoiceItemQty = Utils::roundSignificant(Utils::parseFloat($item['qty']));
|
||||
$discount = empty($item['discount']) ? 0 : round(Utils::parseFloat($item['discount']), 2);
|
||||
$lineTotal = $invoiceItemCost * $invoiceItemQty;
|
||||
|
||||
if ($discount) {
|
||||
if ($invoice->is_amount_discount) {
|
||||
$lineTotal -= $discount;
|
||||
} else {
|
||||
$lineTotal -= round($lineTotal * $discount / 100, 2);
|
||||
}
|
||||
}
|
||||
|
||||
if ($invoice->discount > 0) {
|
||||
if ($invoice->is_amount_discount) {
|
||||
if ($total != 0) {
|
||||
@ -576,7 +595,7 @@ class InvoiceRepository extends BaseRepository
|
||||
}
|
||||
}
|
||||
|
||||
if ($invoice->discount > 0) {
|
||||
if ($invoice->discount != 0) {
|
||||
if ($invoice->is_amount_discount) {
|
||||
$total -= $invoice->discount;
|
||||
} else {
|
||||
@ -722,7 +741,9 @@ class InvoiceRepository extends BaseRepository
|
||||
}
|
||||
if ($product && (Auth::user()->can('edit', $product))) {
|
||||
$product->notes = ($task || $expense) ? '' : $item['notes'];
|
||||
$product->cost = $expense ? 0 : $item['cost'];
|
||||
if (! $account->convert_products) {
|
||||
$product->cost = $expense ? 0 : $item['cost'];
|
||||
}
|
||||
$product->tax_name1 = isset($item['tax_name1']) ? $item['tax_name1'] : null;
|
||||
$product->tax_rate1 = isset($item['tax_rate1']) ? $item['tax_rate1'] : 0;
|
||||
$product->tax_name2 = isset($item['tax_name2']) ? $item['tax_name2'] : null;
|
||||
@ -1190,6 +1211,40 @@ class InvoiceRepository extends BaseRepository
|
||||
return $invoices;
|
||||
}
|
||||
|
||||
public function findNeedingEndlessReminding(Account $account)
|
||||
{
|
||||
$frequencyId = $account->account_email_settings->frequency_id_reminder4;
|
||||
$frequency = Utils::getFromCache($frequencyId, 'frequencies');
|
||||
|
||||
$lastSentDate = date_create();
|
||||
$lastSentDate->sub(date_interval_create_from_date_string($frequency->date_interval));
|
||||
|
||||
$invoices = Invoice::invoiceType(INVOICE_TYPE_STANDARD)
|
||||
->with('client', 'invoice_items')
|
||||
->whereHas('client', function ($query) {
|
||||
$query->whereSendReminders(true);
|
||||
})
|
||||
->whereAccountId($account->id)
|
||||
->where('balance', '>', 0)
|
||||
->where('is_recurring', '=', false)
|
||||
->whereIsPublic(true)
|
||||
->where('last_sent_date', '<', $lastSentDate);
|
||||
|
||||
for ($i=1; $i<=3; $i++) {
|
||||
if (!$account->{"enable_reminder{$i}"}) {
|
||||
continue;
|
||||
}
|
||||
$field = $account->{"field_reminder{$i}"} == REMINDER_FIELD_DUE_DATE ? 'due_date' : 'invoice_date';
|
||||
$date = date_create();
|
||||
if ($account->{"direction_reminder{$i}"} == REMINDER_DIRECTION_AFTER) {
|
||||
$date->sub(date_interval_create_from_date_string($account->{"num_days_reminder{$i}"} . ' days'));
|
||||
}
|
||||
$invoices->where($field, '<', $date);
|
||||
}
|
||||
|
||||
return $invoices->get();
|
||||
}
|
||||
|
||||
public function clearGatewayFee($invoice)
|
||||
{
|
||||
$account = $invoice->account;
|
||||
|
@ -5,6 +5,7 @@ namespace App\Ninja\Repositories;
|
||||
use App\Models\Project;
|
||||
use Auth;
|
||||
use DB;
|
||||
use Utils;
|
||||
|
||||
class ProjectRepository extends BaseRepository
|
||||
{
|
||||
@ -37,6 +38,9 @@ class ProjectRepository extends BaseRepository
|
||||
'projects.deleted_at',
|
||||
'projects.task_rate',
|
||||
'projects.is_deleted',
|
||||
'projects.due_date',
|
||||
'projects.budgeted_hours',
|
||||
'projects.private_notes',
|
||||
DB::raw("COALESCE(NULLIF(clients.name,''), NULLIF(CONCAT(contacts.first_name, ' ', contacts.last_name),''), NULLIF(contacts.email,'')) client_name"),
|
||||
'clients.user_id as client_user_id',
|
||||
'clients.public_id as client_public_id'
|
||||
@ -71,6 +75,11 @@ class ProjectRepository extends BaseRepository
|
||||
}
|
||||
|
||||
$project->fill($input);
|
||||
|
||||
if (isset($input['due_date'])) {
|
||||
$project->due_date = Utils::toSqlDate($input['due_date']);
|
||||
}
|
||||
|
||||
$project->save();
|
||||
|
||||
return $project;
|
||||
|
@ -5,6 +5,7 @@ namespace App\Ninja\Repositories;
|
||||
use App\Models\Client;
|
||||
use App\Models\Project;
|
||||
use App\Models\Task;
|
||||
use App\Models\TaskStatus;
|
||||
use Auth;
|
||||
use Session;
|
||||
use DB;
|
||||
@ -17,13 +18,14 @@ class TaskRepository extends BaseRepository
|
||||
return 'App\Models\Task';
|
||||
}
|
||||
|
||||
public function find($clientPublicId = null, $filter = null)
|
||||
public function find($clientPublicId = null, $projectPublicId = null, $filter = null)
|
||||
{
|
||||
$query = DB::table('tasks')
|
||||
->leftJoin('clients', 'tasks.client_id', '=', 'clients.id')
|
||||
->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->leftJoin('invoices', 'invoices.id', '=', 'tasks.invoice_id')
|
||||
->leftJoin('projects', 'projects.id', '=', 'tasks.project_id')
|
||||
->leftJoin('task_statuses', 'task_statuses.id', '=', 'tasks.task_status_id')
|
||||
->where('tasks.account_id', '=', Auth::user()->account_id)
|
||||
->where(function ($query) { // handle when client isn't set
|
||||
$query->where('contacts.is_primary', '=', true)
|
||||
@ -55,10 +57,13 @@ class TaskRepository extends BaseRepository
|
||||
'tasks.user_id',
|
||||
'projects.name as project',
|
||||
'projects.public_id as project_public_id',
|
||||
'projects.user_id as project_user_id'
|
||||
'projects.user_id as project_user_id',
|
||||
'task_statuses.name as task_status'
|
||||
);
|
||||
|
||||
if ($clientPublicId) {
|
||||
if ($projectPublicId) {
|
||||
$query->where('projects.public_id', '=', $projectPublicId);
|
||||
} elseif ($clientPublicId) {
|
||||
$query->where('clients.public_id', '=', $clientPublicId);
|
||||
} else {
|
||||
$query->whereNull('clients.deleted_at');
|
||||
@ -85,6 +90,11 @@ class TaskRepository extends BaseRepository
|
||||
if (in_array(TASK_STATUS_PAID, $statuses)) {
|
||||
$query->orWhere('invoices.balance', '=', 0);
|
||||
}
|
||||
$query->orWhere(function ($query) use ($statuses) {
|
||||
$query->whereIn('task_statuses.public_id', $statuses)
|
||||
->whereNull('tasks.invoice_id');
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@ -142,6 +152,7 @@ class TaskRepository extends BaseRepository
|
||||
$task = Task::scope($publicId)->withTrashed()->firstOrFail();
|
||||
} else {
|
||||
$task = Task::createNew();
|
||||
$task->task_status_sort_order = 9999;
|
||||
}
|
||||
|
||||
if ($task->is_deleted) {
|
||||
@ -161,6 +172,12 @@ class TaskRepository extends BaseRepository
|
||||
if (isset($data['description'])) {
|
||||
$task->description = trim($data['description']);
|
||||
}
|
||||
if (isset($data['task_status_id'])) {
|
||||
$task->task_status_id = $data['task_status_id'] ? TaskStatus::getPrivateId($data['task_status_id']) : null;
|
||||
}
|
||||
if (isset($data['task_status_sort_order'])) {
|
||||
$task->task_status_sort_order = $data['task_status_sort_order'];
|
||||
}
|
||||
|
||||
if (isset($data['time_log'])) {
|
||||
$timeLog = json_decode($data['time_log']);
|
||||
|
@ -40,9 +40,11 @@ class AccountEmailSettingsTransformer extends EntityTransformer
|
||||
'email_subject_reminder1' => $settings->email_subject_reminder1,
|
||||
'email_subject_reminder2' => $settings->email_subject_reminder2,
|
||||
'email_subject_reminder3' => $settings->email_subject_reminder3,
|
||||
'email_subject_reminder4' => $settings->email_subject_reminder4,
|
||||
'email_template_reminder1' => $settings->email_template_reminder1,
|
||||
'email_template_reminder2' => $settings->email_template_reminder2,
|
||||
'email_template_reminder3' => $settings->email_template_reminder3,
|
||||
'email_template_reminder4' => $settings->email_template_reminder4,
|
||||
'late_fee1_amount' => $settings->late_fee1_amount,
|
||||
'late_fee1_percent' => $settings->late_fee1_percent,
|
||||
'late_fee2_amount' => $settings->late_fee2_amount,
|
||||
|
@ -211,6 +211,7 @@ class AccountTransformer extends EntityTransformer
|
||||
'enable_reminder1' => $account->enable_reminder1,
|
||||
'enable_reminder2' => $account->enable_reminder2,
|
||||
'enable_reminder3' => $account->enable_reminder3,
|
||||
'enable_reminder4' => $account->enable_reminder4,
|
||||
'num_days_reminder1' => $account->num_days_reminder1,
|
||||
'num_days_reminder2' => $account->num_days_reminder2,
|
||||
'num_days_reminder3' => $account->num_days_reminder3,
|
||||
@ -276,6 +277,8 @@ class AccountTransformer extends EntityTransformer
|
||||
'custom_contact_label2' => $account->custom_contact_label2,
|
||||
'task_rate' => (float) $account->task_rate,
|
||||
'inclusive_taxes' => (bool) $account->inclusive_taxes,
|
||||
'convert_products' => (bool) $account->convert_products,
|
||||
'signature_on_pdf' => (bool) $account->signature_on_pdf,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ class InvoiceItemTransformer extends EntityTransformer
|
||||
'invoice_item_type_id' => (int) $item->invoice_item_type_id,
|
||||
'custom_value1' => $item->custom_value1,
|
||||
'custom_value2' => $item->custom_value2,
|
||||
'discount' => (float) $item->discount,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -204,8 +204,8 @@ class AppServiceProvider extends ServiceProvider
|
||||
Validator::extend('valid_invoice_items', function ($attribute, $value, $parameters) {
|
||||
$total = 0;
|
||||
foreach ($value as $item) {
|
||||
$qty = ! empty($item['qty']) ? $item['qty'] : 1;
|
||||
$cost = ! empty($item['cost']) ? $item['cost'] : 1;
|
||||
$qty = ! empty($item['qty']) ? Utils::parseFloat($item['qty']) : 1;
|
||||
$cost = ! empty($item['cost']) ? Utils::parseFloat($item['cost']) : 1;
|
||||
$total += $qty * $cost;
|
||||
}
|
||||
|
||||
|
@ -21,8 +21,12 @@ class EventServiceProvider extends ServiceProvider
|
||||
'App\Events\ClientWasArchived' => [
|
||||
'App\Listeners\ActivityListener@archivedClient',
|
||||
],
|
||||
'App\Events\ClientWasUpdated' => [
|
||||
'App\Listeners\SubscriptionListener@updatedClient',
|
||||
],
|
||||
'App\Events\ClientWasDeleted' => [
|
||||
'App\Listeners\ActivityListener@deletedClient',
|
||||
'App\Listeners\SubscriptionListener@deletedClient',
|
||||
],
|
||||
'App\Events\ClientWasRestored' => [
|
||||
'App\Listeners\ActivityListener@restoredClient',
|
||||
@ -55,6 +59,7 @@ class EventServiceProvider extends ServiceProvider
|
||||
'App\Listeners\ActivityListener@restoredInvoice',
|
||||
],
|
||||
'App\Events\InvoiceWasEmailed' => [
|
||||
'App\Listeners\InvoiceListener@emailedInvoice',
|
||||
'App\Listeners\NotificationListener@emailedInvoice',
|
||||
],
|
||||
'App\Events\InvoiceInvitationWasEmailed' => [
|
||||
@ -89,6 +94,7 @@ class EventServiceProvider extends ServiceProvider
|
||||
'App\Listeners\ActivityListener@restoredQuote',
|
||||
],
|
||||
'App\Events\QuoteWasEmailed' => [
|
||||
'App\Listeners\QuoteListener@emailedQuote',
|
||||
'App\Listeners\NotificationListener@emailedQuote',
|
||||
],
|
||||
'App\Events\QuoteInvitationWasEmailed' => [
|
||||
@ -102,6 +108,7 @@ class EventServiceProvider extends ServiceProvider
|
||||
'App\Events\QuoteInvitationWasApproved' => [
|
||||
'App\Listeners\ActivityListener@approvedQuote',
|
||||
'App\Listeners\NotificationListener@approvedQuote',
|
||||
'App\Listeners\SubscriptionListener@approvedQuote',
|
||||
],
|
||||
|
||||
// Payments
|
||||
@ -119,6 +126,7 @@ class EventServiceProvider extends ServiceProvider
|
||||
'App\Listeners\ActivityListener@deletedPayment',
|
||||
'App\Listeners\InvoiceListener@deletedPayment',
|
||||
'App\Listeners\CreditListener@deletedPayment',
|
||||
'App\Listeners\SubscriptionListener@deletedPayment',
|
||||
],
|
||||
'App\Events\PaymentWasRefunded' => [
|
||||
'App\Listeners\ActivityListener@refundedPayment',
|
||||
@ -140,7 +148,6 @@ class EventServiceProvider extends ServiceProvider
|
||||
// Credits
|
||||
'App\Events\CreditWasCreated' => [
|
||||
'App\Listeners\ActivityListener@createdCredit',
|
||||
'App\Listeners\SubscriptionListener@createdCredit',
|
||||
],
|
||||
'App\Events\CreditWasArchived' => [
|
||||
'App\Listeners\ActivityListener@archivedCredit',
|
||||
@ -166,9 +173,11 @@ class EventServiceProvider extends ServiceProvider
|
||||
// Task events
|
||||
'App\Events\TaskWasCreated' => [
|
||||
'App\Listeners\ActivityListener@createdTask',
|
||||
'App\Listeners\SubscriptionListener@createdTask',
|
||||
],
|
||||
'App\Events\TaskWasUpdated' => [
|
||||
'App\Listeners\ActivityListener@updatedTask',
|
||||
'App\Listeners\SubscriptionListener@updatedTask',
|
||||
],
|
||||
'App\Events\TaskWasRestored' => [
|
||||
'App\Listeners\ActivityListener@restoredTask',
|
||||
@ -178,14 +187,28 @@ class EventServiceProvider extends ServiceProvider
|
||||
],
|
||||
'App\Events\TaskWasDeleted' => [
|
||||
'App\Listeners\ActivityListener@deletedTask',
|
||||
'App\Listeners\SubscriptionListener@deletedTask',
|
||||
],
|
||||
|
||||
// Vendor events
|
||||
'App\Events\VendorWasCreated' => [
|
||||
'App\Listeners\SubscriptionListener@createdVendor',
|
||||
],
|
||||
'App\Events\VendorWasUpdated' => [
|
||||
'App\Listeners\SubscriptionListener@updatedVendor',
|
||||
],
|
||||
'App\Events\VendorWasDeleted' => [
|
||||
'App\Listeners\SubscriptionListener@deletedVendor',
|
||||
],
|
||||
|
||||
// Expense events
|
||||
'App\Events\ExpenseWasCreated' => [
|
||||
'App\Listeners\ActivityListener@createdExpense',
|
||||
'App\Listeners\SubscriptionListener@createdExpense',
|
||||
],
|
||||
'App\Events\ExpenseWasUpdated' => [
|
||||
'App\Listeners\ActivityListener@updatedExpense',
|
||||
'App\Listeners\SubscriptionListener@updatedExpense',
|
||||
],
|
||||
'App\Events\ExpenseWasRestored' => [
|
||||
'App\Listeners\ActivityListener@restoredExpense',
|
||||
@ -195,6 +218,7 @@ class EventServiceProvider extends ServiceProvider
|
||||
],
|
||||
'App\Events\ExpenseWasDeleted' => [
|
||||
'App\Listeners\ActivityListener@deletedExpense',
|
||||
'App\Listeners\SubscriptionListener@deletedExpense',
|
||||
],
|
||||
|
||||
'Illuminate\Queue\Events\JobExceptionOccurred' => [
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Ninja\Datatables\ProjectTaskDatatable;
|
||||
use App\Ninja\Datatables\TaskDatatable;
|
||||
use App\Ninja\Repositories\TaskRepository;
|
||||
use Auth;
|
||||
@ -41,10 +42,15 @@ class TaskService extends BaseService
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function getDatatable($clientPublicId, $search)
|
||||
public function getDatatable($clientPublicId, $projectPublicId, $search)
|
||||
{
|
||||
$datatable = new TaskDatatable(true, $clientPublicId);
|
||||
$query = $this->taskRepo->find($clientPublicId, $search);
|
||||
if ($projectPublicId) {
|
||||
$datatable = new ProjectTaskDatatable(true, true);
|
||||
} else {
|
||||
$datatable = new TaskDatatable(true, $clientPublicId);
|
||||
}
|
||||
|
||||
$query = $this->taskRepo->find($clientPublicId, $projectPublicId, $search);
|
||||
|
||||
if (! Utils::hasPermission('view_all')) {
|
||||
$query->where('tasks.user_id', '=', Auth::user()->id);
|
||||
|
@ -39,7 +39,8 @@
|
||||
"fullcalendar": "^3.5.1",
|
||||
"toastr": "^2.1.3",
|
||||
"jt.timepicker": "jquery-timepicker-jt#^1.11.12",
|
||||
"qrcode.js": "qrcode-js#*"
|
||||
"qrcode.js": "qrcode-js#*",
|
||||
"money.js": "^0.1.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"jquery": "~1.11"
|
||||
|
@ -19,4 +19,7 @@ return [
|
||||
'coupon_75_off' => env('COUPON_75_OFF', false),
|
||||
'coupon_free_year' => env('COUPON_FREE_YEAR', false),
|
||||
|
||||
// data services
|
||||
'exchange_rates_url' => env('EXCHANGE_RATES_URL', 'https://api.fixer.io/latest'),
|
||||
|
||||
];
|
||||
|
159
database/migrations/2017_12_13_074024_add_remember_2fa_token.php
Normal file
159
database/migrations/2017_12_13_074024_add_remember_2fa_token.php
Normal file
@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddRemember2faToken extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function ($table) {
|
||||
$table->string('remember_2fa_token', 100)->nullable();
|
||||
});
|
||||
|
||||
Schema::dropIfExists('task_statuses');
|
||||
Schema::create('task_statuses', function ($table) {
|
||||
$table->increments('id');
|
||||
$table->unsignedInteger('user_id');
|
||||
$table->unsignedInteger('account_id')->index();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->string('name')->nullable();
|
||||
$table->smallInteger('sort_order')->default(0);
|
||||
|
||||
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||
|
||||
$table->unsignedInteger('public_id')->index();
|
||||
$table->unique(['account_id', 'public_id']);
|
||||
});
|
||||
|
||||
Schema::table('tasks', function ($table) {
|
||||
$table->unsignedInteger('task_status_id')->index()->nullable();
|
||||
$table->smallInteger('task_status_sort_order')->default(0);
|
||||
});
|
||||
|
||||
Schema::table('tasks', function ($table) {
|
||||
$table->foreign('task_status_id')->references('id')->on('task_statuses')->onDelete('cascade');
|
||||
});
|
||||
|
||||
Schema::table('currencies', function ($table) {
|
||||
$table->decimal('exchange_rate', 13, 4)->nullable();
|
||||
});
|
||||
|
||||
Schema::table('accounts', function ($table) {
|
||||
$table->boolean('convert_products')->default(false);
|
||||
$table->boolean('enable_reminder4')->default(false);
|
||||
$table->boolean('signature_on_pdf')->default(false);
|
||||
});
|
||||
|
||||
Schema::table('invoice_items', function ($table) {
|
||||
$table->float('discount');
|
||||
});
|
||||
|
||||
Schema::table('projects', function ($table) {
|
||||
$table->date('due_date')->nullable();
|
||||
$table->text('private_notes')->nullable();
|
||||
$table->float('budgeted_hours');
|
||||
});
|
||||
|
||||
Schema::table('account_email_settings', function ($table) {
|
||||
$table->string('email_subject_reminder4')->nullable();
|
||||
$table->text('email_template_reminder4')->nullable();
|
||||
$table->unsignedInteger('frequency_id_reminder4')->nullable();
|
||||
});
|
||||
|
||||
Schema::table('frequencies', function ($table) {
|
||||
$table->string('date_interval')->nullable();
|
||||
});
|
||||
|
||||
DB::statement("update invoices, (
|
||||
select max(created_at) created_at, invoice_id
|
||||
from activities
|
||||
where activity_type_id = 6
|
||||
group by invoice_id
|
||||
) as activities
|
||||
set invoices.last_sent_date = activities.created_at
|
||||
where invoices.id = activities.invoice_id
|
||||
and invoices.is_recurring = 0
|
||||
and invoices.invoice_type_id = 1");
|
||||
|
||||
DB::statement("update invoices, (
|
||||
select max(created_at) created_at, invoice_id
|
||||
from activities
|
||||
where activity_type_id = 20
|
||||
group by invoice_id
|
||||
) as activities
|
||||
set invoices.last_sent_date = activities.created_at
|
||||
where invoices.id = activities.invoice_id
|
||||
and invoices.is_recurring = 0
|
||||
and invoices.invoice_type_id = 2");
|
||||
|
||||
if (! Utils::isNinja()) {
|
||||
Schema::table('activities', function ($table) {
|
||||
$table->index('user_id');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function ($table) {
|
||||
$table->dropColumn('remember_2fa_token');
|
||||
});
|
||||
|
||||
Schema::table('tasks', function ($table) {
|
||||
$table->dropForeign('tasks_task_status_id_foreign');
|
||||
});
|
||||
|
||||
Schema::table('tasks', function ($table) {
|
||||
$table->dropColumn('task_status_id');
|
||||
$table->dropColumn('task_status_sort_order');
|
||||
});
|
||||
|
||||
Schema::dropIfExists('task_statuses');
|
||||
|
||||
Schema::table('currencies', function ($table) {
|
||||
$table->dropColumn('exchange_rate');
|
||||
});
|
||||
|
||||
Schema::table('accounts', function ($table) {
|
||||
$table->dropColumn('convert_products');
|
||||
$table->dropColumn('enable_reminder4');
|
||||
$table->dropColumn('signature_on_pdf');
|
||||
});
|
||||
|
||||
Schema::table('invoice_items', function ($table) {
|
||||
$table->dropColumn('discount');
|
||||
});
|
||||
|
||||
Schema::table('projects', function ($table) {
|
||||
$table->dropColumn('due_date');
|
||||
$table->dropColumn('private_notes');
|
||||
$table->dropColumn('budgeted_hours');
|
||||
});
|
||||
|
||||
Schema::table('account_email_settings', function ($table) {
|
||||
$table->dropColumn('email_subject_reminder4');
|
||||
$table->dropColumn('email_template_reminder4');
|
||||
$table->dropColumn('frequency_id_reminder4');
|
||||
});
|
||||
|
||||
Schema::table('frequencies', function ($table) {
|
||||
$table->dropColumn('date_interval');
|
||||
});
|
||||
|
||||
}
|
||||
}
|
@ -9,21 +9,23 @@ class FrequencySeeder extends Seeder
|
||||
Eloquent::unguard();
|
||||
|
||||
$frequencies = [
|
||||
['name' => 'Weekly'],
|
||||
['name' => 'Two weeks'],
|
||||
['name' => 'Four weeks'],
|
||||
['name' => 'Monthly'],
|
||||
['name' => 'Two months'],
|
||||
['name' => 'Three months'],
|
||||
['name' => 'Four months'],
|
||||
['name' => 'Six months'],
|
||||
['name' => 'Annually'],
|
||||
['name' => 'Weekly', 'date_interval' => '1 week'],
|
||||
['name' => 'Two weeks', 'date_interval' => '2 weeks'],
|
||||
['name' => 'Four weeks', 'date_interval' => '4 weeks'],
|
||||
['name' => 'Monthly', 'date_interval' => '1 month'],
|
||||
['name' => 'Two months', 'date_interval' => '2 months'],
|
||||
['name' => 'Three months', 'date_interval' => '3 months'],
|
||||
['name' => 'Four months', 'date_interval' => '4 months'],
|
||||
['name' => 'Six months', 'date_interval' => '6 months'],
|
||||
['name' => 'Annually', 'date_interval' => '1 year'],
|
||||
['name' => 'Two years', 'date_interval' => '2 years'],
|
||||
];
|
||||
|
||||
foreach ($frequencies as $frequency) {
|
||||
$record = Frequency::whereName($frequency['name'])->first();
|
||||
if ($record) {
|
||||
//$record->save();
|
||||
$record->date_interval = $frequency['date_interval'];
|
||||
$record->save();
|
||||
} else {
|
||||
Frequency::create($frequency);
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
32
docs/api.rst
32
docs/api.rst
@ -10,7 +10,7 @@ To access the API you first need to create a token using the "API Tokens” page
|
||||
- **PHP SDK**: https://github.com/invoiceninja/sdk-php
|
||||
- **Zend Framework**: https://github.com/alexz707/InvoiceNinjaModule
|
||||
|
||||
.. NOTE:: Replace ninja.dev with https://app.invoiceninja.com to access a hosted account.
|
||||
.. NOTE:: Replace ninja.test with https://app.invoiceninja.com to access a hosted account.
|
||||
|
||||
Reading Data
|
||||
""""""""""""
|
||||
@ -19,39 +19,39 @@ Here’s an example of reading the list of clients using cURL from the command l
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
curl -X GET ninja.dev/api/v1/clients -H "X-Ninja-Token: TOKEN"
|
||||
curl -X GET ninja.test/api/v1/clients -H "X-Ninja-Token: TOKEN"
|
||||
|
||||
For invoices, quotes, tasks and payments simply change the object type.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
curl -X GET ninja.dev/api/v1/invoices -H "X-Ninja-Token: TOKEN"
|
||||
curl -X GET ninja.test/api/v1/invoices -H "X-Ninja-Token: TOKEN"
|
||||
|
||||
You can search clients by their email address or id number and invoices by their invoice number.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
curl -X GET ninja.dev/api/v1/clients?email=<value> -H "X-Ninja-Token: TOKEN"
|
||||
curl -X GET ninja.dev/api/v1/clients?id_number=<value> -H "X-Ninja-Token: TOKEN"
|
||||
curl -X GET ninja.dev/api/v1/invoices?invoice_number=<value> -H "X-Ninja-Token: TOKEN"
|
||||
curl -X GET ninja.test/api/v1/clients?email=<value> -H "X-Ninja-Token: TOKEN"
|
||||
curl -X GET ninja.test/api/v1/clients?id_number=<value> -H "X-Ninja-Token: TOKEN"
|
||||
curl -X GET ninja.test/api/v1/invoices?invoice_number=<value> -H "X-Ninja-Token: TOKEN"
|
||||
|
||||
To load a single record specify the Id in the URL.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
curl -X GET ninja.dev/api/v1/invoices/1 -H "X-Ninja-Token: TOKEN"
|
||||
curl -X GET ninja.test/api/v1/invoices/1 -H "X-Ninja-Token: TOKEN"
|
||||
|
||||
You can specify additional relationships to load using the ``include`` parameter.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
curl -X GET ninja.dev/api/v1/clients/1?include=invoices.invitations -H "X-Ninja-Token: TOKEN"
|
||||
curl -X GET ninja.test/api/v1/clients/1?include=invoices.invitations -H "X-Ninja-Token: TOKEN"
|
||||
|
||||
You can download a PDF using the following URL
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
curl -X GET ninja.dev/api/v1/download/1 -H "X-Ninja-Token: TOKEN"
|
||||
curl -X GET ninja.test/api/v1/download/1 -H "X-Ninja-Token: TOKEN"
|
||||
|
||||
Optional Settings
|
||||
"""""""""""""""""
|
||||
@ -74,18 +74,20 @@ Here’s an example of creating a client. Note that email address is a property
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
curl -X POST ninja.dev/api/v1/clients -H "Content-Type:application/json" \
|
||||
curl -X POST ninja.test/api/v1/clients -H "Content-Type:application/json" \
|
||||
-d '{"name":"Client","contact":{"email":"test@example.com"}}' -H "X-Ninja-Token: TOKEN"
|
||||
|
||||
You can also update a client by specifying a value for ‘id’. Next, here’s an example of creating an invoice.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
curl -X POST ninja.dev/api/v1/invoices -H "Content-Type:application/json" \
|
||||
curl -X POST ninja.test/api/v1/invoices -H "Content-Type:application/json" \
|
||||
-d '{"client_id":"1", "invoice_items":[{"product_key": "ITEM", "notes":"Test", "cost":10, "qty":1}]}' \
|
||||
-H "X-Ninja-Token: TOKEN"
|
||||
|
||||
If the product_key is set and matches an existing record the product fields will be auto-populated. If the email field is set then we’ll search for a matching client. If no matches are found a new client will be created.
|
||||
If the email field is set we’ll search for a matching client, if no matches are found a new client will be created.
|
||||
|
||||
If the product_key is set and matches an existing record the product fields will be auto-populated. You can use a comma-separated value to create an invoice with multiple products.
|
||||
|
||||
Options
|
||||
^^^^^^^
|
||||
@ -104,7 +106,7 @@ Updating Data
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
curl -X PUT ninja.dev/api/v1/clients/1 -H "Content-Type:application/json" \
|
||||
curl -X PUT ninja.test/api/v1/clients/1 -H "Content-Type:application/json" \
|
||||
-d '{"name":"test", "contacts":[{"id": 1, "first_name": "test"}]}' \
|
||||
-H "X-Ninja-Token: TOKEN"
|
||||
|
||||
@ -112,7 +114,7 @@ You can archive, delete or restore an entity by setting ``action`` in the reques
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
curl -X PUT ninja.dev/api/v1/invoices/1?action=archive \
|
||||
curl -X PUT ninja.test/api/v1/invoices/1?action=archive \
|
||||
-H "X-Ninja-Token: TOKEN"
|
||||
|
||||
.. TIP:: For invoices use `mark_sent` to manually mark the invoice as sent
|
||||
@ -124,5 +126,5 @@ To email an invoice use the email_invoice command passing the id of the invoice.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
curl -X POST ninja.dev/api/v1/email_invoice -d '{"id":1}' \
|
||||
curl -X POST ninja.test/api/v1/email_invoice -d '{"id":1}' \
|
||||
-H "Content-Type:application/json" -H "X-Ninja-Token: TOKEN"
|
||||
|
@ -1,15 +1,78 @@
|
||||
Client Portal
|
||||
=============
|
||||
|
||||
The invoicing process is a two-way street. You bill the client; the client needs to view the invoice and make the payment. Why not make it as easy as possible for you – and for your clients? This is the purpose of Invoice Ninja's Client Portal. With Invoice Ninja, you can provide a portal for your clients where they can open and view your invoices, and even make payments, all via the Invoice Ninja website pages.
|
||||
The invoicing process is a two-way street. You bill the client; the client views and pays the invoice. Why not make it as easy as possible for you – and for your clients? This is the purpose of Invoice Ninja's Client Portal. With Invoice Ninja, you can choose to provide a portal for your clients where they can open and view your invoices, and even make payments, all via the Invoice Ninja website.
|
||||
|
||||
You can modify the Client Portal with a range of settings. To modify the client portal, go to Advanced Settings > Client Portal.
|
||||
|
||||
The Client Portal page is divided into three boxes: Settings, Authorization and Buy Now Buttons. Let's go through them one by one.
|
||||
|
||||
Settings
|
||||
""""""""
|
||||
|
||||
The Settings box has three sections – Link, Navigation and Custom CSS. Click on the tab to open the section you need.
|
||||
|
||||
Link
|
||||
^^^^
|
||||
|
||||
- **Domain**: Select your preferred domain name for the client portal and invoice emails.
|
||||
|
||||
- **Customize**: Customize the portal URL with a subdomain name or display the invoice on your own website. To add a subdomain, check Subdomain and enter the name in the field. To add your own website, check Website and enter the URL of your website in the field.
|
||||
|
||||
- **Preview**: This is a preview display of your portal URL. It will adjust automatically according to the text you enter in the Customize section.
|
||||
|
||||
Navigation
|
||||
^^^^^^^^^^
|
||||
|
||||
- **Client Portal**: You can choose whether or not you want to provide a portal for your clients. To show the portal, check Enable. To hide the portal from your clients' view, uncheck Enable.
|
||||
|
||||
- **Dashboard**: The Dashboard is a summary page of the client portal that displays all the client's invoicing activity with you. You can choose to show or hide the dashboard. Check the Enable box to show the dashboard. Uncheck to hide the dashboard.
|
||||
|
||||
Custom CSS
|
||||
^^^^^^^^^^
|
||||
|
||||
Do you have some experience in web design? Want to put your individual fingerprint on your client portal? You can control the look and feel of your client portal by entering custom CSS in your portal layout. Enter the CSS in the Custom CSS field.
|
||||
|
||||
Authorization
|
||||
"""""""""""""
|
||||
|
||||
The Authorization box has three sections – Password, Checkbox and Signature. Click on the tab to open the section you need.
|
||||
|
||||
Password
|
||||
^^^^^^^^
|
||||
|
||||
- **Password protect invoices**: If you're concerned about privacy, you can choose to set a password for clients to access their portal and invoices. To enable password protection, check the Enable box. To set a password for a specific client, go to the Client / Edit page. If you don't want to use passwords for client portals and invoices, uncheck the Enable box.
|
||||
|
||||
- **Dashboard**: The Client Portal Dashboard is a summary page that shows all your invoicing activity with the specific client. Do you want to display the Dashboard page for your clients when they use the Client Portal? You can choose to show or hide the dashboard. Check the Enable box to show the dashboard. Uncheck to hide the dashboard.
|
||||
- **Password protect invoices**: To increase security of invoice viewing, you can opt to make the invoices password protected for each individual client. To view your invoice, the client will need to enter a specific password. Check the box to enable the password protect function.
|
||||
- **Generate password automatically**: If your client does not already have a password for the portal, you can ensure they get one by enabling the system to automatically generate a password. The password will be sent to the client together with the first invoice. To enable this function, check the box.
|
||||
|
||||
Checkbox
|
||||
^^^^^^^^
|
||||
|
||||
Want to streamline communication with your clients? Add a confirmation checkbox to your quotes and invoices so your clients can accept the terms of the quote or invoice directly on the page.
|
||||
|
||||
- **Invoice Terms Checkbox**: Check Enable to require clients to confirm they accept your invoice terms.
|
||||
|
||||
- **Quote Terms Checkbox**: Check Enable to require clients to confirm they accept your quote terms.
|
||||
|
||||
Signature
|
||||
^^^^^^^^^
|
||||
|
||||
You can require clients to accept a quote or invoice by providing a signature.
|
||||
|
||||
- **Invoice Signature**: Check Enable to require clients to provide a signature on invoices.
|
||||
|
||||
- **Quote Signature**: Check Enable to require clients to provide a signature on quotes.
|
||||
|
||||
Custom CSS
|
||||
""""""""""
|
||||
|
||||
Do you have some experience in web design? Want to put your individual fingerprint on your client portal? You can control the look and feel of your client portal by entering custom CSS in your portal layout. Enter the CSS is the Custom CSS field.
|
||||
|
||||
Buy Now Buttons
|
||||
"""""""""""""""
|
||||
|
||||
Streamline the purchase payment process by adding a Buy Now button to the client portals. Clients can click the button to pay the invoice instantly online.
|
||||
|
||||
To enable support for Buy Now buttons in your client portals, check Enable.
|
||||
|
||||
To apply all changes to the Client Portal, click the green Save button at the bottom of the page.
|
||||
|
@ -57,9 +57,9 @@ author = u'Invoice Ninja'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = u'4.0'
|
||||
version = u'4.1'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = u'4.0.1'
|
||||
release = u'4.1.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
@ -44,7 +44,7 @@ Create an application in either Google, Facebook, GitHub or LinkedIn and then se
|
||||
|
||||
GOOGLE_CLIENT_ID=
|
||||
GOOGLE_CLIENT_SECRET=
|
||||
GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google
|
||||
GOOGLE_OAUTH_REDIRECT=http://ninja.test/auth/google
|
||||
|
||||
PhantomJS
|
||||
"""""""""
|
||||
@ -99,9 +99,9 @@ Security
|
||||
|
||||
To require a password to update the app add ``UPDATE_SECRET=random_value`` to the .env file and then use /update?secret=random_value to update.
|
||||
|
||||
By default the app clears the session when the browser is closed and automatically logs the user out after 8 hours.
|
||||
By default the app clears the session when the browser is closed and automatically logs the user out after 8 hours. This can be modified by setting ``REMEMBER_ME_ENABLED`` and ``AUTO_LOGOUT_SECONDS`` in the .env file.
|
||||
|
||||
This can be modified by setting ``REMEMBER_ME_ENABLED`` and ``AUTO_LOGOUT_SECONDS`` in the .env file.
|
||||
To include a secret when notifying subscriptions add ``SUBSCRIPTION_SECRET=random_value`` to the .env file.
|
||||
|
||||
Google Map
|
||||
""""""""""
|
||||
|
@ -7,37 +7,48 @@ You can design you invoice using the simple selection tools, or go deeper and cu
|
||||
|
||||
The Invoice Design page is divided into two sections. The top section presents the various options for customization and the bottom section displays a real time PDF sample of your invoice, so you can see your changes as you go along.
|
||||
|
||||
To customize the invoice design, there are five tabs to choose from. Let's go through them one by one:
|
||||
|
||||
General Settings
|
||||
""""""""""""""""
|
||||
|
||||
To customize the invoice design general settings, click on the General Settings tab. You have a number of options for setting changes:
|
||||
Standard Design: Invoice Ninja provides a selection of design templates. Examples include 'Bold', 'Elegant', 'Hipster' and 'Business'. The default template setting is 'Clean'. To change the template setting, select a design from the drop down menu. The PDF invoice display below will automatically update to show you a preview of the design template.
|
||||
|
||||
Design Invoice Ninja provides a selection of design templates to choose from. Examples include 'Bold', 'Elegant', 'Hipster' and 'Business'. The default template setting is 'Clean'. To change the template setting, select a design from the drop down menu. The PDF invoice display below will automatically update to show you a preview of the design template.
|
||||
Quote Design: You can also select a design template for your quotes. Choose from the available options in the drop down menu.
|
||||
|
||||
.. TIP:: Your chosen design will only apply after you click the Save button. Feel free to play with the various designs and explore your options before saving.
|
||||
|
||||
- **Body Font**: Select the desired font for the body text of the invoice.
|
||||
- **Header Font**: Select the desired font for the header text of the invoice.
|
||||
- **Page Size**: Select the desired page size, from among the dozens available.
|
||||
- **Font Size**: Select the desired font size for the text that appears in the invoice.
|
||||
- **Primary Color**: Select the desired primary color of the invoice design.
|
||||
- **Secondary Color**: Select the desired secondary color of the invoice design.
|
||||
|
||||
.. TIP:: The invoice design templates are based on a two-tone color scheme. Make sure to select primary and secondary colors that are complementary and reflect your design taste and your company's design theme.
|
||||
.. TIP: The invoice design templates are based on a two-tone color scheme. Make sure to select primary and secondary colors that are complementary and reflect your design taste and your company's design theme.
|
||||
|
||||
Invoice Labels
|
||||
""""""""""""""
|
||||
|
||||
Want to change the column names or terms on your invoice template? To customize the names of the Item, Description, Unit Cost, Quantity, Line Total and Terms on your invoice, enter the desired text in the relevant field.
|
||||
Want to change the column names or field names on your invoice template? To customize any field name on your invoice, choose the relevant label from the drop down menu, and enter the new name in the empty field. After you click the green Save button, the new field name(s) will appear on the live PDF of the invoice template.
|
||||
|
||||
Invoice Fields
|
||||
"""""""""""""""
|
||||
You can change the order and location of any fields on your invoice template by using the 'drop and drag' feature. The fields are coordinated in groups: Invoice Fields, Client Fields and Company Fields. Use your mouse to drag and drop the fields in the order and location you prefer.
|
||||
|
||||
Product Fields
|
||||
""""""""""""""
|
||||
|
||||
You can change the order and location of any product or task fields on your invoice template by using the 'drop and drag' feature. Use your mouse to drag and drop the fields in the order and location you prefer.
|
||||
|
||||
Invoice Options
|
||||
"""""""""""""""
|
||||
|
||||
Hide Quantity If your line items are always 1, then you can opt to hide the Quantity field. To hide Quantity, check the box.
|
||||
|
||||
Hide Paid to Date If you wish to hide the Paid to Date column until payment has been made, check the box. Then, your invoices won't ever display 'Paid to Date 0.00'.
|
||||
|
||||
Header/Footer
|
||||
"""""""""""""
|
||||
Embed documents You can choose to attach documents to your invoice, such as samples, testimonials, etc. Check the box to enable embedding of documents to your invoices.
|
||||
|
||||
Show Header on / Show Footer on Want your header and footer to appear on all pages of the invoice, or the first/last page only? Select the desired setting here.
|
||||
|
||||
Want your header and footer to appear on all pages of the invoice, or the first page only? Select the desired setting here.
|
||||
|
||||
@ -54,4 +65,4 @@ To save your customized changes, click the green Save button at the bottom of th
|
||||
|
||||
To cancel your customized changes, click the gray Cancel button at the bottom of the page.
|
||||
|
||||
Need help with your customized coding? Click the gray Help button at the bottom of the page. You'll be provided with a link to our support forum and to the website of the software provider, where you can explore how to use it.
|
||||
Need help with your customized coding? Click the gray Help button at the bottom of the page.
|
||||
|
@ -31,19 +31,16 @@ Version 4.0
|
||||
|
||||
The minimum PHP version is now 7.0.0
|
||||
|
||||
If you're using a rijndael cipher run ``php artisan ninja:update-key --legacy=true`` to change to AES-256-CBC.
|
||||
|
||||
Version 3.2
|
||||
"""""""""""
|
||||
|
||||
An import folder has been adding to storage/, you may need to run ``sudo chown -R www-data:www-data storage``
|
||||
|
||||
Version 2.6
|
||||
Version 2.5
|
||||
"""""""""""
|
||||
|
||||
Make sure the .env file includes ``APP_CIPHER=rijndael-128``
|
||||
|
||||
Version 2.5.1
|
||||
"""""""""""""
|
||||
|
||||
The minimum PHP version is now 5.5.9
|
||||
|
||||
Version 2.0
|
||||
@ -51,6 +48,6 @@ Version 2.0
|
||||
|
||||
Copy .env.example to .env and set config settings
|
||||
|
||||
Set the app cipher to ``rijndael-256`` to support existing passwords
|
||||
If unset, set the app cipher to ``rijndael-256``.
|
||||
|
||||
Check that ``/path/to/ninja/storage`` has 755 permissions and is owned by the webserver user
|
||||
|
@ -160,6 +160,7 @@ elixir(function(mix) {
|
||||
bowerDir + '/dropzone/dist/dropzone.js',
|
||||
bowerDir + '/typeahead.js/dist/typeahead.jquery.js',
|
||||
bowerDir + '/accounting/accounting.js',
|
||||
bowerDir + '/money.js/money.js',
|
||||
bowerDir + '/spectrum/spectrum.js',
|
||||
bowerDir + '/moment/moment.js',
|
||||
bowerDir + '/moment-timezone/builds/moment-timezone-with-data.js',
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
public/css/built.css
vendored
4
public/css/built.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
18
resources/assets/css/colors.css
vendored
18
resources/assets/css/colors.css
vendored
@ -3,33 +3,29 @@ body {
|
||||
}
|
||||
|
||||
.nav-tabs.nav-justified>.active>a, .nav-tabs.nav-justified>.active>a:hover, .nav-tabs.nav-justified>.active>a:focus {
|
||||
background-color: #127cc0;
|
||||
background-color: #117cc1;
|
||||
}
|
||||
|
||||
.navbar .dropdown-menu {
|
||||
border-top: 1px solid #127cc0;
|
||||
border-top: 1px solid #117cc1;
|
||||
}
|
||||
|
||||
.active-clients {
|
||||
background-color: #127cc0;
|
||||
background-color: #117cc1;
|
||||
}
|
||||
|
||||
.pagination>.active>a, .pagination>.active>span, .pagination>.active>a:hover, .pagination>.active>span:hover, .pagination>.active>a:focus, .pagination>.active>span:focus {
|
||||
background-color: #127cc0;
|
||||
border-color: #127cc0;
|
||||
background-color: #117cc1;
|
||||
border-color: #117cc1;
|
||||
}
|
||||
|
||||
.navbar,
|
||||
.navbar-collapse {
|
||||
xbackground-color: #337ab7 !important;
|
||||
background-color: #127cc0 !important;
|
||||
background-color: #117cc1 !important;
|
||||
}
|
||||
|
||||
.panel-heading {
|
||||
xbackground-color: #286090 !important;
|
||||
background-color: #093e60 !important;
|
||||
xbackground-color: #054B78 !important;
|
||||
xbackground-color: #07619B !important;
|
||||
background-color: #0a3f60 !important;
|
||||
}
|
||||
|
||||
|
||||
|
@ -14,7 +14,6 @@ NINJA.TEMPLATES = {
|
||||
};
|
||||
|
||||
function GetPdfMake(invoice, javascript, callback) {
|
||||
|
||||
var itemsTable = false;
|
||||
if (invoice.hasTasks) {
|
||||
// check if we need to add a second table for tasks
|
||||
@ -153,6 +152,7 @@ function GetPdfMake(invoice, javascript, callback) {
|
||||
|
||||
// support setting noWrap as a style
|
||||
dd.styles.noWrap = {'noWrap': true};
|
||||
dd.styles.discount = {'alignment': 'right'};
|
||||
|
||||
// set page size
|
||||
dd.pageSize = invoice.account.page_size;
|
||||
@ -251,6 +251,8 @@ NINJA.decodeJavascript = function(invoice, javascript)
|
||||
'fontSizeSmaller': NINJA.fontSize - 1,
|
||||
'bodyFont': NINJA.bodyFont,
|
||||
'headerFont': NINJA.headerFont,
|
||||
'signatureBase64': NINJA.signatureImage(invoice),
|
||||
'signatureDate': NINJA.signatureDate(invoice),
|
||||
}
|
||||
|
||||
for (var key in json) {
|
||||
@ -357,6 +359,62 @@ NINJA.decodeJavascript = function(invoice, javascript)
|
||||
return javascript;
|
||||
}
|
||||
|
||||
NINJA.signatureImage = function(invoice) {
|
||||
var blankImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=';
|
||||
|
||||
if (! invoice.invitations || ! invoice.invitations.length) {
|
||||
return blankImage;
|
||||
}
|
||||
|
||||
if (! parseInt(invoice.account.signature_on_pdf)) {
|
||||
return blankImage;
|
||||
}
|
||||
|
||||
for (var i=0; i<invoice.invitations.length; i++) {
|
||||
var invitation = invoice.invitations[i];
|
||||
if (invitation.signature_base64) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! invitation.signature_base64) {
|
||||
return blankImage;
|
||||
}
|
||||
|
||||
return invitation.signature_base64 || blankImage;
|
||||
}
|
||||
|
||||
NINJA.signatureDate = function(invoice) {
|
||||
if (! invoice.invitations || ! invoice.invitations.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (! parseInt(invoice.account.signature_on_pdf)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
for (var i=0; i<invoice.invitations.length; i++) {
|
||||
var invitation = invoice.invitations[i];
|
||||
if (invitation.signature_base64) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! invitation.signature_base64) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var date = invitation.signature_date;
|
||||
return NINJA.formatDateTime(date, invoice.account);
|
||||
}
|
||||
|
||||
NINJA.formatDateTime = function(date, account) {
|
||||
var format = account.datetime_format ? account.datetime_format.format_moment : 'LLL';
|
||||
var timezone = account.timezone ? account.timezone.name : '{{ DEFAULT_TIMEZONE }}';
|
||||
|
||||
return date ? moment.utc(date).tz(timezone).format(format) : '';
|
||||
}
|
||||
|
||||
NINJA.entityType = function(invoice)
|
||||
{
|
||||
if (invoice.is_delivery_note) {
|
||||
@ -461,6 +519,12 @@ NINJA.invoiceColumns = function(invoice, design, isTasks)
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else if (field == 'product.discount') {
|
||||
if (invoice.has_item_discounts) {
|
||||
width = 15;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else if (field == 'product.description') {
|
||||
width = 0;
|
||||
} else {
|
||||
@ -560,6 +624,7 @@ NINJA.invoiceLines = function(invoice, isSecondTable) {
|
||||
'product.rate',
|
||||
'product.tax',
|
||||
'product.line_total',
|
||||
'product.discount',
|
||||
];
|
||||
|
||||
if (isSecondTable) {
|
||||
@ -593,6 +658,8 @@ NINJA.invoiceLines = function(invoice, isSecondTable) {
|
||||
}
|
||||
} else if (field == 'tax' && ! invoice.has_item_taxes) {
|
||||
continue;
|
||||
} else if (field == 'discount' && ! invoice.has_item_discounts) {
|
||||
continue;
|
||||
} else if (field == 'unit_cost' || field == 'rate' || field == 'hours') {
|
||||
headerStyles.push('cost');
|
||||
}
|
||||
@ -609,8 +676,9 @@ NINJA.invoiceLines = function(invoice, isSecondTable) {
|
||||
for (var i=0; i<invoice.invoice_items.length; i++) {
|
||||
var row = [];
|
||||
var item = invoice.invoice_items[i];
|
||||
var cost = NINJA.parseFloat(item.cost) ? formatMoneyInvoice(item.cost, invoice, null, getPrecision(item.cost)) : ' ';
|
||||
var qty = NINJA.parseFloat(item.qty) ? roundSignificant(NINJA.parseFloat(item.qty)) + '' : ' ';
|
||||
var cost = NINJA.parseFloat(item.cost) ? formatMoneyInvoice(NINJA.parseFloat(item.cost), invoice, null, getPrecision(NINJA.parseFloat(item.cost))) : ' ';
|
||||
var qty = NINJA.parseFloat(item.qty) ? formatMoneyInvoice(NINJA.parseFloat(item.qty), invoice, 'none', getPrecision(NINJA.parseFloat(item.qty))) + '' : ' ';
|
||||
var discount = roundToTwo(NINJA.parseFloat(item.discount));
|
||||
var notes = item.notes;
|
||||
var productKey = item.product_key;
|
||||
var tax1 = '';
|
||||
@ -651,6 +719,15 @@ NINJA.invoiceLines = function(invoice, isSecondTable) {
|
||||
}
|
||||
|
||||
var lineTotal = roundSignificant(NINJA.parseFloat(item.cost) * NINJA.parseFloat(item.qty));
|
||||
|
||||
if (discount != 0) {
|
||||
if (parseInt(invoice.is_amount_discount)) {
|
||||
lineTotal -= discount;
|
||||
} else {
|
||||
lineTotal -= (lineTotal * discount / 100);
|
||||
}
|
||||
}
|
||||
|
||||
if (account.include_item_taxes_inline == '1') {
|
||||
var taxAmount1 = 0;
|
||||
var taxAmount2 = 0;
|
||||
@ -684,6 +761,8 @@ NINJA.invoiceLines = function(invoice, isSecondTable) {
|
||||
continue;
|
||||
} else if (field == 'tax' && ! invoice.has_item_taxes) {
|
||||
continue;
|
||||
} else if (field == 'discount' && ! invoice.has_item_discounts) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field == 'item' || field == 'service') {
|
||||
@ -699,6 +778,14 @@ NINJA.invoiceLines = function(invoice, isSecondTable) {
|
||||
if (field == 'hours') {
|
||||
styles.push('cost');
|
||||
}
|
||||
} else if (field == 'discount') {
|
||||
if (parseInt(invoice.is_amount_discount)) {
|
||||
value = formatMoneyInvoice(discount, invoice);
|
||||
} else {
|
||||
if (discount) {
|
||||
value = discount + '%';
|
||||
}
|
||||
}
|
||||
} else if (field == 'tax') {
|
||||
value = ' ';
|
||||
if (item.tax_name1) {
|
||||
@ -797,10 +884,10 @@ NINJA.subtotals = function(invoice, hideBalance)
|
||||
}
|
||||
|
||||
var customValue1 = NINJA.parseFloat(invoice.custom_value1);
|
||||
var customValue1Label = customValue1 >= 0 ? (account.custom_invoice_label1 || invoiceLabels.surcharge) : invoiceLabels.discount;
|
||||
var customValue1Label = account.custom_invoice_label1 || invoiceLabels.surcharge;
|
||||
|
||||
var customValue2 = NINJA.parseFloat(invoice.custom_value2);
|
||||
var customValue2Label = customValue2 >= 0 ? (account.custom_invoice_label2 || invoiceLabels.surcharge) : invoiceLabels.discount;
|
||||
var customValue2Label = account.custom_invoice_label2 || invoiceLabels.surcharge;
|
||||
|
||||
if (customValue1 && invoice.custom_taxes1 == '1') {
|
||||
data.push([{text: customValue1Label, style: ['subtotalsLabel', 'customTax1Label']}, {text: formatMoneyInvoice(invoice.custom_value1, invoice), style: ['subtotals', 'customTax1']}]);
|
||||
@ -976,7 +1063,7 @@ NINJA.renderField = function(invoice, field, twoColumn) {
|
||||
}
|
||||
var account = invoice.account;
|
||||
var contact = client.contacts[0];
|
||||
var clientName = client.name || (contact.first_name || contact.last_name ? (contact.first_name + ' ' + contact.last_name) : contact.email);
|
||||
var clientName = client.name || (contact.first_name || contact.last_name ? ((contact.first_name || '') + ' ' + (contact.last_name || '')) : contact.email);
|
||||
|
||||
var label = false;
|
||||
var value = false;
|
||||
@ -984,7 +1071,7 @@ NINJA.renderField = function(invoice, field, twoColumn) {
|
||||
if (field == 'client.client_name') {
|
||||
value = clientName || ' ';
|
||||
} else if (field == 'client.contact_name') {
|
||||
value = (contact.first_name || contact.last_name) ? contact.first_name + ' ' + contact.last_name : false;
|
||||
value = (contact.first_name || contact.last_name) ? (contact.first_name || '') + ' ' + (contact.last_name || '') : false;
|
||||
} else if (field == 'client.id_number') {
|
||||
value = client.id_number;
|
||||
if (invoiceLabels.id_number_orig) {
|
||||
|
@ -31,6 +31,16 @@ function generatePDF(invoice, javascript, force, cb) {
|
||||
}
|
||||
|
||||
invoice = calculateAmounts(invoice);
|
||||
|
||||
if (parseInt(invoice.account.signature_on_pdf)) {
|
||||
invoice = convertSignature(invoice);
|
||||
}
|
||||
|
||||
// convertSignature returns false to wait for the canvas to draw
|
||||
if (! invoice) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var pdfDoc = GetPdfMake(invoice, javascript, cb);
|
||||
|
||||
if (cb) {
|
||||
@ -571,11 +581,24 @@ function calculateAmounts(invoice) {
|
||||
|
||||
var hasStandard = false;
|
||||
var hasTask = false;
|
||||
var hasDiscount = false;
|
||||
|
||||
// sum line item
|
||||
for (var i=0; i<invoice.invoice_items.length; i++) {
|
||||
var item = invoice.invoice_items[i];
|
||||
var lineTotal = invoice.is_statement ? roundToTwo(NINJA.parseFloat(item.balance)) : roundSignificant(NINJA.parseFloat(item.cost) * NINJA.parseFloat(item.qty));
|
||||
if (invoice.is_statement) {
|
||||
var lineTotal = roundToTwo(NINJA.parseFloat(item.balance));
|
||||
} else {
|
||||
var lineTotal = roundSignificant(NINJA.parseFloat(item.cost)) * roundSignificant(NINJA.parseFloat(item.qty));
|
||||
var discount = roundToTwo(NINJA.parseFloat(item.discount));
|
||||
if (discount != 0) {
|
||||
if (parseInt(invoice.is_amount_discount)) {
|
||||
lineTotal -= discount;
|
||||
} else {
|
||||
lineTotal -= (lineTotal * discount / 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
lineTotal = roundToTwo(lineTotal);
|
||||
if (lineTotal) {
|
||||
total += lineTotal;
|
||||
@ -622,16 +645,30 @@ function calculateAmounts(invoice) {
|
||||
}
|
||||
|
||||
// calculate line item tax
|
||||
var lineTotal = roundSignificant(NINJA.parseFloat(item.cost) * NINJA.parseFloat(item.qty));
|
||||
if (invoice.discount != 0) {
|
||||
var lineTotal = roundSignificant(NINJA.parseFloat(item.cost)) * roundSignificant(NINJA.parseFloat(item.qty));
|
||||
var discount = roundToTwo(NINJA.parseFloat(item.discount));
|
||||
if (discount != 0) {
|
||||
hasDiscount = true;
|
||||
if (parseInt(invoice.is_amount_discount)) {
|
||||
lineTotal -= roundToTwo((lineTotal/total) * invoice.discount);
|
||||
lineTotal -= discount;
|
||||
} else {
|
||||
lineTotal -= roundToTwo(lineTotal * invoice.discount / 100);
|
||||
lineTotal -= (lineTotal * discount / 100);
|
||||
}
|
||||
}
|
||||
lineTotal = roundToTwo(lineTotal);
|
||||
|
||||
if (invoice.discount != 0) {
|
||||
var discount = roundToTwo(NINJA.parseFloat(invoice.discount));
|
||||
if (parseInt(invoice.is_amount_discount)) {
|
||||
lineTotal -= roundToTwo((lineTotal/total) * discount);
|
||||
} else {
|
||||
lineTotal -= roundToTwo(lineTotal * discount / 100);
|
||||
}
|
||||
}
|
||||
|
||||
if (invoice.account.inclusive_taxes != '1') {
|
||||
if (! taxRate1) {
|
||||
var taxAmount1 = 0;
|
||||
} else if (invoice.account.inclusive_taxes != '1') {
|
||||
var taxAmount1 = roundToTwo(lineTotal * taxRate1 / 100);
|
||||
} else {
|
||||
var taxAmount1 = roundToTwo((lineTotal * 100) / (100 + (taxRate1 * 100)));
|
||||
@ -646,7 +683,9 @@ function calculateAmounts(invoice) {
|
||||
}
|
||||
}
|
||||
|
||||
if (invoice.account.inclusive_taxes != '1') {
|
||||
if (! taxRate2) {
|
||||
var taxAmount2 = 0;
|
||||
} else if (invoice.account.inclusive_taxes != '1') {
|
||||
var taxAmount2 = roundToTwo(lineTotal * taxRate2 / 100);
|
||||
} else {
|
||||
var taxAmount2 = roundToTwo((lineTotal * 100) / (100 + (taxRate2 * 100)));
|
||||
@ -663,6 +702,7 @@ function calculateAmounts(invoice) {
|
||||
}
|
||||
|
||||
invoice.has_item_taxes = hasTaxes;
|
||||
invoice.has_item_discounts = hasDiscount;
|
||||
invoice.subtotal_amount = total;
|
||||
|
||||
var discount = 0;
|
||||
@ -670,7 +710,7 @@ function calculateAmounts(invoice) {
|
||||
if (parseInt(invoice.is_amount_discount)) {
|
||||
discount = roundToTwo(invoice.discount);
|
||||
} else {
|
||||
discount = roundToTwo(total * invoice.discount / 100);
|
||||
discount = roundToTwo(total * roundToTwo(invoice.discount) / 100);
|
||||
}
|
||||
total -= discount;
|
||||
}
|
||||
|
@ -1224,7 +1224,7 @@ $LANG = array(
|
||||
'secret' => 'Secret',
|
||||
'public_key' => 'Public Key',
|
||||
'plaid_optional' => '(optional)',
|
||||
'plaid_environment_help' => 'When a Stripe test key is given, Plaid\'s development environement (tartan) will be used.',
|
||||
'plaid_environment_help' => 'When a Stripe test key is given, Plaid\'s development environment (tartan) will be used.',
|
||||
'other_providers' => 'Other Providers',
|
||||
'country_not_supported' => 'That country is not supported.',
|
||||
'invalid_routing_number' => 'The routing number is not valid.',
|
||||
@ -1262,6 +1262,7 @@ $LANG = array(
|
||||
'webhook_url' => 'Webhook URL',
|
||||
'stripe_webhook_help' => 'You must :link.',
|
||||
'stripe_webhook_help_link_text' => 'add this URL as an endpoint at Stripe',
|
||||
'gocardless_webhook_help_link_text' => 'You must add this URL as an endpoint in GoCardless',
|
||||
'payment_method_error' => 'There was an error adding your payment methd. Please try again later.',
|
||||
'notification_invoice_payment_failed_subject' => 'Payment failed for Invoice :invoice',
|
||||
'notification_invoice_payment_failed' => 'A payment made by client :client towards Invoice :invoice failed. The payment has been marked as failed and :amount has been added to the client\'s balance.',
|
||||
@ -1389,6 +1390,7 @@ $LANG = array(
|
||||
'freq_four_months' => 'Four months',
|
||||
'freq_six_months' => 'Six months',
|
||||
'freq_annually' => 'Annually',
|
||||
'freq_two_years' => 'Two years',
|
||||
|
||||
// Payment types
|
||||
'payment_type_Apply Credit' => 'Apply Credit',
|
||||
@ -2417,7 +2419,8 @@ $LANG = array(
|
||||
'contact_custom1' => 'Contact First Custom',
|
||||
'contact_custom2' => 'Contact Second Custom',
|
||||
'currency' => 'Currency',
|
||||
'ofx_help' => 'In most cases the default values should work, if you\'re unable to connect it may help to adjust the settings.',
|
||||
'ofx_help' => 'In most cases the default values should work, if you\'re unable to connect it may help to :link.',
|
||||
'adjust_the_settings' => 'adjust the settings',
|
||||
|
||||
'item_product' => 'Item Product',
|
||||
'item_notes' => 'Item Notes',
|
||||
@ -2456,7 +2459,6 @@ $LANG = array(
|
||||
'enable_alipay' => 'Accept Alipay',
|
||||
'enable_sofort' => 'Accept EU bank transfers',
|
||||
'stripe_alipay_help' => 'These gateways also need to be activated in :link.',
|
||||
'gocardless_webhook_help_link_text' => 'add this URL as an endpoint in GoCardless',
|
||||
'calendar' => 'Calendar',
|
||||
'pro_plan_calendar' => ':link to enable the calendar by joining the Pro Plan',
|
||||
|
||||
@ -2490,7 +2492,7 @@ $LANG = array(
|
||||
'clear' => 'Clear',
|
||||
'warn_payment_gateway' => 'Note: accepting online payments requires a payment gateway, :link to add one.',
|
||||
'task_rate' => 'Task Rate',
|
||||
'task_rate_help' => 'Set the default <b>rate for invoiced tasks</b>.',
|
||||
'task_rate_help' => 'Set the default rate for invoiced tasks.',
|
||||
'past_due' => 'Past Due',
|
||||
'document' => 'Document',
|
||||
'invoice_or_expense' => 'Invoice/Expense',
|
||||
@ -2581,6 +2583,18 @@ $LANG = array(
|
||||
'subscription_event_7' => 'Deleted Quote',
|
||||
'subscription_event_8' => 'Updated Invoice',
|
||||
'subscription_event_9' => 'Deleted Invoice',
|
||||
'subscription_event_10' => 'Updated Client',
|
||||
'subscription_event_11' => 'Deleted Client',
|
||||
'subscription_event_12' => 'Deleted Payment',
|
||||
'subscription_event_13' => 'Updated Vendor',
|
||||
'subscription_event_14' => 'Deleted Vendor',
|
||||
'subscription_event_15' => 'Created Expense',
|
||||
'subscription_event_16' => 'Updated Expense',
|
||||
'subscription_event_17' => 'Deleted Expense',
|
||||
'subscription_event_18' => 'Created Task',
|
||||
'subscription_event_19' => 'Updated Task',
|
||||
'subscription_event_20' => 'Deleted Task',
|
||||
'subscription_event_21' => 'Approved Quote',
|
||||
'subscriptions' => 'Subscriptions',
|
||||
'updated_subscription' => 'Successfully updated subscription',
|
||||
'created_subscription' => 'Successfully created subscription',
|
||||
@ -2605,11 +2619,34 @@ $LANG = array(
|
||||
'mcrypt_warning' => 'Warning: Mcrypt is deprecated, run <code>php artisan ninja:update-key --legacy=true</code> to update your cipher.',
|
||||
'edit_times' => 'Edit Times',
|
||||
'inclusive_taxes_help' => 'Include <b>taxes in the cost</b>',
|
||||
'inclusive_taxes_notice' => 'This setting can not be changed once an invoice has been created.',
|
||||
'inclusive_taxes_warning' => 'Warning: existing invoices will need to be resaved',
|
||||
'copy_shipping' => 'Copy Shipping',
|
||||
'copy_billing' => 'Copy Billing',
|
||||
'quote_has_expired' => 'The quote has expired, please contact the merchant.',
|
||||
'empty_table_footer' => 'Showing 0 to 0 of 0 entries',
|
||||
'do_not_trust' => 'Do not remember this device',
|
||||
'trust_for_30_days' => 'Trust for 30 days',
|
||||
'trust_forever' => 'Trust forever',
|
||||
'kanban' => 'Kanban',
|
||||
'backlog' => 'Backlog',
|
||||
'ready_to_do' => 'Ready to do',
|
||||
'in_progress' => 'In progress',
|
||||
'add_status' => 'Add status',
|
||||
'archive_status' => 'Archive Status',
|
||||
'new_status' => 'New Status',
|
||||
'convert_products' => 'Convert Products',
|
||||
'convert_products_help' => 'Automatically convert product prices to the client\'s currency',
|
||||
'improve_client_portal_link' => 'Set a subdomain to shorten the client portal link.',
|
||||
'budgeted_hours' => 'Budgeted Hours',
|
||||
'progress' => 'Progress',
|
||||
'view_project' => 'View Project',
|
||||
'summary' => 'Summary',
|
||||
'endless_reminder' => 'Endless Reminder',
|
||||
'signature_on_invoice_help' => 'Add the following code to show your client\'s signature on the PDF.',
|
||||
'signature_on_pdf' => 'Show on PDF',
|
||||
'signature_on_pdf_help' => 'Show the client signature on the invoice/quote PDF.',
|
||||
'expired_white_label' => 'The white label license has expired',
|
||||
|
||||
);
|
||||
|
||||
|
@ -1226,7 +1226,7 @@ $LANG = array(
|
||||
'secret' => 'Secret',
|
||||
'public_key' => 'Public Key',
|
||||
'plaid_optional' => '(optional)',
|
||||
'plaid_environment_help' => 'When a Stripe test key is given, Plaid\'s development environement (tartan) will be used.',
|
||||
'plaid_environment_help' => 'When a Stripe test key is given, Plaid\'s development environment (tartan) will be used.',
|
||||
'other_providers' => 'Other Providers',
|
||||
'country_not_supported' => 'That country is not supported.',
|
||||
'invalid_routing_number' => 'The routing number is not valid.',
|
||||
@ -1264,6 +1264,7 @@ $LANG = array(
|
||||
'webhook_url' => 'Webhook URL',
|
||||
'stripe_webhook_help' => 'You must :link.',
|
||||
'stripe_webhook_help_link_text' => 'add this URL as an endpoint at Stripe',
|
||||
'gocardless_webhook_help_link_text' => 'You must add this URL as an endpoint in GoCardless',
|
||||
'payment_method_error' => 'There was an error adding your payment methd. Please try again later.',
|
||||
'notification_invoice_payment_failed_subject' => 'Payment failed for Invoice :invoice',
|
||||
'notification_invoice_payment_failed' => 'A payment made by client :client towards Invoice :invoice failed. The payment has been marked as failed and :amount has been added to the client\'s balance.',
|
||||
@ -1391,6 +1392,7 @@ $LANG = array(
|
||||
'freq_four_months' => 'Four months',
|
||||
'freq_six_months' => 'Six months',
|
||||
'freq_annually' => 'Annually',
|
||||
'freq_two_years' => 'Two years',
|
||||
|
||||
// Payment types
|
||||
'payment_type_Apply Credit' => 'Apply Credit',
|
||||
@ -2419,7 +2421,8 @@ $LANG = array(
|
||||
'contact_custom1' => 'Contact First Custom',
|
||||
'contact_custom2' => 'Contact Second Custom',
|
||||
'currency' => 'Currency',
|
||||
'ofx_help' => 'In most cases the default values should work, if you\'re unable to connect it may help to adjust the settings.',
|
||||
'ofx_help' => 'In most cases the default values should work, if you\'re unable to connect it may help to :link.',
|
||||
'adjust_the_settings' => 'adjust the settings',
|
||||
|
||||
'item_product' => 'Item Product',
|
||||
'item_notes' => 'Item Notes',
|
||||
@ -2458,7 +2461,6 @@ $LANG = array(
|
||||
'enable_alipay' => 'Accept Alipay',
|
||||
'enable_sofort' => 'Accept EU bank transfers',
|
||||
'stripe_alipay_help' => 'These gateways also need to be activated in :link.',
|
||||
'gocardless_webhook_help_link_text' => 'add this URL as an endpoint in GoCardless',
|
||||
'calendar' => 'Calendar',
|
||||
'pro_plan_calendar' => ':link to enable the calendar by joining the Pro Plan',
|
||||
|
||||
@ -2492,7 +2494,7 @@ $LANG = array(
|
||||
'clear' => 'Clear',
|
||||
'warn_payment_gateway' => 'Note: accepting online payments requires a payment gateway, :link to add one.',
|
||||
'task_rate' => 'Task Rate',
|
||||
'task_rate_help' => 'Set the default <b>rate for invoiced tasks</b>.',
|
||||
'task_rate_help' => 'Set the default rate for invoiced tasks.',
|
||||
'past_due' => 'Past Due',
|
||||
'document' => 'Document',
|
||||
'invoice_or_expense' => 'Invoice/Expense',
|
||||
@ -2583,6 +2585,18 @@ $LANG = array(
|
||||
'subscription_event_7' => 'Deleted Quote',
|
||||
'subscription_event_8' => 'Updated Invoice',
|
||||
'subscription_event_9' => 'Deleted Invoice',
|
||||
'subscription_event_10' => 'Updated Client',
|
||||
'subscription_event_11' => 'Deleted Client',
|
||||
'subscription_event_12' => 'Deleted Payment',
|
||||
'subscription_event_13' => 'Updated Vendor',
|
||||
'subscription_event_14' => 'Deleted Vendor',
|
||||
'subscription_event_15' => 'Created Expense',
|
||||
'subscription_event_16' => 'Updated Expense',
|
||||
'subscription_event_17' => 'Deleted Expense',
|
||||
'subscription_event_18' => 'Created Task',
|
||||
'subscription_event_19' => 'Updated Task',
|
||||
'subscription_event_20' => 'Deleted Task',
|
||||
'subscription_event_21' => 'Approved Quote',
|
||||
'subscriptions' => 'Subscriptions',
|
||||
'updated_subscription' => 'Successfully updated subscription',
|
||||
'created_subscription' => 'Successfully created subscription',
|
||||
@ -2607,11 +2621,34 @@ $LANG = array(
|
||||
'mcrypt_warning' => 'Warning: Mcrypt is deprecated, run <code>php artisan ninja:update-key --legacy=true</code> to update your cipher.',
|
||||
'edit_times' => 'Edit Times',
|
||||
'inclusive_taxes_help' => 'Include <b>taxes in the cost</b>',
|
||||
'inclusive_taxes_notice' => 'This setting can not be changed once an invoice has been created.',
|
||||
'inclusive_taxes_warning' => 'Warning: existing invoices will need to be resaved',
|
||||
'copy_shipping' => 'Copy Shipping',
|
||||
'copy_billing' => 'Copy Billing',
|
||||
'quote_has_expired' => 'The quote has expired, please contact the merchant.',
|
||||
'empty_table_footer' => 'Showing 0 to 0 of 0 entries',
|
||||
'do_not_trust' => 'Do not remember this device',
|
||||
'trust_for_30_days' => 'Trust for 30 days',
|
||||
'trust_forever' => 'Trust forever',
|
||||
'kanban' => 'Kanban',
|
||||
'backlog' => 'Backlog',
|
||||
'ready_to_do' => 'Ready to do',
|
||||
'in_progress' => 'In progress',
|
||||
'add_status' => 'Add status',
|
||||
'archive_status' => 'Archive Status',
|
||||
'new_status' => 'New Status',
|
||||
'convert_products' => 'Convert Products',
|
||||
'convert_products_help' => 'Automatically convert product prices to the client\'s currency',
|
||||
'improve_client_portal_link' => 'Set a subdomain to shorten the client portal link.',
|
||||
'budgeted_hours' => 'Budgeted Hours',
|
||||
'progress' => 'Progress',
|
||||
'view_project' => 'View Project',
|
||||
'summary' => 'Summary',
|
||||
'endless_reminder' => 'Endless Reminder',
|
||||
'signature_on_invoice_help' => 'Add the following code to show your client\'s signature on the PDF.',
|
||||
'signature_on_pdf' => 'Show on PDF',
|
||||
'signature_on_pdf_help' => 'Show the client signature on the invoice/quote PDF.',
|
||||
'expired_white_label' => 'The white label license has expired',
|
||||
|
||||
);
|
||||
|
||||
|
@ -1224,7 +1224,7 @@ $LANG = array(
|
||||
'secret' => 'Hemmelighed',
|
||||
'public_key' => 'Offentlig nøgle',
|
||||
'plaid_optional' => '(valgfrit)',
|
||||
'plaid_environment_help' => 'When a Stripe test key is given, Plaid\'s development environement (tartan) will be used.',
|
||||
'plaid_environment_help' => 'When a Stripe test key is given, Plaid\'s development environment (tartan) will be used.',
|
||||
'other_providers' => 'Andre udbydere',
|
||||
'country_not_supported' => 'Landet er ikke understøttet.',
|
||||
'invalid_routing_number' => 'Routing nummeret er ugyldigt.',
|
||||
@ -1262,6 +1262,7 @@ $LANG = array(
|
||||
'webhook_url' => 'Webhook URL',
|
||||
'stripe_webhook_help' => 'You must :link.',
|
||||
'stripe_webhook_help_link_text' => 'add this URL as an endpoint at Stripe',
|
||||
'gocardless_webhook_help_link_text' => 'You must add this URL as an endpoint in GoCardless',
|
||||
'payment_method_error' => 'There was an error adding your payment methd. Please try again later.',
|
||||
'notification_invoice_payment_failed_subject' => 'Payment failed for Invoice :invoice',
|
||||
'notification_invoice_payment_failed' => 'A payment made by client :client towards Invoice :invoice failed. The payment has been marked as failed and :amount has been added to the client\'s balance.',
|
||||
@ -1389,6 +1390,7 @@ $LANG = array(
|
||||
'freq_four_months' => 'Four months',
|
||||
'freq_six_months' => 'Seks måneder',
|
||||
'freq_annually' => 'Årlig',
|
||||
'freq_two_years' => 'Two years',
|
||||
|
||||
// Payment types
|
||||
'payment_type_Apply Credit' => 'Tilføj kredit',
|
||||
@ -2417,7 +2419,8 @@ $LANG = array(
|
||||
'contact_custom1' => 'Contact First Custom',
|
||||
'contact_custom2' => 'Contact Second Custom',
|
||||
'currency' => 'Currency',
|
||||
'ofx_help' => 'In most cases the default values should work, if you\'re unable to connect it may help to adjust the settings.',
|
||||
'ofx_help' => 'In most cases the default values should work, if you\'re unable to connect it may help to :link.',
|
||||
'adjust_the_settings' => 'adjust the settings',
|
||||
|
||||
'item_product' => 'Item Product',
|
||||
'item_notes' => 'Item Notes',
|
||||
@ -2456,7 +2459,6 @@ $LANG = array(
|
||||
'enable_alipay' => 'Accept Alipay',
|
||||
'enable_sofort' => 'Accept EU bank transfers',
|
||||
'stripe_alipay_help' => 'These gateways also need to be activated in :link.',
|
||||
'gocardless_webhook_help_link_text' => 'add this URL as an endpoint in GoCardless',
|
||||
'calendar' => 'Calendar',
|
||||
'pro_plan_calendar' => ':link to enable the calendar by joining the Pro Plan',
|
||||
|
||||
@ -2490,7 +2492,7 @@ $LANG = array(
|
||||
'clear' => 'Clear',
|
||||
'warn_payment_gateway' => 'Note: accepting online payments requires a payment gateway, :link to add one.',
|
||||
'task_rate' => 'Task Rate',
|
||||
'task_rate_help' => 'Set the default <b>rate for invoiced tasks</b>.',
|
||||
'task_rate_help' => 'Set the default rate for invoiced tasks.',
|
||||
'past_due' => 'Past Due',
|
||||
'document' => 'Document',
|
||||
'invoice_or_expense' => 'Invoice/Expense',
|
||||
@ -2581,6 +2583,18 @@ $LANG = array(
|
||||
'subscription_event_7' => 'Deleted Quote',
|
||||
'subscription_event_8' => 'Updated Invoice',
|
||||
'subscription_event_9' => 'Deleted Invoice',
|
||||
'subscription_event_10' => 'Updated Client',
|
||||
'subscription_event_11' => 'Deleted Client',
|
||||
'subscription_event_12' => 'Deleted Payment',
|
||||
'subscription_event_13' => 'Updated Vendor',
|
||||
'subscription_event_14' => 'Deleted Vendor',
|
||||
'subscription_event_15' => 'Created Expense',
|
||||
'subscription_event_16' => 'Updated Expense',
|
||||
'subscription_event_17' => 'Deleted Expense',
|
||||
'subscription_event_18' => 'Created Task',
|
||||
'subscription_event_19' => 'Updated Task',
|
||||
'subscription_event_20' => 'Deleted Task',
|
||||
'subscription_event_21' => 'Approved Quote',
|
||||
'subscriptions' => 'Subscriptions',
|
||||
'updated_subscription' => 'Successfully updated subscription',
|
||||
'created_subscription' => 'Successfully created subscription',
|
||||
@ -2605,11 +2619,34 @@ $LANG = array(
|
||||
'mcrypt_warning' => 'Warning: Mcrypt is deprecated, run <code>php artisan ninja:update-key --legacy=true</code> to update your cipher.',
|
||||
'edit_times' => 'Edit Times',
|
||||
'inclusive_taxes_help' => 'Include <b>taxes in the cost</b>',
|
||||
'inclusive_taxes_notice' => 'This setting can not be changed once an invoice has been created.',
|
||||
'inclusive_taxes_warning' => 'Warning: existing invoices will need to be resaved',
|
||||
'copy_shipping' => 'Copy Shipping',
|
||||
'copy_billing' => 'Copy Billing',
|
||||
'quote_has_expired' => 'The quote has expired, please contact the merchant.',
|
||||
'empty_table_footer' => 'Showing 0 to 0 of 0 entries',
|
||||
'do_not_trust' => 'Do not remember this device',
|
||||
'trust_for_30_days' => 'Trust for 30 days',
|
||||
'trust_forever' => 'Trust forever',
|
||||
'kanban' => 'Kanban',
|
||||
'backlog' => 'Backlog',
|
||||
'ready_to_do' => 'Ready to do',
|
||||
'in_progress' => 'In progress',
|
||||
'add_status' => 'Add status',
|
||||
'archive_status' => 'Archive Status',
|
||||
'new_status' => 'New Status',
|
||||
'convert_products' => 'Convert Products',
|
||||
'convert_products_help' => 'Automatically convert product prices to the client\'s currency',
|
||||
'improve_client_portal_link' => 'Set a subdomain to shorten the client portal link.',
|
||||
'budgeted_hours' => 'Budgeted Hours',
|
||||
'progress' => 'Progress',
|
||||
'view_project' => 'View Project',
|
||||
'summary' => 'Summary',
|
||||
'endless_reminder' => 'Endless Reminder',
|
||||
'signature_on_invoice_help' => 'Add the following code to show your client\'s signature on the PDF.',
|
||||
'signature_on_pdf' => 'Show on PDF',
|
||||
'signature_on_pdf_help' => 'Show the client signature on the invoice/quote PDF.',
|
||||
'expired_white_label' => 'The white label license has expired',
|
||||
|
||||
);
|
||||
|
||||
|
@ -1224,7 +1224,7 @@ $LANG = array(
|
||||
'secret' => 'Passwort',
|
||||
'public_key' => 'Öffentlicher Schlüssel',
|
||||
'plaid_optional' => '(optional)',
|
||||
'plaid_environment_help' => 'Falls ein Stripe Testschlüssel gegeben ist, wird Plaids Entwicklungsumgebung (tartan) verwendet.',
|
||||
'plaid_environment_help' => 'When a Stripe test key is given, Plaid\'s development environment (tartan) will be used.',
|
||||
'other_providers' => 'Andere Anbieter',
|
||||
'country_not_supported' => 'Dieses Land wird nicht unterstützt.',
|
||||
'invalid_routing_number' => 'Die Bankleitzahl ist nicht gültig.',
|
||||
@ -1262,6 +1262,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
|
||||
'webhook_url' => 'Webhook URL',
|
||||
'stripe_webhook_help' => 'Sie müssen :link',
|
||||
'stripe_webhook_help_link_text' => 'fügen Sie diese URL als Endpunkt zu Stripe hinzu',
|
||||
'gocardless_webhook_help_link_text' => 'You must add this URL as an endpoint in GoCardless',
|
||||
'payment_method_error' => 'Es gab einen Fehler beim Hinzufügen Ihrer Zahlungsmethode. Bitte versuchen Sie es später erneut.',
|
||||
'notification_invoice_payment_failed_subject' => 'Zahlung für Rechnung :invoice fehlgeschlagen',
|
||||
'notification_invoice_payment_failed' => 'Eine Zahlung Ihres Kunden :client für die Rechnung :invoice schlug fehl. Die Zahlung wurde als Fehlgeschlagen markiert und :amount wurde dem Saldo Ihres Kunden hinzugefügt.',
|
||||
@ -1389,6 +1390,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
|
||||
'freq_four_months' => 'Vier Monate',
|
||||
'freq_six_months' => 'Halbjährlich',
|
||||
'freq_annually' => 'Jährlich',
|
||||
'freq_two_years' => 'Two years',
|
||||
|
||||
// Payment types
|
||||
'payment_type_Apply Credit' => 'Guthaben anwenden',
|
||||
@ -2417,7 +2419,8 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
|
||||
'contact_custom1' => 'Kontakt Benutzerdefiniert 1',
|
||||
'contact_custom2' => 'Kontakt Benutzerdefiniert 2',
|
||||
'currency' => 'Währung',
|
||||
'ofx_help' => 'In den meisten Fällen sollten die Standardwerte funktionieren. Wenn Sie keine Verbindung herstellen können, passen Sie die Einstellungen an.',
|
||||
'ofx_help' => 'In most cases the default values should work, if you\'re unable to connect it may help to :link.',
|
||||
'adjust_the_settings' => 'adjust the settings',
|
||||
|
||||
'item_product' => 'Bezeichnung',
|
||||
'item_notes' => 'Beschreibung',
|
||||
@ -2456,7 +2459,6 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
|
||||
'enable_alipay' => 'Alipay akzeptieren',
|
||||
'enable_sofort' => 'EU-Überweisungen akzeptieren',
|
||||
'stripe_alipay_help' => 'Dieses Gateway muss unter :link aktiviert werden.',
|
||||
'gocardless_webhook_help_link_text' => 'Verwenden Sie diese URL als Ziel (endpoint) in GoCardless',
|
||||
'calendar' => 'Kalender',
|
||||
'pro_plan_calendar' => ':link um den Kalender im Pro-Tarif zu aktivieren',
|
||||
|
||||
@ -2490,7 +2492,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
|
||||
'clear' => 'Löschen',
|
||||
'warn_payment_gateway' => 'Hinweis: Die Annahme von Online-Zahlungen erfordert ein Zahlungs-Gateway. :Link zum Hinzufügen einer.',
|
||||
'task_rate' => 'Kosten für Tätigkeit',
|
||||
'task_rate_help' => 'Set the default <b>rate for invoiced tasks</b>.',
|
||||
'task_rate_help' => 'Set the default rate for invoiced tasks.',
|
||||
'past_due' => 'Überfällig',
|
||||
'document' => 'Dokument',
|
||||
'invoice_or_expense' => 'Rechnung/Kosten',
|
||||
@ -2560,7 +2562,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
|
||||
'scheduled_report_error' => 'Failed to create schedule report',
|
||||
'invalid_one_time_password' => 'Invalid one time password',
|
||||
'apple_pay' => 'Apple/Google Pay',
|
||||
'enable_apple_pay' => 'Accept Apple Pay and Pay with Google',
|
||||
'enable_apple_pay' => 'Akzeptiere Apple Pay und Pay mit Google',
|
||||
'requires_subdomain' => 'This payment type requires that a :link.',
|
||||
'subdomain_is_set' => 'subdomain is set',
|
||||
'verification_file' => 'Verification File',
|
||||
@ -2581,6 +2583,18 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
|
||||
'subscription_event_7' => 'Deleted Quote',
|
||||
'subscription_event_8' => 'Aktualisierte Rechnung',
|
||||
'subscription_event_9' => 'Gelöschte Rechnung',
|
||||
'subscription_event_10' => 'Updated Client',
|
||||
'subscription_event_11' => 'Deleted Client',
|
||||
'subscription_event_12' => 'Deleted Payment',
|
||||
'subscription_event_13' => 'Updated Vendor',
|
||||
'subscription_event_14' => 'Deleted Vendor',
|
||||
'subscription_event_15' => 'Created Expense',
|
||||
'subscription_event_16' => 'Updated Expense',
|
||||
'subscription_event_17' => 'Deleted Expense',
|
||||
'subscription_event_18' => 'Created Task',
|
||||
'subscription_event_19' => 'Updated Task',
|
||||
'subscription_event_20' => 'Deleted Task',
|
||||
'subscription_event_21' => 'Approved Quote',
|
||||
'subscriptions' => 'Abonnements',
|
||||
'updated_subscription' => 'Abonnement erfolgreich aktualisiert',
|
||||
'created_subscription' => 'Abonnement erfolgreich erstellt',
|
||||
@ -2591,7 +2605,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
|
||||
'invoice_project' => 'Invoice Project',
|
||||
'module_recurring_invoice' => 'Wiederkehrende Rechnungen',
|
||||
'module_credit' => 'Guthaben',
|
||||
'module_quote' => 'Quotes',
|
||||
'module_quote' => 'Angebote',
|
||||
'module_task' => 'Tasks & Projects',
|
||||
'module_expense' => 'Expenses & Vendors',
|
||||
'reminders' => 'Erinnerungen',
|
||||
@ -2603,13 +2617,36 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
|
||||
'please_register' => 'Please register your account',
|
||||
'processing_request' => 'Processing request',
|
||||
'mcrypt_warning' => 'Warning: Mcrypt is deprecated, run <code>php artisan ninja:update-key --legacy=true</code> to update your cipher.',
|
||||
'edit_times' => 'Edit Times',
|
||||
'edit_times' => 'Zeiten bearbeiten',
|
||||
'inclusive_taxes_help' => 'Include <b>taxes in the cost</b>',
|
||||
'inclusive_taxes_notice' => 'This setting can not be changed once an invoice has been created.',
|
||||
'inclusive_taxes_warning' => 'Warning: existing invoices will need to be resaved',
|
||||
'copy_shipping' => 'Copy Shipping',
|
||||
'copy_billing' => 'Copy Billing',
|
||||
'quote_has_expired' => 'The quote has expired, please contact the merchant.',
|
||||
'empty_table_footer' => 'Showing 0 to 0 of 0 entries',
|
||||
'do_not_trust' => 'Dieses Gerät nicht merken',
|
||||
'trust_for_30_days' => 'Trust for 30 days',
|
||||
'trust_forever' => 'Trust forever',
|
||||
'kanban' => 'Kanban',
|
||||
'backlog' => 'Backlog',
|
||||
'ready_to_do' => 'Ready to do',
|
||||
'in_progress' => 'In progress',
|
||||
'add_status' => 'Add status',
|
||||
'archive_status' => 'Archive Status',
|
||||
'new_status' => 'New Status',
|
||||
'convert_products' => 'Convert Products',
|
||||
'convert_products_help' => 'Automatically convert product prices to the client\'s currency',
|
||||
'improve_client_portal_link' => 'Set a subdomain to shorten the client portal link.',
|
||||
'budgeted_hours' => 'Budgeted Hours',
|
||||
'progress' => 'Progress',
|
||||
'view_project' => 'View Project',
|
||||
'summary' => 'Summary',
|
||||
'endless_reminder' => 'Endless Reminder',
|
||||
'signature_on_invoice_help' => 'Add the following code to show your client\'s signature on the PDF.',
|
||||
'signature_on_pdf' => 'Show on PDF',
|
||||
'signature_on_pdf_help' => 'Show the client signature on the invoice/quote PDF.',
|
||||
'expired_white_label' => 'The white label license has expired',
|
||||
|
||||
);
|
||||
|
||||
|
@ -1224,7 +1224,7 @@ email που είναι συνδεδεμένη με το λογαριασμό σ
|
||||
'secret' => 'Κρυφό',
|
||||
'public_key' => 'Δημόσιο Κλειδί',
|
||||
'plaid_optional' => '(προαιρετικό)',
|
||||
'plaid_environment_help' => 'Όταν έχει οριστεί ένα δοκιμαστικό Stripe κλειδί, θα χρησιμοποιηθεί το περιβάλλον ανάπτυξης του Plaid (tartan).',
|
||||
'plaid_environment_help' => 'Όταν δοθεί ένα δοκιμαστικό κλειδί από το Stripe, θα χρησιμοποιηθεί το περιβάλλον ανάπτυξης (tartan) Plaid.',
|
||||
'other_providers' => 'Άλλοι Providers',
|
||||
'country_not_supported' => 'Αυτή η χώρα δεν υποστηρίζεται.',
|
||||
'invalid_routing_number' => 'Ο αριθμός δρομολόγησης δεν είναι έγκυρος.',
|
||||
@ -1262,6 +1262,7 @@ email που είναι συνδεδεμένη με το λογαριασμό σ
|
||||
'webhook_url' => 'Διεύθυνση URL του Webhook ',
|
||||
'stripe_webhook_help' => 'Πρέπει να :link.',
|
||||
'stripe_webhook_help_link_text' => 'προσθέσετε αυτό το URL σαν σημείο τερματισμού στο Stripe',
|
||||
'gocardless_webhook_help_link_text' => 'You must add this URL as an endpoint in GoCardless',
|
||||
'payment_method_error' => 'Προέκυψε ένα σφάλμα κατά τη διαδικασία προσθήκης της μεθόδου πληρωμής. Παρακαλώ, δοκιμάστε ξανά σε λίγο.',
|
||||
'notification_invoice_payment_failed_subject' => 'Η πληρωμή για το τιμολόγιο :invoice απέτυχε',
|
||||
'notification_invoice_payment_failed' => 'Μια πληρωμή που έγινε από τον πελάτη :client που αφορούσε το Τιμολόγιο :invoice απέτυχε. Η πληρωμή έχει μαρκαριστεί ως αποτυχημένη και ποσό :amount προστέθηκε στο υπόλοιπο του πελάτη.',
|
||||
@ -1389,6 +1390,7 @@ email που είναι συνδεδεμένη με το λογαριασμό σ
|
||||
'freq_four_months' => 'Τέσσερις μήνες',
|
||||
'freq_six_months' => 'Έξι μήνες',
|
||||
'freq_annually' => 'Έτος',
|
||||
'freq_two_years' => 'Δύο χρόνια',
|
||||
|
||||
// Payment types
|
||||
'payment_type_Apply Credit' => 'Εφαρμογή Πίστωσης',
|
||||
@ -2417,7 +2419,8 @@ email που είναι συνδεδεμένη με το λογαριασμό σ
|
||||
'contact_custom1' => 'Πρώτη Προσαρμογή Επαφής',
|
||||
'contact_custom2' => 'Δεύτερη Προσαρμογή Επαφής',
|
||||
'currency' => 'Νόμισμα',
|
||||
'ofx_help' => 'Στις περισσότερες περιπτώσεις οι προκαθορισμένες τιμές πρέπει να λειτουργούν, εάν δεν μπορείτε να συνδεθείτε ίσως βοηθήσει να προσαρμόσετε τις ρυθμίσεις.',
|
||||
'ofx_help' => 'Στις περισσότερες περιπτώσεις οι προκαθορισμένες τιμές λειτουργούν, εάν δεν μπορείτε να συνδεθείτε μπορεί να βοηθήσει το :link.',
|
||||
'adjust_the_settings' => 'προσαρμόστε τις ρυθμίσεις',
|
||||
|
||||
'item_product' => 'Προϊόν',
|
||||
'item_notes' => 'Σημειώσεις Προϊόντος',
|
||||
@ -2456,7 +2459,6 @@ email που είναι συνδεδεμένη με το λογαριασμό σ
|
||||
'enable_alipay' => 'Αποδοχή Alipay',
|
||||
'enable_sofort' => 'Αποδοχή τραπεζικών εμβασμάτων από τράπεζες της Ευρώπης',
|
||||
'stripe_alipay_help' => 'Αυτές οι πύλες πληρωμών πρέπει επίσης να ενεργοποιηθούν στο :link.',
|
||||
'gocardless_webhook_help_link_text' => 'προσθέστε αυτή τη διεύθυνση internet στο τέλος της διεύθυνσης internet στο GoCardless',
|
||||
'calendar' => 'Ημερολόγιο',
|
||||
'pro_plan_calendar' => ':link για να ενεργοποιήσετε το ημερολόγιο συμμετέχοντας στο Επαγγελματικό Πλάνο',
|
||||
|
||||
@ -2490,7 +2492,7 @@ email που είναι συνδεδεμένη με το λογαριασμό σ
|
||||
'clear' => 'Καθαρισμός',
|
||||
'warn_payment_gateway' => 'Σημείωση: η αποδοχή online πληρωμών απαιτεί μία πύλη πληρωμών, :link για να προσθέσετε μία.',
|
||||
'task_rate' => 'Κόστος Εργασίας',
|
||||
'task_rate_help' => 'Ορίζει το προεπιλεγμένο <b>κόστος εργασίας για τις τιμολογημένες εργασίες</b>.',
|
||||
'task_rate_help' => 'Ορίστε το προκαθορισμένο ποσοστό για τις τιμολογημένες εργασίες.',
|
||||
'past_due' => 'Ληγμένα',
|
||||
'document' => 'Έγγραφο',
|
||||
'invoice_or_expense' => 'Τιμολόγιο/Δαπάνη',
|
||||
@ -2581,6 +2583,18 @@ email που είναι συνδεδεμένη με το λογαριασμό σ
|
||||
'subscription_event_7' => 'Διαγράφηκε Προσφορά',
|
||||
'subscription_event_8' => 'Ενημερώθηκε Τιμολόγιο',
|
||||
'subscription_event_9' => 'Διαγράφηκε Τιμολόγιο',
|
||||
'subscription_event_10' => 'Ενημερώθηκε ο Πελάτης',
|
||||
'subscription_event_11' => 'Διαγράφηκε ο Πελάτης',
|
||||
'subscription_event_12' => 'Διαγράφηκε η Πληρωμή',
|
||||
'subscription_event_13' => 'Ενημερώθηκε ο Προμηθευτής',
|
||||
'subscription_event_14' => 'Διαγράφηκε ο Προμηθευτής',
|
||||
'subscription_event_15' => 'Δημιουργήθηκε η Δαπάνη',
|
||||
'subscription_event_16' => 'Ενημερώθηκε η Δαπάνη',
|
||||
'subscription_event_17' => 'Διαγράφηκε η Δαπάνη',
|
||||
'subscription_event_18' => 'Δημιουργήθηκε η Εργασία',
|
||||
'subscription_event_19' => 'Ενημερώθηκε η Εργασία',
|
||||
'subscription_event_20' => 'Διαγράφηκε η Εργασία',
|
||||
'subscription_event_21' => 'Αποδεκτή Προσφορά',
|
||||
'subscriptions' => 'Εγγραφές',
|
||||
'updated_subscription' => 'Επιτυχής ενημέρωση συνδρομής',
|
||||
'created_subscription' => 'Επιτυχής δημιουργία συνδρομής',
|
||||
@ -2605,11 +2619,34 @@ email που είναι συνδεδεμένη με το λογαριασμό σ
|
||||
'mcrypt_warning' => 'Προειδοποίηση: Το Mcrypt βρίσκεται στη διαδικασία απόσυρσης, εκτελέστε το <code>php artisan ninja:update-key --legacy=true</code> για να ενημερώσετε την κρυπτογράφησή σας.',
|
||||
'edit_times' => 'Επεξεργασία Επαναλήψεων',
|
||||
'inclusive_taxes_help' => 'Συμπεριλάβετε <b>φόρους στο κόστος</b>',
|
||||
'inclusive_taxes_notice' => 'Αυτή η ρύθμιση δεν μπορεί να αλλάξει όταν ένα τιμολόγιο έχει δημιουργηθεί.',
|
||||
'inclusive_taxes_warning' => 'Προειδοποίηση: τα υπάρχοντα τιμολόγια θα χρειαστούν επαναποθήκευση',
|
||||
'copy_shipping' => 'Αντιγραφή Αποστολής',
|
||||
'copy_billing' => 'Αντιγραφή Χρέωσης',
|
||||
'quote_has_expired' => 'Η προσφορά έχει λήξει, παρακαλούμε επικοινωνήστε με τον έμπορο.',
|
||||
'empty_table_footer' => 'Εμφάνιση 0 έως 0 από 0 εγγραφές',
|
||||
'do_not_trust' => 'Μην θυμηθείς αυτή τη συσκευή',
|
||||
'trust_for_30_days' => 'Εμπιστευτείτε για 30 μέρες',
|
||||
'trust_forever' => 'Εμπιστευτείτε για πάντα',
|
||||
'kanban' => 'Kanban',
|
||||
'backlog' => 'Αναμονή',
|
||||
'ready_to_do' => 'Έτοιμο',
|
||||
'in_progress' => 'Σε εξέλιξη',
|
||||
'add_status' => 'Προσθήκη κατάστασης',
|
||||
'archive_status' => 'Αρχειοθέτηση Κατάστασης',
|
||||
'new_status' => 'Νέα Κατάσταση',
|
||||
'convert_products' => 'Μετατροπή Τιμών Προϊόντων',
|
||||
'convert_products_help' => 'Αυτόματη μετατροπή τιμών προϊόντων στο νόμισμα συναλλαγών του πελάτη',
|
||||
'improve_client_portal_link' => 'Ορίστε ένα subdomain για να μειώσετε το μήκος του συνδέσμου του portal του πελάτη.',
|
||||
'budgeted_hours' => 'Χρεώσιμες Ώρες',
|
||||
'progress' => 'Πρόοδος',
|
||||
'view_project' => 'Εμφάνιση Project',
|
||||
'summary' => 'Περίληψη',
|
||||
'endless_reminder' => 'Συνεχής Υπενθύμιση',
|
||||
'signature_on_invoice_help' => 'Προσθέστε τον ακόλουθο κώδικα για να εμφανίζεται η υπογραφή του πελάτη σας στο PDF.',
|
||||
'signature_on_pdf' => 'Εμφάνισε στο PDF',
|
||||
'signature_on_pdf_help' => 'Εμφάνισε την υπογραφή του πελάτη στο PDF του τιμολογίου/προσφοράς.',
|
||||
'expired_white_label' => 'Η άδεια λευκής ετικέτας έληξε',
|
||||
|
||||
);
|
||||
|
||||
|
@ -1224,7 +1224,7 @@ $LANG = array(
|
||||
'secret' => 'Secret',
|
||||
'public_key' => 'Public Key',
|
||||
'plaid_optional' => '(optional)',
|
||||
'plaid_environment_help' => 'When a Stripe test key is given, Plaid\'s development environement (tartan) will be used.',
|
||||
'plaid_environment_help' => 'When a Stripe test key is given, Plaid\'s development environment (tartan) will be used.',
|
||||
'other_providers' => 'Other Providers',
|
||||
'country_not_supported' => 'That country is not supported.',
|
||||
'invalid_routing_number' => 'The routing number is not valid.',
|
||||
@ -1262,6 +1262,7 @@ $LANG = array(
|
||||
'webhook_url' => 'Webhook URL',
|
||||
'stripe_webhook_help' => 'You must :link.',
|
||||
'stripe_webhook_help_link_text' => 'add this URL as an endpoint at Stripe',
|
||||
'gocardless_webhook_help_link_text' => 'You must add this URL as an endpoint in GoCardless',
|
||||
'payment_method_error' => 'There was an error adding your payment methd. Please try again later.',
|
||||
'notification_invoice_payment_failed_subject' => 'Payment failed for Invoice :invoice',
|
||||
'notification_invoice_payment_failed' => 'A payment made by client :client towards Invoice :invoice failed. The payment has been marked as failed and :amount has been added to the client\'s balance.',
|
||||
@ -1389,6 +1390,7 @@ $LANG = array(
|
||||
'freq_four_months' => 'Four months',
|
||||
'freq_six_months' => 'Six months',
|
||||
'freq_annually' => 'Annually',
|
||||
'freq_two_years' => 'Two years',
|
||||
|
||||
// Payment types
|
||||
'payment_type_Apply Credit' => 'Apply Credit',
|
||||
@ -2417,7 +2419,8 @@ $LANG = array(
|
||||
'contact_custom1' => 'Contact First Custom',
|
||||
'contact_custom2' => 'Contact Second Custom',
|
||||
'currency' => 'Currency',
|
||||
'ofx_help' => 'In most cases the default values should work, if you\'re unable to connect it may help to adjust the settings.',
|
||||
'ofx_help' => 'In most cases the default values should work, if you\'re unable to connect it may help to :link.',
|
||||
'adjust_the_settings' => 'adjust the settings',
|
||||
|
||||
'item_product' => 'Item Product',
|
||||
'item_notes' => 'Item Notes',
|
||||
@ -2456,7 +2459,6 @@ $LANG = array(
|
||||
'enable_alipay' => 'Accept Alipay',
|
||||
'enable_sofort' => 'Accept EU bank transfers',
|
||||
'stripe_alipay_help' => 'These gateways also need to be activated in :link.',
|
||||
'gocardless_webhook_help_link_text' => 'add this URL as an endpoint in GoCardless',
|
||||
'calendar' => 'Calendar',
|
||||
'pro_plan_calendar' => ':link to enable the calendar by joining the Pro Plan',
|
||||
|
||||
@ -2490,7 +2492,7 @@ $LANG = array(
|
||||
'clear' => 'Clear',
|
||||
'warn_payment_gateway' => 'Note: accepting online payments requires a payment gateway, :link to add one.',
|
||||
'task_rate' => 'Task Rate',
|
||||
'task_rate_help' => 'Set the default <b>rate for invoiced tasks</b>.',
|
||||
'task_rate_help' => 'Set the default rate for invoiced tasks.',
|
||||
'past_due' => 'Past Due',
|
||||
'document' => 'Document',
|
||||
'invoice_or_expense' => 'Invoice/Expense',
|
||||
@ -2581,6 +2583,18 @@ $LANG = array(
|
||||
'subscription_event_7' => 'Deleted Quote',
|
||||
'subscription_event_8' => 'Updated Invoice',
|
||||
'subscription_event_9' => 'Deleted Invoice',
|
||||
'subscription_event_10' => 'Updated Client',
|
||||
'subscription_event_11' => 'Deleted Client',
|
||||
'subscription_event_12' => 'Deleted Payment',
|
||||
'subscription_event_13' => 'Updated Vendor',
|
||||
'subscription_event_14' => 'Deleted Vendor',
|
||||
'subscription_event_15' => 'Created Expense',
|
||||
'subscription_event_16' => 'Updated Expense',
|
||||
'subscription_event_17' => 'Deleted Expense',
|
||||
'subscription_event_18' => 'Created Task',
|
||||
'subscription_event_19' => 'Updated Task',
|
||||
'subscription_event_20' => 'Deleted Task',
|
||||
'subscription_event_21' => 'Approved Quote',
|
||||
'subscriptions' => 'Subscriptions',
|
||||
'updated_subscription' => 'Successfully updated subscription',
|
||||
'created_subscription' => 'Successfully created subscription',
|
||||
@ -2605,11 +2619,34 @@ $LANG = array(
|
||||
'mcrypt_warning' => 'Warning: Mcrypt is deprecated, run <code>php artisan ninja:update-key --legacy=true</code> to update your cipher.',
|
||||
'edit_times' => 'Edit Times',
|
||||
'inclusive_taxes_help' => 'Include <b>taxes in the cost</b>',
|
||||
'inclusive_taxes_notice' => 'This setting can not be changed once an invoice has been created.',
|
||||
'inclusive_taxes_warning' => 'Warning: existing invoices will need to be resaved',
|
||||
'copy_shipping' => 'Copy Shipping',
|
||||
'copy_billing' => 'Copy Billing',
|
||||
'quote_has_expired' => 'The quote has expired, please contact the merchant.',
|
||||
'empty_table_footer' => 'Showing 0 to 0 of 0 entries',
|
||||
'do_not_trust' => 'Do not remember this device',
|
||||
'trust_for_30_days' => 'Trust for 30 days',
|
||||
'trust_forever' => 'Trust forever',
|
||||
'kanban' => 'Kanban',
|
||||
'backlog' => 'Backlog',
|
||||
'ready_to_do' => 'Ready to do',
|
||||
'in_progress' => 'In progress',
|
||||
'add_status' => 'Add status',
|
||||
'archive_status' => 'Archive Status',
|
||||
'new_status' => 'New Status',
|
||||
'convert_products' => 'Convert Products',
|
||||
'convert_products_help' => 'Automatically convert product prices to the client\'s currency',
|
||||
'improve_client_portal_link' => 'Set a subdomain to shorten the client portal link.',
|
||||
'budgeted_hours' => 'Budgeted Hours',
|
||||
'progress' => 'Progress',
|
||||
'view_project' => 'View Project',
|
||||
'summary' => 'Summary',
|
||||
'endless_reminder' => 'Endless Reminder',
|
||||
'signature_on_invoice_help' => 'Add the following code to show your client\'s signature on the PDF.',
|
||||
'signature_on_pdf' => 'Show on PDF',
|
||||
'signature_on_pdf_help' => 'Show the client signature on the invoice/quote PDF.',
|
||||
'expired_white_label' => 'The white label license has expired',
|
||||
|
||||
);
|
||||
|
||||
|
@ -1220,7 +1220,7 @@ $LANG = array(
|
||||
'secret' => 'Secret',
|
||||
'public_key' => 'Public Key',
|
||||
'plaid_optional' => '(optional)',
|
||||
'plaid_environment_help' => 'When a Stripe test key is given, Plaid\'s development environement (tartan) will be used.',
|
||||
'plaid_environment_help' => 'When a Stripe test key is given, Plaid\'s development environment (tartan) will be used.',
|
||||
'other_providers' => 'Other Providers',
|
||||
'country_not_supported' => 'That country is not supported.',
|
||||
'invalid_routing_number' => 'The routing number is not valid.',
|
||||
|
@ -481,8 +481,8 @@ $LANG = array(
|
||||
'updated_password' => 'Contraseña actualizada correctamente',
|
||||
'api_tokens' => 'API Tokens',
|
||||
'users_and_tokens' => 'Usuarios & Tokens',
|
||||
'account_login' => 'Ingreso',
|
||||
'recover_password' => 'Recupere su contraseña',
|
||||
'account_login' => 'Iniciar Sesión',
|
||||
'recover_password' => 'Recuperar contraseña',
|
||||
'forgot_password' => 'Olvidó su contraseña?',
|
||||
'email_address' => 'Correo Electrónico',
|
||||
'lets_go' => 'Acceder',
|
||||
@ -604,7 +604,7 @@ $LANG = array(
|
||||
'new_company' => 'Nueva Compañia',
|
||||
'associated_accounts' => 'Cuentas conectadas con éxito',
|
||||
'unlinked_account' => 'Cuentas desconectadas con éxito',
|
||||
'login' => 'Ingresar',
|
||||
'login' => 'Iniciar Sesión',
|
||||
'or' => 'o',
|
||||
'email_error' => 'Hubo un problema enviando el correo',
|
||||
'confirm_recurring_timing' => 'Nota: los correos son enviados al inicio de la hora.',
|
||||
@ -1217,7 +1217,7 @@ $LANG = array(
|
||||
'secret' => 'Secret',
|
||||
'public_key' => 'Public Key',
|
||||
'plaid_optional' => '(optional)',
|
||||
'plaid_environment_help' => 'When a Stripe test key is given, Plaid\'s development environement (tartan) will be used.',
|
||||
'plaid_environment_help' => 'When a Stripe test key is given, Plaid\'s development environment (tartan) will be used.',
|
||||
'other_providers' => 'Other Providers',
|
||||
'country_not_supported' => 'That country is not supported.',
|
||||
'invalid_routing_number' => 'The routing number is not valid.',
|
||||
@ -1255,6 +1255,7 @@ $LANG = array(
|
||||
'webhook_url' => 'Webhook URL',
|
||||
'stripe_webhook_help' => 'You must :link.',
|
||||
'stripe_webhook_help_link_text' => 'add this URL as an endpoint at Stripe',
|
||||
'gocardless_webhook_help_link_text' => 'You must add this URL as an endpoint in GoCardless',
|
||||
'payment_method_error' => 'There was an error adding your payment methd. Please try again later.',
|
||||
'notification_invoice_payment_failed_subject' => 'Payment failed for Invoice :invoice',
|
||||
'notification_invoice_payment_failed' => 'A payment made by client :client towards Invoice :invoice failed. The payment has been marked as failed and :amount has been added to the client\'s balance.',
|
||||
@ -1382,6 +1383,7 @@ $LANG = array(
|
||||
'freq_four_months' => 'Four months',
|
||||
'freq_six_months' => 'Six months',
|
||||
'freq_annually' => 'Annually',
|
||||
'freq_two_years' => 'Two years',
|
||||
|
||||
// Payment types
|
||||
'payment_type_Apply Credit' => 'Apply Credit',
|
||||
@ -2113,7 +2115,7 @@ $LANG = array(
|
||||
'sign_up_now' => 'Cree Una Cuenta Ahora',
|
||||
'not_a_member_yet' => 'Aún no tiene una cuenta?',
|
||||
'login_create_an_account' => 'Crear una Cuenta!',
|
||||
'client_login' => 'Ingreso de Cliente',
|
||||
'client_login' => 'Inicio de Sesión del Cliente',
|
||||
|
||||
// New Client Portal styling
|
||||
'invoice_from' => 'Facturas de:',
|
||||
@ -2410,7 +2412,8 @@ $LANG = array(
|
||||
'contact_custom1' => 'Contact First Custom',
|
||||
'contact_custom2' => 'Contact Second Custom',
|
||||
'currency' => 'Currency',
|
||||
'ofx_help' => 'In most cases the default values should work, if you\'re unable to connect it may help to adjust the settings.',
|
||||
'ofx_help' => 'In most cases the default values should work, if you\'re unable to connect it may help to :link.',
|
||||
'adjust_the_settings' => 'adjust the settings',
|
||||
|
||||
'item_product' => 'Item Product',
|
||||
'item_notes' => 'Item Notes',
|
||||
@ -2449,7 +2452,6 @@ $LANG = array(
|
||||
'enable_alipay' => 'Accept Alipay',
|
||||
'enable_sofort' => 'Accept EU bank transfers',
|
||||
'stripe_alipay_help' => 'These gateways also need to be activated in :link.',
|
||||
'gocardless_webhook_help_link_text' => 'add this URL as an endpoint in GoCardless',
|
||||
'calendar' => 'Calendar',
|
||||
'pro_plan_calendar' => ':link to enable the calendar by joining the Pro Plan',
|
||||
|
||||
@ -2483,7 +2485,7 @@ $LANG = array(
|
||||
'clear' => 'Clear',
|
||||
'warn_payment_gateway' => 'Note: accepting online payments requires a payment gateway, :link to add one.',
|
||||
'task_rate' => 'Task Rate',
|
||||
'task_rate_help' => 'Set the default <b>rate for invoiced tasks</b>.',
|
||||
'task_rate_help' => 'Set the default rate for invoiced tasks.',
|
||||
'past_due' => 'Past Due',
|
||||
'document' => 'Document',
|
||||
'invoice_or_expense' => 'Invoice/Expense',
|
||||
@ -2522,7 +2524,7 @@ $LANG = array(
|
||||
'local_storage_required' => 'Error: local storage is not available.',
|
||||
'your_password_reset_link' => 'Your Password Reset Link',
|
||||
'subdomain_taken' => 'The subdomain is already in use',
|
||||
'client_login' => 'Ingreso de Cliente',
|
||||
'client_login' => 'Inicio de Sesión del Cliente',
|
||||
'converted_amount' => 'Converted Amount',
|
||||
'default' => 'Default',
|
||||
'shipping_address' => 'Shipping Address',
|
||||
@ -2574,6 +2576,18 @@ $LANG = array(
|
||||
'subscription_event_7' => 'Deleted Quote',
|
||||
'subscription_event_8' => 'Updated Invoice',
|
||||
'subscription_event_9' => 'Deleted Invoice',
|
||||
'subscription_event_10' => 'Updated Client',
|
||||
'subscription_event_11' => 'Deleted Client',
|
||||
'subscription_event_12' => 'Deleted Payment',
|
||||
'subscription_event_13' => 'Updated Vendor',
|
||||
'subscription_event_14' => 'Deleted Vendor',
|
||||
'subscription_event_15' => 'Created Expense',
|
||||
'subscription_event_16' => 'Updated Expense',
|
||||
'subscription_event_17' => 'Deleted Expense',
|
||||
'subscription_event_18' => 'Created Task',
|
||||
'subscription_event_19' => 'Updated Task',
|
||||
'subscription_event_20' => 'Deleted Task',
|
||||
'subscription_event_21' => 'Approved Quote',
|
||||
'subscriptions' => 'Subscriptions',
|
||||
'updated_subscription' => 'Successfully updated subscription',
|
||||
'created_subscription' => 'Successfully created subscription',
|
||||
@ -2598,11 +2612,34 @@ $LANG = array(
|
||||
'mcrypt_warning' => 'Warning: Mcrypt is deprecated, run <code>php artisan ninja:update-key --legacy=true</code> to update your cipher.',
|
||||
'edit_times' => 'Edit Times',
|
||||
'inclusive_taxes_help' => 'Include <b>taxes in the cost</b>',
|
||||
'inclusive_taxes_notice' => 'This setting can not be changed once an invoice has been created.',
|
||||
'inclusive_taxes_warning' => 'Warning: existing invoices will need to be resaved',
|
||||
'copy_shipping' => 'Copy Shipping',
|
||||
'copy_billing' => 'Copy Billing',
|
||||
'quote_has_expired' => 'The quote has expired, please contact the merchant.',
|
||||
'empty_table_footer' => 'Showing 0 to 0 of 0 entries',
|
||||
'do_not_trust' => 'Do not remember this device',
|
||||
'trust_for_30_days' => 'Trust for 30 days',
|
||||
'trust_forever' => 'Trust forever',
|
||||
'kanban' => 'Kanban',
|
||||
'backlog' => 'Backlog',
|
||||
'ready_to_do' => 'Ready to do',
|
||||
'in_progress' => 'In progress',
|
||||
'add_status' => 'Add status',
|
||||
'archive_status' => 'Archive Status',
|
||||
'new_status' => 'New Status',
|
||||
'convert_products' => 'Convert Products',
|
||||
'convert_products_help' => 'Automatically convert product prices to the client\'s currency',
|
||||
'improve_client_portal_link' => 'Set a subdomain to shorten the client portal link.',
|
||||
'budgeted_hours' => 'Budgeted Hours',
|
||||
'progress' => 'Progress',
|
||||
'view_project' => 'View Project',
|
||||
'summary' => 'Summary',
|
||||
'endless_reminder' => 'Endless Reminder',
|
||||
'signature_on_invoice_help' => 'Add the following code to show your client\'s signature on the PDF.',
|
||||
'signature_on_pdf' => 'Show on PDF',
|
||||
'signature_on_pdf_help' => 'Show the client signature on the invoice/quote PDF.',
|
||||
'expired_white_label' => 'The white label license has expired',
|
||||
|
||||
);
|
||||
|
||||
|
@ -380,7 +380,7 @@ $LANG = array(
|
||||
'gateway_help_2' => ':link para registrarse en Authorize.net.',
|
||||
'gateway_help_17' => ':link para obtener su firma API de PayPal.',
|
||||
'gateway_help_27' => ':link para registrarse en 2Checkout.com. Para asegurar que los Pagos son monitorizados, establezca :complete_link como redireccion de URL en Account > Site Management en el portal de 2Checkout.',
|
||||
'gateway_help_60' => ':link to create a WePay account.',
|
||||
'gateway_help_60' => ':link para crear una cuenta de WePay',
|
||||
'more_designs' => 'Más diseños',
|
||||
'more_designs_title' => 'Diseños Adicionales de Facturas',
|
||||
'more_designs_cloud_header' => 'Cambia a Pro para añadir más Diseños de Facturas',
|
||||
@ -481,9 +481,9 @@ $LANG = array(
|
||||
'updated_password' => 'Contraseña actualizada Correctamente',
|
||||
'api_tokens' => 'API Tokens',
|
||||
'users_and_tokens' => 'Usuarios & Tokens',
|
||||
'account_login' => 'Iniciar Sesión',
|
||||
'recover_password' => 'Recuperar su Contraseña',
|
||||
'forgot_password' => '¿Olvidaste tu Contraseña?',
|
||||
'account_login' => 'Inicio de Sesión con su Cuenta',
|
||||
'recover_password' => 'Recuperar Contraseña',
|
||||
'forgot_password' => '¿Olvidó su Contraseña?',
|
||||
'email_address' => 'Dirección de Email',
|
||||
'lets_go' => 'Acceder',
|
||||
'password_recovery' => 'Recuperar Contraseña',
|
||||
@ -604,7 +604,7 @@ $LANG = array(
|
||||
'new_company' => 'Nueva Compañía',
|
||||
'associated_accounts' => 'Cuentas conectadas con éxito',
|
||||
'unlinked_account' => 'Cuentas desconectadas con éxito',
|
||||
'login' => 'Login',
|
||||
'login' => 'Iniciar Sesión',
|
||||
'or' => 'o',
|
||||
'email_error' => 'Ocurrió un problema enviando el correo',
|
||||
'confirm_recurring_timing' => 'Nota: correos enviados cada hora en punto.',
|
||||
@ -1214,7 +1214,7 @@ $LANG = array(
|
||||
'secret' => 'Secreto',
|
||||
'public_key' => 'Llave pública',
|
||||
'plaid_optional' => '(opcional)',
|
||||
'plaid_environment_help' => 'Cuando se da una clave de prueba de Stripe, se usará el entorno de desarrollo Plaid (tartan).',
|
||||
'plaid_environment_help' => 'Cuando se de una clave de prueba de Stripe, se utilizará un entorno de desarrollo de Plaid.',
|
||||
'other_providers' => 'Otros proveedores',
|
||||
'country_not_supported' => 'Este país no está soportado.',
|
||||
'invalid_routing_number' => 'El número de routing no es valido.',
|
||||
@ -1252,6 +1252,7 @@ Una vez que tenga los montos, vuelva a esta página de métodos de pago y haga c
|
||||
'webhook_url' => 'Webhook URL',
|
||||
'stripe_webhook_help' => 'Debes :link.',
|
||||
'stripe_webhook_help_link_text' => 'agregue esta URL como endpoint en Stripe',
|
||||
'gocardless_webhook_help_link_text' => 'You must add this URL as an endpoint in GoCardless',
|
||||
'payment_method_error' => 'Se produjo un error al agregar su método de pago. Por favor, inténtelo de nuevo más tarde.',
|
||||
'notification_invoice_payment_failed_subject' => 'Pago fallido para la Factura :invoice',
|
||||
'notification_invoice_payment_failed' => 'Un pago realizado por el cliente :client hacia la factura :invoice falló. El pago ha sido marcado como fallido y :amount se ha agregado al saldo del cliente.',
|
||||
@ -1379,6 +1380,7 @@ Una vez que tenga los montos, vuelva a esta página de métodos de pago y haga c
|
||||
'freq_four_months' => 'Cuatro meses',
|
||||
'freq_six_months' => 'Seis meses',
|
||||
'freq_annually' => 'Anual',
|
||||
'freq_two_years' => 'Two years',
|
||||
|
||||
// Payment types
|
||||
'payment_type_Apply Credit' => 'Aplicar Crédito',
|
||||
@ -1911,7 +1913,7 @@ Eso es todo, tu cuenta está verificada.<br/>',
|
||||
'enterprise_upgrade_feature1' => 'Establecer permisos para múltiples usuarios',
|
||||
'enterprise_upgrade_feature2' => 'Adjunte archivos de terceros a facturas y gastos',
|
||||
'much_more' => '¡Mucho mas!',
|
||||
'all_pro_fetaures' => 'Plus all pro features!',
|
||||
'all_pro_fetaures' => 'Mas todas las caracteristicas Pro!',
|
||||
|
||||
'currency_symbol' => 'Símbolo',
|
||||
'currency_code' => 'Código',
|
||||
@ -1986,14 +1988,14 @@ Eso es todo, tu cuenta está verificada.<br/>',
|
||||
'created_project' => 'Proyecto creado con éxito',
|
||||
'archived_project' => 'Proyecto archivado con éxito',
|
||||
'archived_projects' => ':count proyectos archivados con exito',
|
||||
'restore_project' => 'Restore Project',
|
||||
'restore_project' => 'Restaurar Proyecto',
|
||||
'restored_project' => 'Proyecto Restaurado Correctamente',
|
||||
'delete_project' => 'Delete Project',
|
||||
'delete_project' => 'Borrar Proyecto',
|
||||
'deleted_project' => 'Proyecto eliminado con éxito',
|
||||
'deleted_projects' => ':count proyecto eliminados Correctamente',
|
||||
'delete_expense_category' => 'Borrar categoría',
|
||||
'deleted_expense_category' => 'Categoría eliminada correctamente',
|
||||
'delete_product' => 'Delete Product',
|
||||
'delete_product' => 'Borrar Producto',
|
||||
'deleted_product' => 'Producto eliminado con éxito',
|
||||
'deleted_products' => ':count productos eliminados con éxito',
|
||||
'restored_product' => 'Producto restaurado Correctamente',
|
||||
@ -2107,7 +2109,7 @@ Eso es todo, tu cuenta está verificada.<br/>',
|
||||
|
||||
// Updated login screen
|
||||
'ninja_tagline' => 'Crea. Envia. Recibe tus Pagos.',
|
||||
'login_or_existing' => 'o Login con una cuenta conectada',
|
||||
'login_or_existing' => 'O inicie sesión con una cuenta existente.',
|
||||
'sign_up_now' => 'Registrarse Ahora',
|
||||
'not_a_member_yet' => '¿No eres miembro todavía?',
|
||||
'login_create_an_account' => 'Crea una Cuenta!',
|
||||
@ -2409,7 +2411,8 @@ Eliminar permanentemente la cuenta junto con todos los datos y la configuración
|
||||
'contact_custom1' => 'Contacto Primer personalizado',
|
||||
'contact_custom2' => 'Contacto Segundo personalizado',
|
||||
'currency' => 'Divisa',
|
||||
'ofx_help' => 'En la mayoría de los casos, los valores predeterminados deberían funcionar; si no puede conectarse, puede ayudar a ajustar la configuración.',
|
||||
'ofx_help' => 'In most cases the default values should work, if you\'re unable to connect it may help to :link.',
|
||||
'adjust_the_settings' => 'adjust the settings',
|
||||
|
||||
'item_product' => 'Producto del artículo',
|
||||
'item_notes' => 'Notas del artículo',
|
||||
@ -2448,7 +2451,6 @@ Eliminar permanentemente la cuenta junto con todos los datos y la configuración
|
||||
'enable_alipay' => 'Aceptar Alipay',
|
||||
'enable_sofort' => 'Aceptar transferencias bancarias de la UE',
|
||||
'stripe_alipay_help' => 'Estas pasarelas también deben activarse en :link.',
|
||||
'gocardless_webhook_help_link_text' => 'agregue esta URL como endpoint en GoCardless',
|
||||
'calendar' => 'Calendario',
|
||||
'pro_plan_calendar' => ':link para habilitar calendario uniéndose al Plan Pro',
|
||||
|
||||
@ -2482,7 +2484,7 @@ Eliminar permanentemente la cuenta junto con todos los datos y la configuración
|
||||
'clear' => 'Limpiar',
|
||||
'warn_payment_gateway' => 'Nota: la aceptación de pagos en línea requiere una puerta de enlace de pago, enlace para agregar uno.',
|
||||
'task_rate' => 'Tasa de tareas',
|
||||
'task_rate_help' => 'Establezca <b>la tasa</b> predeterminada para las tareas facturadas.',
|
||||
'task_rate_help' => 'Indique el ratio por defecto para las tareas facturadas.',
|
||||
'past_due' => 'Vencido',
|
||||
'document' => 'Documento',
|
||||
'invoice_or_expense' => 'Factura/Gasto',
|
||||
@ -2514,40 +2516,40 @@ Eliminar permanentemente la cuenta junto con todos los datos y la configuración
|
||||
'set_phone_for_two_factor' => 'Indica tu telefono para habilitar',
|
||||
'enabled_two_factor' => 'Autenticacion en dos pasos habilitada correctamente',
|
||||
'add_product' => 'Añadir Producto',
|
||||
'email_will_be_sent_on' => 'Note: the email will be sent on :date.',
|
||||
'invoice_product' => 'Invoice Product',
|
||||
'email_will_be_sent_on' => 'Nota: el correo se enviará el :date.',
|
||||
'invoice_product' => 'Producto de Factura',
|
||||
'self_host_login' => 'Self-Host Login',
|
||||
'set_self_hoat_url' => 'Self-Host URL',
|
||||
'local_storage_required' => 'Error: local storage is not available.',
|
||||
'your_password_reset_link' => 'Your Password Reset Link',
|
||||
'subdomain_taken' => 'The subdomain is already in use',
|
||||
'local_storage_required' => 'Error: almacenaje local no disponible.',
|
||||
'your_password_reset_link' => 'Enlace para reiniciar su password',
|
||||
'subdomain_taken' => 'El subdominio ya está en uso',
|
||||
'client_login' => 'Acceso de Clientes',
|
||||
'converted_amount' => 'Converted Amount',
|
||||
'default' => 'Default',
|
||||
'shipping_address' => 'Shipping Address',
|
||||
'bllling_address' => 'Billing Address',
|
||||
'billing_address1' => 'Billing Street',
|
||||
'billing_address2' => 'Billing Apt/Suite',
|
||||
'billing_city' => 'Billing City',
|
||||
'billing_state' => 'Billing State/Province',
|
||||
'billing_postal_code' => 'Billing Postal Code',
|
||||
'billing_country' => 'Billing Country',
|
||||
'shipping_address1' => 'Shipping Street',
|
||||
'shipping_address2' => 'Shipping Apt/Suite',
|
||||
'shipping_city' => 'Shipping City',
|
||||
'shipping_state' => 'Shipping State/Province',
|
||||
'shipping_postal_code' => 'Shipping Postal Code',
|
||||
'shipping_country' => 'Shipping Country',
|
||||
'classify' => 'Classify',
|
||||
'show_shipping_address_help' => 'Require client to provide their shipping address',
|
||||
'ship_to_billing_address' => 'Ship to billing address',
|
||||
'delivery_note' => 'Delivery Note',
|
||||
'show_tasks_in_portal' => 'Show tasks in the client portal',
|
||||
'cancel_schedule' => 'Cancel Schedule',
|
||||
'scheduled_report' => 'Scheduled Report',
|
||||
'scheduled_report_help' => 'Email the :report report as :format to :email',
|
||||
'created_scheduled_report' => 'Successfully scheduled report',
|
||||
'deleted_scheduled_report' => 'Successfully canceled scheduled report',
|
||||
'converted_amount' => 'Cuenta convertida',
|
||||
'default' => 'Por defecto',
|
||||
'shipping_address' => 'Direccion de Envio',
|
||||
'bllling_address' => 'Dirección de Facturación',
|
||||
'billing_address1' => 'Calle de Facturacion',
|
||||
'billing_address2' => 'Piso de Facturacion',
|
||||
'billing_city' => 'Ciudad de Facturacion',
|
||||
'billing_state' => 'Provincia de Facturacion',
|
||||
'billing_postal_code' => 'Cod. Postal de Facturacion',
|
||||
'billing_country' => 'Pais de Facturacion',
|
||||
'shipping_address1' => 'Calle de Envio',
|
||||
'shipping_address2' => 'Piso de Envio',
|
||||
'shipping_city' => 'Ciudad de Envio',
|
||||
'shipping_state' => 'Provincia de Envio',
|
||||
'shipping_postal_code' => 'Cod. Postal de Envio',
|
||||
'shipping_country' => 'Pais de Envio',
|
||||
'classify' => 'Clasificar',
|
||||
'show_shipping_address_help' => 'Requerir al cliente que indique su dirección de envio',
|
||||
'ship_to_billing_address' => 'Enviar a la Dirección de Facturación',
|
||||
'delivery_note' => 'Nota para el envio',
|
||||
'show_tasks_in_portal' => 'Mostrar tareas en el Portal del Cliente',
|
||||
'cancel_schedule' => 'Cancelar Programacion',
|
||||
'scheduled_report' => 'Informe Programado',
|
||||
'scheduled_report_help' => 'Enviar por correo el informe :report como :format a :email',
|
||||
'created_scheduled_report' => 'Informe Programado correctamente',
|
||||
'deleted_scheduled_report' => 'Informe Programado cancelado correctamente',
|
||||
'scheduled_report_attached' => 'Your scheduled :type report is attached.',
|
||||
'scheduled_report_error' => 'Failed to create schedule report',
|
||||
'invalid_one_time_password' => 'Invalid one time password',
|
||||
@ -2564,8 +2566,8 @@ Eliminar permanentemente la cuenta junto con todos los datos y la configuración
|
||||
'target_url' => 'Target',
|
||||
'target_url_help' => 'When the selected event occurs the app will post the entity as JSON to the target URL.',
|
||||
'event' => 'Event',
|
||||
'subscription_event_1' => 'Created Client',
|
||||
'subscription_event_2' => 'Created Invoice',
|
||||
'subscription_event_1' => 'Cliente creado ',
|
||||
'subscription_event_2' => 'Factura creada',
|
||||
'subscription_event_3' => 'Created Quote',
|
||||
'subscription_event_4' => 'Created Payment',
|
||||
'subscription_event_5' => 'Created Vendor',
|
||||
@ -2573,6 +2575,18 @@ Eliminar permanentemente la cuenta junto con todos los datos y la configuración
|
||||
'subscription_event_7' => 'Deleted Quote',
|
||||
'subscription_event_8' => 'Updated Invoice',
|
||||
'subscription_event_9' => 'Deleted Invoice',
|
||||
'subscription_event_10' => 'Updated Client',
|
||||
'subscription_event_11' => 'Deleted Client',
|
||||
'subscription_event_12' => 'Deleted Payment',
|
||||
'subscription_event_13' => 'Updated Vendor',
|
||||
'subscription_event_14' => 'Deleted Vendor',
|
||||
'subscription_event_15' => 'Created Expense',
|
||||
'subscription_event_16' => 'Updated Expense',
|
||||
'subscription_event_17' => 'Deleted Expense',
|
||||
'subscription_event_18' => 'Created Task',
|
||||
'subscription_event_19' => 'Updated Task',
|
||||
'subscription_event_20' => 'Deleted Task',
|
||||
'subscription_event_21' => 'Approved Quote',
|
||||
'subscriptions' => 'Subscriptions',
|
||||
'updated_subscription' => 'Successfully updated subscription',
|
||||
'created_subscription' => 'Successfully created subscription',
|
||||
@ -2597,11 +2611,34 @@ Eliminar permanentemente la cuenta junto con todos los datos y la configuración
|
||||
'mcrypt_warning' => 'Warning: Mcrypt is deprecated, run <code>php artisan ninja:update-key --legacy=true</code> to update your cipher.',
|
||||
'edit_times' => 'Edit Times',
|
||||
'inclusive_taxes_help' => 'Include <b>taxes in the cost</b>',
|
||||
'inclusive_taxes_notice' => 'This setting can not be changed once an invoice has been created.',
|
||||
'inclusive_taxes_warning' => 'Warning: existing invoices will need to be resaved',
|
||||
'copy_shipping' => 'Copy Shipping',
|
||||
'copy_billing' => 'Copy Billing',
|
||||
'quote_has_expired' => 'The quote has expired, please contact the merchant.',
|
||||
'empty_table_footer' => 'Showing 0 to 0 of 0 entries',
|
||||
'do_not_trust' => 'Do not remember this device',
|
||||
'trust_for_30_days' => 'Trust for 30 days',
|
||||
'trust_forever' => 'Trust forever',
|
||||
'kanban' => 'Kanban',
|
||||
'backlog' => 'Backlog',
|
||||
'ready_to_do' => 'Ready to do',
|
||||
'in_progress' => 'In progress',
|
||||
'add_status' => 'Add status',
|
||||
'archive_status' => 'Archive Status',
|
||||
'new_status' => 'New Status',
|
||||
'convert_products' => 'Convert Products',
|
||||
'convert_products_help' => 'Automatically convert product prices to the client\'s currency',
|
||||
'improve_client_portal_link' => 'Set a subdomain to shorten the client portal link.',
|
||||
'budgeted_hours' => 'Budgeted Hours',
|
||||
'progress' => 'Progress',
|
||||
'view_project' => 'View Project',
|
||||
'summary' => 'Summary',
|
||||
'endless_reminder' => 'Endless Reminder',
|
||||
'signature_on_invoice_help' => 'Add the following code to show your client\'s signature on the PDF.',
|
||||
'signature_on_pdf' => 'Show on PDF',
|
||||
'signature_on_pdf_help' => 'Show the client signature on the invoice/quote PDF.',
|
||||
'expired_white_label' => 'The white label license has expired',
|
||||
|
||||
);
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user