mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Merge branch 'release-3.3.0'
This commit is contained in:
commit
be4878db43
@ -8,6 +8,7 @@ use App\Ninja\Mailers\ContactMailer as Mailer;
|
|||||||
use App\Ninja\Repositories\AccountRepository;
|
use App\Ninja\Repositories\AccountRepository;
|
||||||
use App\Services\PaymentService;
|
use App\Services\PaymentService;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Carbon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ChargeRenewalInvoices.
|
* Class ChargeRenewalInvoices.
|
||||||
@ -83,6 +84,11 @@ class ChargeRenewalInvoices extends Command
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Carbon::parse($company->plan_expires)->isFuture()) {
|
||||||
|
$this->info('Skipping invoice ' . $invoice->invoice_number . ' [plan not expired]');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$this->info("Charging invoice {$invoice->invoice_number}");
|
$this->info("Charging invoice {$invoice->invoice_number}");
|
||||||
if (! $this->paymentService->autoBillInvoice($invoice)) {
|
if (! $this->paymentService->autoBillInvoice($invoice)) {
|
||||||
$this->info('Failed to auto-bill, emailing invoice');
|
$this->info('Failed to auto-bill, emailing invoice');
|
||||||
|
@ -9,6 +9,8 @@ use Illuminate\Console\Command;
|
|||||||
use Mail;
|
use Mail;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Utils;
|
use Utils;
|
||||||
|
use App\Models\Contact;
|
||||||
|
use App\Models\Invitation;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
@ -63,13 +65,15 @@ class CheckData extends Command
|
|||||||
$this->logMessage(date('Y-m-d') . ' Running CheckData...');
|
$this->logMessage(date('Y-m-d') . ' Running CheckData...');
|
||||||
|
|
||||||
if (! $this->option('client_id')) {
|
if (! $this->option('client_id')) {
|
||||||
$this->checkPaidToDate();
|
|
||||||
$this->checkBlankInvoiceHistory();
|
$this->checkBlankInvoiceHistory();
|
||||||
|
$this->checkPaidToDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->checkBalances();
|
$this->checkBalances();
|
||||||
|
$this->checkContacts();
|
||||||
|
|
||||||
if (! $this->option('client_id')) {
|
if (! $this->option('client_id')) {
|
||||||
|
$this->checkInvitations();
|
||||||
$this->checkFailedJobs();
|
$this->checkFailedJobs();
|
||||||
$this->checkAccountData();
|
$this->checkAccountData();
|
||||||
}
|
}
|
||||||
@ -94,6 +98,62 @@ class CheckData extends Command
|
|||||||
$this->log .= $str . "\n";
|
$this->log .= $str . "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function checkContacts()
|
||||||
|
{
|
||||||
|
$clients = DB::table('clients')
|
||||||
|
->leftJoin('contacts', function($join) {
|
||||||
|
$join->on('contacts.client_id', '=', 'clients.id')
|
||||||
|
->whereNull('contacts.deleted_at');
|
||||||
|
})
|
||||||
|
->groupBy('clients.id', 'clients.user_id', 'clients.account_id')
|
||||||
|
->havingRaw('count(contacts.id) = 0');
|
||||||
|
|
||||||
|
if ($this->option('client_id')) {
|
||||||
|
$clients->where('clients.id', '=', $this->option('client_id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$clients = $clients->get(['clients.id', 'clients.user_id', 'clients.account_id']);
|
||||||
|
$this->logMessage(count($clients) . ' clients without any contacts');
|
||||||
|
|
||||||
|
if (count($clients) > 0) {
|
||||||
|
$this->isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->option('fix') == 'true') {
|
||||||
|
foreach ($clients as $client) {
|
||||||
|
$contact = new Contact();
|
||||||
|
$contact->account_id = $client->account_id;
|
||||||
|
$contact->user_id = $client->user_id;
|
||||||
|
$contact->client_id = $client->id;
|
||||||
|
$contact->is_primary = true;
|
||||||
|
$contact->send_invoice = true;
|
||||||
|
$contact->contact_key = strtolower(str_random(RANDOM_KEY_LENGTH));
|
||||||
|
$contact->public_id = Contact::whereAccountId($client->account_id)->withTrashed()->max('public_id') + 1;
|
||||||
|
$contact->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$clients = DB::table('clients')
|
||||||
|
->leftJoin('contacts', function($join) {
|
||||||
|
$join->on('contacts.client_id', '=', 'clients.id')
|
||||||
|
->where('contacts.is_primary', '=', true)
|
||||||
|
->whereNull('contacts.deleted_at');
|
||||||
|
})
|
||||||
|
->groupBy('clients.id')
|
||||||
|
->havingRaw('count(contacts.id) != 1');
|
||||||
|
|
||||||
|
if ($this->option('client_id')) {
|
||||||
|
$clients->where('clients.id', '=', $this->option('client_id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$clients = $clients->get(['clients.id', DB::raw('count(contacts.id)')]);
|
||||||
|
$this->logMessage(count($clients) . ' clients without a single primary contact');
|
||||||
|
|
||||||
|
if (count($clients) > 0) {
|
||||||
|
$this->isValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function checkFailedJobs()
|
private function checkFailedJobs()
|
||||||
{
|
{
|
||||||
$count = DB::table('failed_jobs')->count();
|
$count = DB::table('failed_jobs')->count();
|
||||||
@ -120,6 +180,34 @@ class CheckData extends Command
|
|||||||
$this->logMessage($count . ' activities with blank invoice backup');
|
$this->logMessage($count . ' activities with blank invoice backup');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function checkInvitations()
|
||||||
|
{
|
||||||
|
$invoices = DB::table('invoices')
|
||||||
|
->leftJoin('invitations', 'invitations.invoice_id', '=', 'invoices.id')
|
||||||
|
->groupBy('invoices.id', 'invoices.user_id', 'invoices.account_id', 'invoices.client_id')
|
||||||
|
->havingRaw('count(invitations.id) = 0')
|
||||||
|
->get(['invoices.id', 'invoices.user_id', 'invoices.account_id', 'invoices.client_id']);
|
||||||
|
|
||||||
|
$this->logMessage(count($invoices) . ' invoices without any invitations');
|
||||||
|
|
||||||
|
if (count($invoices) > 0) {
|
||||||
|
$this->isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->option('fix') == 'true') {
|
||||||
|
foreach ($invoices as $invoice) {
|
||||||
|
$invitation = new Invitation();
|
||||||
|
$invitation->account_id = $invoice->account_id;
|
||||||
|
$invitation->user_id = $invoice->user_id;
|
||||||
|
$invitation->invoice_id = $invoice->id;
|
||||||
|
$invitation->contact_id = Contact::whereClientId($invoice->client_id)->whereIsPrimary(true)->first()->id;
|
||||||
|
$invitation->invitation_key = strtolower(str_random(RANDOM_KEY_LENGTH));
|
||||||
|
$invitation->public_id = Invitation::whereAccountId($invoice->account_id)->withTrashed()->max('public_id') + 1;
|
||||||
|
$invitation->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function checkAccountData()
|
private function checkAccountData()
|
||||||
{
|
{
|
||||||
$tables = [
|
$tables = [
|
||||||
@ -159,6 +247,7 @@ class CheckData extends Command
|
|||||||
],
|
],
|
||||||
'products' => [
|
'products' => [
|
||||||
ENTITY_USER,
|
ENTITY_USER,
|
||||||
|
ENTITY_TAX_RATE,
|
||||||
],
|
],
|
||||||
'vendors' => [
|
'vendors' => [
|
||||||
ENTITY_USER,
|
ENTITY_USER,
|
||||||
@ -173,14 +262,28 @@ class CheckData extends Command
|
|||||||
ENTITY_USER,
|
ENTITY_USER,
|
||||||
ENTITY_CLIENT,
|
ENTITY_CLIENT,
|
||||||
],
|
],
|
||||||
|
'accounts' => [
|
||||||
|
ENTITY_TAX_RATE,
|
||||||
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($tables as $table => $entityTypes) {
|
foreach ($tables as $table => $entityTypes) {
|
||||||
foreach ($entityTypes as $entityType) {
|
foreach ($entityTypes as $entityType) {
|
||||||
$tableName = Utils::pluralizeEntityType($entityType);
|
$tableName = Utils::pluralizeEntityType($entityType);
|
||||||
|
if ($entityType == ENTITY_TAX_RATE) {
|
||||||
|
$field = 'default_' . $entityType;
|
||||||
|
} else {
|
||||||
|
$field = $entityType;
|
||||||
|
}
|
||||||
|
if ($table == 'accounts') {
|
||||||
|
$accountId = 'id';
|
||||||
|
} else {
|
||||||
|
$accountId = 'account_id';
|
||||||
|
}
|
||||||
|
|
||||||
$records = DB::table($table)
|
$records = DB::table($table)
|
||||||
->join($tableName, "{$tableName}.id", '=', "{$table}.{$entityType}_id")
|
->join($tableName, "{$tableName}.id", '=', "{$table}.{$field}_id")
|
||||||
->where("{$table}.account_id", '!=', DB::raw("{$tableName}.account_id"))
|
->where("{$table}.{$accountId}", '!=', DB::raw("{$tableName}.account_id"))
|
||||||
->get(["{$table}.id"]);
|
->get(["{$table}.id"]);
|
||||||
|
|
||||||
if (count($records)) {
|
if (count($records)) {
|
||||||
|
223
app/Console/Commands/CreateLuisData.php
Normal file
223
app/Console/Commands/CreateLuisData.php
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Utils;
|
||||||
|
use stdClass;
|
||||||
|
use App\Models\Account;
|
||||||
|
use Faker\Factory;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class CreateLuisData.
|
||||||
|
*/
|
||||||
|
class CreateLuisData extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Create LUIS Data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'ninja:create-luis-data {faker_field=name}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CreateLuisData constructor.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->faker = Factory::create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function fire()
|
||||||
|
{
|
||||||
|
$this->fakerField = $this->argument('faker_field');
|
||||||
|
|
||||||
|
$intents = [];
|
||||||
|
$entityTypes = [
|
||||||
|
ENTITY_INVOICE,
|
||||||
|
ENTITY_QUOTE,
|
||||||
|
ENTITY_CLIENT,
|
||||||
|
ENTITY_CREDIT,
|
||||||
|
ENTITY_EXPENSE,
|
||||||
|
ENTITY_PAYMENT,
|
||||||
|
ENTITY_PRODUCT,
|
||||||
|
ENTITY_RECURRING_INVOICE,
|
||||||
|
ENTITY_TASK,
|
||||||
|
ENTITY_VENDOR,
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($entityTypes as $entityType) {
|
||||||
|
$intents = array_merge($intents, $this->createIntents($entityType));
|
||||||
|
}
|
||||||
|
|
||||||
|
$intents = array_merge($intents, $this->getNavigateToIntents($entityType));
|
||||||
|
|
||||||
|
$this->info(json_encode($intents));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createIntents($entityType)
|
||||||
|
{
|
||||||
|
$intents = [];
|
||||||
|
|
||||||
|
$intents = array_merge($intents, $this->getCreateEntityIntents($entityType));
|
||||||
|
$intents = array_merge($intents, $this->getFindEntityIntents($entityType));
|
||||||
|
$intents = array_merge($intents, $this->getListEntityIntents($entityType));
|
||||||
|
|
||||||
|
return $intents;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCreateEntityIntents($entityType)
|
||||||
|
{
|
||||||
|
$intents = [];
|
||||||
|
$phrases = [
|
||||||
|
"create new {$entityType}",
|
||||||
|
"new {$entityType}",
|
||||||
|
"make a {$entityType}",
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($phrases as $phrase) {
|
||||||
|
$intents[] = $this->createIntent('CreateEntity', $phrase, [
|
||||||
|
$entityType => 'EntityType',
|
||||||
|
]);
|
||||||
|
if ($entityType != ENTITY_CLIENT) {
|
||||||
|
$client = $this->faker->{$this->fakerField};
|
||||||
|
$phrase .= " for {$client}";
|
||||||
|
$intents[] = $this->createIntent('CreateEntity', $phrase, [
|
||||||
|
$entityType => 'EntityType',
|
||||||
|
$client => 'Name',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $intents;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFindEntityIntents($entityType)
|
||||||
|
{
|
||||||
|
$intents = [];
|
||||||
|
|
||||||
|
if (in_array($entityType, [ENTITY_CLIENT, ENTITY_INVOICE, ENTITY_QUOTE])) {
|
||||||
|
$name = $entityType === ENTITY_CLIENT ? $this->faker->{$this->fakerField} : $this->faker->randomNumber(4);
|
||||||
|
$intents[] = $this->createIntent('FindEntity', "find {$entityType} {$name}", [
|
||||||
|
$entityType => 'EntityType',
|
||||||
|
$name => 'Name',
|
||||||
|
]);
|
||||||
|
if ($entityType === ENTITY_CLIENT) {
|
||||||
|
$name = $this->faker->{$this->fakerField};
|
||||||
|
$intents[] = $this->createIntent('FindEntity', "find {$name}", [
|
||||||
|
$name => 'Name',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $intents;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getListEntityIntents($entityType)
|
||||||
|
{
|
||||||
|
$intents = [];
|
||||||
|
$entityTypePlural = Utils::pluralizeEntityType($entityType);
|
||||||
|
|
||||||
|
$intents[] = $this->createIntent('ListEntity', "show me {$entityTypePlural}", [
|
||||||
|
$entityTypePlural => 'EntityType',
|
||||||
|
]);
|
||||||
|
$intents[] = $this->createIntent('ListEntity', "list {$entityTypePlural}", [
|
||||||
|
$entityTypePlural => 'EntityType',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$intents[] = $this->createIntent('ListEntity', "show me active {$entityTypePlural}", [
|
||||||
|
$entityTypePlural => 'EntityType',
|
||||||
|
'active' => 'Filter',
|
||||||
|
]);
|
||||||
|
$intents[] = $this->createIntent('ListEntity', "list archived and deleted {$entityTypePlural}", [
|
||||||
|
$entityTypePlural => 'EntityType',
|
||||||
|
'archived' => 'Filter',
|
||||||
|
'deleted' => 'Filter',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($entityType != ENTITY_CLIENT) {
|
||||||
|
$client = $this->faker->{$this->fakerField};
|
||||||
|
$intents[] = $this->createIntent('ListEntity', "list {$entityTypePlural} for {$client}", [
|
||||||
|
$entityTypePlural => 'EntityType',
|
||||||
|
$client => 'Name',
|
||||||
|
]);
|
||||||
|
$intents[] = $this->createIntent('ListEntity', "show me {$client}'s {$entityTypePlural}", [
|
||||||
|
$entityTypePlural => 'EntityType',
|
||||||
|
$client . '\'s' => 'Name',
|
||||||
|
]);
|
||||||
|
$intents[] = $this->createIntent('ListEntity', "show me {$client}'s active {$entityTypePlural}", [
|
||||||
|
$entityTypePlural => 'EntityType',
|
||||||
|
$client . '\'s' => 'Name',
|
||||||
|
'active' => 'Filter',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $intents;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getNavigateToIntents($entityType)
|
||||||
|
{
|
||||||
|
$intents = [];
|
||||||
|
$locations = array_merge(Account::$basicSettings, Account::$advancedSettings);
|
||||||
|
|
||||||
|
foreach ($locations as $location) {
|
||||||
|
$location = str_replace('_', ' ', $location);
|
||||||
|
$intents[] = $this->createIntent('NavigateTo', "go to {$location}", [
|
||||||
|
$location => 'Location',
|
||||||
|
]);
|
||||||
|
$intents[] = $this->createIntent('NavigateTo', "show me {$location}", [
|
||||||
|
$location => 'Location',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $intents;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createIntent($name, $text, $entities)
|
||||||
|
{
|
||||||
|
$intent = new stdClass();
|
||||||
|
$intent->intent = $name;
|
||||||
|
$intent->text = $text;
|
||||||
|
$intent->entities = [];
|
||||||
|
|
||||||
|
foreach ($entities as $value => $entity) {
|
||||||
|
$startPos = strpos($text, (string)$value);
|
||||||
|
if (! $startPos) {
|
||||||
|
dd("Failed to find {$value} in {$text}");
|
||||||
|
}
|
||||||
|
$entityClass = new stdClass();
|
||||||
|
$entityClass->entity = $entity;
|
||||||
|
$entityClass->startPos = $startPos;
|
||||||
|
$entityClass->endPos = $entityClass->startPos + strlen($value) - 1;
|
||||||
|
$intent->entities[] = $entityClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getArguments()
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getOptions()
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,6 @@ use Utils;
|
|||||||
*/
|
*/
|
||||||
class CreateTestData extends Command
|
class CreateTestData extends Command
|
||||||
{
|
{
|
||||||
//protected $name = 'ninja:create-test-data';
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
@ -126,6 +125,7 @@ class CreateTestData extends Command
|
|||||||
{
|
{
|
||||||
for ($i = 0; $i < $this->count; $i++) {
|
for ($i = 0; $i < $this->count; $i++) {
|
||||||
$data = [
|
$data = [
|
||||||
|
'is_public' => true,
|
||||||
'client_id' => $client->id,
|
'client_id' => $client->id,
|
||||||
'invoice_date_sql' => date_create()->modify(rand(-100, 100) . ' days')->format('Y-m-d'),
|
'invoice_date_sql' => date_create()->modify(rand(-100, 100) . ' days')->format('Y-m-d'),
|
||||||
'due_date_sql' => date_create()->modify(rand(-100, 100) . ' days')->format('Y-m-d'),
|
'due_date_sql' => date_create()->modify(rand(-100, 100) . ' days')->format('Y-m-d'),
|
||||||
|
@ -20,6 +20,7 @@ class Kernel extends ConsoleKernel
|
|||||||
'App\Console\Commands\CheckData',
|
'App\Console\Commands\CheckData',
|
||||||
'App\Console\Commands\PruneData',
|
'App\Console\Commands\PruneData',
|
||||||
'App\Console\Commands\CreateTestData',
|
'App\Console\Commands\CreateTestData',
|
||||||
|
'App\Console\Commands\CreateLuisData',
|
||||||
'App\Console\Commands\SendRenewalInvoices',
|
'App\Console\Commands\SendRenewalInvoices',
|
||||||
'App\Console\Commands\ChargeRenewalInvoices',
|
'App\Console\Commands\ChargeRenewalInvoices',
|
||||||
'App\Console\Commands\SendReminders',
|
'App\Console\Commands\SendReminders',
|
||||||
|
@ -200,8 +200,11 @@ if (! defined('APP_NAME')) {
|
|||||||
define('TASK_STATUS_PAID', 4);
|
define('TASK_STATUS_PAID', 4);
|
||||||
|
|
||||||
define('EXPENSE_STATUS_LOGGED', 1);
|
define('EXPENSE_STATUS_LOGGED', 1);
|
||||||
define('EXPENSE_STATUS_INVOICED', 2);
|
define('EXPENSE_STATUS_PENDING', 2);
|
||||||
define('EXPENSE_STATUS_PAID', 3);
|
define('EXPENSE_STATUS_INVOICED', 3);
|
||||||
|
define('EXPENSE_STATUS_BILLED', 4);
|
||||||
|
define('EXPENSE_STATUS_PAID', 5);
|
||||||
|
define('EXPENSE_STATUS_UNPAID', 6);
|
||||||
|
|
||||||
define('CUSTOM_DESIGN', 11);
|
define('CUSTOM_DESIGN', 11);
|
||||||
|
|
||||||
@ -296,7 +299,7 @@ if (! defined('APP_NAME')) {
|
|||||||
define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com'));
|
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_DOCS_URL', env('NINJA_DOCS_URL', 'http://docs.invoiceninja.com/en/latest'));
|
||||||
define('NINJA_DATE', '2000-01-01');
|
define('NINJA_DATE', '2000-01-01');
|
||||||
define('NINJA_VERSION', '3.2.1' . env('NINJA_VERSION_SUFFIX'));
|
define('NINJA_VERSION', '3.3.0' . env('NINJA_VERSION_SUFFIX'));
|
||||||
|
|
||||||
define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'));
|
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'));
|
define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja'));
|
||||||
@ -306,6 +309,7 @@ if (! defined('APP_NAME')) {
|
|||||||
define('NINJA_CONTACT_URL', env('NINJA_CONTACT_URL', 'https://www.invoiceninja.com/contact/'));
|
define('NINJA_CONTACT_URL', env('NINJA_CONTACT_URL', 'https://www.invoiceninja.com/contact/'));
|
||||||
define('NINJA_FROM_EMAIL', env('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com'));
|
define('NINJA_FROM_EMAIL', env('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com'));
|
||||||
define('NINJA_IOS_APP_URL', 'https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=1220337560&mt=8');
|
define('NINJA_IOS_APP_URL', 'https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=1220337560&mt=8');
|
||||||
|
define('NINJA_ANDROID_APP_URL', 'https://play.google.com/store/apps/details?id=com.invoiceninja.invoiceninja');
|
||||||
define('RELEASES_URL', env('RELEASES_URL', 'https://trello.com/b/63BbiVVe/invoice-ninja'));
|
define('RELEASES_URL', env('RELEASES_URL', 'https://trello.com/b/63BbiVVe/invoice-ninja'));
|
||||||
define('ZAPIER_URL', env('ZAPIER_URL', 'https://zapier.com/zapbook/invoice-ninja'));
|
define('ZAPIER_URL', env('ZAPIER_URL', 'https://zapier.com/zapbook/invoice-ninja'));
|
||||||
define('OUTDATE_BROWSER_URL', env('OUTDATE_BROWSER_URL', 'http://browsehappy.com/'));
|
define('OUTDATE_BROWSER_URL', env('OUTDATE_BROWSER_URL', 'http://browsehappy.com/'));
|
||||||
@ -317,6 +321,7 @@ if (! defined('APP_NAME')) {
|
|||||||
define('OFX_HOME_URL', env('OFX_HOME_URL', 'http://www.ofxhome.com/index.php/home/directory/all'));
|
define('OFX_HOME_URL', env('OFX_HOME_URL', 'http://www.ofxhome.com/index.php/home/directory/all'));
|
||||||
define('GOOGLE_ANALYITCS_URL', env('GOOGLE_ANALYITCS_URL', 'https://www.google-analytics.com/collect'));
|
define('GOOGLE_ANALYITCS_URL', env('GOOGLE_ANALYITCS_URL', 'https://www.google-analytics.com/collect'));
|
||||||
define('TRANSIFEX_URL', env('TRANSIFEX_URL', 'https://www.transifex.com/invoice-ninja/invoice-ninja'));
|
define('TRANSIFEX_URL', env('TRANSIFEX_URL', 'https://www.transifex.com/invoice-ninja/invoice-ninja'));
|
||||||
|
define('IP_LOOKUP_URL', env('IP_LOOKUP_URL', 'http://whatismyipaddress.com/ip/'));
|
||||||
define('CHROME_PDF_HELP_URL', 'https://support.google.com/chrome/answer/6213030?hl=en');
|
define('CHROME_PDF_HELP_URL', 'https://support.google.com/chrome/answer/6213030?hl=en');
|
||||||
define('FIREFOX_PDF_HELP_URL', 'https://support.mozilla.org/en-US/kb/view-pdf-files-firefox');
|
define('FIREFOX_PDF_HELP_URL', 'https://support.mozilla.org/en-US/kb/view-pdf-files-firefox');
|
||||||
|
|
||||||
@ -441,7 +446,7 @@ if (! defined('APP_NAME')) {
|
|||||||
define('CURRENCY_DECORATOR_NONE', 'none');
|
define('CURRENCY_DECORATOR_NONE', 'none');
|
||||||
|
|
||||||
define('RESELLER_REVENUE_SHARE', 'A');
|
define('RESELLER_REVENUE_SHARE', 'A');
|
||||||
define('RESELLER_LIMITED_USERS', 'B');
|
define('RESELLER_ACCOUNT_COUNT', 'B');
|
||||||
|
|
||||||
define('AUTO_BILL_OFF', 1);
|
define('AUTO_BILL_OFF', 1);
|
||||||
define('AUTO_BILL_OPT_IN', 2);
|
define('AUTO_BILL_OPT_IN', 2);
|
||||||
|
29
app/Events/InvoiceItemsWereCreated.php
Normal file
29
app/Events/InvoiceItemsWereCreated.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class InvoiceItemsWereCreated.
|
||||||
|
*/
|
||||||
|
class InvoiceItemsWereCreated extends Event
|
||||||
|
{
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Invoice
|
||||||
|
*/
|
||||||
|
public $invoice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*
|
||||||
|
* @param Invoice $invoice
|
||||||
|
*/
|
||||||
|
public function __construct(Invoice $invoice)
|
||||||
|
{
|
||||||
|
$this->invoice = $invoice;
|
||||||
|
}
|
||||||
|
}
|
29
app/Events/InvoiceItemsWereUpdated.php
Normal file
29
app/Events/InvoiceItemsWereUpdated.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class InvoiceItemsWereUpdated.
|
||||||
|
*/
|
||||||
|
class InvoiceItemsWereUpdated extends Event
|
||||||
|
{
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Invoice
|
||||||
|
*/
|
||||||
|
public $invoice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*
|
||||||
|
* @param Invoice $invoice
|
||||||
|
*/
|
||||||
|
public function __construct(Invoice $invoice)
|
||||||
|
{
|
||||||
|
$this->invoice = $invoice;
|
||||||
|
}
|
||||||
|
}
|
@ -17,13 +17,19 @@ class InvoiceWasEmailed extends Event
|
|||||||
*/
|
*/
|
||||||
public $invoice;
|
public $invoice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $notes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new event instance.
|
* Create a new event instance.
|
||||||
*
|
*
|
||||||
* @param Invoice $invoice
|
* @param Invoice $invoice
|
||||||
*/
|
*/
|
||||||
public function __construct(Invoice $invoice)
|
public function __construct(Invoice $invoice, $notes)
|
||||||
{
|
{
|
||||||
$this->invoice = $invoice;
|
$this->invoice = $invoice;
|
||||||
|
$this->notes = $notes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
app/Events/QuoteItemsWereCreated.php
Normal file
24
app/Events/QuoteItemsWereCreated.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class QuoteItemsWereCreated.
|
||||||
|
*/
|
||||||
|
class QuoteItemsWereCreated extends Event
|
||||||
|
{
|
||||||
|
use SerializesModels;
|
||||||
|
public $quote;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*
|
||||||
|
* @param $quote
|
||||||
|
*/
|
||||||
|
public function __construct($quote)
|
||||||
|
{
|
||||||
|
$this->quote = $quote;
|
||||||
|
}
|
||||||
|
}
|
24
app/Events/QuoteItemsWereUpdated.php
Normal file
24
app/Events/QuoteItemsWereUpdated.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class QuoteItemsWereUpdated.
|
||||||
|
*/
|
||||||
|
class QuoteItemsWereUpdated extends Event
|
||||||
|
{
|
||||||
|
use SerializesModels;
|
||||||
|
public $quote;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*
|
||||||
|
* @param $quote
|
||||||
|
*/
|
||||||
|
public function __construct($quote)
|
||||||
|
{
|
||||||
|
$this->quote = $quote;
|
||||||
|
}
|
||||||
|
}
|
@ -12,13 +12,19 @@ class QuoteWasEmailed extends Event
|
|||||||
use SerializesModels;
|
use SerializesModels;
|
||||||
public $quote;
|
public $quote;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $notes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new event instance.
|
* Create a new event instance.
|
||||||
*
|
*
|
||||||
* @param $quote
|
* @param $quote
|
||||||
*/
|
*/
|
||||||
public function __construct($quote)
|
public function __construct($quote, $notes)
|
||||||
{
|
{
|
||||||
$this->quote = $quote;
|
$this->quote = $quote;
|
||||||
|
$this->notes = $notes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ class AccountApiController extends BaseAPIController
|
|||||||
$updatedAt = $request->updated_at ? date('Y-m-d H:i:s', $request->updated_at) : false;
|
$updatedAt = $request->updated_at ? date('Y-m-d H:i:s', $request->updated_at) : false;
|
||||||
|
|
||||||
$transformer = new AccountTransformer(null, $request->serializer);
|
$transformer = new AccountTransformer(null, $request->serializer);
|
||||||
$account->load(array_merge($transformer->getDefaultIncludes(), ['projects.client']));
|
$account->load(array_merge($transformer->getDefaultIncludes(), ['projects.client', 'products.default_tax_rate']));
|
||||||
$account = $this->createItem($account, $transformer, 'account');
|
$account = $this->createItem($account, $transformer, 'account');
|
||||||
|
|
||||||
return $this->response($account);
|
return $this->response($account);
|
||||||
|
@ -546,6 +546,7 @@ class AccountController extends BaseController
|
|||||||
$client->postal_code = '10000';
|
$client->postal_code = '10000';
|
||||||
$client->work_phone = '(212) 555-0000';
|
$client->work_phone = '(212) 555-0000';
|
||||||
$client->work_email = 'sample@example.com';
|
$client->work_email = 'sample@example.com';
|
||||||
|
$client->balance = 100;
|
||||||
|
|
||||||
$invoice->invoice_number = '0000';
|
$invoice->invoice_number = '0000';
|
||||||
$invoice->invoice_date = Utils::fromSqlDate(date('Y-m-d'));
|
$invoice->invoice_date = Utils::fromSqlDate(date('Y-m-d'));
|
||||||
@ -892,6 +893,8 @@ class AccountController extends BaseController
|
|||||||
$account->custom_value2 = trim(Input::get('custom_value2'));
|
$account->custom_value2 = trim(Input::get('custom_value2'));
|
||||||
$account->custom_client_label1 = trim(Input::get('custom_client_label1'));
|
$account->custom_client_label1 = trim(Input::get('custom_client_label1'));
|
||||||
$account->custom_client_label2 = trim(Input::get('custom_client_label2'));
|
$account->custom_client_label2 = trim(Input::get('custom_client_label2'));
|
||||||
|
$account->custom_contact_label1 = trim(Input::get('custom_contact_label1'));
|
||||||
|
$account->custom_contact_label2 = trim(Input::get('custom_contact_label2'));
|
||||||
$account->custom_invoice_label1 = trim(Input::get('custom_invoice_label1'));
|
$account->custom_invoice_label1 = trim(Input::get('custom_invoice_label1'));
|
||||||
$account->custom_invoice_label2 = trim(Input::get('custom_invoice_label2'));
|
$account->custom_invoice_label2 = trim(Input::get('custom_invoice_label2'));
|
||||||
$account->custom_invoice_taxes1 = Input::get('custom_invoice_taxes1') ? true : false;
|
$account->custom_invoice_taxes1 = Input::get('custom_invoice_taxes1') ? true : false;
|
||||||
@ -1016,13 +1019,9 @@ class AccountController extends BaseController
|
|||||||
/* Logo image file */
|
/* Logo image file */
|
||||||
if ($uploaded = Input::file('logo')) {
|
if ($uploaded = Input::file('logo')) {
|
||||||
$path = Input::file('logo')->getRealPath();
|
$path = Input::file('logo')->getRealPath();
|
||||||
|
|
||||||
$disk = $account->getLogoDisk();
|
$disk = $account->getLogoDisk();
|
||||||
if ($account->hasLogo() && ! Utils::isNinjaProd()) {
|
|
||||||
$disk->delete($account->logo);
|
|
||||||
}
|
|
||||||
|
|
||||||
$extension = strtolower($uploaded->getClientOriginalExtension());
|
$extension = strtolower($uploaded->getClientOriginalExtension());
|
||||||
|
|
||||||
if (empty(Document::$types[$extension]) && ! empty(Document::$extraExtensions[$extension])) {
|
if (empty(Document::$types[$extension]) && ! empty(Document::$extraExtensions[$extension])) {
|
||||||
$documentType = Document::$extraExtensions[$extension];
|
$documentType = Document::$extraExtensions[$extension];
|
||||||
} else {
|
} else {
|
||||||
@ -1038,7 +1037,7 @@ class AccountController extends BaseController
|
|||||||
$size = filesize($filePath);
|
$size = filesize($filePath);
|
||||||
|
|
||||||
if ($size / 1000 > MAX_DOCUMENT_SIZE) {
|
if ($size / 1000 > MAX_DOCUMENT_SIZE) {
|
||||||
Session::flash('warning', trans('texts.logo_warning_too_large'));
|
Session::flash('error', trans('texts.logo_warning_too_large'));
|
||||||
} else {
|
} else {
|
||||||
if ($documentType != 'gif') {
|
if ($documentType != 'gif') {
|
||||||
$account->logo = $account->account_key.'.'.$documentType;
|
$account->logo = $account->account_key.'.'.$documentType;
|
||||||
@ -1055,24 +1054,25 @@ class AccountController extends BaseController
|
|||||||
$image->interlace(false);
|
$image->interlace(false);
|
||||||
$imageStr = (string) $image->encode($documentType);
|
$imageStr = (string) $image->encode($documentType);
|
||||||
$disk->put($account->logo, $imageStr);
|
$disk->put($account->logo, $imageStr);
|
||||||
|
|
||||||
$account->logo_size = strlen($imageStr);
|
$account->logo_size = strlen($imageStr);
|
||||||
} else {
|
} else {
|
||||||
$stream = fopen($filePath, 'r');
|
if (Utils::isInterlaced($filePath)) {
|
||||||
$disk->getDriver()->putStream($account->logo, $stream, ['mimetype' => $documentTypeData['mime']]);
|
$account->clearLogo();
|
||||||
fclose($stream);
|
Session::flash('error', trans('texts.logo_warning_invalid'));
|
||||||
|
} else {
|
||||||
|
$stream = fopen($filePath, 'r');
|
||||||
|
$disk->getDriver()->putStream($account->logo, $stream, ['mimetype' => $documentTypeData['mime']]);
|
||||||
|
fclose($stream);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception $exception) {
|
} catch (Exception $exception) {
|
||||||
Session::flash('warning', trans('texts.logo_warning_invalid'));
|
$account->clearLogo();
|
||||||
|
Session::flash('error', trans('texts.logo_warning_invalid'));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (extension_loaded('fileinfo')) {
|
if (extension_loaded('fileinfo')) {
|
||||||
$image = Image::make($path);
|
|
||||||
$image->resize(200, 120, function ($constraint) {
|
|
||||||
$constraint->aspectRatio();
|
|
||||||
});
|
|
||||||
|
|
||||||
$account->logo = $account->account_key.'.png';
|
$account->logo = $account->account_key.'.png';
|
||||||
|
$image = Image::make($path);
|
||||||
$image = Image::canvas($image->width(), $image->height(), '#FFFFFF')->insert($image);
|
$image = Image::canvas($image->width(), $image->height(), '#FFFFFF')->insert($image);
|
||||||
$imageStr = (string) $image->encode('png');
|
$imageStr = (string) $image->encode('png');
|
||||||
$disk->put($account->logo, $imageStr);
|
$disk->put($account->logo, $imageStr);
|
||||||
@ -1081,7 +1081,7 @@ class AccountController extends BaseController
|
|||||||
$account->logo_width = $image->width();
|
$account->logo_width = $image->width();
|
||||||
$account->logo_height = $image->height();
|
$account->logo_height = $image->height();
|
||||||
} else {
|
} else {
|
||||||
Session::flash('warning', trans('texts.logo_warning_fileinfo'));
|
Session::flash('error', trans('texts.logo_warning_fileinfo'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -274,21 +274,21 @@ class AccountGatewayController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$plaidClientId = trim(Input::get('plaid_client_id'));
|
$plaidClientId = trim(Input::get('plaid_client_id'));
|
||||||
if ($plaidClientId = str_replace('*', '', $plaidClientId)) {
|
if (! $plaidClientId || $plaidClientId = str_replace('*', '', $plaidClientId)) {
|
||||||
$config->plaidClientId = $plaidClientId;
|
$config->plaidClientId = $plaidClientId;
|
||||||
} elseif ($oldConfig && property_exists($oldConfig, 'plaidClientId')) {
|
} elseif ($oldConfig && property_exists($oldConfig, 'plaidClientId')) {
|
||||||
$config->plaidClientId = $oldConfig->plaidClientId;
|
$config->plaidClientId = $oldConfig->plaidClientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
$plaidSecret = trim(Input::get('plaid_secret'));
|
$plaidSecret = trim(Input::get('plaid_secret'));
|
||||||
if ($plaidSecret = str_replace('*', '', $plaidSecret)) {
|
if (! $plaidSecret || $plaidSecret = str_replace('*', '', $plaidSecret)) {
|
||||||
$config->plaidSecret = $plaidSecret;
|
$config->plaidSecret = $plaidSecret;
|
||||||
} elseif ($oldConfig && property_exists($oldConfig, 'plaidSecret')) {
|
} elseif ($oldConfig && property_exists($oldConfig, 'plaidSecret')) {
|
||||||
$config->plaidSecret = $oldConfig->plaidSecret;
|
$config->plaidSecret = $oldConfig->plaidSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
$plaidPublicKey = trim(Input::get('plaid_public_key'));
|
$plaidPublicKey = trim(Input::get('plaid_public_key'));
|
||||||
if ($plaidPublicKey = str_replace('*', '', $plaidPublicKey)) {
|
if (! $plaidPublicKey || $plaidPublicKey = str_replace('*', '', $plaidPublicKey)) {
|
||||||
$config->plaidPublicKey = $plaidPublicKey;
|
$config->plaidPublicKey = $plaidPublicKey;
|
||||||
} elseif ($oldConfig && property_exists($oldConfig, 'plaidPublicKey')) {
|
} elseif ($oldConfig && property_exists($oldConfig, 'plaidPublicKey')) {
|
||||||
$config->plaidPublicKey = $oldConfig->plaidPublicKey;
|
$config->plaidPublicKey = $oldConfig->plaidPublicKey;
|
||||||
|
@ -83,10 +83,11 @@ class AppController extends BaseController
|
|||||||
|
|
||||||
$_ENV['APP_ENV'] = 'production';
|
$_ENV['APP_ENV'] = 'production';
|
||||||
$_ENV['APP_DEBUG'] = $app['debug'];
|
$_ENV['APP_DEBUG'] = $app['debug'];
|
||||||
$_ENV['REQUIRE_HTTPS'] = $app['https'];
|
$_ENV['APP_LOCALE'] = 'en';
|
||||||
$_ENV['APP_URL'] = $app['url'];
|
$_ENV['APP_URL'] = $app['url'];
|
||||||
$_ENV['APP_KEY'] = $app['key'];
|
$_ENV['APP_KEY'] = $app['key'];
|
||||||
$_ENV['APP_CIPHER'] = env('APP_CIPHER', 'AES-256-CBC');
|
$_ENV['APP_CIPHER'] = env('APP_CIPHER', 'AES-256-CBC');
|
||||||
|
$_ENV['REQUIRE_HTTPS'] = $app['https'];
|
||||||
$_ENV['DB_TYPE'] = $dbType;
|
$_ENV['DB_TYPE'] = $dbType;
|
||||||
$_ENV['DB_HOST'] = $database['type']['host'];
|
$_ENV['DB_HOST'] = $database['type']['host'];
|
||||||
$_ENV['DB_DATABASE'] = $database['type']['database'];
|
$_ENV['DB_DATABASE'] = $database['type']['database'];
|
||||||
|
@ -118,7 +118,17 @@ class AuthController extends Controller
|
|||||||
public function getLoginWrapper()
|
public function getLoginWrapper()
|
||||||
{
|
{
|
||||||
if (! Utils::isNinja() && ! User::count()) {
|
if (! Utils::isNinja() && ! User::count()) {
|
||||||
return redirect()->to('invoice_now');
|
return redirect()->to('/setup');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Utils::isNinja() && ! Utils::isTravis()) {
|
||||||
|
// make sure the user is on SITE_URL/login to ensure OAuth works
|
||||||
|
$requestURL = request()->url();
|
||||||
|
$loginURL = SITE_URL . '/login';
|
||||||
|
$subdomain = Utils::getSubdomain(request()->url());
|
||||||
|
if ($requestURL != $loginURL && ! strstr($subdomain, 'webapp-')) {
|
||||||
|
return redirect()->to($loginURL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::getLogin();
|
return self::getLogin();
|
||||||
|
@ -95,6 +95,9 @@ class ClientController extends BaseController
|
|||||||
if (Utils::hasFeature(FEATURE_QUOTES) && $user->can('create', ENTITY_QUOTE)) {
|
if (Utils::hasFeature(FEATURE_QUOTES) && $user->can('create', ENTITY_QUOTE)) {
|
||||||
$actionLinks[] = ['label' => trans('texts.new_quote'), 'url' => URL::to('/quotes/create/'.$client->public_id)];
|
$actionLinks[] = ['label' => trans('texts.new_quote'), 'url' => URL::to('/quotes/create/'.$client->public_id)];
|
||||||
}
|
}
|
||||||
|
if ($user->can('create', ENTITY_RECURRING_INVOICE)) {
|
||||||
|
$actionLinks[] = ['label' => trans('texts.new_recurring_invoice'), 'url' => URL::to('/recurring_invoices/create/'.$client->public_id)];
|
||||||
|
}
|
||||||
|
|
||||||
if (! empty($actionLinks)) {
|
if (! empty($actionLinks)) {
|
||||||
$actionLinks[] = \DropdownButton::DIVIDER;
|
$actionLinks[] = \DropdownButton::DIVIDER;
|
||||||
|
@ -651,7 +651,9 @@ class ClientPortalController extends BaseController
|
|||||||
$documents = $invoice->documents;
|
$documents = $invoice->documents;
|
||||||
|
|
||||||
foreach ($invoice->expenses as $expense) {
|
foreach ($invoice->expenses as $expense) {
|
||||||
$documents = $documents->merge($expense->documents);
|
if ($expense->invoice_documents) {
|
||||||
|
$documents = $documents->merge($expense->documents);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$documents = $documents->sortBy('size');
|
$documents = $documents->sortBy('size');
|
||||||
@ -740,7 +742,7 @@ class ClientPortalController extends BaseController
|
|||||||
$document = Document::scope($publicId, $invitation->account_id)->firstOrFail();
|
$document = Document::scope($publicId, $invitation->account_id)->firstOrFail();
|
||||||
|
|
||||||
$authorized = false;
|
$authorized = false;
|
||||||
if ($document->expense && $document->expense->client_id == $invitation->invoice->client_id) {
|
if ($document->expense && $document->expense->invoice_documents && $document->expense->client_id == $invitation->invoice->client_id) {
|
||||||
$authorized = true;
|
$authorized = true;
|
||||||
} elseif ($document->invoice && $document->invoice->client_id == $invitation->invoice->client_id) {
|
} elseif ($document->invoice && $document->invoice->client_id == $invitation->invoice->client_id) {
|
||||||
$authorized = true;
|
$authorized = true;
|
||||||
|
@ -98,8 +98,6 @@ class ExpenseController extends BaseController
|
|||||||
{
|
{
|
||||||
$expense = $request->entity();
|
$expense = $request->entity();
|
||||||
|
|
||||||
$expense->expense_date = Utils::fromSqlDate($expense->expense_date);
|
|
||||||
|
|
||||||
$actions = [];
|
$actions = [];
|
||||||
if ($expense->invoice) {
|
if ($expense->invoice) {
|
||||||
$actions[] = ['url' => URL::to("invoices/{$expense->invoice->public_id}/edit"), 'label' => trans('texts.view_invoice')];
|
$actions[] = ['url' => URL::to("invoices/{$expense->invoice->public_id}/edit"), 'label' => trans('texts.view_invoice')];
|
||||||
|
@ -37,7 +37,7 @@ class ExportController extends BaseController
|
|||||||
|
|
||||||
// set the filename based on the entity types selected
|
// set the filename based on the entity types selected
|
||||||
if ($request->include == 'all') {
|
if ($request->include == 'all') {
|
||||||
$fileName = "invoice-ninja-{$date}";
|
$fileName = "{$date}-invoiceninja";
|
||||||
} else {
|
} else {
|
||||||
$fields = $request->all();
|
$fields = $request->all();
|
||||||
$fields = array_filter(array_map(function ($key) {
|
$fields = array_filter(array_map(function ($key) {
|
||||||
@ -47,7 +47,7 @@ class ExportController extends BaseController
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}, array_keys($fields), $fields));
|
}, array_keys($fields), $fields));
|
||||||
$fileName = 'invoice-ninja-' . implode('-', $fields) . "-{$date}";
|
$fileName = $date. '-invoiceninja-' . implode('-', $fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($format === 'JSON') {
|
if ($format === 'JSON') {
|
||||||
|
@ -34,16 +34,26 @@ class ImportController extends BaseController
|
|||||||
$fileName = $entityType;
|
$fileName = $entityType;
|
||||||
if ($request->hasFile($fileName)) {
|
if ($request->hasFile($fileName)) {
|
||||||
$file = $request->file($fileName);
|
$file = $request->file($fileName);
|
||||||
$destinationPath = storage_path() . '/import';
|
$destinationPath = env('FILE_IMPORT_PATH') ?: storage_path() . '/import';
|
||||||
$extension = $file->getClientOriginalExtension();
|
$extension = $file->getClientOriginalExtension();
|
||||||
|
|
||||||
if (! in_array($extension, ['csv', 'xls', 'xlsx', 'json'])) {
|
if ($source === IMPORT_CSV) {
|
||||||
continue;
|
if ($extension != 'csv') {
|
||||||
|
return redirect()->to('/settings/' . ACCOUNT_IMPORT_EXPORT)->withError(trans('texts.invalid_file'));
|
||||||
|
}
|
||||||
|
} elseif ($source === IMPORT_JSON) {
|
||||||
|
if ($extension != 'json') {
|
||||||
|
return redirect()->to('/settings/' . ACCOUNT_IMPORT_EXPORT)->withError(trans('texts.invalid_file'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (! in_array($extension, ['csv', 'xls', 'xlsx', 'json'])) {
|
||||||
|
return redirect()->to('/settings/' . ACCOUNT_IMPORT_EXPORT)->withError(trans('texts.invalid_file'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$newFileName = sprintf('%s_%s_%s.%s', Auth::user()->account_id, $timestamp, $fileName, $extension);
|
$newFileName = sprintf('%s_%s_%s.%s', Auth::user()->account_id, $timestamp, $fileName, $extension);
|
||||||
$file->move($destinationPath, $newFileName);
|
$file->move($destinationPath, $newFileName);
|
||||||
$files[$entityType] = $newFileName;
|
$files[$entityType] = $destinationPath . '/' . $newFileName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,6 +112,7 @@ class ImportController extends BaseController
|
|||||||
$map = Input::get('map');
|
$map = Input::get('map');
|
||||||
$headers = Input::get('headers');
|
$headers = Input::get('headers');
|
||||||
$timestamp = Input::get('timestamp');
|
$timestamp = Input::get('timestamp');
|
||||||
|
|
||||||
if (config('queue.default') === 'sync') {
|
if (config('queue.default') === 'sync') {
|
||||||
$results = $this->importService->importCSV($map, $headers, $timestamp);
|
$results = $this->importService->importCSV($map, $headers, $timestamp);
|
||||||
$message = $this->importService->presentResults($results);
|
$message = $this->importService->presentResults($results);
|
||||||
|
@ -164,8 +164,9 @@ class InvoiceController extends BaseController
|
|||||||
foreach ($invoice->invitations as $invitation) {
|
foreach ($invoice->invitations as $invitation) {
|
||||||
foreach ($client->contacts as $contact) {
|
foreach ($client->contacts as $contact) {
|
||||||
if ($invitation->contact_id == $contact->id) {
|
if ($invitation->contact_id == $contact->id) {
|
||||||
|
$hasPassword = $account->isClientPortalPasswordEnabled() && $contact->password;
|
||||||
$contact->email_error = $invitation->email_error;
|
$contact->email_error = $invitation->email_error;
|
||||||
$contact->invitation_link = $invitation->getLink();
|
$contact->invitation_link = $invitation->getLink('view', $hasPassword, $hasPassword);
|
||||||
$contact->invitation_viewed = $invitation->viewed_date && $invitation->viewed_date != '0000-00-00 00:00:00' ? $invitation->viewed_date : false;
|
$contact->invitation_viewed = $invitation->viewed_date && $invitation->viewed_date != '0000-00-00 00:00:00' ? $invitation->viewed_date : false;
|
||||||
$contact->invitation_openend = $invitation->opened_date && $invitation->opened_date != '0000-00-00 00:00:00' ? $invitation->opened_date : false;
|
$contact->invitation_openend = $invitation->opened_date && $invitation->opened_date != '0000-00-00 00:00:00' ? $invitation->opened_date : false;
|
||||||
$contact->invitation_status = $contact->email_error ? false : $invitation->getStatus();
|
$contact->invitation_status = $contact->email_error ? false : $invitation->getStatus();
|
||||||
@ -313,7 +314,7 @@ class InvoiceController extends BaseController
|
|||||||
'recurringHelp' => $recurringHelp,
|
'recurringHelp' => $recurringHelp,
|
||||||
'recurringDueDateHelp' => $recurringDueDateHelp,
|
'recurringDueDateHelp' => $recurringDueDateHelp,
|
||||||
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
|
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
|
||||||
'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null,
|
'tasks' => Session::get('tasks') ? Session::get('tasks') : null,
|
||||||
'expenseCurrencyId' => Session::get('expenseCurrencyId') ?: null,
|
'expenseCurrencyId' => Session::get('expenseCurrencyId') ?: null,
|
||||||
'expenses' => Session::get('expenses') ? Expense::scope(Session::get('expenses'))->with('documents', 'expense_category')->get() : [],
|
'expenses' => Session::get('expenses') ? Expense::scope(Session::get('expenses'))->with('documents', 'expense_category')->get() : [],
|
||||||
];
|
];
|
||||||
@ -404,7 +405,8 @@ class InvoiceController extends BaseController
|
|||||||
if ($invoice->is_recurring) {
|
if ($invoice->is_recurring) {
|
||||||
$response = $this->emailRecurringInvoice($invoice);
|
$response = $this->emailRecurringInvoice($invoice);
|
||||||
} else {
|
} else {
|
||||||
$this->dispatch(new SendInvoiceEmail($invoice, $reminder, $template));
|
$userId = Auth::user()->id;
|
||||||
|
$this->dispatch(new SendInvoiceEmail($invoice, $userId, $reminder, $template));
|
||||||
$response = true;
|
$response = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -436,7 +438,8 @@ class InvoiceController extends BaseController
|
|||||||
if ($invoice->isPaid()) {
|
if ($invoice->isPaid()) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
$this->dispatch(new SendInvoiceEmail($invoice));
|
$userId = Auth::user()->id;
|
||||||
|
$this->dispatch(new SendInvoiceEmail($invoice, $userId));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -334,12 +334,14 @@ class OnlinePaymentController extends BaseController
|
|||||||
'custom_value1' => Input::get('custom_client1'),
|
'custom_value1' => Input::get('custom_client1'),
|
||||||
'custom_value2' => Input::get('custom_client2'),
|
'custom_value2' => Input::get('custom_client2'),
|
||||||
];
|
];
|
||||||
|
if (request()->currency_code) {
|
||||||
|
$data['currency_code'] = request()->currency_code;
|
||||||
|
}
|
||||||
$client = $clientRepo->save($data, $client);
|
$client = $clientRepo->save($data, $client);
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'client_id' => $client->id,
|
'client_id' => $client->id,
|
||||||
'is_public' => true,
|
|
||||||
'is_recurring' => filter_var(Input::get('is_recurring'), FILTER_VALIDATE_BOOLEAN),
|
'is_recurring' => filter_var(Input::get('is_recurring'), FILTER_VALIDATE_BOOLEAN),
|
||||||
'frequency_id' => Input::get('frequency_id'),
|
'frequency_id' => Input::get('frequency_id'),
|
||||||
'auto_bill_id' => Input::get('auto_bill_id'),
|
'auto_bill_id' => Input::get('auto_bill_id'),
|
||||||
|
223
app/Http/Controllers/ProjectApiController.php
Normal file
223
app/Http/Controllers/ProjectApiController.php
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Requests\CreateProjectRequest;
|
||||||
|
use App\Http\Requests\ProjectRequest;
|
||||||
|
use App\Http\Requests\UpdateProjectRequest;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Ninja\Repositories\ProjectRepository;
|
||||||
|
use App\Services\ProjectService;
|
||||||
|
use Auth;
|
||||||
|
use Input;
|
||||||
|
use Session;
|
||||||
|
use View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ProjectApiController
|
||||||
|
* @package App\Http\Controllers
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ProjectApiController extends BaseAPIController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ProjectRepository
|
||||||
|
*/
|
||||||
|
|
||||||
|
protected $projectRepo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ProjectService
|
||||||
|
*/
|
||||||
|
|
||||||
|
protected $projectService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
|
||||||
|
protected $entityType = ENTITY_PROJECT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ProjectApiController constructor.
|
||||||
|
* @param ProjectRepository $projectRepo
|
||||||
|
* @param ProjectService $projectService
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function __construct(ProjectRepository $projectRepo, ProjectService $projectService)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->projectRepo = $projectRepo;
|
||||||
|
$this->projectService = $projectService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @SWG\Get(
|
||||||
|
* path="/projects",
|
||||||
|
* summary="List projects",
|
||||||
|
* operationId="listProjects",
|
||||||
|
* tags={"project"},
|
||||||
|
* @SWG\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="A list of projects",
|
||||||
|
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Project"))
|
||||||
|
* ),
|
||||||
|
* @SWG\Response(
|
||||||
|
* response="default",
|
||||||
|
* description="an ""unexpected"" error"
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$projects = Project::scope()
|
||||||
|
->withTrashed()
|
||||||
|
->orderBy('created_at', 'desc');
|
||||||
|
|
||||||
|
return $this->listResponse($projects);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @SWG\Get(
|
||||||
|
* path="/projects/{project_id}",
|
||||||
|
* summary="Retrieve a project",
|
||||||
|
* operationId="getProject",
|
||||||
|
* tags={"project"},
|
||||||
|
* @SWG\Parameter(
|
||||||
|
* in="path",
|
||||||
|
* name="project_id",
|
||||||
|
* type="integer",
|
||||||
|
* required=true
|
||||||
|
* ),
|
||||||
|
* @SWG\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="A single project",
|
||||||
|
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Project"))
|
||||||
|
* ),
|
||||||
|
* @SWG\Response(
|
||||||
|
* response="default",
|
||||||
|
* description="an ""unexpected"" error"
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function show(ProjectRequest $request)
|
||||||
|
{
|
||||||
|
return $this->itemResponse($request->entity());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @SWG\Post(
|
||||||
|
* path="/projects",
|
||||||
|
* summary="Create a project",
|
||||||
|
* operationId="createProject",
|
||||||
|
* tags={"project"},
|
||||||
|
* @SWG\Parameter(
|
||||||
|
* in="body",
|
||||||
|
* name="body",
|
||||||
|
* @SWG\Schema(ref="#/definitions/project")
|
||||||
|
* ),
|
||||||
|
* @SWG\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="New project",
|
||||||
|
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/project"))
|
||||||
|
* ),
|
||||||
|
* @SWG\Response(
|
||||||
|
* response="default",
|
||||||
|
* description="an ""unexpected"" error"
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function store(CreateProjectRequest $request)
|
||||||
|
{
|
||||||
|
$project = $this->projectService->save($request->input());
|
||||||
|
|
||||||
|
return $this->itemResponse($project);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @SWG\Put(
|
||||||
|
* path="/projects/{project_id}",
|
||||||
|
* summary="Update a project",
|
||||||
|
* operationId="updateProject",
|
||||||
|
* tags={"project"},
|
||||||
|
* @SWG\Parameter(
|
||||||
|
* in="path",
|
||||||
|
* name="project_id",
|
||||||
|
* type="integer",
|
||||||
|
* required=true
|
||||||
|
* ),
|
||||||
|
* @SWG\Parameter(
|
||||||
|
* in="body",
|
||||||
|
* name="project",
|
||||||
|
* @SWG\Schema(ref="#/definitions/project")
|
||||||
|
* ),
|
||||||
|
* @SWG\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="Updated project",
|
||||||
|
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/project"))
|
||||||
|
* ),
|
||||||
|
* @SWG\Response(
|
||||||
|
* response="default",
|
||||||
|
* description="an ""unexpected"" error"
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* @param mixed $publicId
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function update(UpdateProjectRequest $request, $publicId)
|
||||||
|
{
|
||||||
|
if ($request->action) {
|
||||||
|
return $this->handleAction($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $request->input();
|
||||||
|
$data['public_id'] = $publicId;
|
||||||
|
$project = $this->projectService->save($request->input(), $request->entity());
|
||||||
|
|
||||||
|
return $this->itemResponse($project);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @SWG\Delete(
|
||||||
|
* path="/projects/{project_id}",
|
||||||
|
* summary="Delete a project",
|
||||||
|
* operationId="deleteProject",
|
||||||
|
* tags={"project"},
|
||||||
|
* @SWG\Parameter(
|
||||||
|
* in="path",
|
||||||
|
* name="project_id",
|
||||||
|
* type="integer",
|
||||||
|
* required=true
|
||||||
|
* ),
|
||||||
|
* @SWG\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="Deleted project",
|
||||||
|
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/project"))
|
||||||
|
* ),
|
||||||
|
* @SWG\Response(
|
||||||
|
* response="default",
|
||||||
|
* description="an ""unexpected"" error"
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function destroy(UpdateProjectRequest $request)
|
||||||
|
{
|
||||||
|
$project = $request->entity();
|
||||||
|
|
||||||
|
$this->projectRepo->delete($project);
|
||||||
|
|
||||||
|
return $this->itemResponse($project);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -98,7 +98,7 @@ class QuoteController extends BaseController
|
|||||||
return [
|
return [
|
||||||
'entityType' => ENTITY_QUOTE,
|
'entityType' => ENTITY_QUOTE,
|
||||||
'account' => $account,
|
'account' => $account,
|
||||||
'products' => Product::scope()->orderBy('id')->get(['product_key', 'notes', 'cost', 'qty']),
|
'products' => Product::scope()->with('default_tax_rate')->orderBy('product_key')->get(),
|
||||||
'taxRateOptions' => $account->present()->taxRateOptions,
|
'taxRateOptions' => $account->present()->taxRateOptions,
|
||||||
'defaultTax' => $account->default_tax_rate,
|
'defaultTax' => $account->default_tax_rate,
|
||||||
'countries' => Cache::get('countries'),
|
'countries' => Cache::get('countries'),
|
||||||
|
@ -27,7 +27,7 @@ class ReportController extends BaseController
|
|||||||
->with(['clients.invoices.invoice_items', 'clients.contacts'])
|
->with(['clients.invoices.invoice_items', 'clients.contacts'])
|
||||||
->first();
|
->first();
|
||||||
$account = $account->hideFieldsForViz();
|
$account = $account->hideFieldsForViz();
|
||||||
$clients = $account->clients->toJson();
|
$clients = $account->clients;
|
||||||
} elseif (file_exists($fileName)) {
|
} elseif (file_exists($fileName)) {
|
||||||
$clients = file_get_contents($fileName);
|
$clients = file_get_contents($fileName);
|
||||||
$message = trans('texts.sample_data');
|
$message = trans('texts.sample_data');
|
||||||
@ -129,11 +129,14 @@ class ReportController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$output = fopen('php://output', 'w') or Utils::fatalError();
|
$output = fopen('php://output', 'w') or Utils::fatalError();
|
||||||
$reportType = trans("texts.{$reportType}s");
|
|
||||||
$date = date('Y-m-d');
|
$date = date('Y-m-d');
|
||||||
|
|
||||||
|
$columns = array_map(function($key, $val) {
|
||||||
|
return is_array($val) ? $key : $val;
|
||||||
|
}, array_keys($columns), $columns);
|
||||||
|
|
||||||
header('Content-Type:application/csv');
|
header('Content-Type:application/csv');
|
||||||
header("Content-Disposition:attachment;filename={$date}_Ninja_{$reportType}.csv");
|
header("Content-Disposition:attachment;filename={$date}-invoiceninja-{$reportType}-report.csv");
|
||||||
|
|
||||||
Utils::exportData($output, $data, Utils::trans($columns));
|
Utils::exportData($output, $data, Utils::trans($columns));
|
||||||
|
|
||||||
|
@ -152,7 +152,6 @@ class VendorController extends BaseController
|
|||||||
'data' => Input::old('data'),
|
'data' => Input::old('data'),
|
||||||
'account' => Auth::user()->account,
|
'account' => Auth::user()->account,
|
||||||
'currencies' => Cache::get('currencies'),
|
'currencies' => Cache::get('currencies'),
|
||||||
'countries' => Cache::get('countries'),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,33 +135,20 @@ class StartupCheck
|
|||||||
$url = (Utils::isNinjaDev() ? SITE_URL : NINJA_APP_URL) . "/claim_license?license_key={$licenseKey}&product_id={$productId}&get_date=true";
|
$url = (Utils::isNinjaDev() ? SITE_URL : NINJA_APP_URL) . "/claim_license?license_key={$licenseKey}&product_id={$productId}&get_date=true";
|
||||||
$data = trim(CurlUtils::get($url));
|
$data = trim(CurlUtils::get($url));
|
||||||
|
|
||||||
if ($productId == PRODUCT_INVOICE_DESIGNS) {
|
if ($data == RESULT_FAILURE) {
|
||||||
if ($data = json_decode($data)) {
|
Session::flash('error', trans('texts.invalid_white_label_license'));
|
||||||
foreach ($data as $item) {
|
} elseif ($data) {
|
||||||
$design = new InvoiceDesign();
|
$company = Auth::user()->account->company;
|
||||||
$design->id = $item->id;
|
$company->plan_term = PLAN_TERM_YEARLY;
|
||||||
$design->name = $item->name;
|
$company->plan_paid = $data;
|
||||||
$design->pdfmake = $item->pdfmake;
|
$date = max(date_create($data), date_create($company->plan_expires));
|
||||||
$design->save();
|
$company->plan_expires = $date->modify('+1 year')->format('Y-m-d');
|
||||||
}
|
$company->plan = PLAN_WHITE_LABEL;
|
||||||
|
$company->save();
|
||||||
|
|
||||||
Cache::forget('invoiceDesigns');
|
Session::flash('message', trans('texts.bought_white_label'));
|
||||||
Session::flash('message', trans('texts.bought_designs'));
|
} else {
|
||||||
}
|
Session::flash('error', trans('texts.white_label_license_error'));
|
||||||
} elseif ($productId == PRODUCT_WHITE_LABEL) {
|
|
||||||
if ($data && $data != RESULT_FAILURE) {
|
|
||||||
$company = Auth::user()->account->company;
|
|
||||||
$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();
|
|
||||||
|
|
||||||
Session::flash('message', trans('texts.bought_white_label'));
|
|
||||||
} else {
|
|
||||||
Session::flash('error', trans('texts.invalid_white_label_license'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Http\ViewComposers;
|
namespace App\Http\ViewComposers;
|
||||||
|
|
||||||
use App\Models\Contact;
|
|
||||||
use DB;
|
use DB;
|
||||||
|
use App\Models\Contact;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,6 +37,7 @@ class ClientPortalHeaderComposer
|
|||||||
}
|
}
|
||||||
|
|
||||||
$client = $contact->client;
|
$client = $contact->client;
|
||||||
|
$account = $contact->account;
|
||||||
|
|
||||||
$hasDocuments = DB::table('invoices')
|
$hasDocuments = DB::table('invoices')
|
||||||
->where('invoices.client_id', '=', $client->id)
|
->where('invoices.client_id', '=', $client->id)
|
||||||
@ -44,8 +45,18 @@ class ClientPortalHeaderComposer
|
|||||||
->join('documents', 'documents.invoice_id', '=', 'invoices.id')
|
->join('documents', 'documents.invoice_id', '=', 'invoices.id')
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
|
$hasPaymentMethods = false;
|
||||||
|
if ($account->getTokenGatewayId() && ! $account->enable_client_portal_dashboard) {
|
||||||
|
$hasPaymentMethods = DB::table('payment_methods')
|
||||||
|
->where('contacts.client_id', '=', $client->id)
|
||||||
|
->whereNull('payment_methods.deleted_at')
|
||||||
|
->join('contacts', 'contacts.id', '=', 'payment_methods.contact_id')
|
||||||
|
->count();
|
||||||
|
}
|
||||||
|
|
||||||
$view->with('hasQuotes', $client->publicQuotes->count());
|
$view->with('hasQuotes', $client->publicQuotes->count());
|
||||||
$view->with('hasCredits', $client->creditsWithBalance->count());
|
$view->with('hasCredits', $client->creditsWithBalance->count());
|
||||||
$view->with('hasDocuments', $hasDocuments);
|
$view->with('hasDocuments', $hasDocuments);
|
||||||
|
$view->with('hasPaymentMethods', $hasPaymentMethods);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -319,6 +319,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function () {
|
|||||||
Route::post('email_invoice', 'InvoiceApiController@emailInvoice');
|
Route::post('email_invoice', 'InvoiceApiController@emailInvoice');
|
||||||
Route::get('user_accounts', 'AccountApiController@getUserAccounts');
|
Route::get('user_accounts', 'AccountApiController@getUserAccounts');
|
||||||
Route::resource('products', 'ProductApiController');
|
Route::resource('products', 'ProductApiController');
|
||||||
|
Route::resource('projects', 'ProjectApiController');
|
||||||
Route::resource('tax_rates', 'TaxRateApiController');
|
Route::resource('tax_rates', 'TaxRateApiController');
|
||||||
Route::resource('users', 'UserApiController');
|
Route::resource('users', 'UserApiController');
|
||||||
Route::resource('expenses', 'ExpenseApiController');
|
Route::resource('expenses', 'ExpenseApiController');
|
||||||
|
@ -8,6 +8,7 @@ use Illuminate\Queue\SerializesModels;
|
|||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
use App\Services\ImportService;
|
use App\Services\ImportService;
|
||||||
use App\Ninja\Mailers\UserMailer;
|
use App\Ninja\Mailers\UserMailer;
|
||||||
|
use App\Models\User;
|
||||||
use Auth;
|
use Auth;
|
||||||
use App;
|
use App;
|
||||||
|
|
||||||
@ -39,7 +40,7 @@ class ImportData extends Job implements ShouldQueue
|
|||||||
* @param mixed $files
|
* @param mixed $files
|
||||||
* @param mixed $settings
|
* @param mixed $settings
|
||||||
*/
|
*/
|
||||||
public function __construct($user, $type, $settings)
|
public function __construct(User $user, $type, $settings)
|
||||||
{
|
{
|
||||||
$this->user = $user;
|
$this->user = $user;
|
||||||
$this->type = $type;
|
$this->type = $type;
|
||||||
|
@ -8,6 +8,8 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
|
use Auth;
|
||||||
|
use App;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class SendInvoiceEmail.
|
* Class SendInvoiceEmail.
|
||||||
@ -31,6 +33,11 @@ class SendInvoiceEmail extends Job implements ShouldQueue
|
|||||||
*/
|
*/
|
||||||
protected $template;
|
protected $template;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $userId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new job instance.
|
* Create a new job instance.
|
||||||
*
|
*
|
||||||
@ -39,9 +46,10 @@ class SendInvoiceEmail extends Job implements ShouldQueue
|
|||||||
* @param bool $reminder
|
* @param bool $reminder
|
||||||
* @param mixed $pdfString
|
* @param mixed $pdfString
|
||||||
*/
|
*/
|
||||||
public function __construct(Invoice $invoice, $reminder = false, $template = false)
|
public function __construct(Invoice $invoice, $userId = false, $reminder = false, $template = false)
|
||||||
{
|
{
|
||||||
$this->invoice = $invoice;
|
$this->invoice = $invoice;
|
||||||
|
$this->userId = $userId;
|
||||||
$this->reminder = $reminder;
|
$this->reminder = $reminder;
|
||||||
$this->template = $template;
|
$this->template = $template;
|
||||||
}
|
}
|
||||||
@ -53,7 +61,16 @@ class SendInvoiceEmail extends Job implements ShouldQueue
|
|||||||
*/
|
*/
|
||||||
public function handle(ContactMailer $mailer)
|
public function handle(ContactMailer $mailer)
|
||||||
{
|
{
|
||||||
|
// send email as user
|
||||||
|
if (App::runningInConsole() && $this->userId) {
|
||||||
|
Auth::onceUsingId($this->userId);
|
||||||
|
}
|
||||||
|
|
||||||
$mailer->sendInvoice($this->invoice, $this->reminder, $this->template);
|
$mailer->sendInvoice($this->invoice, $this->reminder, $this->template);
|
||||||
|
|
||||||
|
if (App::runningInConsole() && $this->userId) {
|
||||||
|
Auth::logout();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -35,6 +35,11 @@ class SendNotificationEmail extends Job implements ShouldQueue
|
|||||||
*/
|
*/
|
||||||
protected $payment;
|
protected $payment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $notes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new job instance.
|
* Create a new job instance.
|
||||||
|
|
||||||
@ -46,12 +51,13 @@ class SendNotificationEmail extends Job implements ShouldQueue
|
|||||||
* @param mixed $type
|
* @param mixed $type
|
||||||
* @param mixed $payment
|
* @param mixed $payment
|
||||||
*/
|
*/
|
||||||
public function __construct($user, $invoice, $type, $payment)
|
public function __construct($user, $invoice, $type, $payment, $notes)
|
||||||
{
|
{
|
||||||
$this->user = $user;
|
$this->user = $user;
|
||||||
$this->invoice = $invoice;
|
$this->invoice = $invoice;
|
||||||
$this->type = $type;
|
$this->type = $type;
|
||||||
$this->payment = $payment;
|
$this->payment = $payment;
|
||||||
|
$this->notes = $notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,6 +67,6 @@ class SendNotificationEmail extends Job implements ShouldQueue
|
|||||||
*/
|
*/
|
||||||
public function handle(UserMailer $userMailer)
|
public function handle(UserMailer $userMailer)
|
||||||
{
|
{
|
||||||
$userMailer->sendNotification($this->user, $this->invoice, $this->type, $this->payment);
|
$userMailer->sendNotification($this->user, $this->invoice, $this->type, $this->payment, $this->notes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,7 +162,7 @@ class HistoryUtils
|
|||||||
$icon = '<i class="fa fa-users" style="width:32px"></i>';
|
$icon = '<i class="fa fa-users" style="width:32px"></i>';
|
||||||
if ($item->client_id) {
|
if ($item->client_id) {
|
||||||
$link = url('/clients/' . $item->client_id);
|
$link = url('/clients/' . $item->client_id);
|
||||||
$name = $item->client_name;
|
$name = e($item->client_name);
|
||||||
|
|
||||||
$buttonLink = url('/invoices/create/' . $item->client_id);
|
$buttonLink = url('/invoices/create/' . $item->client_id);
|
||||||
$button = '<a type="button" class="btn btn-primary btn-sm pull-right" href="' . $buttonLink . '">
|
$button = '<a type="button" class="btn btn-primary btn-sm pull-right" href="' . $buttonLink . '">
|
||||||
|
@ -969,10 +969,11 @@ class Utils
|
|||||||
return $str;
|
return $str;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getSubdomainPlaceholder()
|
public static function getSubdomain($url)
|
||||||
{
|
{
|
||||||
$parts = parse_url(SITE_URL);
|
$parts = parse_url($url);
|
||||||
$subdomain = '';
|
$subdomain = '';
|
||||||
|
|
||||||
if (isset($parts['host'])) {
|
if (isset($parts['host'])) {
|
||||||
$host = explode('.', $parts['host']);
|
$host = explode('.', $parts['host']);
|
||||||
if (count($host) > 2) {
|
if (count($host) > 2) {
|
||||||
@ -983,6 +984,11 @@ class Utils
|
|||||||
return $subdomain;
|
return $subdomain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getSubdomainPlaceholder()
|
||||||
|
{
|
||||||
|
return static::getSubdomain(SITE_URL);
|
||||||
|
}
|
||||||
|
|
||||||
public static function getDomainPlaceholder()
|
public static function getDomainPlaceholder()
|
||||||
{
|
{
|
||||||
$parts = parse_url(SITE_URL);
|
$parts = parse_url(SITE_URL);
|
||||||
@ -1234,4 +1240,13 @@ class Utils
|
|||||||
{
|
{
|
||||||
return strlen($string) > $length ? rtrim(substr($string, 0, $length)) . '...' : $string;
|
return strlen($string) > $length ? rtrim(substr($string, 0, $length)) . '...' : $string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// http://stackoverflow.com/a/14238078/497368
|
||||||
|
public static function isInterlaced($filename)
|
||||||
|
{
|
||||||
|
$handle = fopen($filename, 'r');
|
||||||
|
$contents = fread($handle, 32);
|
||||||
|
fclose($handle);
|
||||||
|
return( ord($contents[28]) != 0 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -392,7 +392,9 @@ class ActivityListener
|
|||||||
$event->payment,
|
$event->payment,
|
||||||
ACTIVITY_TYPE_CREATE_PAYMENT,
|
ACTIVITY_TYPE_CREATE_PAYMENT,
|
||||||
$event->payment->amount * -1,
|
$event->payment->amount * -1,
|
||||||
$event->payment->amount
|
$event->payment->amount,
|
||||||
|
false,
|
||||||
|
\App::runningInConsole() ? 'auto_billed' : ''
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,8 +56,10 @@ class HandleUserLoggedIn
|
|||||||
|
|
||||||
$account->loadLocalizationSettings();
|
$account->loadLocalizationSettings();
|
||||||
|
|
||||||
if (strpos($_SERVER['HTTP_USER_AGENT'], 'iPhone') || strpos($_SERVER['HTTP_USER_AGENT'], 'iPad')) {
|
if (strstr($_SERVER['HTTP_USER_AGENT'], 'iPhone') || strstr($_SERVER['HTTP_USER_AGENT'], 'iPad')) {
|
||||||
Session::flash('warning', trans('texts.iphone_app_message', ['link' => link_to(NINJA_IOS_APP_URL, trans('texts.iphone_app'))]));
|
Session::flash('warning', trans('texts.iphone_app_message', ['link' => link_to(NINJA_IOS_APP_URL, trans('texts.iphone_app'))]));
|
||||||
|
} elseif (strstr($_SERVER['HTTP_USER_AGENT'], 'Android')) {
|
||||||
|
Session::flash('warning', trans('texts.iphone_app_message', ['link' => link_to(NINJA_ANDROID_APP_URL, trans('texts.android_app'))]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// if they're using Stripe make sure they're using Stripe.js
|
// if they're using Stripe make sure they're using Stripe.js
|
||||||
|
@ -46,13 +46,13 @@ class NotificationListener
|
|||||||
* @param $type
|
* @param $type
|
||||||
* @param null $payment
|
* @param null $payment
|
||||||
*/
|
*/
|
||||||
private function sendEmails($invoice, $type, $payment = null)
|
private function sendEmails($invoice, $type, $payment = null, $notes = false)
|
||||||
{
|
{
|
||||||
foreach ($invoice->account->users as $user)
|
foreach ($invoice->account->users as $user)
|
||||||
{
|
{
|
||||||
if ($user->{"notify_{$type}"})
|
if ($user->{"notify_{$type}"})
|
||||||
{
|
{
|
||||||
$this->userMailer->sendNotification($user, $invoice, $type, $payment);
|
dispatch(new SendNotificationEmail($user, $invoice, $type, $payment, $notes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,7 +62,7 @@ class NotificationListener
|
|||||||
*/
|
*/
|
||||||
public function emailedInvoice(InvoiceWasEmailed $event)
|
public function emailedInvoice(InvoiceWasEmailed $event)
|
||||||
{
|
{
|
||||||
$this->sendEmails($event->invoice, 'sent');
|
$this->sendEmails($event->invoice, 'sent', null, $event->notes);
|
||||||
$this->pushService->sendNotification($event->invoice, 'sent');
|
$this->pushService->sendNotification($event->invoice, 'sent');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ class NotificationListener
|
|||||||
*/
|
*/
|
||||||
public function emailedQuote(QuoteWasEmailed $event)
|
public function emailedQuote(QuoteWasEmailed $event)
|
||||||
{
|
{
|
||||||
$this->sendEmails($event->quote, 'sent');
|
$this->sendEmails($event->quote, 'sent', null, $event->notes);
|
||||||
$this->pushService->sendNotification($event->quote, 'sent');
|
$this->pushService->sendNotification($event->quote, 'sent');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,13 +5,13 @@ namespace App\Listeners;
|
|||||||
use App\Events\ClientWasCreated;
|
use App\Events\ClientWasCreated;
|
||||||
use App\Events\CreditWasCreated;
|
use App\Events\CreditWasCreated;
|
||||||
use App\Events\ExpenseWasCreated;
|
use App\Events\ExpenseWasCreated;
|
||||||
use App\Events\InvoiceWasCreated;
|
use App\Events\QuoteItemsWereCreated;
|
||||||
|
use App\Events\QuoteItemsWereUpdated;
|
||||||
use App\Events\InvoiceWasDeleted;
|
use App\Events\InvoiceWasDeleted;
|
||||||
use App\Events\InvoiceWasUpdated;
|
|
||||||
use App\Events\PaymentWasCreated;
|
use App\Events\PaymentWasCreated;
|
||||||
use App\Events\QuoteWasCreated;
|
use App\Events\InvoiceItemsWereCreated;
|
||||||
|
use App\Events\InvoiceItemsWereUpdated;
|
||||||
use App\Events\QuoteWasDeleted;
|
use App\Events\QuoteWasDeleted;
|
||||||
use App\Events\QuoteWasUpdated;
|
|
||||||
use App\Events\VendorWasCreated;
|
use App\Events\VendorWasCreated;
|
||||||
use App\Models\EntityModel;
|
use App\Models\EntityModel;
|
||||||
use App\Ninja\Serializers\ArraySerializer;
|
use App\Ninja\Serializers\ArraySerializer;
|
||||||
@ -36,15 +36,6 @@ class SubscriptionListener
|
|||||||
$this->checkSubscriptions(EVENT_CREATE_CLIENT, $event->client, $transformer);
|
$this->checkSubscriptions(EVENT_CREATE_CLIENT, $event->client, $transformer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param QuoteWasCreated $event
|
|
||||||
*/
|
|
||||||
public function createdQuote(QuoteWasCreated $event)
|
|
||||||
{
|
|
||||||
$transformer = new InvoiceTransformer($event->quote->account);
|
|
||||||
$this->checkSubscriptions(EVENT_CREATE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param PaymentWasCreated $event
|
* @param PaymentWasCreated $event
|
||||||
*/
|
*/
|
||||||
@ -54,15 +45,6 @@ class SubscriptionListener
|
|||||||
$this->checkSubscriptions(EVENT_CREATE_PAYMENT, $event->payment, $transformer, [ENTITY_CLIENT, ENTITY_INVOICE]);
|
$this->checkSubscriptions(EVENT_CREATE_PAYMENT, $event->payment, $transformer, [ENTITY_CLIENT, ENTITY_INVOICE]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param InvoiceWasCreated $event
|
|
||||||
*/
|
|
||||||
public function createdInvoice(InvoiceWasCreated $event)
|
|
||||||
{
|
|
||||||
$transformer = new InvoiceTransformer($event->invoice->account);
|
|
||||||
$this->checkSubscriptions(EVENT_CREATE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CreditWasCreated $event
|
* @param CreditWasCreated $event
|
||||||
*/
|
*/
|
||||||
@ -84,15 +66,42 @@ class SubscriptionListener
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InvoiceWasCreated $event
|
||||||
|
*/
|
||||||
|
public function createdInvoice(InvoiceItemsWereCreated $event)
|
||||||
|
{
|
||||||
|
$transformer = new InvoiceTransformer($event->invoice->account);
|
||||||
|
$this->checkSubscriptions(EVENT_CREATE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param InvoiceWasUpdated $event
|
* @param InvoiceWasUpdated $event
|
||||||
*/
|
*/
|
||||||
public function updatedInvoice(InvoiceWasUpdated $event)
|
public function updatedInvoice(InvoiceItemsWereUpdated $event)
|
||||||
{
|
{
|
||||||
$transformer = new InvoiceTransformer($event->invoice->account);
|
$transformer = new InvoiceTransformer($event->invoice->account);
|
||||||
$this->checkSubscriptions(EVENT_UPDATE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT);
|
$this->checkSubscriptions(EVENT_UPDATE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param QuoteWasCreated $event
|
||||||
|
*/
|
||||||
|
public function createdQuote(QuoteItemsWereCreated $event)
|
||||||
|
{
|
||||||
|
$transformer = new InvoiceTransformer($event->quote->account);
|
||||||
|
$this->checkSubscriptions(EVENT_CREATE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param QuoteWasUpdated $event
|
||||||
|
*/
|
||||||
|
public function updatedQuote(QuoteItemsWereUpdated $event)
|
||||||
|
{
|
||||||
|
$transformer = new InvoiceTransformer($event->quote->account);
|
||||||
|
$this->checkSubscriptions(EVENT_UPDATE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param InvoiceWasDeleted $event
|
* @param InvoiceWasDeleted $event
|
||||||
*/
|
*/
|
||||||
@ -102,15 +111,6 @@ class SubscriptionListener
|
|||||||
$this->checkSubscriptions(EVENT_DELETE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT);
|
$this->checkSubscriptions(EVENT_DELETE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param QuoteWasUpdated $event
|
|
||||||
*/
|
|
||||||
public function updatedQuote(QuoteWasUpdated $event)
|
|
||||||
{
|
|
||||||
$transformer = new InvoiceTransformer($event->quote->account);
|
|
||||||
$this->checkSubscriptions(EVENT_UPDATE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param InvoiceWasDeleted $event
|
* @param InvoiceWasDeleted $event
|
||||||
*/
|
*/
|
||||||
|
@ -7,13 +7,13 @@ use App\Events\UserSettingsChanged;
|
|||||||
use App\Models\Traits\GeneratesNumbers;
|
use App\Models\Traits\GeneratesNumbers;
|
||||||
use App\Models\Traits\PresentsInvoice;
|
use App\Models\Traits\PresentsInvoice;
|
||||||
use App\Models\Traits\SendsEmails;
|
use App\Models\Traits\SendsEmails;
|
||||||
|
use App\Models\Traits\HasLogo;
|
||||||
use Cache;
|
use Cache;
|
||||||
use Carbon;
|
use Carbon;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use Eloquent;
|
use Eloquent;
|
||||||
use Event;
|
use Event;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Laracasts\Presenter\PresentableTrait;
|
use Laracasts\Presenter\PresentableTrait;
|
||||||
use Session;
|
use Session;
|
||||||
use Utils;
|
use Utils;
|
||||||
@ -28,6 +28,7 @@ class Account extends Eloquent
|
|||||||
use PresentsInvoice;
|
use PresentsInvoice;
|
||||||
use GeneratesNumbers;
|
use GeneratesNumbers;
|
||||||
use SendsEmails;
|
use SendsEmails;
|
||||||
|
use HasLogo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
@ -161,6 +162,8 @@ class Account extends Eloquent
|
|||||||
'payment_type_id',
|
'payment_type_id',
|
||||||
'gateway_fee_enabled',
|
'gateway_fee_enabled',
|
||||||
'reset_counter_date',
|
'reset_counter_date',
|
||||||
|
'custom_contact_label1',
|
||||||
|
'custom_contact_label2',
|
||||||
'domain_id',
|
'domain_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -370,6 +373,14 @@ class Account extends Eloquent
|
|||||||
return $this->belongsTo('App\Models\TaxRate');
|
return $this->belongsTo('App\Models\TaxRate');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
|
*/
|
||||||
|
public function payment_type()
|
||||||
|
{
|
||||||
|
return $this->belongsTo('App\Models\PaymentType');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
@ -826,101 +837,6 @@ class Account extends Eloquent
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function hasLogo()
|
|
||||||
{
|
|
||||||
return ! empty($this->logo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function getLogoDisk()
|
|
||||||
{
|
|
||||||
return Storage::disk(env('LOGO_FILESYSTEM', 'logos'));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function calculateLogoDetails()
|
|
||||||
{
|
|
||||||
$disk = $this->getLogoDisk();
|
|
||||||
|
|
||||||
if ($disk->exists($this->account_key.'.png')) {
|
|
||||||
$this->logo = $this->account_key.'.png';
|
|
||||||
} elseif ($disk->exists($this->account_key.'.jpg')) {
|
|
||||||
$this->logo = $this->account_key.'.jpg';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! empty($this->logo)) {
|
|
||||||
$image = imagecreatefromstring($disk->get($this->logo));
|
|
||||||
$this->logo_width = imagesx($image);
|
|
||||||
$this->logo_height = imagesy($image);
|
|
||||||
$this->logo_size = $disk->size($this->logo);
|
|
||||||
} else {
|
|
||||||
$this->logo = null;
|
|
||||||
}
|
|
||||||
$this->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return null
|
|
||||||
*/
|
|
||||||
public function getLogoRaw()
|
|
||||||
{
|
|
||||||
if (! $this->hasLogo()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$disk = $this->getLogoDisk();
|
|
||||||
|
|
||||||
return $disk->get($this->logo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param bool $cachebuster
|
|
||||||
*
|
|
||||||
* @return null|string
|
|
||||||
*/
|
|
||||||
public function getLogoURL($cachebuster = false)
|
|
||||||
{
|
|
||||||
if (! $this->hasLogo()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$disk = $this->getLogoDisk();
|
|
||||||
$adapter = $disk->getAdapter();
|
|
||||||
|
|
||||||
if ($adapter instanceof \League\Flysystem\Adapter\Local) {
|
|
||||||
// Stored locally
|
|
||||||
$logoUrl = url('/logo/' . $this->logo);
|
|
||||||
|
|
||||||
if ($cachebuster) {
|
|
||||||
$logoUrl .= '?no_cache='.time();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $logoUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Document::getDirectFileUrl($this->logo, $this->getLogoDisk());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLogoPath()
|
|
||||||
{
|
|
||||||
if (! $this->hasLogo()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$disk = $this->getLogoDisk();
|
|
||||||
$adapter = $disk->getAdapter();
|
|
||||||
|
|
||||||
if ($adapter instanceof \League\Flysystem\Adapter\Local) {
|
|
||||||
return $adapter->applyPathPrefix($this->logo);
|
|
||||||
} else {
|
|
||||||
return Document::getDirectFileUrl($this->logo, $this->getLogoDisk());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
@ -948,30 +864,6 @@ class Account extends Eloquent
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed|null
|
|
||||||
*/
|
|
||||||
public function getLogoWidth()
|
|
||||||
{
|
|
||||||
if (! $this->hasLogo()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->logo_width;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed|null
|
|
||||||
*/
|
|
||||||
public function getLogoHeight()
|
|
||||||
{
|
|
||||||
if (! $this->hasLogo()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->logo_height;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $entityType
|
* @param $entityType
|
||||||
* @param null $clientId
|
* @param null $clientId
|
||||||
@ -1338,26 +1230,6 @@ class Account extends Eloquent
|
|||||||
return Carbon::instance($date);
|
return Carbon::instance($date);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return float|null
|
|
||||||
*/
|
|
||||||
public function getLogoSize()
|
|
||||||
{
|
|
||||||
if (! $this->hasLogo()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return round($this->logo_size / 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isLogoTooLarge()
|
|
||||||
{
|
|
||||||
return $this->getLogoSize() > MAX_LOGO_FILE_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $eventId
|
* @param $eventId
|
||||||
*
|
*
|
||||||
@ -1776,6 +1648,11 @@ class Account extends Eloquent
|
|||||||
|
|
||||||
return $yearStart->format('Y-m-d');
|
return $yearStart->format('Y-m-d');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isClientPortalPasswordEnabled()
|
||||||
|
{
|
||||||
|
return $this->hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD) && $this->enable_portal_password;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Account::updated(function ($account) {
|
Account::updated(function ($account) {
|
||||||
|
@ -294,7 +294,7 @@ class Client extends EntityModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Utils::hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD) && $this->account->enable_portal_password) {
|
if ($this->account->isClientPortalPasswordEnabled()) {
|
||||||
if (! empty($data['password']) && $data['password'] != '-%unchanged%-') {
|
if (! empty($data['password']) && $data['password'] != '-%unchanged%-') {
|
||||||
$contact->password = bcrypt($data['password']);
|
$contact->password = bcrypt($data['password']);
|
||||||
} elseif (empty($data['password'])) {
|
} elseif (empty($data['password'])) {
|
||||||
|
@ -37,6 +37,8 @@ class Contact extends EntityModel implements AuthenticatableContract, CanResetPa
|
|||||||
'email',
|
'email',
|
||||||
'phone',
|
'phone',
|
||||||
'send_invoice',
|
'send_invoice',
|
||||||
|
'custom_value1',
|
||||||
|
'custom_value2',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,6 +47,10 @@ class Expense extends EntityModel
|
|||||||
'tax_name1',
|
'tax_name1',
|
||||||
'tax_rate2',
|
'tax_rate2',
|
||||||
'tax_name2',
|
'tax_name2',
|
||||||
|
'payment_date',
|
||||||
|
'payment_type_id',
|
||||||
|
'transaction_reference',
|
||||||
|
'invoice_documents',
|
||||||
];
|
];
|
||||||
|
|
||||||
public static function getImportColumns()
|
public static function getImportColumns()
|
||||||
@ -129,6 +133,14 @@ class Expense extends EntityModel
|
|||||||
return $this->hasMany('App\Models\Document')->orderBy('id');
|
return $this->hasMany('App\Models\Document')->orderBy('id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
|
*/
|
||||||
|
public function payment_type()
|
||||||
|
{
|
||||||
|
return $this->belongsTo('App\Models\PaymentType');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
@ -175,6 +187,14 @@ class Expense extends EntityModel
|
|||||||
return $this->invoice_currency_id != $this->expense_currency_id || $this->exchange_rate != 1;
|
return $this->invoice_currency_id != $this->expense_currency_id || $this->exchange_rate != 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isPaid()
|
||||||
|
{
|
||||||
|
return $this->payment_date || $this->payment_type_id;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
@ -221,19 +241,23 @@ class Expense extends EntityModel
|
|||||||
{
|
{
|
||||||
$statuses = [];
|
$statuses = [];
|
||||||
$statuses[EXPENSE_STATUS_LOGGED] = trans('texts.logged');
|
$statuses[EXPENSE_STATUS_LOGGED] = trans('texts.logged');
|
||||||
|
$statuses[EXPENSE_STATUS_PENDING] = trans('texts.pending');
|
||||||
$statuses[EXPENSE_STATUS_INVOICED] = trans('texts.invoiced');
|
$statuses[EXPENSE_STATUS_INVOICED] = trans('texts.invoiced');
|
||||||
|
$statuses[EXPENSE_STATUS_BILLED] = trans('texts.billed');
|
||||||
$statuses[EXPENSE_STATUS_PAID] = trans('texts.paid');
|
$statuses[EXPENSE_STATUS_PAID] = trans('texts.paid');
|
||||||
|
$statuses[EXPENSE_STATUS_UNPAID] = trans('texts.unpaid');
|
||||||
|
|
||||||
|
|
||||||
return $statuses;
|
return $statuses;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function calcStatusLabel($shouldBeInvoiced, $invoiceId, $balance)
|
public static function calcStatusLabel($shouldBeInvoiced, $invoiceId, $balance, $paymentDate)
|
||||||
{
|
{
|
||||||
if ($invoiceId) {
|
if ($invoiceId) {
|
||||||
if (floatval($balance) > 0) {
|
if (floatval($balance) > 0) {
|
||||||
$label = 'invoiced';
|
$label = 'invoiced';
|
||||||
} else {
|
} else {
|
||||||
$label = 'paid';
|
$label = 'billed';
|
||||||
}
|
}
|
||||||
} elseif ($shouldBeInvoiced) {
|
} elseif ($shouldBeInvoiced) {
|
||||||
$label = 'pending';
|
$label = 'pending';
|
||||||
@ -241,7 +265,13 @@ class Expense extends EntityModel
|
|||||||
$label = 'logged';
|
$label = 'logged';
|
||||||
}
|
}
|
||||||
|
|
||||||
return trans("texts.{$label}");
|
$label = trans("texts.{$label}");
|
||||||
|
|
||||||
|
if ($paymentDate) {
|
||||||
|
$label = trans('texts.paid') . ' | ' . $label;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $label;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function calcStatusClass($shouldBeInvoiced, $invoiceId, $balance)
|
public static function calcStatusClass($shouldBeInvoiced, $invoiceId, $balance)
|
||||||
@ -270,7 +300,7 @@ class Expense extends EntityModel
|
|||||||
{
|
{
|
||||||
$balance = $this->invoice ? $this->invoice->balance : 0;
|
$balance = $this->invoice ? $this->invoice->balance : 0;
|
||||||
|
|
||||||
return static::calcStatusLabel($this->should_be_invoiced, $this->invoice_id, $balance);
|
return static::calcStatusLabel($this->should_be_invoiced, $this->invoice_id, $balance, $this->payment_date);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ class Invitation extends EntityModel
|
|||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getLink($type = 'view', $forceOnsite = false)
|
public function getLink($type = 'view', $forceOnsite = false, $forcePlain = false)
|
||||||
{
|
{
|
||||||
if (! $this->account) {
|
if (! $this->account) {
|
||||||
$this->load('account');
|
$this->load('account');
|
||||||
@ -87,7 +87,7 @@ class Invitation extends EntityModel
|
|||||||
|
|
||||||
if ($iframe_url && ! $forceOnsite) {
|
if ($iframe_url && ! $forceOnsite) {
|
||||||
return "{$iframe_url}?{$this->invitation_key}";
|
return "{$iframe_url}?{$this->invitation_key}";
|
||||||
} elseif ($this->account->subdomain) {
|
} elseif ($this->account->subdomain && ! $forcePlain) {
|
||||||
$url = Utils::replaceSubdomain($url, $account->subdomain);
|
$url = Utils::replaceSubdomain($url, $account->subdomain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,54 +93,23 @@ class Invoice extends EntityModel implements BalanceAffecting
|
|||||||
INVOICE_STATUS_PAID => 'success',
|
INVOICE_STATUS_PAID => 'success',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public static $fieldInvoiceNumber = 'invoice_number';
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public static $fieldPONumber = 'po_number';
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public static $fieldInvoiceDate = 'invoice_date';
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public static $fieldDueDate = 'due_date';
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public static $fieldAmount = 'amount';
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public static $fieldPaid = 'paid';
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public static $fieldNotes = 'notes';
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public static $fieldTerms = 'terms';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function getImportColumns()
|
public static function getImportColumns()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Client::$fieldName,
|
'name',
|
||||||
self::$fieldInvoiceNumber,
|
'invoice_number',
|
||||||
self::$fieldPONumber,
|
'po_number',
|
||||||
self::$fieldInvoiceDate,
|
'invoice_date',
|
||||||
self::$fieldDueDate,
|
'due_date',
|
||||||
self::$fieldAmount,
|
'amount',
|
||||||
self::$fieldPaid,
|
'paid',
|
||||||
self::$fieldNotes,
|
'notes',
|
||||||
self::$fieldTerms,
|
'terms',
|
||||||
|
'product',
|
||||||
|
'quantity',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,6 +128,8 @@ class Invoice extends EntityModel implements BalanceAffecting
|
|||||||
'due date' => 'due_date',
|
'due date' => 'due_date',
|
||||||
'terms' => 'terms',
|
'terms' => 'terms',
|
||||||
'notes' => 'notes',
|
'notes' => 'notes',
|
||||||
|
'product|item' => 'product',
|
||||||
|
'quantity|qty' => 'quantity',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -930,6 +901,8 @@ class Invoice extends EntityModel implements BalanceAffecting
|
|||||||
'last_name',
|
'last_name',
|
||||||
'email',
|
'email',
|
||||||
'phone',
|
'phone',
|
||||||
|
'custom_value1',
|
||||||
|
'custom_value2',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1439,6 +1412,22 @@ class Invoice extends EntityModel implements BalanceAffecting
|
|||||||
$taxes[$key]['paid'] += $paid;
|
$taxes[$key]['paid'] += $paid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function countDocuments()
|
||||||
|
{
|
||||||
|
$count = count($this->documents);
|
||||||
|
|
||||||
|
foreach ($this->expenses as $expense) {
|
||||||
|
if ($expense->invoice_documents) {
|
||||||
|
$count += count($expense->documents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
@ -1457,7 +1446,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
|||||||
public function hasExpenseDocuments()
|
public function hasExpenseDocuments()
|
||||||
{
|
{
|
||||||
foreach ($this->expenses as $expense) {
|
foreach ($this->expenses as $expense) {
|
||||||
if (count($expense->documents)) {
|
if ($expense->invoice_documents && count($expense->documents)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,11 @@ class PaymentTerm extends EntityModel
|
|||||||
return ENTITY_PAYMENT_TERM;
|
return ENTITY_PAYMENT_TERM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getNumDays()
|
||||||
|
{
|
||||||
|
return $this->num_days == -1 ? 0 : $this->num_days;
|
||||||
|
}
|
||||||
|
|
||||||
public static function getSelectOptions()
|
public static function getSelectOptions()
|
||||||
{
|
{
|
||||||
$terms = Cache::get('paymentTerms');
|
$terms = Cache::get('paymentTerms');
|
||||||
@ -37,6 +42,10 @@ class PaymentTerm extends EntityModel
|
|||||||
$terms->push($term);
|
$terms->push($term);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($terms as $term) {
|
||||||
|
$term->name = trans('texts.payment_terms_net') . ' ' . $term->getNumDays();
|
||||||
|
}
|
||||||
|
|
||||||
return $terms->sortBy('num_days');
|
return $terms->sortBy('num_days');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
163
app/Models/Traits/HasLogo.php
Normal file
163
app/Models/Traits/HasLogo.php
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\Traits;
|
||||||
|
|
||||||
|
use Utils;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class HasLogo.
|
||||||
|
*/
|
||||||
|
trait HasLogo
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasLogo()
|
||||||
|
{
|
||||||
|
return ! empty($this->logo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getLogoDisk()
|
||||||
|
{
|
||||||
|
return Storage::disk(env('LOGO_FILESYSTEM', 'logos'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function calculateLogoDetails()
|
||||||
|
{
|
||||||
|
$disk = $this->getLogoDisk();
|
||||||
|
|
||||||
|
if ($disk->exists($this->account_key.'.png')) {
|
||||||
|
$this->logo = $this->account_key.'.png';
|
||||||
|
} elseif ($disk->exists($this->account_key.'.jpg')) {
|
||||||
|
$this->logo = $this->account_key.'.jpg';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! empty($this->logo)) {
|
||||||
|
$image = imagecreatefromstring($disk->get($this->logo));
|
||||||
|
$this->logo_width = imagesx($image);
|
||||||
|
$this->logo_height = imagesy($image);
|
||||||
|
$this->logo_size = $disk->size($this->logo);
|
||||||
|
} else {
|
||||||
|
$this->logo = null;
|
||||||
|
}
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function getLogoRaw()
|
||||||
|
{
|
||||||
|
if (! $this->hasLogo()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$disk = $this->getLogoDisk();
|
||||||
|
|
||||||
|
if (! $disk->exists($this->logo)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $disk->get($this->logo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool $cachebuster
|
||||||
|
*
|
||||||
|
* @return null|string
|
||||||
|
*/
|
||||||
|
public function getLogoURL($cachebuster = false)
|
||||||
|
{
|
||||||
|
if (! $this->hasLogo()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$disk = $this->getLogoDisk();
|
||||||
|
$adapter = $disk->getAdapter();
|
||||||
|
|
||||||
|
if ($adapter instanceof \League\Flysystem\Adapter\Local) {
|
||||||
|
// Stored locally
|
||||||
|
$logoUrl = url('/logo/' . $this->logo);
|
||||||
|
|
||||||
|
if ($cachebuster) {
|
||||||
|
$logoUrl .= '?no_cache='.time();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $logoUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Document::getDirectFileUrl($this->logo, $this->getLogoDisk());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLogoPath()
|
||||||
|
{
|
||||||
|
if (! $this->hasLogo()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$disk = $this->getLogoDisk();
|
||||||
|
$adapter = $disk->getAdapter();
|
||||||
|
|
||||||
|
if ($adapter instanceof \League\Flysystem\Adapter\Local) {
|
||||||
|
return $adapter->applyPathPrefix($this->logo);
|
||||||
|
} else {
|
||||||
|
return Document::getDirectFileUrl($this->logo, $this->getLogoDisk());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed|null
|
||||||
|
*/
|
||||||
|
public function getLogoWidth()
|
||||||
|
{
|
||||||
|
if (! $this->hasLogo()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->logo_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed|null
|
||||||
|
*/
|
||||||
|
public function getLogoHeight()
|
||||||
|
{
|
||||||
|
if (! $this->hasLogo()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->logo_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return float|null
|
||||||
|
*/
|
||||||
|
public function getLogoSize()
|
||||||
|
{
|
||||||
|
if (! $this->hasLogo()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return round($this->logo_size / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isLogoTooLarge()
|
||||||
|
{
|
||||||
|
return $this->getLogoSize() > MAX_LOGO_FILE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clearLogo()
|
||||||
|
{
|
||||||
|
$this->logo = '';
|
||||||
|
$this->logo_width = 0;
|
||||||
|
$this->logo_height = 0;
|
||||||
|
$this->logo_size = 0;
|
||||||
|
}
|
||||||
|
}
|
@ -68,6 +68,12 @@ trait PresentsInvoice
|
|||||||
if ($this->custom_client_label2) {
|
if ($this->custom_client_label2) {
|
||||||
$fields[INVOICE_FIELDS_CLIENT][] = 'client.custom_value2';
|
$fields[INVOICE_FIELDS_CLIENT][] = 'client.custom_value2';
|
||||||
}
|
}
|
||||||
|
if ($this->custom_contact_label1) {
|
||||||
|
$fields[INVOICE_FIELDS_CLIENT][] = 'contact.custom_value1';
|
||||||
|
}
|
||||||
|
if ($this->custom_contact_label2) {
|
||||||
|
$fields[INVOICE_FIELDS_CLIENT][] = 'contact.custom_value2';
|
||||||
|
}
|
||||||
if ($this->custom_label1) {
|
if ($this->custom_label1) {
|
||||||
$fields['account_fields2'][] = 'account.custom_value1';
|
$fields['account_fields2'][] = 'account.custom_value1';
|
||||||
}
|
}
|
||||||
@ -86,8 +92,10 @@ trait PresentsInvoice
|
|||||||
'invoice.po_number',
|
'invoice.po_number',
|
||||||
'invoice.invoice_date',
|
'invoice.invoice_date',
|
||||||
'invoice.due_date',
|
'invoice.due_date',
|
||||||
|
'invoice.invoice_total',
|
||||||
'invoice.balance_due',
|
'invoice.balance_due',
|
||||||
'invoice.partial_due',
|
'invoice.partial_due',
|
||||||
|
'invoice.outstanding',
|
||||||
'invoice.custom_text_value1',
|
'invoice.custom_text_value1',
|
||||||
'invoice.custom_text_value2',
|
'invoice.custom_text_value2',
|
||||||
'.blank',
|
'.blank',
|
||||||
@ -108,6 +116,8 @@ trait PresentsInvoice
|
|||||||
'client.phone',
|
'client.phone',
|
||||||
'client.custom_value1',
|
'client.custom_value1',
|
||||||
'client.custom_value2',
|
'client.custom_value2',
|
||||||
|
'contact.custom_value1',
|
||||||
|
'contact.custom_value2',
|
||||||
'.blank',
|
'.blank',
|
||||||
],
|
],
|
||||||
INVOICE_FIELDS_ACCOUNT => [
|
INVOICE_FIELDS_ACCOUNT => [
|
||||||
@ -227,6 +237,8 @@ trait PresentsInvoice
|
|||||||
'credit_to',
|
'credit_to',
|
||||||
'your_credit',
|
'your_credit',
|
||||||
'work_phone',
|
'work_phone',
|
||||||
|
'invoice_total',
|
||||||
|
'outstanding',
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($fields as $field) {
|
foreach ($fields as $field) {
|
||||||
@ -242,12 +254,14 @@ trait PresentsInvoice
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ([
|
foreach ([
|
||||||
|
'account.custom_value1' => 'custom_label1',
|
||||||
|
'account.custom_value2' => 'custom_label2',
|
||||||
'invoice.custom_text_value1' => 'custom_invoice_text_label1',
|
'invoice.custom_text_value1' => 'custom_invoice_text_label1',
|
||||||
'invoice.custom_text_value2' => 'custom_invoice_text_label2',
|
'invoice.custom_text_value2' => 'custom_invoice_text_label2',
|
||||||
'client.custom_value1' => 'custom_client_label1',
|
'client.custom_value1' => 'custom_client_label1',
|
||||||
'client.custom_value2' => 'custom_client_label2',
|
'client.custom_value2' => 'custom_client_label2',
|
||||||
'account.custom_value1' => 'custom_label1',
|
'contact.custom_value1' => 'custom_contact_label1',
|
||||||
'account.custom_value2' => 'custom_label2',
|
'contact.custom_value2' => 'custom_contact_label2',
|
||||||
] as $field => $property) {
|
] as $field => $property) {
|
||||||
$data[$field] = $this->$property ?: trans('texts.custom_field');
|
$data[$field] = $this->$property ?: trans('texts.custom_field');
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ trait SendsEmails
|
|||||||
$template .= "$message<p/>";
|
$template .= "$message<p/>";
|
||||||
}
|
}
|
||||||
|
|
||||||
return $template . '$footer';
|
return $template . '$emailSignature';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,7 +14,19 @@ class ActivityDatatable extends EntityDatatable
|
|||||||
[
|
[
|
||||||
'activities.id',
|
'activities.id',
|
||||||
function ($model) {
|
function ($model) {
|
||||||
return Utils::timestampToDateTimeString(strtotime($model->created_at));
|
$str = Utils::timestampToDateTimeString(strtotime($model->created_at));
|
||||||
|
|
||||||
|
if ($model->is_system && in_array($model->activity_type_id, [
|
||||||
|
ACTIVITY_TYPE_VIEW_INVOICE,
|
||||||
|
ACTIVITY_TYPE_VIEW_QUOTE,
|
||||||
|
ACTIVITY_TYPE_CREATE_PAYMENT,
|
||||||
|
ACTIVITY_TYPE_APPROVE_QUOTE,
|
||||||
|
])) {
|
||||||
|
$ipLookUpLink = IP_LOOKUP_URL . $model->ip;
|
||||||
|
$str .= sprintf(' <i class="fa fa-globe" style="cursor:pointer" title="%s" onclick="openUrl(\'%s\', \'IP Lookup\')"></i>', $model->ip, $ipLookUpLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $str;
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
@ -90,7 +90,7 @@ class ExpenseDatatable extends EntityDatatable
|
|||||||
[
|
[
|
||||||
'status',
|
'status',
|
||||||
function ($model) {
|
function ($model) {
|
||||||
return self::getStatusLabel($model->invoice_id, $model->should_be_invoiced, $model->balance);
|
return self::getStatusLabel($model->invoice_id, $model->should_be_invoiced, $model->balance, $model->payment_date);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
@ -129,9 +129,9 @@ class ExpenseDatatable extends EntityDatatable
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getStatusLabel($invoiceId, $shouldBeInvoiced, $balance)
|
private function getStatusLabel($invoiceId, $shouldBeInvoiced, $balance, $paymentDate)
|
||||||
{
|
{
|
||||||
$label = Expense::calcStatusLabel($shouldBeInvoiced, $invoiceId, $balance);
|
$label = Expense::calcStatusLabel($shouldBeInvoiced, $invoiceId, $balance, $paymentDate);
|
||||||
$class = Expense::calcStatusClass($shouldBeInvoiced, $invoiceId, $balance);
|
$class = Expense::calcStatusClass($shouldBeInvoiced, $invoiceId, $balance);
|
||||||
|
|
||||||
return "<h4><div class=\"label label-{$class}\">$label</div></h4>";
|
return "<h4><div class=\"label label-{$class}\">$label</div></h4>";
|
||||||
|
@ -52,7 +52,7 @@ class PaymentDatatable extends EntityDatatable
|
|||||||
[
|
[
|
||||||
'method',
|
'method',
|
||||||
function ($model) {
|
function ($model) {
|
||||||
return ($model->payment_type && ! $model->last4) ? $model->payment_type : ($model->account_gateway_id ? $model->gateway_name : '');
|
return ($model->payment_type && ! $model->last4) ? trans('texts.payment_type_' . $model->payment_type) : ($model->account_gateway_id ? $model->gateway_name : '');
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
@ -37,10 +37,10 @@ class InvoiceTransformer extends BaseTransformer
|
|||||||
'due_date_sql' => $this->getDate($data, 'due_date'),
|
'due_date_sql' => $this->getDate($data, 'due_date'),
|
||||||
'invoice_items' => [
|
'invoice_items' => [
|
||||||
[
|
[
|
||||||
'product_key' => '',
|
'product_key' => $this->getString($data, 'product'),
|
||||||
'notes' => $this->getString($data, 'notes'),
|
'notes' => $this->getString($data, 'notes'),
|
||||||
'cost' => $this->getFloat($data, 'amount'),
|
'cost' => $this->getFloat($data, 'amount'),
|
||||||
'qty' => 1,
|
'qty' => $this->getFloat($data, 'quantity') ?: 1,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -237,7 +237,6 @@ class BaseIntent
|
|||||||
foreach ($compositeEntity->children as $child) {
|
foreach ($compositeEntity->children as $child) {
|
||||||
if ($child->type == 'Field') {
|
if ($child->type == 'Field') {
|
||||||
$field = $child->value;
|
$field = $child->value;
|
||||||
;
|
|
||||||
} elseif ($child->type == 'Value') {
|
} elseif ($child->type == 'Value') {
|
||||||
$value = $child->value;
|
$value = $child->value;
|
||||||
}
|
}
|
||||||
|
@ -104,9 +104,9 @@ class ContactMailer extends Mailer
|
|||||||
|
|
||||||
if ($sent === true) {
|
if ($sent === true) {
|
||||||
if ($invoice->isType(INVOICE_TYPE_QUOTE)) {
|
if ($invoice->isType(INVOICE_TYPE_QUOTE)) {
|
||||||
event(new QuoteWasEmailed($invoice));
|
event(new QuoteWasEmailed($invoice, $reminder));
|
||||||
} else {
|
} else {
|
||||||
event(new InvoiceWasEmailed($invoice));
|
event(new InvoiceWasEmailed($invoice, $reminder));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ class ContactMailer extends Mailer
|
|||||||
$account = $invoice->account;
|
$account = $invoice->account;
|
||||||
$user = $invitation->user;
|
$user = $invitation->user;
|
||||||
|
|
||||||
if ($invitation->user->trashed()) {
|
if ($user->trashed()) {
|
||||||
$user = $account->users()->orderBy('id')->first();
|
$user = $account->users()->orderBy('id')->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +166,7 @@ class ContactMailer extends Mailer
|
|||||||
$variables['autobill'] = $invoice->present()->autoBillEmailMessage();
|
$variables['autobill'] = $invoice->present()->autoBillEmailMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($invitation->contact->password) && $account->hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD) && $account->enable_portal_password && $account->send_portal_password) {
|
if (empty($invitation->contact->password) && $account->isClientPortalPasswordEnabled() && $account->send_portal_password) {
|
||||||
// The contact needs a password
|
// The contact needs a password
|
||||||
$variables['password'] = $password = $this->generatePassword();
|
$variables['password'] = $password = $this->generatePassword();
|
||||||
$invitation->contact->password = bcrypt($password);
|
$invitation->contact->password = bcrypt($password);
|
||||||
@ -254,7 +254,7 @@ class ContactMailer extends Mailer
|
|||||||
$invitation = $payment->invitation;
|
$invitation = $payment->invitation;
|
||||||
} else {
|
} else {
|
||||||
$user = $payment->user;
|
$user = $payment->user;
|
||||||
$contact = $client->contacts[0];
|
$contact = count($client->contacts) ? $client->contacts[0] : '';
|
||||||
$invitation = $payment->invoice->invitations[0];
|
$invitation = $payment->invoice->invitations[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,8 @@ class UserMailer extends Mailer
|
|||||||
User $user,
|
User $user,
|
||||||
Invoice $invoice,
|
Invoice $invoice,
|
||||||
$notificationType,
|
$notificationType,
|
||||||
Payment $payment = null
|
Payment $payment = null,
|
||||||
|
$notes = false
|
||||||
) {
|
) {
|
||||||
if (! $user->email || $user->cannot('view', $invoice)) {
|
if (! $user->email || $user->cannot('view', $invoice)) {
|
||||||
return;
|
return;
|
||||||
@ -81,6 +82,10 @@ class UserMailer extends Mailer
|
|||||||
'client' => $client->getDisplayName(),
|
'client' => $client->getDisplayName(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($notes) {
|
||||||
|
$subject .= ' [' . trans('texts.notes_' . $notes) . ']';
|
||||||
|
}
|
||||||
|
|
||||||
$this->sendTo($user->email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
|
$this->sendTo($user->email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,6 +155,8 @@ class AccountPresenter extends Presenter
|
|||||||
$fields = [
|
$fields = [
|
||||||
'custom_client_label1' => 'custom_client1',
|
'custom_client_label1' => 'custom_client1',
|
||||||
'custom_client_label2' => 'custom_client2',
|
'custom_client_label2' => 'custom_client2',
|
||||||
|
'custom_contact_label1' => 'custom_contact1',
|
||||||
|
'custom_contact_label2' => 'custom_contact2',
|
||||||
'custom_invoice_text_label1' => 'custom_invoice1',
|
'custom_invoice_text_label1' => 'custom_invoice1',
|
||||||
'custom_invoice_text_label2' => 'custom_invoice2',
|
'custom_invoice_text_label2' => 'custom_invoice2',
|
||||||
'custom_invoice_item_label1' => 'custom_product1',
|
'custom_invoice_item_label1' => 'custom_product1',
|
||||||
|
@ -26,6 +26,14 @@ class ExpensePresenter extends EntityPresenter
|
|||||||
return Utils::fromSqlDate($this->entity->expense_date);
|
return Utils::fromSqlDate($this->entity->expense_date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \DateTime|string
|
||||||
|
*/
|
||||||
|
public function payment_date()
|
||||||
|
{
|
||||||
|
return Utils::fromSqlDate($this->entity->payment_date);
|
||||||
|
}
|
||||||
|
|
||||||
public function month()
|
public function month()
|
||||||
{
|
{
|
||||||
return Carbon::parse($this->entity->payment_date)->format('Y m');
|
return Carbon::parse($this->entity->payment_date)->format('Y m');
|
||||||
|
@ -80,4 +80,39 @@ class AbstractReport
|
|||||||
|
|
||||||
return $str;
|
return $str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convert the date format to one supported by tablesorter
|
||||||
|
public function convertDateFormat()
|
||||||
|
{
|
||||||
|
$account = Auth::user()->account;
|
||||||
|
$format = $account->getMomentDateFormat();
|
||||||
|
$format = strtolower($format);
|
||||||
|
$format = str_replace('do', '', $format);
|
||||||
|
|
||||||
|
$orignalFormat = $format;
|
||||||
|
$format = preg_replace("/[^mdy]/", '', $format);
|
||||||
|
|
||||||
|
$lastLetter = false;
|
||||||
|
$reportParts = [];
|
||||||
|
$phpParts = [];
|
||||||
|
|
||||||
|
foreach (str_split($format) as $letter) {
|
||||||
|
if ($lastLetter && $letter == $lastLetter) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$lastLetter = $letter;
|
||||||
|
if ($letter == 'm') {
|
||||||
|
$reportParts[] = 'mm';
|
||||||
|
$phpParts[] = 'm';
|
||||||
|
} elseif ($letter == 'd') {
|
||||||
|
$reportParts[] = 'dd';
|
||||||
|
$phpParts[] = 'd';
|
||||||
|
} elseif ($letter == 'y') {
|
||||||
|
$reportParts[] = 'yyyy';
|
||||||
|
$phpParts[] = 'Y';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return join('', $reportParts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -356,6 +356,9 @@ class AccountRepository
|
|||||||
$account->company_id = $company->id;
|
$account->company_id = $company->id;
|
||||||
$account->save();
|
$account->save();
|
||||||
|
|
||||||
|
$emailSettings = new AccountEmailSettings();
|
||||||
|
$account->account_email_settings()->save($emailSettings);
|
||||||
|
|
||||||
$random = strtolower(str_random(RANDOM_KEY_LENGTH));
|
$random = strtolower(str_random(RANDOM_KEY_LENGTH));
|
||||||
$user = new User();
|
$user = new User();
|
||||||
$user->registered = true;
|
$user->registered = true;
|
||||||
|
@ -90,6 +90,8 @@ class ActivityRepository
|
|||||||
'activities.balance',
|
'activities.balance',
|
||||||
'activities.adjustment',
|
'activities.adjustment',
|
||||||
'activities.notes',
|
'activities.notes',
|
||||||
|
'activities.ip',
|
||||||
|
'activities.is_system',
|
||||||
'users.first_name as user_first_name',
|
'users.first_name as user_first_name',
|
||||||
'users.last_name as user_last_name',
|
'users.last_name as user_last_name',
|
||||||
'users.email as user_email',
|
'users.email as user_email',
|
||||||
|
@ -60,6 +60,7 @@ class ClientRepository extends BaseRepository
|
|||||||
if ($filter) {
|
if ($filter) {
|
||||||
$query->where(function ($query) use ($filter) {
|
$query->where(function ($query) use ($filter) {
|
||||||
$query->where('clients.name', 'like', '%'.$filter.'%')
|
$query->where('clients.name', 'like', '%'.$filter.'%')
|
||||||
|
->orWhere('clients.id_number', 'like', '%'.$filter.'%')
|
||||||
->orWhere('contacts.first_name', 'like', '%'.$filter.'%')
|
->orWhere('contacts.first_name', 'like', '%'.$filter.'%')
|
||||||
->orWhere('contacts.last_name', 'like', '%'.$filter.'%')
|
->orWhere('contacts.last_name', 'like', '%'.$filter.'%')
|
||||||
->orWhere('contacts.email', 'like', '%'.$filter.'%');
|
->orWhere('contacts.email', 'like', '%'.$filter.'%');
|
||||||
|
@ -81,6 +81,7 @@ class ExpenseRepository extends BaseRepository
|
|||||||
'expenses.user_id',
|
'expenses.user_id',
|
||||||
'expenses.tax_rate1',
|
'expenses.tax_rate1',
|
||||||
'expenses.tax_rate2',
|
'expenses.tax_rate2',
|
||||||
|
'expenses.payment_date',
|
||||||
'expense_categories.name as category',
|
'expense_categories.name as category',
|
||||||
'expense_categories.user_id as category_user_id',
|
'expense_categories.user_id as category_user_id',
|
||||||
'expense_categories.public_id as category_public_id',
|
'expense_categories.public_id as category_public_id',
|
||||||
@ -112,14 +113,24 @@ class ExpenseRepository extends BaseRepository
|
|||||||
}
|
}
|
||||||
if (in_array(EXPENSE_STATUS_INVOICED, $statuses)) {
|
if (in_array(EXPENSE_STATUS_INVOICED, $statuses)) {
|
||||||
$query->orWhere('expenses.invoice_id', '>', 0);
|
$query->orWhere('expenses.invoice_id', '>', 0);
|
||||||
if (! in_array(EXPENSE_STATUS_PAID, $statuses)) {
|
if (! in_array(EXPENSE_STATUS_BILLED, $statuses)) {
|
||||||
$query->where('invoices.balance', '>', 0);
|
$query->where('invoices.balance', '>', 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (in_array(EXPENSE_STATUS_PAID, $statuses)) {
|
if (in_array(EXPENSE_STATUS_BILLED, $statuses)) {
|
||||||
$query->orWhere('invoices.balance', '=', 0)
|
$query->orWhere('invoices.balance', '=', 0)
|
||||||
->where('expenses.invoice_id', '>', 0);
|
->where('expenses.invoice_id', '>', 0);
|
||||||
}
|
}
|
||||||
|
if (in_array(EXPENSE_STATUS_PAID, $statuses)) {
|
||||||
|
$query->orWhereNotNull('expenses.payment_date');
|
||||||
|
}
|
||||||
|
if (in_array(EXPENSE_STATUS_UNPAID, $statuses)) {
|
||||||
|
$query->orWhereNull('expenses.payment_date');
|
||||||
|
}
|
||||||
|
if (in_array(EXPENSE_STATUS_PENDING, $statuses)) {
|
||||||
|
$query->orWhere('expenses.should_be_invoiced', '=', 1)
|
||||||
|
->whereNull('expenses.invoice_id');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,6 +172,9 @@ class ExpenseRepository extends BaseRepository
|
|||||||
if (isset($input['expense_date'])) {
|
if (isset($input['expense_date'])) {
|
||||||
$expense->expense_date = Utils::toSqlDate($input['expense_date']);
|
$expense->expense_date = Utils::toSqlDate($input['expense_date']);
|
||||||
}
|
}
|
||||||
|
if (isset($input['payment_date'])) {
|
||||||
|
$expense->payment_date = Utils::toSqlDate($input['payment_date']);
|
||||||
|
}
|
||||||
|
|
||||||
$expense->should_be_invoiced = isset($input['should_be_invoiced']) && floatval($input['should_be_invoiced']) || $expense->client_id ? true : false;
|
$expense->should_be_invoiced = isset($input['should_be_invoiced']) && floatval($input['should_be_invoiced']) || $expense->client_id ? true : false;
|
||||||
|
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Ninja\Repositories;
|
namespace App\Ninja\Repositories;
|
||||||
|
|
||||||
use App\Events\InvoiceWasCreated;
|
use App\Events\QuoteItemsWereCreated;
|
||||||
use App\Events\InvoiceWasUpdated;
|
use App\Events\QuoteItemsWereUpdated;
|
||||||
use App\Events\QuoteWasCreated;
|
use App\Events\InvoiceItemsWereCreated;
|
||||||
use App\Events\QuoteWasUpdated;
|
use App\Events\InvoiceItemsWereUpdated;
|
||||||
use App\Jobs\SendInvoiceEmail;
|
use App\Jobs\SendInvoiceEmail;
|
||||||
use App\Models\Account;
|
use App\Models\Account;
|
||||||
use App\Models\Client;
|
use App\Models\Client;
|
||||||
@ -693,11 +693,13 @@ class InvoiceRepository extends BaseRepository
|
|||||||
$invoice->invoice_items()->save($invoiceItem);
|
$invoice->invoice_items()->save($invoiceItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$invoice->load('invoice_items');
|
||||||
|
|
||||||
if (Auth::check()) {
|
if (Auth::check()) {
|
||||||
$invoice = $this->saveInvitations($invoice);
|
$invoice = $this->saveInvitations($invoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
//$this->dispachEvents($invoice);
|
$this->dispatchEvents($invoice);
|
||||||
|
|
||||||
return $invoice;
|
return $invoice;
|
||||||
}
|
}
|
||||||
@ -708,6 +710,10 @@ class InvoiceRepository extends BaseRepository
|
|||||||
$client->load('contacts');
|
$client->load('contacts');
|
||||||
$sendInvoiceIds = [];
|
$sendInvoiceIds = [];
|
||||||
|
|
||||||
|
if (! count($client->contacts)) {
|
||||||
|
return $invoice;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($client->contacts as $contact) {
|
foreach ($client->contacts as $contact) {
|
||||||
if ($contact->send_invoice) {
|
if ($contact->send_invoice) {
|
||||||
$sendInvoiceIds[] = $contact->id;
|
$sendInvoiceIds[] = $contact->id;
|
||||||
@ -740,19 +746,19 @@ class InvoiceRepository extends BaseRepository
|
|||||||
return $invoice;
|
return $invoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function dispachEvents($invoice)
|
private function dispatchEvents($invoice)
|
||||||
{
|
{
|
||||||
if ($invoice->isType(INVOICE_TYPE_QUOTE)) {
|
if ($invoice->isType(INVOICE_TYPE_QUOTE)) {
|
||||||
if ($invoice->wasRecentlyCreated) {
|
if ($invoice->wasRecentlyCreated) {
|
||||||
event(new QuoteWasCreated($invoice));
|
event(new QuoteItemsWereCreated($invoice));
|
||||||
} else {
|
} else {
|
||||||
event(new QuoteWasUpdated($invoice));
|
event(new QuoteItemsWereUpdated($invoice));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ($invoice->wasRecentlyCreated) {
|
if ($invoice->wasRecentlyCreated) {
|
||||||
event(new InvoiceWasCreated($invoice));
|
event(new InvoiceItemsWereCreated($invoice));
|
||||||
} else {
|
} else {
|
||||||
event(new InvoiceWasUpdated($invoice));
|
event(new InvoiceItemsWereUpdated($invoice));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1110,7 +1116,6 @@ class InvoiceRepository extends BaseRepository
|
|||||||
if ($item['invoice_item_type_id'] == INVOICE_ITEM_TYPE_PENDING_GATEWAY_FEE) {
|
if ($item['invoice_item_type_id'] == INVOICE_ITEM_TYPE_PENDING_GATEWAY_FEE) {
|
||||||
unset($data['invoice_items'][$key]);
|
unset($data['invoice_items'][$key]);
|
||||||
$this->save($data, $invoice);
|
$this->save($data, $invoice);
|
||||||
$invoice->load('invoice_items');
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1147,7 +1152,6 @@ class InvoiceRepository extends BaseRepository
|
|||||||
$data['invoice_items'][] = $item;
|
$data['invoice_items'][] = $item;
|
||||||
|
|
||||||
$this->save($data, $invoice);
|
$this->save($data, $invoice);
|
||||||
$invoice->load('invoice_items');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findPhonetically($invoiceNumber)
|
public function findPhonetically($invoiceNumber)
|
||||||
|
@ -7,6 +7,7 @@ use App\Models\Project;
|
|||||||
use App\Models\Task;
|
use App\Models\Task;
|
||||||
use Auth;
|
use Auth;
|
||||||
use Session;
|
use Session;
|
||||||
|
use DB;
|
||||||
|
|
||||||
class TaskRepository extends BaseRepository
|
class TaskRepository extends BaseRepository
|
||||||
{
|
{
|
||||||
@ -17,7 +18,7 @@ class TaskRepository extends BaseRepository
|
|||||||
|
|
||||||
public function find($clientPublicId = null, $filter = null)
|
public function find($clientPublicId = null, $filter = null)
|
||||||
{
|
{
|
||||||
$query = \DB::table('tasks')
|
$query = DB::table('tasks')
|
||||||
->leftJoin('clients', 'tasks.client_id', '=', 'clients.id')
|
->leftJoin('clients', 'tasks.client_id', '=', 'clients.id')
|
||||||
->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
|
->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||||
->leftJoin('invoices', 'invoices.id', '=', 'tasks.invoice_id')
|
->leftJoin('invoices', 'invoices.id', '=', 'tasks.invoice_id')
|
||||||
@ -30,7 +31,7 @@ class TaskRepository extends BaseRepository
|
|||||||
->where('contacts.deleted_at', '=', null)
|
->where('contacts.deleted_at', '=', null)
|
||||||
->select(
|
->select(
|
||||||
'tasks.public_id',
|
'tasks.public_id',
|
||||||
\DB::raw("COALESCE(NULLIF(clients.name,''), NULLIF(CONCAT(contacts.first_name, ' ', contacts.last_name),''), NULLIF(contacts.email,'')) client_name"),
|
DB::raw("COALESCE(NULLIF(clients.name,''), NULLIF(CONCAT(contacts.first_name, ' ', contacts.last_name),''), NULLIF(contacts.email,'')) client_name"),
|
||||||
'clients.public_id as client_public_id',
|
'clients.public_id as client_public_id',
|
||||||
'clients.user_id as client_user_id',
|
'clients.user_id as client_user_id',
|
||||||
'contacts.first_name',
|
'contacts.first_name',
|
||||||
@ -49,7 +50,7 @@ class TaskRepository extends BaseRepository
|
|||||||
'tasks.time_log',
|
'tasks.time_log',
|
||||||
'tasks.time_log as duration',
|
'tasks.time_log as duration',
|
||||||
'tasks.created_at',
|
'tasks.created_at',
|
||||||
'tasks.created_at as date',
|
DB::raw("SUBSTRING(time_log, 3, 10) date"),
|
||||||
'tasks.user_id',
|
'tasks.user_id',
|
||||||
'projects.name as project',
|
'projects.name as project',
|
||||||
'projects.public_id as project_public_id',
|
'projects.public_id as project_public_id',
|
||||||
|
@ -213,7 +213,7 @@ class AccountTransformer extends EntityTransformer
|
|||||||
'num_days_reminder3' => $account->num_days_reminder3,
|
'num_days_reminder3' => $account->num_days_reminder3,
|
||||||
'custom_invoice_text_label1' => $account->custom_invoice_text_label1,
|
'custom_invoice_text_label1' => $account->custom_invoice_text_label1,
|
||||||
'custom_invoice_text_label2' => $account->custom_invoice_text_label2,
|
'custom_invoice_text_label2' => $account->custom_invoice_text_label2,
|
||||||
'default_tax_rate_id' => $account->default_tax_rate_id,
|
'default_tax_rate_id' => $account->default_tax_rate_id ? $account->default_tax_rate->public_id : 0,
|
||||||
'recurring_hour' => $account->recurring_hour,
|
'recurring_hour' => $account->recurring_hour,
|
||||||
'invoice_number_pattern' => $account->invoice_number_pattern,
|
'invoice_number_pattern' => $account->invoice_number_pattern,
|
||||||
'quote_number_pattern' => $account->quote_number_pattern,
|
'quote_number_pattern' => $account->quote_number_pattern,
|
||||||
@ -266,6 +266,8 @@ class AccountTransformer extends EntityTransformer
|
|||||||
'payment_type_id' => (int) $account->payment_type_id,
|
'payment_type_id' => (int) $account->payment_type_id,
|
||||||
'gateway_fee_enabled' => (bool) $account->gateway_fee_enabled,
|
'gateway_fee_enabled' => (bool) $account->gateway_fee_enabled,
|
||||||
'reset_counter_date' => $account->reset_counter_date,
|
'reset_counter_date' => $account->reset_counter_date,
|
||||||
|
'custom_contact_label1' => $account->custom_contact_label1,
|
||||||
|
'custom_contact_label2' => $account->custom_contact_label2,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,8 @@ class ContactTransformer extends EntityTransformer
|
|||||||
* @SWG\Property(property="phone", type="string", example="(212) 555-1212")
|
* @SWG\Property(property="phone", type="string", example="(212) 555-1212")
|
||||||
* @SWG\Property(property="last_login", type="string", format="date-time", example="2016-01-01 12:10:00")
|
* @SWG\Property(property="last_login", type="string", format="date-time", example="2016-01-01 12:10:00")
|
||||||
* @SWG\Property(property="send_invoice", type="boolean", example=false)
|
* @SWG\Property(property="send_invoice", type="boolean", example=false)
|
||||||
|
* @SWG\Property(property="custom_value1", type="string", example="Value")
|
||||||
|
* @SWG\Property(property="custom_value2", type="string", example="Value")
|
||||||
*/
|
*/
|
||||||
public function transform(Contact $contact)
|
public function transform(Contact $contact)
|
||||||
{
|
{
|
||||||
@ -40,6 +42,8 @@ class ContactTransformer extends EntityTransformer
|
|||||||
'phone' => $contact->phone,
|
'phone' => $contact->phone,
|
||||||
'last_login' => $contact->last_login,
|
'last_login' => $contact->last_login,
|
||||||
'send_invoice' => (bool) $contact->send_invoice,
|
'send_invoice' => (bool) $contact->send_invoice,
|
||||||
|
'custom_value1' => $contact->custom_value1,
|
||||||
|
'custom_value2' => $contact->custom_value2,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ class ProductTransformer extends EntityTransformer
|
|||||||
'notes' => $product->notes,
|
'notes' => $product->notes,
|
||||||
'cost' => $product->cost,
|
'cost' => $product->cost,
|
||||||
'qty' => $product->qty,
|
'qty' => $product->qty,
|
||||||
'default_tax_rate_id' => $product->default_tax_rate_id,
|
'default_tax_rate_id' => $product->default_tax_rate_id ? $product->default_tax_rate->public_id : 0,
|
||||||
'updated_at' => $this->getTimestamp($product->updated_at),
|
'updated_at' => $this->getTimestamp($product->updated_at),
|
||||||
'archived_at' => $this->getTimestamp($product->deleted_at),
|
'archived_at' => $this->getTimestamp($product->deleted_at),
|
||||||
]);
|
]);
|
||||||
|
@ -28,7 +28,7 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
$contents = $image;
|
$contents = $image;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'data:image/jpeg;base64,' . base64_encode($contents);
|
return $contents ? 'data:image/jpeg;base64,' . base64_encode($contents) : '';
|
||||||
});
|
});
|
||||||
|
|
||||||
Form::macro('nav_link', function ($url, $text) {
|
Form::macro('nav_link', function ($url, $text) {
|
||||||
|
@ -17,9 +17,12 @@ class ComposerServiceProvider extends ServiceProvider
|
|||||||
[
|
[
|
||||||
'accounts.details',
|
'accounts.details',
|
||||||
'clients.edit',
|
'clients.edit',
|
||||||
|
'vendors.edit',
|
||||||
'payments.edit',
|
'payments.edit',
|
||||||
'invoices.edit',
|
'invoices.edit',
|
||||||
|
'expenses.edit',
|
||||||
'accounts.localization',
|
'accounts.localization',
|
||||||
|
'payments.credit_card',
|
||||||
],
|
],
|
||||||
'App\Http\ViewComposers\TranslationComposer'
|
'App\Http\ViewComposers\TranslationComposer'
|
||||||
);
|
);
|
||||||
|
@ -32,12 +32,16 @@ class EventServiceProvider extends ServiceProvider
|
|||||||
// Invoices
|
// Invoices
|
||||||
'App\Events\InvoiceWasCreated' => [
|
'App\Events\InvoiceWasCreated' => [
|
||||||
'App\Listeners\ActivityListener@createdInvoice',
|
'App\Listeners\ActivityListener@createdInvoice',
|
||||||
'App\Listeners\SubscriptionListener@createdInvoice',
|
|
||||||
'App\Listeners\InvoiceListener@createdInvoice',
|
'App\Listeners\InvoiceListener@createdInvoice',
|
||||||
],
|
],
|
||||||
'App\Events\InvoiceWasUpdated' => [
|
'App\Events\InvoiceWasUpdated' => [
|
||||||
'App\Listeners\ActivityListener@updatedInvoice',
|
'App\Listeners\ActivityListener@updatedInvoice',
|
||||||
'App\Listeners\InvoiceListener@updatedInvoice',
|
'App\Listeners\InvoiceListener@updatedInvoice',
|
||||||
|
],
|
||||||
|
'App\Events\InvoiceItemsWereCreated' => [
|
||||||
|
'App\Listeners\SubscriptionListener@createdInvoice',
|
||||||
|
],
|
||||||
|
'App\Events\InvoiceItemsWereUpdated' => [
|
||||||
'App\Listeners\SubscriptionListener@updatedInvoice',
|
'App\Listeners\SubscriptionListener@updatedInvoice',
|
||||||
],
|
],
|
||||||
'App\Events\InvoiceWasArchived' => [
|
'App\Events\InvoiceWasArchived' => [
|
||||||
@ -66,10 +70,14 @@ class EventServiceProvider extends ServiceProvider
|
|||||||
// Quotes
|
// Quotes
|
||||||
'App\Events\QuoteWasCreated' => [
|
'App\Events\QuoteWasCreated' => [
|
||||||
'App\Listeners\ActivityListener@createdQuote',
|
'App\Listeners\ActivityListener@createdQuote',
|
||||||
'App\Listeners\SubscriptionListener@createdQuote',
|
|
||||||
],
|
],
|
||||||
'App\Events\QuoteWasUpdated' => [
|
'App\Events\QuoteWasUpdated' => [
|
||||||
'App\Listeners\ActivityListener@updatedQuote',
|
'App\Listeners\ActivityListener@updatedQuote',
|
||||||
|
],
|
||||||
|
'App\Events\QuoteItemsWereCreated' => [
|
||||||
|
'App\Listeners\SubscriptionListener@createdQuote',
|
||||||
|
],
|
||||||
|
'App\Events\QuoteItemsWereUpdated' => [
|
||||||
'App\Listeners\SubscriptionListener@updatedQuote',
|
'App\Listeners\SubscriptionListener@updatedQuote',
|
||||||
],
|
],
|
||||||
'App\Events\QuoteWasArchived' => [
|
'App\Events\QuoteWasArchived' => [
|
||||||
|
@ -24,6 +24,7 @@ use Auth;
|
|||||||
use Cache;
|
use Cache;
|
||||||
use Excel;
|
use Excel;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use File;
|
||||||
use League\Fractal\Manager;
|
use League\Fractal\Manager;
|
||||||
use parsecsv;
|
use parsecsv;
|
||||||
use Session;
|
use Session;
|
||||||
@ -145,10 +146,9 @@ class ImportService
|
|||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function importJSON($file, $includeData, $includeSettings)
|
public function importJSON($fileName, $includeData, $includeSettings)
|
||||||
{
|
{
|
||||||
$this->initMaps();
|
$this->initMaps();
|
||||||
$fileName = storage_path() . '/import/' . $file;
|
|
||||||
$this->checkForFile($fileName);
|
$this->checkForFile($fileName);
|
||||||
$file = file_get_contents($fileName);
|
$file = file_get_contents($fileName);
|
||||||
$json = json_decode($file, true);
|
$json = json_decode($file, true);
|
||||||
@ -229,7 +229,7 @@ class ImportService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@unlink($fileName);
|
File::delete($fileName);
|
||||||
|
|
||||||
return $this->results;
|
return $this->results;
|
||||||
}
|
}
|
||||||
@ -278,7 +278,7 @@ class ImportService
|
|||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function execute($source, $entityType, $file)
|
private function execute($source, $entityType, $fileName)
|
||||||
{
|
{
|
||||||
$results = [
|
$results = [
|
||||||
RESULT_SUCCESS => [],
|
RESULT_SUCCESS => [],
|
||||||
@ -287,7 +287,6 @@ class ImportService
|
|||||||
|
|
||||||
// Convert the data
|
// Convert the data
|
||||||
$row_list = [];
|
$row_list = [];
|
||||||
$fileName = storage_path() . '/import/' . $file;
|
|
||||||
$this->checkForFile($fileName);
|
$this->checkForFile($fileName);
|
||||||
|
|
||||||
Excel::load($fileName, function ($reader) use ($source, $entityType, &$row_list, &$results) {
|
Excel::load($fileName, function ($reader) use ($source, $entityType, &$row_list, &$results) {
|
||||||
@ -321,7 +320,7 @@ class ImportService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@unlink($fileName);
|
File::delete($fileName);
|
||||||
|
|
||||||
return $results;
|
return $results;
|
||||||
}
|
}
|
||||||
@ -590,7 +589,6 @@ class ImportService
|
|||||||
{
|
{
|
||||||
require_once app_path().'/Includes/parsecsv.lib.php';
|
require_once app_path().'/Includes/parsecsv.lib.php';
|
||||||
|
|
||||||
$fileName = storage_path() . '/import/' . $fileName;
|
|
||||||
$this->checkForFile($fileName);
|
$this->checkForFile($fileName);
|
||||||
|
|
||||||
$csv = new parseCSV();
|
$csv = new parseCSV();
|
||||||
@ -686,7 +684,8 @@ class ImportService
|
|||||||
];
|
];
|
||||||
$source = IMPORT_CSV;
|
$source = IMPORT_CSV;
|
||||||
|
|
||||||
$fileName = sprintf('%s_%s_%s.csv', Auth::user()->account_id, $timestamp, $entityType);
|
$path = env('FILE_IMPORT_PATH') ?: storage_path() . '/import';
|
||||||
|
$fileName = sprintf('%s/%s_%s_%s.csv', $path, Auth::user()->account_id, $timestamp, $entityType);
|
||||||
$data = $this->getCsvData($fileName);
|
$data = $this->getCsvData($fileName);
|
||||||
$this->checkData($entityType, count($data));
|
$this->checkData($entityType, count($data));
|
||||||
$this->initMaps();
|
$this->initMaps();
|
||||||
@ -726,7 +725,7 @@ class ImportService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@unlink(storage_path() . '/import/' . $fileName);
|
File::delete($fileName);
|
||||||
|
|
||||||
return $results;
|
return $results;
|
||||||
}
|
}
|
||||||
@ -868,7 +867,7 @@ class ImportService
|
|||||||
$this->maps['client'][$name] = $client->id;
|
$this->maps['client'][$name] = $client->id;
|
||||||
$this->maps['client_ids'][$client->public_id] = $client->id;
|
$this->maps['client_ids'][$client->public_id] = $client->id;
|
||||||
}
|
}
|
||||||
if ($name = strtolower(trim($client->contacts[0]->email))) {
|
if (count($client->contacts) && $name = strtolower(trim($client->contacts[0]->email))) {
|
||||||
$this->maps['client'][$name] = $client->id;
|
$this->maps['client'][$name] = $client->id;
|
||||||
$this->maps['client_ids'][$client->public_id] = $client->id;
|
$this->maps['client_ids'][$client->public_id] = $client->id;
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ class TemplateService
|
|||||||
$invitation = $data['invitation'];
|
$invitation = $data['invitation'];
|
||||||
|
|
||||||
$invoice = $invitation->invoice;
|
$invoice = $invitation->invoice;
|
||||||
|
$contact = $invitation->contact;
|
||||||
$passwordHTML = isset($data['password']) ? '<p>'.trans('texts.password').': '.$data['password'].'<p>' : false;
|
$passwordHTML = isset($data['password']) ? '<p>'.trans('texts.password').': '.$data['password'].'<p>' : false;
|
||||||
$documentsHTML = '';
|
$documentsHTML = '';
|
||||||
|
|
||||||
@ -46,12 +47,13 @@ class TemplateService
|
|||||||
|
|
||||||
$variables = [
|
$variables = [
|
||||||
'$footer' => $account->getEmailFooter(),
|
'$footer' => $account->getEmailFooter(),
|
||||||
|
'$emailSignature' => $account->getEmailFooter(),
|
||||||
'$client' => $client->getDisplayName(),
|
'$client' => $client->getDisplayName(),
|
||||||
'$account' => $account->getDisplayName(),
|
'$account' => $account->getDisplayName(),
|
||||||
'$dueDate' => $account->formatDate($invoice->due_date),
|
'$dueDate' => $account->formatDate($invoice->due_date),
|
||||||
'$invoiceDate' => $account->formatDate($invoice->invoice_date),
|
'$invoiceDate' => $account->formatDate($invoice->invoice_date),
|
||||||
'$contact' => $invitation->contact->getDisplayName(),
|
'$contact' => $contact->getDisplayName(),
|
||||||
'$firstName' => $invitation->contact->first_name,
|
'$firstName' => $contact->first_name,
|
||||||
'$amount' => $account->formatMoney($data['amount'], $client),
|
'$amount' => $account->formatMoney($data['amount'], $client),
|
||||||
'$invoice' => $invoice->invoice_number,
|
'$invoice' => $invoice->invoice_number,
|
||||||
'$quote' => $invoice->invoice_number,
|
'$quote' => $invoice->invoice_number,
|
||||||
@ -63,6 +65,8 @@ class TemplateService
|
|||||||
'$paymentButton' => Form::emailPaymentButton($invitation->getLink('payment')).'$password',
|
'$paymentButton' => Form::emailPaymentButton($invitation->getLink('payment')).'$password',
|
||||||
'$customClient1' => $client->custom_value1,
|
'$customClient1' => $client->custom_value1,
|
||||||
'$customClient2' => $client->custom_value2,
|
'$customClient2' => $client->custom_value2,
|
||||||
|
'$customContact1' => $contact->custom_value1,
|
||||||
|
'$customContact2' => $contact->custom_value2,
|
||||||
'$customInvoice1' => $invoice->custom_text_value1,
|
'$customInvoice1' => $invoice->custom_text_value1,
|
||||||
'$customInvoice2' => $invoice->custom_text_value2,
|
'$customInvoice2' => $invoice->custom_text_value2,
|
||||||
'$documents' => $documentsHTML,
|
'$documents' => $documentsHTML,
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"ext-gmp": "*",
|
"ext-gmp": "*",
|
||||||
"ext-gd": "*",
|
"ext-gd": "*",
|
||||||
"turbo124/laravel-push-notification": "2.*",
|
"turbo124/laravel-push-notification": "2.*",
|
||||||
"omnipay/mollie": "dev-master#22956c1a62a9662afa5f5d119723b413770ac525",
|
"omnipay/mollie": "3.*",
|
||||||
"omnipay/2checkout": "dev-master#e9c079c2dde0d7ba461903b3b7bd5caf6dee1248",
|
"omnipay/2checkout": "dev-master#e9c079c2dde0d7ba461903b3b7bd5caf6dee1248",
|
||||||
"omnipay/gocardless": "dev-master",
|
"omnipay/gocardless": "dev-master",
|
||||||
"omnipay/stripe": "dev-master",
|
"omnipay/stripe": "dev-master",
|
||||||
|
551
composer.lock
generated
551
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,145 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class AddCustomContactFields extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('accounts', function ($table) {
|
||||||
|
$table->string('custom_contact_label1')->nullable();
|
||||||
|
$table->string('custom_contact_label2')->nullable();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('contacts', function ($table) {
|
||||||
|
$table->string('custom_value1')->nullable();
|
||||||
|
$table->string('custom_value2')->nullable();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('payment_methods', function ($table) {
|
||||||
|
$table->unsignedInteger('account_gateway_token_id')->nullable()->change();
|
||||||
|
$table->dropForeign('payment_methods_account_gateway_token_id_foreign');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('payment_methods', function ($table) {
|
||||||
|
$table->foreign('account_gateway_token_id')->references('id')->on('account_gateway_tokens')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('payments', function ($table) {
|
||||||
|
$table->dropForeign('payments_payment_method_id_foreign');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('payments', function ($table) {
|
||||||
|
$table->foreign('payment_method_id')->references('id')->on('payment_methods')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('expenses', function($table) {
|
||||||
|
$table->unsignedInteger('payment_type_id')->nullable();
|
||||||
|
$table->date('payment_date')->nullable();
|
||||||
|
$table->string('transaction_reference')->nullable();
|
||||||
|
$table->foreign('payment_type_id')->references('id')->on('payment_types');
|
||||||
|
$table->boolean('invoice_documents')->default(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove duplicate annual frequency
|
||||||
|
if (DB::table('frequencies')->count() == 9) {
|
||||||
|
DB::statement('update invoices set frequency_id = 8 where is_recurring = 1 and frequency_id = 9');
|
||||||
|
DB::statement('update accounts set reset_counter_frequency_id = 8 where reset_counter_frequency_id = 9');
|
||||||
|
DB::statement('update frequencies set name = "Annually" where id = 8');
|
||||||
|
DB::statement('delete from frequencies where id = 9');
|
||||||
|
}
|
||||||
|
|
||||||
|
Schema::create('db_servers', function ($table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->string('name');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('lookup_companies', function ($table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->unsignedInteger('db_server_id');
|
||||||
|
|
||||||
|
$table->foreign('db_server_id')->references('id')->on('db_servers');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('lookup_accounts', function ($table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->unsignedInteger('lookup_company_id')->index();
|
||||||
|
$table->string('account_key');
|
||||||
|
|
||||||
|
$table->foreign('lookup_company_id')->references('id')->on('lookup_companies')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('lookup_users', function ($table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->unsignedInteger('lookup_account_id')->index();
|
||||||
|
$table->string('email');
|
||||||
|
|
||||||
|
$table->foreign('lookup_account_id')->references('id')->on('lookup_accounts')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('lookup_contacts', function ($table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->unsignedInteger('lookup_account_id')->index();
|
||||||
|
$table->string('contact_key');
|
||||||
|
|
||||||
|
$table->foreign('lookup_account_id')->references('id')->on('lookup_accounts')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('lookup_invitations', function ($table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->unsignedInteger('lookup_account_id')->index();
|
||||||
|
$table->string('invitation_key');
|
||||||
|
$table->string('message_id');
|
||||||
|
|
||||||
|
$table->foreign('lookup_account_id')->references('id')->on('lookup_accounts')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('lookup_tokens', function ($table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->unsignedInteger('lookup_account_id')->index();
|
||||||
|
$table->string('token');
|
||||||
|
|
||||||
|
$table->foreign('lookup_account_id')->references('id')->on('lookup_accounts')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('accounts', function ($table) {
|
||||||
|
$table->dropColumn('custom_contact_label1');
|
||||||
|
$table->dropColumn('custom_contact_label2');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('contacts', function ($table) {
|
||||||
|
$table->dropColumn('custom_value1');
|
||||||
|
$table->dropColumn('custom_value2');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('expenses', function($table) {
|
||||||
|
$table->dropColumn('payment_type_id');
|
||||||
|
$table->dropColumn('payment_date');
|
||||||
|
$table->dropColumn('transaction_reference');
|
||||||
|
$table->dropColumn('invoice_documents');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::dropIfExists('db_servers');
|
||||||
|
Schema::dropIfExists('lookup_companies');
|
||||||
|
Schema::dropIfExists('lookup_accounts');
|
||||||
|
Schema::dropIfExists('lookup_users');
|
||||||
|
Schema::dropIfExists('lookup_contacts');
|
||||||
|
Schema::dropIfExists('lookup_invitations');
|
||||||
|
Schema::dropIfExists('lookup_tokens');
|
||||||
|
}
|
||||||
|
}
|
@ -72,6 +72,7 @@ class CurrenciesSeeder extends Seeder
|
|||||||
['name' => 'Taiwan New Dollar', 'code' => 'TWD', 'symbol' => 'NT$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
['name' => 'Taiwan New Dollar', 'code' => 'TWD', 'symbol' => 'NT$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||||
['name' => 'Dominican Peso', 'code' => 'DOP', 'symbol' => 'RD$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
['name' => 'Dominican Peso', 'code' => 'DOP', 'symbol' => 'RD$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||||
['name' => 'Chilean Peso', 'code' => 'CLP', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
|
['name' => 'Chilean Peso', 'code' => 'CLP', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
|
||||||
|
['name' => 'Icelandic Króna', 'code' => 'ISK', 'symbol' => 'kr', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ',', 'swap_currency_symbol' => true],
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($currencies as $currency) {
|
foreach ($currencies as $currency) {
|
||||||
|
@ -31,6 +31,7 @@ class LanguageSeeder extends Seeder
|
|||||||
['name' => 'Croatian', 'locale' => 'hr'],
|
['name' => 'Croatian', 'locale' => 'hr'],
|
||||||
['name' => 'Albanian', 'locale' => 'sq'],
|
['name' => 'Albanian', 'locale' => 'sq'],
|
||||||
['name' => 'Greek', 'locale' => 'el'],
|
['name' => 'Greek', 'locale' => 'el'],
|
||||||
|
['name' => 'English - United Kingdom', 'locale' => 'en_UK'],
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($languages as $language) {
|
foreach ($languages as $language) {
|
||||||
|
File diff suppressed because one or more lines are too long
18
docs/api.rst
18
docs/api.rst
@ -6,7 +6,9 @@ Invoice Ninja provides a REST based API, `click here <https://app.invoiceninja.c
|
|||||||
To access the API you first need to create a token using the "Tokens” page under "Advanced Settings”.
|
To access the API you first need to create a token using the "Tokens” page under "Advanced Settings”.
|
||||||
|
|
||||||
- **Zapier** [hosted or self-host]: https://zapier.com/zapbook/invoice-ninja/
|
- **Zapier** [hosted or self-host]: https://zapier.com/zapbook/invoice-ninja/
|
||||||
|
- **Integromat**: https://www.integromat.com/en/integrations/invoiceninja
|
||||||
- **PHP SDK**: https://github.com/invoiceninja/sdk-php
|
- **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.dev with https://app.invoiceninja.com to access a hosted account.
|
||||||
|
|
||||||
@ -73,7 +75,7 @@ Here’s an example of creating a client. Note that email address is a property
|
|||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
curl -X POST ninja.dev/api/v1/clients -H "Content-Type:application/json"
|
curl -X POST ninja.dev/api/v1/clients -H "Content-Type:application/json"
|
||||||
-d '{"name":"Client","contact":{"email":"test@gmail.com"}}' -H "X-Ninja-Token: TOKEN"
|
-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.
|
You can also update a client by specifying a value for ‘id’. Next, here’s an example of creating an invoice.
|
||||||
|
|
||||||
@ -87,10 +89,24 @@ If the product_key is set and matches an existing record the product fields will
|
|||||||
|
|
||||||
Options
|
Options
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
|
|
||||||
|
The following options are available when creating an invoice.
|
||||||
|
|
||||||
- ``email_invoice``: Email the invoice to the client.
|
- ``email_invoice``: Email the invoice to the client.
|
||||||
- ``auto_bill``: Attempt to auto-bill the invoice using stored payment methods or credits.
|
- ``auto_bill``: Attempt to auto-bill the invoice using stored payment methods or credits.
|
||||||
- ``paid``: Create a payment for the defined amount.
|
- ``paid``: Create a payment for the defined amount.
|
||||||
|
|
||||||
|
Updating Data
|
||||||
|
"""""""""""""
|
||||||
|
|
||||||
|
.. NOTE:: When updating a client it's important to include the contact ids.
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
curl -X PUT ninja.dev/api/v1/clients/1 -H "Content-Type:application/json"
|
||||||
|
-d '{"name":"test", "contacts":[{"id": 1, "first_name": "test"}]}'
|
||||||
|
-H "X-Ninja-Token: TOKEN"
|
||||||
|
|
||||||
Emailing Invoices
|
Emailing Invoices
|
||||||
"""""""""""""""""
|
"""""""""""""""""
|
||||||
|
|
||||||
|
@ -57,9 +57,9 @@ author = u'Invoice Ninja'
|
|||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = u'3.2'
|
version = u'3.3'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = u'3.2.1'
|
release = u'3.3.0'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
@ -94,6 +94,19 @@ You need to create a Google Maps API key for the Javascript, Geocoding and Embed
|
|||||||
|
|
||||||
You can disable the feature by adding ``GOOGLE_MAPS_ENABLED=false`` to the .env file.
|
You can disable the feature by adding ``GOOGLE_MAPS_ENABLED=false`` to the .env file.
|
||||||
|
|
||||||
|
Voice Commands
|
||||||
|
""""""""""""""
|
||||||
|
|
||||||
|
Supporting voice commands requires creating a `LUIS.ai <https://www.luis.ai/home/index>`_ app, once the app is created you can import this `model file <https://download.invoiceninja.com/luis.json>`_.
|
||||||
|
|
||||||
|
You'll also need to set the following values in the .env file.
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
SPEECH_ENABLED=true
|
||||||
|
MSBOT_LUIS_APP_ID=...
|
||||||
|
MSBOT_LUIS_SUBSCRIPTION_KEY=...
|
||||||
|
|
||||||
Using a Proxy
|
Using a Proxy
|
||||||
"""""""""""""
|
"""""""""""""
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ Want to find out everything there is to know about how to use your Invoice Ninja
|
|||||||
install
|
install
|
||||||
configure
|
configure
|
||||||
update
|
update
|
||||||
iphone_app
|
mobile_apps
|
||||||
api
|
api
|
||||||
developer_guide
|
developer_guide
|
||||||
custom_modules
|
custom_modules
|
||||||
|
@ -29,7 +29,7 @@ Step 1: Download the code
|
|||||||
|
|
||||||
You can either download the zip file below or checkout the code from our GitHub repository. The zip includes all third party libraries whereas using GitHub requires you to use Composer to install the dependencies.
|
You can either download the zip file below or checkout the code from our GitHub repository. The zip includes all third party libraries whereas using GitHub requires you to use Composer to install the dependencies.
|
||||||
|
|
||||||
https://download.invoiceninja.com/ninja-v3.2.0.zip
|
https://download.invoiceninja.com/ninja-v3.2.1.zip
|
||||||
|
|
||||||
.. Note:: All Pro and Enterprise features from our hosted app are included in both the zip file and the GitHub repository. We offer a $20 per year white-label license to remove our branding.
|
.. Note:: All Pro and Enterprise features from our hosted app are included in both the zip file and the GitHub repository. We offer a $20 per year white-label license to remove our branding.
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
iPhone Application
|
Mobile Applications
|
||||||
==================
|
===================
|
||||||
|
|
||||||
The Invoice Ninja iPhone application allows a user to connect to their self-hosted Invoice Ninja web application.
|
The Invoice Ninja iPhone and Android applications allows a user to connect to their self-hosted Invoice Ninja web application.
|
||||||
|
|
||||||
Connecting your iPhone to your self-hosted invoice ninja installation requires a couple of easy steps.
|
Connecting your to your self-hosted invoice ninja installation requires a couple of easy steps.
|
||||||
|
|
||||||
Web app configuration
|
Web App configuration
|
||||||
"""""""""""""""""""""
|
"""""""""""""""""""""
|
||||||
|
|
||||||
Firstly you'll need to add an additional field to your .env file which is located in the root directory of your self-hosted Invoice Ninja installation.
|
First, you'll need to add an additional field to your .env file which is located in the root directory of your self-hosted Invoice Ninja installation.
|
||||||
|
|
||||||
The additional field to add is API_SECRET, set this to your own defined alphanumeric string.
|
The additional field to add is API_SECRET, set this to your own defined alphanumeric string.
|
||||||
|
|
||||||
@ -17,10 +17,10 @@ The additional field to add is API_SECRET, set this to your own defined alphanum
|
|||||||
Save your .env file and now open Invoice Ninja on your iPhone.
|
Save your .env file and now open Invoice Ninja on your iPhone.
|
||||||
|
|
||||||
|
|
||||||
iPhone configuration
|
Mobile App configuration
|
||||||
""""""""""""""""""""
|
""""""""""""""""""""""""
|
||||||
|
|
||||||
Once you have completed the in-app purchase to unlock the iPhone to connect to your own server, you'll be presented with two fields.
|
Once you have completed the in-app purchase to unlock the mobile app to connect to your own server, you'll be presented with two fields.
|
||||||
|
|
||||||
The first is the Base URL of your self-hosted installation, ie http://ninja.yourapp.com
|
The first is the Base URL of your self-hosted installation, ie http://ninja.yourapp.com
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ The second field is the API_SECRET, enter in the API_SECRET you used in your .en
|
|||||||
|
|
||||||
Click SAVE.
|
Click SAVE.
|
||||||
|
|
||||||
You should be able to login now from your iPhone!
|
You should now be able to login!
|
||||||
|
|
||||||
|
|
||||||
FAQ:
|
FAQ:
|
||||||
@ -40,9 +40,9 @@ Q: I get a HTTP 500 error.
|
|||||||
|
|
||||||
A: Most likely you have not entered your API_SECRET in your .env file
|
A: Most likely you have not entered your API_SECRET in your .env file
|
||||||
|
|
||||||
Q: I get a HTTP 403 error when i attempt to login with the iPhone.
|
Q: I get a HTTP 403 error when i attempt to login with the iPhone or Android device.
|
||||||
|
|
||||||
A: Most likely your API_SECRET on the iPhone does not match that on your self-hosted installation.
|
A: Most likely your API_SECRET on the iPhone/Android device does not match that on your self-hosted installation.
|
||||||
|
|
||||||
Q: Do I need to create a token on the server?
|
Q: Do I need to create a token on the server?
|
||||||
|
|
@ -16,6 +16,11 @@ If the auto-update fails you can manually run the update with the following comm
|
|||||||
|
|
||||||
.. NOTE:: If you've downloaded the code from GitHub you also need to run ``composer install``
|
.. NOTE:: If you've downloaded the code from GitHub you also need to run ``composer install``
|
||||||
|
|
||||||
|
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.6
|
||||||
"""""""""""
|
"""""""""""
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
public/css/built.css
vendored
2
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
12
resources/assets/css/style.css
vendored
12
resources/assets/css/style.css
vendored
@ -424,6 +424,18 @@ ul.dropdown-menu,
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul.typeahead li:first-child {
|
||||||
|
border-top: solid 1px #EEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.typeahead li {
|
||||||
|
border-bottom: solid 1px #EEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combobox-container .active {
|
||||||
|
border-color: #EEE !important;
|
||||||
|
}
|
||||||
|
|
||||||
.panel-default,
|
.panel-default,
|
||||||
canvas {
|
canvas {
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
|
@ -746,6 +746,7 @@ NINJA.accountAddress = function(invoice) {
|
|||||||
NINJA.renderInvoiceField = function(invoice, field) {
|
NINJA.renderInvoiceField = function(invoice, field) {
|
||||||
|
|
||||||
var account = invoice.account;
|
var account = invoice.account;
|
||||||
|
var client = invoice.client;
|
||||||
|
|
||||||
if (field == 'invoice.invoice_number') {
|
if (field == 'invoice.invoice_number') {
|
||||||
if (invoice.is_statement) {
|
if (invoice.is_statement) {
|
||||||
@ -803,6 +804,24 @@ NINJA.renderInvoiceField = function(invoice, field) {
|
|||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if (field == 'invoice.invoice_total') {
|
||||||
|
if (invoice.is_statement || invoice.is_quote || invoice.balance_amount < 0) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
{text: invoiceLabels.invoice_total, style: ['invoiceTotalLabel']},
|
||||||
|
{text: formatMoneyInvoice(invoice.amount, invoice), style: ['invoiceTotal']}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} else if (field == 'invoice.outstanding') {
|
||||||
|
if (invoice.is_statement || invoice.is_quote) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
{text: invoiceLabels.outstanding, style: ['invoiceOutstandingLabel']},
|
||||||
|
{text: formatMoneyInvoice(client.balance, invoice), style: ['outstanding']}
|
||||||
|
];
|
||||||
|
}
|
||||||
} else if (field == '.blank') {
|
} else if (field == '.blank') {
|
||||||
return [{text: ' '}, {text: ' '}];
|
return [{text: ' '}, {text: ' '}];
|
||||||
}
|
}
|
||||||
@ -884,6 +903,10 @@ NINJA.renderClientOrAccountField = function(invoice, field) {
|
|||||||
return {text: account.custom_client_label1 && client.custom_value1 ? account.custom_client_label1 + ' ' + client.custom_value1 : false};
|
return {text: account.custom_client_label1 && client.custom_value1 ? account.custom_client_label1 + ' ' + client.custom_value1 : false};
|
||||||
} else if (field == 'client.custom_value2') {
|
} else if (field == 'client.custom_value2') {
|
||||||
return {text: account.custom_client_label2 && client.custom_value2 ? account.custom_client_label2 + ' ' + client.custom_value2 : false};
|
return {text: account.custom_client_label2 && client.custom_value2 ? account.custom_client_label2 + ' ' + client.custom_value2 : false};
|
||||||
|
} else if (field == 'contact.custom_value1') {
|
||||||
|
return {text:contact.custom_value1};
|
||||||
|
} else if (field == 'contact.custom_value2') {
|
||||||
|
return {text:contact.custom_value2};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field == 'account.company_name') {
|
if (field == 'account.company_name') {
|
||||||
@ -948,6 +971,8 @@ NINJA.clientDetails = function(invoice) {
|
|||||||
'client.email',
|
'client.email',
|
||||||
'client.custom_value1',
|
'client.custom_value1',
|
||||||
'client.custom_value2',
|
'client.custom_value2',
|
||||||
|
'contact.custom_value1',
|
||||||
|
'contact.custom_value2',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
var data = [];
|
var data = [];
|
||||||
|
@ -447,6 +447,27 @@ if (window.ko) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function comboboxHighlighter(item) {
|
||||||
|
var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
|
||||||
|
var result = item.replace(new RegExp('<br/>', 'g'), "\n");
|
||||||
|
result = result.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
|
||||||
|
return match ? '<strong>' + match + '</strong>' : query;
|
||||||
|
});
|
||||||
|
result = result.replace(new RegExp("\n", 'g'), '<br/>');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function comboboxMatcher(item) {
|
||||||
|
return ~stripHtmlTags(item).toLowerCase().indexOf(this.query.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripHtmlTags(text) {
|
||||||
|
// http://stackoverflow.com/a/5002618/497368
|
||||||
|
var div = document.createElement("div");
|
||||||
|
div.innerHTML = text;
|
||||||
|
return div.textContent || div.innerText || '';
|
||||||
|
}
|
||||||
|
|
||||||
function getContactDisplayName(contact)
|
function getContactDisplayName(contact)
|
||||||
{
|
{
|
||||||
if (contact.first_name || contact.last_name) {
|
if (contact.first_name || contact.last_name) {
|
||||||
@ -456,6 +477,25 @@ function getContactDisplayName(contact)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getContactDisplayNameWithEmail(contact)
|
||||||
|
{
|
||||||
|
var str = '';
|
||||||
|
|
||||||
|
if (contact.first_name || contact.last_name) {
|
||||||
|
str += $.trim((contact.first_name || '') + ' ' + (contact.last_name || ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contact.email) {
|
||||||
|
if (str) {
|
||||||
|
str += ' - ';
|
||||||
|
}
|
||||||
|
|
||||||
|
str += contact.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $.trim(str);
|
||||||
|
}
|
||||||
|
|
||||||
function getClientDisplayName(client)
|
function getClientDisplayName(client)
|
||||||
{
|
{
|
||||||
var contact = client.contacts ? client.contacts[0] : false;
|
var contact = client.contacts ? client.contacts[0] : false;
|
||||||
@ -716,8 +756,8 @@ function calculateAmounts(invoice) {
|
|||||||
if (invoice.tax_rate2 && parseFloat(invoice.tax_rate2)) {
|
if (invoice.tax_rate2 && parseFloat(invoice.tax_rate2)) {
|
||||||
taxRate2 = parseFloat(invoice.tax_rate2);
|
taxRate2 = parseFloat(invoice.tax_rate2);
|
||||||
}
|
}
|
||||||
taxAmount1 = roundToTwo(total * (taxRate1/100));
|
taxAmount1 = roundToTwo(total * taxRate1 / 100);
|
||||||
taxAmount2 = roundToTwo(total * (taxRate2/100));
|
taxAmount2 = roundToTwo(total * taxRate2 / 100);
|
||||||
total = total + taxAmount1 + taxAmount2;
|
total = total + taxAmount1 + taxAmount2;
|
||||||
|
|
||||||
for (var key in taxes) {
|
for (var key in taxes) {
|
||||||
|
@ -1711,6 +1711,7 @@ $LANG = array(
|
|||||||
'lang_Spanish - Spain' => 'Spanish - Spain',
|
'lang_Spanish - Spain' => 'Spanish - Spain',
|
||||||
'lang_Swedish' => 'Swedish',
|
'lang_Swedish' => 'Swedish',
|
||||||
'lang_Albanian' => 'Albanian',
|
'lang_Albanian' => 'Albanian',
|
||||||
|
'lang_English - United Kingdom' => 'English - United Kingdom',
|
||||||
|
|
||||||
// Frequencies
|
// Frequencies
|
||||||
'freq_weekly' => 'Weekly',
|
'freq_weekly' => 'Weekly',
|
||||||
@ -2256,7 +2257,7 @@ $LANG = array(
|
|||||||
'edit_credit' => 'Edit Credit',
|
'edit_credit' => 'Edit Credit',
|
||||||
'live_preview_help' => 'Display a live PDF preview on the invoice page.<br/>Disable this to improve performance when editing invoices.',
|
'live_preview_help' => 'Display a live PDF preview on the invoice page.<br/>Disable this to improve performance when editing invoices.',
|
||||||
'force_pdfjs_help' => 'Replace the built-in PDF viewer in :chrome_link and :firefox_link.<br/>Enable this if your browser is automatically downloading the PDF.',
|
'force_pdfjs_help' => 'Replace the built-in PDF viewer in :chrome_link and :firefox_link.<br/>Enable this if your browser is automatically downloading the PDF.',
|
||||||
'force_pdfjs' => 'PDF Viewer',
|
'force_pdfjs' => 'Prevent Download',
|
||||||
'redirect_url' => 'Redirect URL',
|
'redirect_url' => 'Redirect URL',
|
||||||
'redirect_url_help' => 'Optionally specify a URL to redirect to after a payment is entered.',
|
'redirect_url_help' => 'Optionally specify a URL to redirect to after a payment is entered.',
|
||||||
'save_draft' => 'Save Draft',
|
'save_draft' => 'Save Draft',
|
||||||
@ -2293,6 +2294,7 @@ $LANG = array(
|
|||||||
'renew_license' => 'Renew License',
|
'renew_license' => 'Renew License',
|
||||||
'iphone_app_message' => 'Consider downloading our :link',
|
'iphone_app_message' => 'Consider downloading our :link',
|
||||||
'iphone_app' => 'iPhone app',
|
'iphone_app' => 'iPhone app',
|
||||||
|
'android_app' => 'Android app',
|
||||||
'logged_in' => 'Logged In',
|
'logged_in' => 'Logged In',
|
||||||
'switch_to_primary' => 'Switch to your primary company (:name) to manage your plan.',
|
'switch_to_primary' => 'Switch to your primary company (:name) to manage your plan.',
|
||||||
'inclusive' => 'Inclusive',
|
'inclusive' => 'Inclusive',
|
||||||
@ -2465,11 +2467,31 @@ $LANG = array(
|
|||||||
'confirm_account_to_import' => 'Please confirm your account to import data.',
|
'confirm_account_to_import' => 'Please confirm your account to import data.',
|
||||||
'import_started' => 'Your import has started, we\'ll send you an email once it completes.',
|
'import_started' => 'Your import has started, we\'ll send you an email once it completes.',
|
||||||
'listening' => 'Listening...',
|
'listening' => 'Listening...',
|
||||||
'microphone_help' => 'Say \'new invoice for...\'',
|
'microphone_help' => 'Say "new invoice for [client]" or "show me [client]\'s archived payments"',
|
||||||
'voice_commands' => 'Voice Commands',
|
'voice_commands' => 'Voice Commands',
|
||||||
'sample_commands' => 'Sample commands',
|
'sample_commands' => 'Sample commands',
|
||||||
'voice_commands_feedback' => 'We\'re actively working to improve this feature, if there\'s a command you\'d like us to support please email us at :email.',
|
'voice_commands_feedback' => 'We\'re actively working to improve this feature, if there\'s a command you\'d like us to support please email us at :email.',
|
||||||
'payment_type_Venmo' => 'Venmo',
|
'payment_type_Venmo' => 'Venmo',
|
||||||
|
'archived_products' => 'Successfully archived :count products',
|
||||||
|
'recommend_on' => 'We recommend <b>enabling</b> this setting.',
|
||||||
|
'recommend_off' => 'We recommend <b>disabling</b> this setting.',
|
||||||
|
'notes_auto_billed' => 'Auto-billed',
|
||||||
|
'surcharge_label' => 'Surcharge Label',
|
||||||
|
'contact_fields' => 'Contact Fields',
|
||||||
|
'custom_contact_fields_help' => 'Add a field when creating a contact and display the label and value on the PDF.',
|
||||||
|
'datatable_info' => 'Showing :start to :end of :total entries',
|
||||||
|
'credit_total' => 'Credit Total',
|
||||||
|
'mark_billable' => 'Mark billable',
|
||||||
|
'billed' => 'Billed',
|
||||||
|
'company_variables' => 'Company Variables',
|
||||||
|
'client_variables' => 'Client Variables',
|
||||||
|
'invoice_variables' => 'Invoice Variables',
|
||||||
|
'navigation_variables' => 'Navigation Variables',
|
||||||
|
'custom_variables' => 'Custom Variables',
|
||||||
|
'invalid_file' => 'Invalid file type',
|
||||||
|
'add_documents_to_invoice' => 'Add documents to invoice',
|
||||||
|
'mark_expense_paid' => 'Mark paid',
|
||||||
|
'white_label_license_error' => 'Failed to validate the license, check storage/logs/laravel-error.log for more details.',
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1713,6 +1713,7 @@ $LANG = array(
|
|||||||
'lang_Spanish - Spain' => 'Spanish - Spain',
|
'lang_Spanish - Spain' => 'Spanish - Spain',
|
||||||
'lang_Swedish' => 'Swedish',
|
'lang_Swedish' => 'Swedish',
|
||||||
'lang_Albanian' => 'Albanian',
|
'lang_Albanian' => 'Albanian',
|
||||||
|
'lang_English - United Kingdom' => 'English - United Kingdom',
|
||||||
|
|
||||||
// Frequencies
|
// Frequencies
|
||||||
'freq_weekly' => 'Weekly',
|
'freq_weekly' => 'Weekly',
|
||||||
@ -2258,7 +2259,7 @@ $LANG = array(
|
|||||||
'edit_credit' => 'Edit Credit',
|
'edit_credit' => 'Edit Credit',
|
||||||
'live_preview_help' => 'Display a live PDF preview on the invoice page.<br/>Disable this to improve performance when editing invoices.',
|
'live_preview_help' => 'Display a live PDF preview on the invoice page.<br/>Disable this to improve performance when editing invoices.',
|
||||||
'force_pdfjs_help' => 'Replace the built-in PDF viewer in :chrome_link and :firefox_link.<br/>Enable this if your browser is automatically downloading the PDF.',
|
'force_pdfjs_help' => 'Replace the built-in PDF viewer in :chrome_link and :firefox_link.<br/>Enable this if your browser is automatically downloading the PDF.',
|
||||||
'force_pdfjs' => 'PDF Viewer',
|
'force_pdfjs' => 'Prevent Download',
|
||||||
'redirect_url' => 'Redirect URL',
|
'redirect_url' => 'Redirect URL',
|
||||||
'redirect_url_help' => 'Optionally specify a URL to redirect to after a payment is entered.',
|
'redirect_url_help' => 'Optionally specify a URL to redirect to after a payment is entered.',
|
||||||
'save_draft' => 'Save Draft',
|
'save_draft' => 'Save Draft',
|
||||||
@ -2295,6 +2296,7 @@ $LANG = array(
|
|||||||
'renew_license' => 'Renew License',
|
'renew_license' => 'Renew License',
|
||||||
'iphone_app_message' => 'Consider downloading our :link',
|
'iphone_app_message' => 'Consider downloading our :link',
|
||||||
'iphone_app' => 'iPhone app',
|
'iphone_app' => 'iPhone app',
|
||||||
|
'android_app' => 'Android app',
|
||||||
'logged_in' => 'Logged In',
|
'logged_in' => 'Logged In',
|
||||||
'switch_to_primary' => 'Switch to your primary company (:name) to manage your plan.',
|
'switch_to_primary' => 'Switch to your primary company (:name) to manage your plan.',
|
||||||
'inclusive' => 'Inclusive',
|
'inclusive' => 'Inclusive',
|
||||||
@ -2467,11 +2469,31 @@ $LANG = array(
|
|||||||
'confirm_account_to_import' => 'Please confirm your account to import data.',
|
'confirm_account_to_import' => 'Please confirm your account to import data.',
|
||||||
'import_started' => 'Your import has started, we\'ll send you an email once it completes.',
|
'import_started' => 'Your import has started, we\'ll send you an email once it completes.',
|
||||||
'listening' => 'Listening...',
|
'listening' => 'Listening...',
|
||||||
'microphone_help' => 'Say \'new invoice for...\'',
|
'microphone_help' => 'Say "new invoice for [client]" or "show me [client]\'s archived payments"',
|
||||||
'voice_commands' => 'Voice Commands',
|
'voice_commands' => 'Voice Commands',
|
||||||
'sample_commands' => 'Sample commands',
|
'sample_commands' => 'Sample commands',
|
||||||
'voice_commands_feedback' => 'We\'re actively working to improve this feature, if there\'s a command you\'d like us to support please email us at :email.',
|
'voice_commands_feedback' => 'We\'re actively working to improve this feature, if there\'s a command you\'d like us to support please email us at :email.',
|
||||||
'payment_type_Venmo' => 'Venmo',
|
'payment_type_Venmo' => 'Venmo',
|
||||||
|
'archived_products' => 'Successfully archived :count products',
|
||||||
|
'recommend_on' => 'We recommend <b>enabling</b> this setting.',
|
||||||
|
'recommend_off' => 'We recommend <b>disabling</b> this setting.',
|
||||||
|
'notes_auto_billed' => 'Auto-billed',
|
||||||
|
'surcharge_label' => 'Surcharge Label',
|
||||||
|
'contact_fields' => 'Contact Fields',
|
||||||
|
'custom_contact_fields_help' => 'Add a field when creating a contact and display the label and value on the PDF.',
|
||||||
|
'datatable_info' => 'Showing :start to :end of :total entries',
|
||||||
|
'credit_total' => 'Credit Total',
|
||||||
|
'mark_billable' => 'Mark billable',
|
||||||
|
'billed' => 'Billed',
|
||||||
|
'company_variables' => 'Company Variables',
|
||||||
|
'client_variables' => 'Client Variables',
|
||||||
|
'invoice_variables' => 'Invoice Variables',
|
||||||
|
'navigation_variables' => 'Navigation Variables',
|
||||||
|
'custom_variables' => 'Custom Variables',
|
||||||
|
'invalid_file' => 'Invalid file type',
|
||||||
|
'add_documents_to_invoice' => 'Add documents to invoice',
|
||||||
|
'mark_expense_paid' => 'Mark paid',
|
||||||
|
'white_label_license_error' => 'Failed to validate the license, check storage/logs/laravel-error.log for more details.',
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1711,6 +1711,7 @@ $LANG = array(
|
|||||||
'lang_Spanish - Spain' => 'Spanish - Spain',
|
'lang_Spanish - Spain' => 'Spanish - Spain',
|
||||||
'lang_Swedish' => 'Swedish',
|
'lang_Swedish' => 'Swedish',
|
||||||
'lang_Albanian' => 'Albanian',
|
'lang_Albanian' => 'Albanian',
|
||||||
|
'lang_English - United Kingdom' => 'English - United Kingdom',
|
||||||
|
|
||||||
// Frequencies
|
// Frequencies
|
||||||
'freq_weekly' => 'Weekly',
|
'freq_weekly' => 'Weekly',
|
||||||
@ -2256,7 +2257,7 @@ $LANG = array(
|
|||||||
'edit_credit' => 'Edit Credit',
|
'edit_credit' => 'Edit Credit',
|
||||||
'live_preview_help' => 'Display a live PDF preview on the invoice page.<br/>Disable this to improve performance when editing invoices.',
|
'live_preview_help' => 'Display a live PDF preview on the invoice page.<br/>Disable this to improve performance when editing invoices.',
|
||||||
'force_pdfjs_help' => 'Replace the built-in PDF viewer in :chrome_link and :firefox_link.<br/>Enable this if your browser is automatically downloading the PDF.',
|
'force_pdfjs_help' => 'Replace the built-in PDF viewer in :chrome_link and :firefox_link.<br/>Enable this if your browser is automatically downloading the PDF.',
|
||||||
'force_pdfjs' => 'PDF Viewer',
|
'force_pdfjs' => 'Prevent Download',
|
||||||
'redirect_url' => 'Redirect URL',
|
'redirect_url' => 'Redirect URL',
|
||||||
'redirect_url_help' => 'Optionally specify a URL to redirect to after a payment is entered.',
|
'redirect_url_help' => 'Optionally specify a URL to redirect to after a payment is entered.',
|
||||||
'save_draft' => 'Save Draft',
|
'save_draft' => 'Save Draft',
|
||||||
@ -2293,6 +2294,7 @@ $LANG = array(
|
|||||||
'renew_license' => 'Renew License',
|
'renew_license' => 'Renew License',
|
||||||
'iphone_app_message' => 'Consider downloading our :link',
|
'iphone_app_message' => 'Consider downloading our :link',
|
||||||
'iphone_app' => 'iPhone app',
|
'iphone_app' => 'iPhone app',
|
||||||
|
'android_app' => 'Android app',
|
||||||
'logged_in' => 'Logged In',
|
'logged_in' => 'Logged In',
|
||||||
'switch_to_primary' => 'Switch to your primary company (:name) to manage your plan.',
|
'switch_to_primary' => 'Switch to your primary company (:name) to manage your plan.',
|
||||||
'inclusive' => 'Inclusive',
|
'inclusive' => 'Inclusive',
|
||||||
@ -2465,11 +2467,31 @@ $LANG = array(
|
|||||||
'confirm_account_to_import' => 'Please confirm your account to import data.',
|
'confirm_account_to_import' => 'Please confirm your account to import data.',
|
||||||
'import_started' => 'Your import has started, we\'ll send you an email once it completes.',
|
'import_started' => 'Your import has started, we\'ll send you an email once it completes.',
|
||||||
'listening' => 'Listening...',
|
'listening' => 'Listening...',
|
||||||
'microphone_help' => 'Say \'new invoice for...\'',
|
'microphone_help' => 'Say "new invoice for [client]" or "show me [client]\'s archived payments"',
|
||||||
'voice_commands' => 'Voice Commands',
|
'voice_commands' => 'Voice Commands',
|
||||||
'sample_commands' => 'Sample commands',
|
'sample_commands' => 'Sample commands',
|
||||||
'voice_commands_feedback' => 'We\'re actively working to improve this feature, if there\'s a command you\'d like us to support please email us at :email.',
|
'voice_commands_feedback' => 'We\'re actively working to improve this feature, if there\'s a command you\'d like us to support please email us at :email.',
|
||||||
'payment_type_Venmo' => 'Venmo',
|
'payment_type_Venmo' => 'Venmo',
|
||||||
|
'archived_products' => 'Successfully archived :count products',
|
||||||
|
'recommend_on' => 'We recommend <b>enabling</b> this setting.',
|
||||||
|
'recommend_off' => 'We recommend <b>disabling</b> this setting.',
|
||||||
|
'notes_auto_billed' => 'Auto-billed',
|
||||||
|
'surcharge_label' => 'Surcharge Label',
|
||||||
|
'contact_fields' => 'Contact Fields',
|
||||||
|
'custom_contact_fields_help' => 'Add a field when creating a contact and display the label and value on the PDF.',
|
||||||
|
'datatable_info' => 'Showing :start to :end of :total entries',
|
||||||
|
'credit_total' => 'Credit Total',
|
||||||
|
'mark_billable' => 'Mark billable',
|
||||||
|
'billed' => 'Billed',
|
||||||
|
'company_variables' => 'Company Variables',
|
||||||
|
'client_variables' => 'Client Variables',
|
||||||
|
'invoice_variables' => 'Invoice Variables',
|
||||||
|
'navigation_variables' => 'Navigation Variables',
|
||||||
|
'custom_variables' => 'Custom Variables',
|
||||||
|
'invalid_file' => 'Invalid file type',
|
||||||
|
'add_documents_to_invoice' => 'Add documents to invoice',
|
||||||
|
'mark_expense_paid' => 'Mark paid',
|
||||||
|
'white_label_license_error' => 'Failed to validate the license, check storage/logs/laravel-error.log for more details.',
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -369,7 +369,7 @@ $LANG = array(
|
|||||||
'confirm_email_quote' => 'Bist du sicher, dass du dieses Angebot per E-Mail versenden möchtest',
|
'confirm_email_quote' => 'Bist du sicher, dass du dieses Angebot per E-Mail versenden möchtest',
|
||||||
'confirm_recurring_email_invoice' => 'Wiederkehrende Rechnung ist aktiv. Bis du sicher, dass du diese Rechnung weiterhin als E-Mail verschicken möchtest?',
|
'confirm_recurring_email_invoice' => 'Wiederkehrende Rechnung ist aktiv. Bis du sicher, dass du diese Rechnung weiterhin als E-Mail verschicken möchtest?',
|
||||||
'cancel_account' => 'Konto Kündigen',
|
'cancel_account' => 'Konto Kündigen',
|
||||||
'cancel_account_message' => 'Warnung: Diese Aktion wird Ihr Konto unwiederbringlich löschen.',
|
'cancel_account_message' => 'Warnung: Diese Aktion wird dein Konto unwiederbringlich löschen.',
|
||||||
'go_back' => 'Zurück',
|
'go_back' => 'Zurück',
|
||||||
'data_visualizations' => 'Datenvisualisierungen',
|
'data_visualizations' => 'Datenvisualisierungen',
|
||||||
'sample_data' => 'Beispieldaten werden angezeigt',
|
'sample_data' => 'Beispieldaten werden angezeigt',
|
||||||
@ -732,7 +732,7 @@ $LANG = array(
|
|||||||
'recurring_hour' => 'Wiederholende Stunde',
|
'recurring_hour' => 'Wiederholende Stunde',
|
||||||
'pattern' => 'Schema',
|
'pattern' => 'Schema',
|
||||||
'pattern_help_title' => 'Schema-Hilfe',
|
'pattern_help_title' => 'Schema-Hilfe',
|
||||||
'pattern_help_1' => 'Create custom numbers by specifying a pattern',
|
'pattern_help_1' => 'Erstellen Sie benutzerdefinierte Nummernkreise durch eigene Nummernsschemata.',
|
||||||
'pattern_help_2' => 'Verfügbare Variablen:',
|
'pattern_help_2' => 'Verfügbare Variablen:',
|
||||||
'pattern_help_3' => 'Zum Beispiel: :example würde zu :value konvertiert werden.',
|
'pattern_help_3' => 'Zum Beispiel: :example würde zu :value konvertiert werden.',
|
||||||
'see_options' => 'Optionen ansehen',
|
'see_options' => 'Optionen ansehen',
|
||||||
@ -851,7 +851,7 @@ $LANG = array(
|
|||||||
'dark' => 'Dunkel',
|
'dark' => 'Dunkel',
|
||||||
'industry_help' => 'Wird genutzt um Vergleiche zwischen den Durchschnittswerten von Firmen ähnlicher Größe und Branche ermitteln zu können.',
|
'industry_help' => 'Wird genutzt um Vergleiche zwischen den Durchschnittswerten von Firmen ähnlicher Größe und Branche ermitteln zu können.',
|
||||||
'subdomain_help' => 'Passen Sie die Rechnungslink-Subdomäne an oder stellen Sie die Rechnung auf Ihrer eigenen Webseite zur Verfügung.',
|
'subdomain_help' => 'Passen Sie die Rechnungslink-Subdomäne an oder stellen Sie die Rechnung auf Ihrer eigenen Webseite zur Verfügung.',
|
||||||
'website_help' => 'Display the invoice in an iFrame on your own website',
|
'website_help' => 'Zeige die Rechnung als iFrame auf deiner eigenen Webseite an',
|
||||||
'invoice_number_help' => 'Geben Sie einen Präfix oder ein benutzerdefiniertes Schema an, um die Rechnungsnummer dynamisch zu erzeugen.',
|
'invoice_number_help' => 'Geben Sie einen Präfix oder ein benutzerdefiniertes Schema an, um die Rechnungsnummer dynamisch zu erzeugen.',
|
||||||
'quote_number_help' => 'Geben Sie einen Präfix oder ein benutzerdefiniertes Schema an, um die Angebotsnummer dynamisch zu erzeugen.',
|
'quote_number_help' => 'Geben Sie einen Präfix oder ein benutzerdefiniertes Schema an, um die Angebotsnummer dynamisch zu erzeugen.',
|
||||||
'custom_client_fields_helps' => 'Füge ein Feld hinzu, wenn ein neuer Kunde erstellt wird und zeige die Bezeichnung und den Wert auf der PDF-Datei an.',
|
'custom_client_fields_helps' => 'Füge ein Feld hinzu, wenn ein neuer Kunde erstellt wird und zeige die Bezeichnung und den Wert auf der PDF-Datei an.',
|
||||||
@ -1042,7 +1042,7 @@ $LANG = array(
|
|||||||
'invoiced_amount' => 'Rechnungsbetrag',
|
'invoiced_amount' => 'Rechnungsbetrag',
|
||||||
'invoice_item_fields' => 'Rechnungspositionsfeld',
|
'invoice_item_fields' => 'Rechnungspositionsfeld',
|
||||||
'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.',
|
'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.',
|
||||||
'recurring_invoice_number' => 'Recurring Number',
|
'recurring_invoice_number' => 'Wiederkehrende Nummer',
|
||||||
'recurring_invoice_number_prefix_help' => 'Geben Sie einen Präfix für wiederkehrende Rechnungen an. Standard ist \'R\'.',
|
'recurring_invoice_number_prefix_help' => 'Geben Sie einen Präfix für wiederkehrende Rechnungen an. Standard ist \'R\'.',
|
||||||
|
|
||||||
// Client Passwords
|
// Client Passwords
|
||||||
@ -1711,6 +1711,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
|
|||||||
'lang_Spanish - Spain' => 'Spanisch - Spanien',
|
'lang_Spanish - Spain' => 'Spanisch - Spanien',
|
||||||
'lang_Swedish' => 'Schwedisch',
|
'lang_Swedish' => 'Schwedisch',
|
||||||
'lang_Albanian' => 'Albanian',
|
'lang_Albanian' => 'Albanian',
|
||||||
|
'lang_English - United Kingdom' => 'Englisch (UK)',
|
||||||
|
|
||||||
// Frequencies
|
// Frequencies
|
||||||
'freq_weekly' => 'Wöchentlich',
|
'freq_weekly' => 'Wöchentlich',
|
||||||
@ -2256,7 +2257,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
|
|||||||
'edit_credit' => 'Saldo bearbeiten',
|
'edit_credit' => 'Saldo bearbeiten',
|
||||||
'live_preview_help' => 'Zeige Live-Vorschau der PDF auf der Rechnungsseite.<br/>Schalte dies ab, falls es zu Leistungsproblemen während der Rechnungsbearbeitung führt.',
|
'live_preview_help' => 'Zeige Live-Vorschau der PDF auf der Rechnungsseite.<br/>Schalte dies ab, falls es zu Leistungsproblemen während der Rechnungsbearbeitung führt.',
|
||||||
'force_pdfjs_help' => 'Ersetze den eingebauten PDF-Viewer in :chrome_link und :firefox_link.<br/>Aktiviere dies, wenn dein Browser die PDFs automatisch herunterlädt.',
|
'force_pdfjs_help' => 'Ersetze den eingebauten PDF-Viewer in :chrome_link und :firefox_link.<br/>Aktiviere dies, wenn dein Browser die PDFs automatisch herunterlädt.',
|
||||||
'force_pdfjs' => 'PDF Viewer',
|
'force_pdfjs' => 'Verhindere Download',
|
||||||
'redirect_url' => 'Umleitungs-URL',
|
'redirect_url' => 'Umleitungs-URL',
|
||||||
'redirect_url_help' => 'Gebe optional eine URL an, zu der umgeleitet werden soll, wenn eine Zahlung getätigt wurde.',
|
'redirect_url_help' => 'Gebe optional eine URL an, zu der umgeleitet werden soll, wenn eine Zahlung getätigt wurde.',
|
||||||
'save_draft' => 'Speichere Entwurf',
|
'save_draft' => 'Speichere Entwurf',
|
||||||
@ -2293,6 +2294,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
|
|||||||
'renew_license' => 'Verlängere die Lizenz',
|
'renew_license' => 'Verlängere die Lizenz',
|
||||||
'iphone_app_message' => 'Berücksichtigen Sie unser :link herunterzuladen',
|
'iphone_app_message' => 'Berücksichtigen Sie unser :link herunterzuladen',
|
||||||
'iphone_app' => 'iPhone-App',
|
'iphone_app' => 'iPhone-App',
|
||||||
|
'android_app' => 'Android app',
|
||||||
'logged_in' => 'Eingeloggt',
|
'logged_in' => 'Eingeloggt',
|
||||||
'switch_to_primary' => 'Wechseln Sie zu Ihrem Primärunternehmen (:name), um Ihren Plan zu managen.',
|
'switch_to_primary' => 'Wechseln Sie zu Ihrem Primärunternehmen (:name), um Ihren Plan zu managen.',
|
||||||
'inclusive' => 'Inklusive',
|
'inclusive' => 'Inklusive',
|
||||||
@ -2338,8 +2340,8 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
|
|||||||
'profit_and_loss' => 'Gewinn und Verlust',
|
'profit_and_loss' => 'Gewinn und Verlust',
|
||||||
'revenue' => 'Einnahmen',
|
'revenue' => 'Einnahmen',
|
||||||
'profit' => 'Profit',
|
'profit' => 'Profit',
|
||||||
'group_when_sorted' => 'Gruppe bei Sortierung',
|
'group_when_sorted' => 'Gruppiere bei Sortierung',
|
||||||
'group_dates_by' => 'Gruppe Daten nach',
|
'group_dates_by' => 'Gruppiere Daten nach',
|
||||||
'year' => 'Jahr',
|
'year' => 'Jahr',
|
||||||
'view_statement' => 'Zeige Bericht',
|
'view_statement' => 'Zeige Bericht',
|
||||||
'statement' => 'Bericht',
|
'statement' => 'Bericht',
|
||||||
@ -2396,7 +2398,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
|
|||||||
'create_vendor' => 'Lieferanten erstellen',
|
'create_vendor' => 'Lieferanten erstellen',
|
||||||
'create_expense_category' => 'Kategorie erstellen',
|
'create_expense_category' => 'Kategorie erstellen',
|
||||||
'pro_plan_reports' => ':link to enable reports by joining the Pro Plan',
|
'pro_plan_reports' => ':link to enable reports by joining the Pro Plan',
|
||||||
'mark_ready' => 'Mark Ready',
|
'mark_ready' => 'Als bereit markieren',
|
||||||
|
|
||||||
'limits' => 'Limits',
|
'limits' => 'Limits',
|
||||||
'fees' => 'Gebühren',
|
'fees' => 'Gebühren',
|
||||||
@ -2409,31 +2411,31 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
|
|||||||
'gateway_fees_disclaimer' => 'Warning: not all states/payment gateways allow adding fees, please review local laws/terms of service.',
|
'gateway_fees_disclaimer' => 'Warning: not all states/payment gateways allow adding fees, please review local laws/terms of service.',
|
||||||
'percent' => 'Prozent',
|
'percent' => 'Prozent',
|
||||||
'location' => 'Ort',
|
'location' => 'Ort',
|
||||||
'line_item' => 'Line Item',
|
'line_item' => 'Posten',
|
||||||
'surcharge' => 'Gebühr',
|
'surcharge' => 'Gebühr',
|
||||||
'location_first_surcharge' => 'Enabled - First surcharge',
|
'location_first_surcharge' => 'Enabled - First surcharge',
|
||||||
'location_second_surcharge' => 'Enabled - Second surcharge',
|
'location_second_surcharge' => 'Enabled - Second surcharge',
|
||||||
'location_line_item' => 'Enabled - Line item',
|
'location_line_item' => 'Aktiv - Posten',
|
||||||
'online_payment_surcharge' => 'Online Payment Surcharge',
|
'online_payment_surcharge' => 'Online Payment Surcharge',
|
||||||
'gateway_fees' => 'Gateway Fees',
|
'gateway_fees' => 'Gateway Fees',
|
||||||
'fees_disabled' => 'Gebühren sind deaktiviert',
|
'fees_disabled' => 'Gebühren sind deaktiviert',
|
||||||
'gateway_fees_help' => 'Automatically add an online payment surcharge/discount.',
|
'gateway_fees_help' => 'Automatically add an online payment surcharge/discount.',
|
||||||
'gateway' => 'Gateway',
|
'gateway' => 'Provider',
|
||||||
'gateway_fee_change_warning' => 'If there are unpaid invoices with fees they need to be updated manually.',
|
'gateway_fee_change_warning' => 'If there are unpaid invoices with fees they need to be updated manually.',
|
||||||
'fees_surcharge_help' => 'Gebühren anpassen :link.',
|
'fees_surcharge_help' => 'Gebühren anpassen :link.',
|
||||||
'label_and_taxes' => 'label and taxes',
|
'label_and_taxes' => 'label and taxes',
|
||||||
'billable' => 'Billable',
|
'billable' => 'Abrechenbar',
|
||||||
'logo_warning_too_large' => 'The image file is too large.',
|
'logo_warning_too_large' => 'Die Bilddatei ist zu groß.',
|
||||||
'logo_warning_fileinfo' => 'Warning: To support gifs the fileinfo PHP extension needs to be enabled.',
|
'logo_warning_fileinfo' => 'Warnung: Um gif-Dateien zu unterstützen muss die fileinfo PHP-Erweiterung aktiv sein.',
|
||||||
'logo_warning_invalid' => 'There was a problem reading the image file, please try a different format.',
|
'logo_warning_invalid' => 'Es gab ein Problem beim Einlesen der Bilddatei. Bitte verwende ein anderes Dateiformat.',
|
||||||
|
|
||||||
'error_refresh_page' => 'An error occurred, please refresh the page and try again.',
|
'error_refresh_page' => 'Es ist ein Fehler aufgetreten. Bitte aktualisiere die Webseite und probiere es erneut.',
|
||||||
'data' => 'Data',
|
'data' => 'Data',
|
||||||
'imported_settings' => 'Einstellungen erfolgreich aktualisiert',
|
'imported_settings' => 'Einstellungen erfolgreich aktualisiert',
|
||||||
'lang_Greek' => 'Griechisch',
|
'lang_Greek' => 'Griechisch',
|
||||||
'reset_counter' => 'Reset Counter',
|
'reset_counter' => 'Zähler-Reset',
|
||||||
'next_reset' => 'Next Reset',
|
'next_reset' => 'Nächster Reset',
|
||||||
'reset_counter_help' => 'Automatically reset the invoice and quote counters.',
|
'reset_counter_help' => 'Setze automatisch den Rechnungs- und Angebotszähler zurück.',
|
||||||
'auto_bill_failed' => 'Auto-billing for invoice :invoice_number failed',
|
'auto_bill_failed' => 'Auto-billing for invoice :invoice_number failed',
|
||||||
'online_payment_discount' => 'Online-Zahlungsrabatt',
|
'online_payment_discount' => 'Online-Zahlungsrabatt',
|
||||||
'created_new_company' => 'Neues Unternehmen erfolgreich erstellt',
|
'created_new_company' => 'Neues Unternehmen erfolgreich erstellt',
|
||||||
@ -2455,7 +2457,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
|
|||||||
'cancel_account_help' => 'Lösche unwiederbringlich das Konto, mitsamt aller Daten und Einstellungen.',
|
'cancel_account_help' => 'Lösche unwiederbringlich das Konto, mitsamt aller Daten und Einstellungen.',
|
||||||
'purge_successful' => 'Erfolgreich Kontodaten gelöscht',
|
'purge_successful' => 'Erfolgreich Kontodaten gelöscht',
|
||||||
'forbidden' => 'Verboten',
|
'forbidden' => 'Verboten',
|
||||||
'purge_data_message' => 'Warning: This will permanently erase your data, there is no undo.',
|
'purge_data_message' => 'Achtung: Alle Daten werden vollständig gelöscht. Dieser Vorgang kann nicht rückgängig gemacht werden.',
|
||||||
'contact_phone' => 'Telefonnummer des Kontakts',
|
'contact_phone' => 'Telefonnummer des Kontakts',
|
||||||
'contact_email' => 'E-Mail-Adresse des Kontakts',
|
'contact_email' => 'E-Mail-Adresse des Kontakts',
|
||||||
'reply_to_email' => 'Antwort-E-Mail-Adresse',
|
'reply_to_email' => 'Antwort-E-Mail-Adresse',
|
||||||
@ -2464,12 +2466,32 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
|
|||||||
'import_complete' => 'Ihr Import wurde erfolgreich abgeschlossen.',
|
'import_complete' => 'Ihr Import wurde erfolgreich abgeschlossen.',
|
||||||
'confirm_account_to_import' => 'Bitte bestätigen Sie Ihr Konto um Daten zu importieren.',
|
'confirm_account_to_import' => 'Bitte bestätigen Sie Ihr Konto um Daten zu importieren.',
|
||||||
'import_started' => 'Ihr Import wurde gestartet, wir senden Ihnen eine E-Mail zu, sobald er abgeschlossen wurde.',
|
'import_started' => 'Ihr Import wurde gestartet, wir senden Ihnen eine E-Mail zu, sobald er abgeschlossen wurde.',
|
||||||
'listening' => 'Listening...',
|
'listening' => 'Höre zu...',
|
||||||
'microphone_help' => 'Say \'new invoice for...\'',
|
'microphone_help' => 'Say "new invoice for [client]" or "show me [client]\'s archived payments"',
|
||||||
'voice_commands' => 'Voice Commands',
|
'voice_commands' => 'Sprachbefehle',
|
||||||
'sample_commands' => 'Sample commands',
|
'sample_commands' => 'Beispiele für Sprachbefehle',
|
||||||
'voice_commands_feedback' => 'We\'re actively working to improve this feature, if there\'s a command you\'d like us to support please email us at :email.',
|
'voice_commands_feedback' => 'We\'re actively working to improve this feature, if there\'s a command you\'d like us to support please email us at :email.',
|
||||||
'payment_type_Venmo' => 'Venmo',
|
'payment_type_Venmo' => 'Venmo',
|
||||||
|
'archived_products' => 'Successfully archived :count products',
|
||||||
|
'recommend_on' => 'We recommend <b>enabling</b> this setting.',
|
||||||
|
'recommend_off' => 'We recommend <b>disabling</b> this setting.',
|
||||||
|
'notes_auto_billed' => 'Auto-billed',
|
||||||
|
'surcharge_label' => 'Surcharge Label',
|
||||||
|
'contact_fields' => 'Contact Fields',
|
||||||
|
'custom_contact_fields_help' => 'Add a field when creating a contact and display the label and value on the PDF.',
|
||||||
|
'datatable_info' => 'Showing :start to :end of :total entries',
|
||||||
|
'credit_total' => 'Credit Total',
|
||||||
|
'mark_billable' => 'Mark billable',
|
||||||
|
'billed' => 'Billed',
|
||||||
|
'company_variables' => 'Company Variables',
|
||||||
|
'client_variables' => 'Client Variables',
|
||||||
|
'invoice_variables' => 'Invoice Variables',
|
||||||
|
'navigation_variables' => 'Navigation Variables',
|
||||||
|
'custom_variables' => 'Custom Variables',
|
||||||
|
'invalid_file' => 'Invalid file type',
|
||||||
|
'add_documents_to_invoice' => 'Add documents to invoice',
|
||||||
|
'mark_expense_paid' => 'Mark paid',
|
||||||
|
'white_label_license_error' => 'Failed to validate the license, check storage/logs/laravel-error.log for more details.',
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -383,7 +383,7 @@ email που είναι συνδεδεμένη με το λογαριασμό σ
|
|||||||
'share_invoice_counter' => 'Μοιραστείτε τον μετρητή τιμολογίου',
|
'share_invoice_counter' => 'Μοιραστείτε τον μετρητή τιμολογίου',
|
||||||
'invoice_issued_to' => 'Έκδοση τιμολογίου προς',
|
'invoice_issued_to' => 'Έκδοση τιμολογίου προς',
|
||||||
'invalid_counter' => 'Για να αποφείγετε πιθανή σύγχυση, παρακαλώ ορίστε σειρά σε τιμολόγιο ή προσφορά',
|
'invalid_counter' => 'Για να αποφείγετε πιθανή σύγχυση, παρακαλώ ορίστε σειρά σε τιμολόγιο ή προσφορά',
|
||||||
'mark_sent' => 'Μαρκάρισε ως Απεσταλμένο',
|
'mark_sent' => 'Σήμανση ως Απεσταλμένο',
|
||||||
'gateway_help_1' => ':link για εγγραφή στο Authorize.net.',
|
'gateway_help_1' => ':link για εγγραφή στο Authorize.net.',
|
||||||
'gateway_help_2' => ':link για εγγραφή στο Authorize.net.',
|
'gateway_help_2' => ':link για εγγραφή στο Authorize.net.',
|
||||||
'gateway_help_17' => ':link για να πάρετε υπογραφή για το API του PayPal.',
|
'gateway_help_17' => ':link για να πάρετε υπογραφή για το API του PayPal.',
|
||||||
@ -851,7 +851,7 @@ email που είναι συνδεδεμένη με το λογαριασμό σ
|
|||||||
'dark' => 'Σκούρο',
|
'dark' => 'Σκούρο',
|
||||||
'industry_help' => 'Χρησιμοποιείται για να παρέχει συγκρίσεις μέσων όρων εταιριών ίδιου μεγέθους στους ίδιους επαγγελματικούς τομείς.',
|
'industry_help' => 'Χρησιμοποιείται για να παρέχει συγκρίσεις μέσων όρων εταιριών ίδιου μεγέθους στους ίδιους επαγγελματικούς τομείς.',
|
||||||
'subdomain_help' => 'Ορίστε τον υποτομέα ή εμφάνιστε το τιμολόγιο στη δική σας ιστοσελίδα',
|
'subdomain_help' => 'Ορίστε τον υποτομέα ή εμφάνιστε το τιμολόγιο στη δική σας ιστοσελίδα',
|
||||||
'website_help' => 'Display the invoice in an iFrame on your own website',
|
'website_help' => 'Εμφανίστε το τιμολόγιο σε ένα iFrame στη δική σας ιστοσελίδα',
|
||||||
'invoice_number_help' => 'Ορίστε ένα πρόθεμα ή χρησιμοποιήστε ένα προσαρμοσμένο μοτίβο για να καθορίζετε δυναμικά τον αριθμό τιμολογίου.',
|
'invoice_number_help' => 'Ορίστε ένα πρόθεμα ή χρησιμοποιήστε ένα προσαρμοσμένο μοτίβο για να καθορίζετε δυναμικά τον αριθμό τιμολογίου.',
|
||||||
'quote_number_help' => 'Ορίστε ένα πρόθεμα ή χρησιμοποιήστε ένα προσαρμοσμένο μοτίβο για να καθορίζετε δυναμικά τον αριθμό προσφοράς.',
|
'quote_number_help' => 'Ορίστε ένα πρόθεμα ή χρησιμοποιήστε ένα προσαρμοσμένο μοτίβο για να καθορίζετε δυναμικά τον αριθμό προσφοράς.',
|
||||||
'custom_client_fields_helps' => 'Προσθέστε ένα πεδίο όταν δημιουργείτε ένα πελάτη και εμφανίστε την ετικέτα και την τιμή στο αρχείο PDF.',
|
'custom_client_fields_helps' => 'Προσθέστε ένα πεδίο όταν δημιουργείτε ένα πελάτη και εμφανίστε την ετικέτα και την τιμή στο αρχείο PDF.',
|
||||||
@ -1042,7 +1042,7 @@ email που είναι συνδεδεμένη με το λογαριασμό σ
|
|||||||
'invoiced_amount' => 'Τιμολογημένο Ποσό',
|
'invoiced_amount' => 'Τιμολογημένο Ποσό',
|
||||||
'invoice_item_fields' => 'Πεδία Προϊόντων Τιμολογίου',
|
'invoice_item_fields' => 'Πεδία Προϊόντων Τιμολογίου',
|
||||||
'custom_invoice_item_fields_help' => 'Προσθέστε ένα πεδίο όταν δημιουργείτε ένα προϊόν τιμολογίου και εμφανίστε την ετικέτα και την τιμή στο αρχείο PDF.',
|
'custom_invoice_item_fields_help' => 'Προσθέστε ένα πεδίο όταν δημιουργείτε ένα προϊόν τιμολογίου και εμφανίστε την ετικέτα και την τιμή στο αρχείο PDF.',
|
||||||
'recurring_invoice_number' => 'Recurring Number',
|
'recurring_invoice_number' => 'Επαναλαμβανόμενος Αριθμός',
|
||||||
'recurring_invoice_number_prefix_help' => 'Ορίστε ένα πρόθεμα που να περιλαμβάνεται στον αριθμό τιμολογίου των επαναλαμβανόμενων τιμολογίων. Η εξ\'ορισμού τιμή είναι \'R\'.',
|
'recurring_invoice_number_prefix_help' => 'Ορίστε ένα πρόθεμα που να περιλαμβάνεται στον αριθμό τιμολογίου των επαναλαμβανόμενων τιμολογίων. Η εξ\'ορισμού τιμή είναι \'R\'.',
|
||||||
|
|
||||||
// Client Passwords
|
// Client Passwords
|
||||||
@ -1711,6 +1711,7 @@ email που είναι συνδεδεμένη με το λογαριασμό σ
|
|||||||
'lang_Spanish - Spain' => 'Ισπανικά Ισπανίας',
|
'lang_Spanish - Spain' => 'Ισπανικά Ισπανίας',
|
||||||
'lang_Swedish' => 'Σουηδικά',
|
'lang_Swedish' => 'Σουηδικά',
|
||||||
'lang_Albanian' => 'Αλβανικά',
|
'lang_Albanian' => 'Αλβανικά',
|
||||||
|
'lang_English - United Kingdom' => 'Αγγλικά - Ηνωμένο Βασίλειο',
|
||||||
|
|
||||||
// Frequencies
|
// Frequencies
|
||||||
'freq_weekly' => 'Εβδομάδα',
|
'freq_weekly' => 'Εβδομάδα',
|
||||||
@ -2256,7 +2257,7 @@ email που είναι συνδεδεμένη με το λογαριασμό σ
|
|||||||
'edit_credit' => 'Επεξεργασία Πίστωσης',
|
'edit_credit' => 'Επεξεργασία Πίστωσης',
|
||||||
'live_preview_help' => 'Εμφάνιση μιας ζωντανής προεπισκόπησης του PDF του τιμολογίου.<br/>Απενεργοποιήστε αυτή την επιλογή για βελτίωση της ταχύτητας επεξεργασίας τιμολογίων.',
|
'live_preview_help' => 'Εμφάνιση μιας ζωντανής προεπισκόπησης του PDF του τιμολογίου.<br/>Απενεργοποιήστε αυτή την επιλογή για βελτίωση της ταχύτητας επεξεργασίας τιμολογίων.',
|
||||||
'force_pdfjs_help' => 'Αντικαταστήστε τον ενσωματωμένο παρουσιαστή PDF στο :chrome_link και :firefox_link.<br/>Ενεργοποιήστε αυτή την επιλογή εάν ο browser σας κατεβάζει αυτόματα το PDF.',
|
'force_pdfjs_help' => 'Αντικαταστήστε τον ενσωματωμένο παρουσιαστή PDF στο :chrome_link και :firefox_link.<br/>Ενεργοποιήστε αυτή την επιλογή εάν ο browser σας κατεβάζει αυτόματα το PDF.',
|
||||||
'force_pdfjs' => 'Παρουσιαστής PDF',
|
'force_pdfjs' => 'Παρεμπόδιση Μεταφόρτωσης',
|
||||||
'redirect_url' => 'URL Ανακατεύθυνσης',
|
'redirect_url' => 'URL Ανακατεύθυνσης',
|
||||||
'redirect_url_help' => 'Εναλλακτικά ορίστε ένα URL για ανακατεύθυνση μετά την πραγματοποίηση μιας πληρωμής.',
|
'redirect_url_help' => 'Εναλλακτικά ορίστε ένα URL για ανακατεύθυνση μετά την πραγματοποίηση μιας πληρωμής.',
|
||||||
'save_draft' => 'Αποθήκευση Πρόχειρου',
|
'save_draft' => 'Αποθήκευση Πρόχειρου',
|
||||||
@ -2293,6 +2294,7 @@ email που είναι συνδεδεμένη με το λογαριασμό σ
|
|||||||
'renew_license' => 'Ανανέωση Άδειας Χρήσης',
|
'renew_license' => 'Ανανέωση Άδειας Χρήσης',
|
||||||
'iphone_app_message' => 'Σκεφτείτε να κατεβάσετε το :link',
|
'iphone_app_message' => 'Σκεφτείτε να κατεβάσετε το :link',
|
||||||
'iphone_app' => 'Εφαρμογή iPhone',
|
'iphone_app' => 'Εφαρμογή iPhone',
|
||||||
|
'android_app' => 'Εφαρμογή Android',
|
||||||
'logged_in' => 'Εισηγμένος',
|
'logged_in' => 'Εισηγμένος',
|
||||||
'switch_to_primary' => 'Αλλάξτε στην πρωτεύουσα επιχείρηση (:name) για να διαχειριστείτε το πλάνο σας.',
|
'switch_to_primary' => 'Αλλάξτε στην πρωτεύουσα επιχείρηση (:name) για να διαχειριστείτε το πλάνο σας.',
|
||||||
'inclusive' => 'Συμπεριλαμβάνεται',
|
'inclusive' => 'Συμπεριλαμβάνεται',
|
||||||
@ -2345,7 +2347,7 @@ email που είναι συνδεδεμένη με το λογαριασμό σ
|
|||||||
'statement' => 'Δήλωση',
|
'statement' => 'Δήλωση',
|
||||||
'statement_date' => 'Ημ/νία Δήλωσης',
|
'statement_date' => 'Ημ/νία Δήλωσης',
|
||||||
'inactivity_logout' => 'Έχετε αποσυνδεθεί λόγω αδράνειας.',
|
'inactivity_logout' => 'Έχετε αποσυνδεθεί λόγω αδράνειας.',
|
||||||
'mark_active' => 'Σήμανση ως Ενεργός',
|
'mark_active' => 'Σήμανση ως Ενεργό',
|
||||||
'send_automatically' => 'Αυτόματη Αποστολή',
|
'send_automatically' => 'Αυτόματη Αποστολή',
|
||||||
'initial_email' => 'Αρχικό Email',
|
'initial_email' => 'Αρχικό Email',
|
||||||
'invoice_not_emailed' => 'Το τιμολόγιο δεν έχει αποσταλεί με email.',
|
'invoice_not_emailed' => 'Το τιμολόγιο δεν έχει αποσταλεί με email.',
|
||||||
@ -2396,7 +2398,7 @@ email που είναι συνδεδεμένη με το λογαριασμό σ
|
|||||||
'create_vendor' => 'Δημιουργία προμηθευτή',
|
'create_vendor' => 'Δημιουργία προμηθευτή',
|
||||||
'create_expense_category' => 'Δημιουργία κατηγορίας',
|
'create_expense_category' => 'Δημιουργία κατηγορίας',
|
||||||
'pro_plan_reports' => ':link για την ενεργοποίηση των εκθέσεων με τη συμμετοχή στο Επαγγελματικό Πλάνο',
|
'pro_plan_reports' => ':link για την ενεργοποίηση των εκθέσεων με τη συμμετοχή στο Επαγγελματικό Πλάνο',
|
||||||
'mark_ready' => 'Μαρκάρισμα ως Έτοιμο',
|
'mark_ready' => 'Σήμανση ως Έτοιμο',
|
||||||
|
|
||||||
'limits' => 'Όρια',
|
'limits' => 'Όρια',
|
||||||
'fees' => 'Προμήθειες',
|
'fees' => 'Προμήθειες',
|
||||||
@ -2464,12 +2466,32 @@ email που είναι συνδεδεμένη με το λογαριασμό σ
|
|||||||
'import_complete' => 'Επιτυχής ολοκλήρωση της εισαγωγής',
|
'import_complete' => 'Επιτυχής ολοκλήρωση της εισαγωγής',
|
||||||
'confirm_account_to_import' => 'Παρακαλώ επιβεβαιώστε το λογαριασμό σας για εισαγωγή δεδομένων',
|
'confirm_account_to_import' => 'Παρακαλώ επιβεβαιώστε το λογαριασμό σας για εισαγωγή δεδομένων',
|
||||||
'import_started' => 'Η εισαγωγή δεδομένων ξεκίνησε, θα σας αποσταλεί email με την ολοκλήρωση.',
|
'import_started' => 'Η εισαγωγή δεδομένων ξεκίνησε, θα σας αποσταλεί email με την ολοκλήρωση.',
|
||||||
'listening' => 'Listening...',
|
'listening' => 'Ηχητική καταγραφή...',
|
||||||
'microphone_help' => 'Say \'new invoice for...\'',
|
'microphone_help' => 'Πείτε "νέο τιμολόγιο για [client]" ή "εμφάνισέ μου τις αρχειοθετημένες πληρωμές του [client]"',
|
||||||
'voice_commands' => 'Voice Commands',
|
'voice_commands' => 'Ηχητικές Εντολές',
|
||||||
'sample_commands' => 'Sample commands',
|
'sample_commands' => 'Δείγματα εντολών',
|
||||||
'voice_commands_feedback' => 'We\'re actively working to improve this feature, if there\'s a command you\'d like us to support please email us at :email.',
|
'voice_commands_feedback' => 'Εργαζόμαστε για να βελτιώσουμε αυτό το χαρακτηριστικό, εάν υπάρχει μία εντολή που θέλετε να υποστηρίζουμε παρακαλούμε στείλτε μας email στο :email.',
|
||||||
'payment_type_Venmo' => 'Venmo',
|
'payment_type_Venmo' => 'Venmo',
|
||||||
|
'archived_products' => 'Επιτυχής αρχειοθέτηση :count προϊόντων',
|
||||||
|
'recommend_on' => 'Προτείνουμε την <b>ενεργοποίηση</b> αυτής της ρύθμισης.',
|
||||||
|
'recommend_off' => 'Προτείνουμε την <b>απενεργοποίηση</b> αυτής της ρύθμισης.',
|
||||||
|
'notes_auto_billed' => 'Αυτόματη χρέωση',
|
||||||
|
'surcharge_label' => 'Ετικέτα Επιβάρυνσης',
|
||||||
|
'contact_fields' => 'Πεδία Επαφής',
|
||||||
|
'custom_contact_fields_help' => 'Προσθέστε ένα πεδίο όταν δημιουργείτε μία επαφή και εμφανίστε την ετικέτα και την τιμή του στο αρχείο PDF.',
|
||||||
|
'datatable_info' => 'Εμφάνιση :start έως :end από :total εγγραφές',
|
||||||
|
'credit_total' => 'Συνολική Πίστωση',
|
||||||
|
'mark_billable' => 'Σήμανση ως χρεώσιμο',
|
||||||
|
'billed' => 'Τιμολογήθηκαν',
|
||||||
|
'company_variables' => 'Μεταβλητές Εταιρίας',
|
||||||
|
'client_variables' => 'Μεταβλητές Πελάτη',
|
||||||
|
'invoice_variables' => 'Μεταβλητές Τιμολογίου',
|
||||||
|
'navigation_variables' => 'Μεταβλητές Πλοήγησης',
|
||||||
|
'custom_variables' => 'Προσαρμοσμένες Μεταβλητές',
|
||||||
|
'invalid_file' => 'Μη έγκυρος τύπος αρχείου',
|
||||||
|
'add_documents_to_invoice' => 'Προσθέστε έγγραφα στο τιμολόγιο',
|
||||||
|
'mark_expense_paid' => 'Σήμανση ως εξοφλημένο',
|
||||||
|
'white_label_license_error' => 'Failed to validate the license, check storage/logs/laravel-error.log for more details.',
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1711,6 +1711,7 @@ $LANG = array(
|
|||||||
'lang_Spanish - Spain' => 'Spanish - Spain',
|
'lang_Spanish - Spain' => 'Spanish - Spain',
|
||||||
'lang_Swedish' => 'Swedish',
|
'lang_Swedish' => 'Swedish',
|
||||||
'lang_Albanian' => 'Albanian',
|
'lang_Albanian' => 'Albanian',
|
||||||
|
'lang_English - United Kingdom' => 'English - United Kingdom',
|
||||||
|
|
||||||
// Frequencies
|
// Frequencies
|
||||||
'freq_weekly' => 'Weekly',
|
'freq_weekly' => 'Weekly',
|
||||||
@ -2256,7 +2257,7 @@ $LANG = array(
|
|||||||
'edit_credit' => 'Edit Credit',
|
'edit_credit' => 'Edit Credit',
|
||||||
'live_preview_help' => 'Display a live PDF preview on the invoice page.<br/>Disable this to improve performance when editing invoices.',
|
'live_preview_help' => 'Display a live PDF preview on the invoice page.<br/>Disable this to improve performance when editing invoices.',
|
||||||
'force_pdfjs_help' => 'Replace the built-in PDF viewer in :chrome_link and :firefox_link.<br/>Enable this if your browser is automatically downloading the PDF.',
|
'force_pdfjs_help' => 'Replace the built-in PDF viewer in :chrome_link and :firefox_link.<br/>Enable this if your browser is automatically downloading the PDF.',
|
||||||
'force_pdfjs' => 'PDF Viewer',
|
'force_pdfjs' => 'Prevent Download',
|
||||||
'redirect_url' => 'Redirect URL',
|
'redirect_url' => 'Redirect URL',
|
||||||
'redirect_url_help' => 'Optionally specify a URL to redirect to after a payment is entered.',
|
'redirect_url_help' => 'Optionally specify a URL to redirect to after a payment is entered.',
|
||||||
'save_draft' => 'Save Draft',
|
'save_draft' => 'Save Draft',
|
||||||
@ -2293,6 +2294,7 @@ $LANG = array(
|
|||||||
'renew_license' => 'Renew License',
|
'renew_license' => 'Renew License',
|
||||||
'iphone_app_message' => 'Consider downloading our :link',
|
'iphone_app_message' => 'Consider downloading our :link',
|
||||||
'iphone_app' => 'iPhone app',
|
'iphone_app' => 'iPhone app',
|
||||||
|
'android_app' => 'Android app',
|
||||||
'logged_in' => 'Logged In',
|
'logged_in' => 'Logged In',
|
||||||
'switch_to_primary' => 'Switch to your primary company (:name) to manage your plan.',
|
'switch_to_primary' => 'Switch to your primary company (:name) to manage your plan.',
|
||||||
'inclusive' => 'Inclusive',
|
'inclusive' => 'Inclusive',
|
||||||
@ -2465,11 +2467,31 @@ $LANG = array(
|
|||||||
'confirm_account_to_import' => 'Please confirm your account to import data.',
|
'confirm_account_to_import' => 'Please confirm your account to import data.',
|
||||||
'import_started' => 'Your import has started, we\'ll send you an email once it completes.',
|
'import_started' => 'Your import has started, we\'ll send you an email once it completes.',
|
||||||
'listening' => 'Listening...',
|
'listening' => 'Listening...',
|
||||||
'microphone_help' => 'Say \'new invoice for...\'',
|
'microphone_help' => 'Say "new invoice for [client]" or "show me [client]\'s archived payments"',
|
||||||
'voice_commands' => 'Voice Commands',
|
'voice_commands' => 'Voice Commands',
|
||||||
'sample_commands' => 'Sample commands',
|
'sample_commands' => 'Sample commands',
|
||||||
'voice_commands_feedback' => 'We\'re actively working to improve this feature, if there\'s a command you\'d like us to support please email us at :email.',
|
'voice_commands_feedback' => 'We\'re actively working to improve this feature, if there\'s a command you\'d like us to support please email us at :email.',
|
||||||
'payment_type_Venmo' => 'Venmo',
|
'payment_type_Venmo' => 'Venmo',
|
||||||
|
'archived_products' => 'Successfully archived :count products',
|
||||||
|
'recommend_on' => 'We recommend <b>enabling</b> this setting.',
|
||||||
|
'recommend_off' => 'We recommend <b>disabling</b> this setting.',
|
||||||
|
'notes_auto_billed' => 'Auto-billed',
|
||||||
|
'surcharge_label' => 'Surcharge Label',
|
||||||
|
'contact_fields' => 'Contact Fields',
|
||||||
|
'custom_contact_fields_help' => 'Add a field when creating a contact and display the label and value on the PDF.',
|
||||||
|
'datatable_info' => 'Showing :start to :end of :total entries',
|
||||||
|
'credit_total' => 'Credit Total',
|
||||||
|
'mark_billable' => 'Mark billable',
|
||||||
|
'billed' => 'Billed',
|
||||||
|
'company_variables' => 'Company Variables',
|
||||||
|
'client_variables' => 'Client Variables',
|
||||||
|
'invoice_variables' => 'Invoice Variables',
|
||||||
|
'navigation_variables' => 'Navigation Variables',
|
||||||
|
'custom_variables' => 'Custom Variables',
|
||||||
|
'invalid_file' => 'Invalid file type',
|
||||||
|
'add_documents_to_invoice' => 'Add documents to invoice',
|
||||||
|
'mark_expense_paid' => 'Mark paid',
|
||||||
|
'white_label_license_error' => 'Failed to validate the license, check storage/logs/laravel-error.log for more details.',
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user