mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-09 20:52:56 +01:00
Merge branch 'release-3.4.0'
This commit is contained in:
commit
2dd141abcf
@ -32,9 +32,6 @@ PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'
|
||||
LOG=single
|
||||
REQUIRE_HTTPS=false
|
||||
API_SECRET=password
|
||||
IOS_DEVICE=
|
||||
ANDROID_DEVICE=
|
||||
FCM_API_TOKEN=
|
||||
|
||||
#TRUSTED_PROXIES=
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
## [Hosted](https://www.invoiceninja.com) | [Self-Hosted](https://www.invoiceninja.org) | [iPhone](https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=1220337560&mt=8) | [Android](https://play.google.com/store/apps/details?id=com.invoiceninja.invoiceninja)
|
||||
|
||||
Watch this [YouTube Video](https://www.youtube.com/watch?v=xHGKvadapbA) for an overview of the app's features.
|
||||
Watch this [YouTube video](https://www.youtube.com/watch?v=xHGKvadapbA) for an overview of the app's features.
|
||||
|
||||
All Pro and Enterprise features from the hosted app are included in the open-source code. We offer a $20 per year white-label license to remove our branding.
|
||||
|
||||
|
90
app/Console/Commands/CalculatePayouts.php
Normal file
90
app/Console/Commands/CalculatePayouts.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\DbServer;
|
||||
use App\Models\User;
|
||||
use App\Models\Company;
|
||||
|
||||
class CalculatePayouts extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'ninja:calculate-payouts';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Calculate referral payouts';
|
||||
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Running CalculatePayouts...');
|
||||
|
||||
$servers = DbServer::orderBy('id')->get(['name']);
|
||||
$userMap = [];
|
||||
|
||||
foreach ($servers as $server) {
|
||||
$this->info('Processing users: ' . $server->name);
|
||||
config(['database.default' => $server->name]);
|
||||
|
||||
$users = User::where('referral_code', '!=', '')
|
||||
->get(['email', 'referral_code']);
|
||||
foreach ($users as $user) {
|
||||
$userMap[$user->referral_code] = $user->email;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($servers as $server) {
|
||||
$this->info('Processing companies: ' . $server->name);
|
||||
config(['database.default' => $server->name]);
|
||||
|
||||
$companies = Company::where('referral_code', '!=', '')
|
||||
->with('payment.client.payments')
|
||||
->whereNotNull('payment_id')
|
||||
->get();
|
||||
|
||||
foreach ($companies as $company) {
|
||||
$user = $userMap[$company->referral_code];
|
||||
$payment = $company->payment;
|
||||
$client = $payment->client;
|
||||
|
||||
$this->info("User: $user");
|
||||
|
||||
foreach ($client->payments as $payment) {
|
||||
$this->info("Date: $payment->payment_date, Amount: $payment->amount, Reference: $payment->transaction_reference");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
}
|
@ -10,6 +10,7 @@ use Mail;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Utils;
|
||||
use App\Models\Contact;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Invitation;
|
||||
|
||||
/*
|
||||
@ -71,6 +72,7 @@ class CheckData extends Command
|
||||
if (! $this->option('client_id')) {
|
||||
$this->checkBlankInvoiceHistory();
|
||||
$this->checkPaidToDate();
|
||||
$this->checkDraftSentInvoices();
|
||||
}
|
||||
|
||||
$this->checkBalances();
|
||||
@ -87,7 +89,6 @@ class CheckData extends Command
|
||||
|
||||
$this->logMessage('Done: ' . strtoupper($this->isValid ? RESULT_SUCCESS : RESULT_FAILURE));
|
||||
$errorEmail = env('ERROR_EMAIL');
|
||||
$this->info($this->log);
|
||||
|
||||
if ($errorEmail) {
|
||||
Mail::raw($this->log, function ($message) use ($errorEmail, $database) {
|
||||
@ -102,9 +103,34 @@ class CheckData extends Command
|
||||
|
||||
private function logMessage($str)
|
||||
{
|
||||
$str = date('Y-m-d h:i:s') . ' ' . $str;
|
||||
$this->info($str);
|
||||
$this->log .= $str . "\n";
|
||||
}
|
||||
|
||||
private function checkDraftSentInvoices()
|
||||
{
|
||||
$invoices = Invoice::whereInvoiceStatusId(INVOICE_STATUS_SENT)
|
||||
->whereIsPublic(false)
|
||||
->withTrashed()
|
||||
->get();
|
||||
|
||||
$this->logMessage(count($invoices) . ' draft sent invoices');
|
||||
|
||||
if (count($invoices) > 0) {
|
||||
$this->isValid = false;
|
||||
}
|
||||
|
||||
if ($this->option('fix') == 'true') {
|
||||
foreach ($invoices as $invoice) {
|
||||
if ($invoice->is_deleted) {
|
||||
$invoice->unsetEventDispatcher();
|
||||
}
|
||||
$invoice->markSent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function checkOAuth()
|
||||
{
|
||||
// check for duplicate oauth ids
|
||||
@ -334,7 +360,10 @@ class CheckData extends Command
|
||||
private function checkInvitations()
|
||||
{
|
||||
$invoices = DB::table('invoices')
|
||||
->leftJoin('invitations', 'invitations.invoice_id', '=', 'invoices.id')
|
||||
->leftJoin('invitations', function ($join) {
|
||||
$join->on('invitations.invoice_id', '=', 'invoices.id')
|
||||
->whereNull('invitations.deleted_at');
|
||||
})
|
||||
->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']);
|
||||
@ -398,7 +427,6 @@ class CheckData extends Command
|
||||
],
|
||||
'products' => [
|
||||
ENTITY_USER,
|
||||
ENTITY_TAX_RATE,
|
||||
],
|
||||
'vendors' => [
|
||||
ENTITY_USER,
|
||||
@ -413,25 +441,17 @@ class CheckData extends Command
|
||||
ENTITY_USER,
|
||||
ENTITY_CLIENT,
|
||||
],
|
||||
'accounts' => [
|
||||
ENTITY_TAX_RATE,
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($tables as $table => $entityTypes) {
|
||||
foreach ($entityTypes as $entityType) {
|
||||
$tableName = Utils::pluralizeEntityType($entityType);
|
||||
if ($entityType == ENTITY_TAX_RATE) {
|
||||
$field = 'default_' . $entityType;
|
||||
} else {
|
||||
$field = $entityType;
|
||||
}
|
||||
$field = $entityType;
|
||||
if ($table == 'accounts') {
|
||||
$accountId = 'id';
|
||||
} else {
|
||||
$accountId = 'account_id';
|
||||
}
|
||||
|
||||
$records = DB::table($table)
|
||||
->join($tableName, "{$tableName}.id", '=', "{$table}.{$field}_id")
|
||||
->where("{$table}.{$accountId}", '!=', DB::raw("{$tableName}.account_id"))
|
||||
|
@ -315,7 +315,9 @@ class InitLookup extends Command
|
||||
|
||||
private function logMessage($str)
|
||||
{
|
||||
$this->log .= date('Y-m-d h:i:s') . ' ' . $str . "\n";
|
||||
$str = date('Y-m-d h:i:s') . ' ' . $str;
|
||||
$this->info($str);
|
||||
$this->log .= $str . "\n";
|
||||
}
|
||||
|
||||
private function logError($str)
|
||||
|
@ -84,12 +84,13 @@ class SendRecurringInvoices extends Command
|
||||
|
||||
foreach ($invoices as $recurInvoice) {
|
||||
$shouldSendToday = $recurInvoice->shouldSendToday();
|
||||
$this->info('Processing Invoice '.$recurInvoice->id.' - Should send '.($shouldSendToday ? 'YES' : 'NO'));
|
||||
|
||||
if (! $shouldSendToday) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->info('Processing Invoice: '. $recurInvoice->id);
|
||||
|
||||
$account = $recurInvoice->account;
|
||||
$account->loadLocalizationSettings($recurInvoice->client);
|
||||
Auth::loginUsingId($recurInvoice->user_id);
|
||||
@ -117,7 +118,7 @@ class SendRecurringInvoices extends Command
|
||||
}
|
||||
|
||||
if ($invoice->getAutoBillEnabled() && $invoice->client->autoBillLater()) {
|
||||
$this->info('Processing Autobill-delayed Invoice ' . $invoice->id);
|
||||
$this->info('Processing Autobill-delayed Invoice: ' . $invoice->id);
|
||||
Auth::loginUsingId($invoice->user_id);
|
||||
$this->paymentService->autoBillInvoice($invoice);
|
||||
Auth::logout();
|
||||
|
@ -28,6 +28,7 @@ class Kernel extends ConsoleKernel
|
||||
'App\Console\Commands\MakeModule',
|
||||
'App\Console\Commands\MakeClass',
|
||||
'App\Console\Commands\InitLookup',
|
||||
'App\Console\Commands\CalculatePayouts',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -138,7 +138,7 @@ if (! defined('APP_NAME')) {
|
||||
define('LOGGED_ERROR_LIMIT', 100);
|
||||
define('RANDOM_KEY_LENGTH', 32);
|
||||
define('MAX_NUM_USERS', 20);
|
||||
define('MAX_IMPORT_ROWS', 1000);
|
||||
define('MAX_IMPORT_ROWS', 5000);
|
||||
define('MAX_SUBDOMAIN_LENGTH', 30);
|
||||
define('MAX_IFRAME_URL_LENGTH', 250);
|
||||
define('MAX_LOGO_FILE_SIZE', 200); // KB
|
||||
@ -162,6 +162,7 @@ if (! defined('APP_NAME')) {
|
||||
define('IMPORT_ZOHO', 'Zoho');
|
||||
define('IMPORT_NUTCACHE', 'Nutcache');
|
||||
define('IMPORT_INVOICEABLE', 'Invoiceable');
|
||||
define('IMPORT_INVOICEPLANE', 'InvoicePlane');
|
||||
define('IMPORT_HARVEST', 'Harvest');
|
||||
|
||||
define('MAX_NUM_CLIENTS', 100);
|
||||
@ -206,7 +207,9 @@ if (! defined('APP_NAME')) {
|
||||
define('EXPENSE_STATUS_PAID', 5);
|
||||
define('EXPENSE_STATUS_UNPAID', 6);
|
||||
|
||||
define('CUSTOM_DESIGN', 11);
|
||||
define('CUSTOM_DESIGN1', 11);
|
||||
define('CUSTOM_DESIGN2', 12);
|
||||
define('CUSTOM_DESIGN3', 13);
|
||||
|
||||
define('FREQUENCY_WEEKLY', 1);
|
||||
define('FREQUENCY_TWO_WEEKS', 2);
|
||||
@ -300,7 +303,7 @@ if (! defined('APP_NAME')) {
|
||||
define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com'));
|
||||
define('NINJA_DOCS_URL', env('NINJA_DOCS_URL', 'http://docs.invoiceninja.com/en/latest'));
|
||||
define('NINJA_DATE', '2000-01-01');
|
||||
define('NINJA_VERSION', '3.3.3' . env('NINJA_VERSION_SUFFIX'));
|
||||
define('NINJA_VERSION', '3.4.0' . env('NINJA_VERSION_SUFFIX'));
|
||||
|
||||
define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'));
|
||||
define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja'));
|
||||
@ -330,6 +333,7 @@ if (! defined('APP_NAME')) {
|
||||
define('MSBOT_LUIS_URL', 'https://westus.api.cognitive.microsoft.com/luis/v2.0/apps');
|
||||
define('SKYPE_API_URL', 'https://apis.skype.com/v3');
|
||||
define('MSBOT_STATE_URL', 'https://state.botframework.com/v3');
|
||||
define('INVOICEPLANE_IMPORT', 'https://github.com/turbo124/Plane2Ninja');
|
||||
|
||||
define('BOT_PLATFORM_WEB_APP', 'WebApp');
|
||||
define('BOT_PLATFORM_SKYPE', 'Skype');
|
||||
@ -341,7 +345,6 @@ if (! defined('APP_NAME')) {
|
||||
define('DB_NINJA_2', 'db-ninja-2');
|
||||
|
||||
define('COUNT_FREE_DESIGNS', 4);
|
||||
define('COUNT_FREE_DESIGNS_SELF_HOST', 5); // include the custom design
|
||||
define('PRODUCT_ONE_CLICK_INSTALL', 1);
|
||||
define('PRODUCT_INVOICE_DESIGNS', 2);
|
||||
define('PRODUCT_WHITE_LABEL', 3);
|
||||
|
@ -59,13 +59,18 @@ class Handler extends ExceptionHandler
|
||||
if (Utils::isNinja() && strpos(request()->url(), '/logo/') !== false) {
|
||||
return false;
|
||||
}
|
||||
// Log 404s to a separate file
|
||||
$errorStr = date('Y-m-d h:i:s') . ' ' . request()->url() . "\n" . json_encode(Utils::prepareErrorData('PHP')) . "\n\n";
|
||||
@file_put_contents(storage_path('logs/not_found.log'), $errorStr, FILE_APPEND);
|
||||
return false;
|
||||
} elseif ($e instanceof HttpResponseException) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Utils::isNinja() && ! Utils::isTravis()) {
|
||||
if (! Utils::isTravis()) {
|
||||
Utils::logError(Utils::getErrorString($e));
|
||||
|
||||
$stacktrace = date('Y-m-d h:i:s') . ' ' . $e->getTraceAsString() . "\n\n";
|
||||
@file_put_contents(storage_path('logs/stacktrace.log'), $stacktrace, FILE_APPEND);
|
||||
return false;
|
||||
} else {
|
||||
return parent::report($e);
|
||||
|
@ -82,7 +82,7 @@ class AccountApiController extends BaseAPIController
|
||||
$updatedAt = $request->updated_at ? date('Y-m-d H:i:s', $request->updated_at) : false;
|
||||
|
||||
$transformer = new AccountTransformer(null, $request->serializer);
|
||||
$account->load(array_merge($transformer->getDefaultIncludes(), ['projects.client', 'products.default_tax_rate']));
|
||||
$account->load(array_merge($transformer->getDefaultIncludes(), ['projects.client']));
|
||||
$account = $this->createItem($account, $transformer, 'account');
|
||||
|
||||
return $this->response($account);
|
||||
|
@ -373,9 +373,18 @@ class AccountController extends BaseController
|
||||
private function showAccountManagement()
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
$planDetails = $account->getPlanDetails(true);
|
||||
$portalLink = false;
|
||||
|
||||
if ($planDetails && $ninjaClient = $this->accountRepo->getNinjaClient($account)) {
|
||||
$contact = $ninjaClient->getPrimaryContact();
|
||||
$portalLink = $contact->link;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'account' => $account,
|
||||
'planDetails' => $account->getPlanDetails(true),
|
||||
'portalLink' => $portalLink,
|
||||
'planDetails' => $planDetails,
|
||||
'title' => trans('texts.account_management'),
|
||||
];
|
||||
|
||||
@ -486,7 +495,7 @@ class AccountController extends BaseController
|
||||
$data = [
|
||||
'account' => Auth::user()->account,
|
||||
'title' => trans('texts.tax_rates'),
|
||||
'taxRates' => TaxRate::scope()->whereIsInclusive(false)->get(['id', 'name', 'rate']),
|
||||
'taxRates' => TaxRate::scope()->whereIsInclusive(false)->get(),
|
||||
];
|
||||
|
||||
return View::make('accounts.tax_rates', $data);
|
||||
@ -571,7 +580,11 @@ class AccountController extends BaseController
|
||||
}
|
||||
|
||||
if ($section == ACCOUNT_CUSTOMIZE_DESIGN) {
|
||||
$data['customDesign'] = ($account->custom_design && ! $design) ? $account->custom_design : $design;
|
||||
if ($custom = $account->getCustomDesign(request()->design_id)) {
|
||||
$data['customDesign'] = $custom;
|
||||
} else {
|
||||
$data['customDesign'] = $design;
|
||||
}
|
||||
|
||||
// sample invoice to help determine variables
|
||||
$invoice = Invoice::scope()
|
||||
@ -736,16 +749,21 @@ class AccountController extends BaseController
|
||||
*/
|
||||
private function saveCustomizeDesign()
|
||||
{
|
||||
$designId = intval(Input::get('design_id')) ?: CUSTOM_DESIGN1;
|
||||
$field = 'custom_design' . ($designId - 10);
|
||||
|
||||
if (Auth::user()->account->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN)) {
|
||||
$account = Auth::user()->account;
|
||||
$account->custom_design = Input::get('custom_design');
|
||||
$account->invoice_design_id = CUSTOM_DESIGN;
|
||||
if (! $account->custom_design1) {
|
||||
$account->invoice_design_id = CUSTOM_DESIGN1;
|
||||
}
|
||||
$account->$field = Input::get('custom_design');
|
||||
$account->save();
|
||||
|
||||
Session::flash('message', trans('texts.updated_settings'));
|
||||
}
|
||||
|
||||
return Redirect::to('settings/'.ACCOUNT_CUSTOMIZE_DESIGN);
|
||||
return Redirect::to('settings/' . ACCOUNT_CUSTOMIZE_DESIGN . '?design_id=' . $designId);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -951,6 +969,7 @@ class AccountController extends BaseController
|
||||
$account->primary_color = Input::get('primary_color');
|
||||
$account->secondary_color = Input::get('secondary_color');
|
||||
$account->invoice_design_id = Input::get('invoice_design_id');
|
||||
$account->quote_design_id = Input::get('quote_design_id');
|
||||
$account->font_size = intval(Input::get('font_size'));
|
||||
$account->page_size = Input::get('page_size');
|
||||
|
||||
@ -996,6 +1015,10 @@ class AccountController extends BaseController
|
||||
$user->notify_approved = Input::get('notify_approved');
|
||||
$user->save();
|
||||
|
||||
$account = $user->account;
|
||||
$account->fill(request()->all());
|
||||
$account->save();
|
||||
|
||||
Session::flash('message', trans('texts.updated_settings'));
|
||||
|
||||
return Redirect::to('settings/'.ACCOUNT_NOTIFICATIONS);
|
||||
@ -1354,20 +1377,18 @@ class AccountController extends BaseController
|
||||
$account = Auth::user()->account;
|
||||
\Log::info("Canceled Account: {$account->name} - {$user->email}");
|
||||
|
||||
$company = $account->company;
|
||||
$refunded = $company->processRefund(Auth::user());
|
||||
$refunded = false;
|
||||
if (! $account->hasMultipleAccounts()) {
|
||||
$company = $account->company;
|
||||
$refunded = $company->processRefund(Auth::user());
|
||||
}
|
||||
|
||||
Document::scope()->each(function ($item, $key) {
|
||||
$item->delete();
|
||||
});
|
||||
|
||||
$this->accountRepo->unlinkAccount($account);
|
||||
|
||||
if ($account->hasMultipleAccounts()) {
|
||||
$account->forceDelete();
|
||||
} else {
|
||||
$account->company->forceDelete();
|
||||
}
|
||||
$account->forceDelete();
|
||||
|
||||
Auth::logout();
|
||||
Session::flush();
|
||||
|
@ -74,6 +74,10 @@ class BaseAPIController extends Controller
|
||||
$entity = $request->entity();
|
||||
$action = $request->action;
|
||||
|
||||
if (! in_array($action, ['archive', 'delete', 'restore'])) {
|
||||
return $this->errorResponse("Action [$action] is not supported");
|
||||
}
|
||||
|
||||
$repo = Utils::toCamelCase($this->entityType) . 'Repo';
|
||||
|
||||
$this->$repo->$action($entity);
|
||||
|
@ -37,7 +37,7 @@ class BaseController extends Controller
|
||||
|
||||
// when restoring redirect to entity
|
||||
if ($action == 'restore' && count($ids) == 1) {
|
||||
return redirect("{$entityTypes}/" . $ids[0] . '/edit');
|
||||
return redirect("{$entityTypes}/" . $ids[0]);
|
||||
// when viewing from a datatable list
|
||||
} elseif (strpos($referer, '/clients/')) {
|
||||
return redirect($referer);
|
||||
|
@ -91,8 +91,8 @@ class ClientPortalController extends BaseController
|
||||
];
|
||||
$invoice->invoice_fonts = $account->getFontsData();
|
||||
|
||||
if ($invoice->invoice_design_id == CUSTOM_DESIGN) {
|
||||
$invoice->invoice_design->javascript = $account->custom_design;
|
||||
if ($design = $account->getCustomDesign($invoice->invoice_design_id)) {
|
||||
$invoice->invoice_design->javascript = $design;
|
||||
} else {
|
||||
$invoice->invoice_design->javascript = $invoice->invoice_design->pdfmake;
|
||||
}
|
||||
@ -200,7 +200,8 @@ class ClientPortalController extends BaseController
|
||||
}
|
||||
|
||||
$invoice = $invitation->invoice;
|
||||
$pdfString = $invoice->getPDFString();
|
||||
$decode = ! request()->base64;
|
||||
$pdfString = $invoice->getPDFString($decode);
|
||||
|
||||
header('Content-Type: application/pdf');
|
||||
header('Content-Length: ' . strlen($pdfString));
|
||||
|
185
app/Http/Controllers/CreditApiController.php
Normal file
185
app/Http/Controllers/CreditApiController.php
Normal file
@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\CreditRequest;
|
||||
use App\Http\Requests\CreateCreditRequest;
|
||||
use App\Http\Requests\UpdateCreditRequest;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Credit;
|
||||
use App\Ninja\Repositories\CreditRepository;
|
||||
use Input;
|
||||
use Response;
|
||||
|
||||
class CreditApiController extends BaseAPIController
|
||||
{
|
||||
protected $creditRepo;
|
||||
|
||||
protected $entityType = ENTITY_CREDIT;
|
||||
|
||||
public function __construct(CreditRepository $creditRepo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->creditRepo = $creditRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/credits",
|
||||
* summary="List credits",
|
||||
* operationId="listCredits",
|
||||
* tags={"credit"},
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="A list of credits",
|
||||
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Credit"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$credits = Credit::scope()
|
||||
->withTrashed()
|
||||
->with(['client'])
|
||||
->orderBy('created_at', 'desc');
|
||||
|
||||
return $this->listResponse($credits);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/credits/{credit_id}",
|
||||
* summary="Retrieve a credit",
|
||||
* operationId="getCredit",
|
||||
* tags={"credit"},
|
||||
* @SWG\Parameter(
|
||||
* in="path",
|
||||
* name="credit_id",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="A single credit",
|
||||
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Credit"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function show(CreditRequest $request)
|
||||
{
|
||||
return $this->itemResponse($request->entity());
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Post(
|
||||
* path="/credits",
|
||||
* summary="Create a credit",
|
||||
* operationId="createCredit",
|
||||
* tags={"credit"},
|
||||
* @SWG\Parameter(
|
||||
* in="body",
|
||||
* name="credit",
|
||||
* @SWG\Schema(ref="#/definitions/Credit")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="New credit",
|
||||
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Credit"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function store(CreateCreditRequest $request)
|
||||
{
|
||||
$credit = $this->creditRepo->save($request->input());
|
||||
|
||||
return $this->itemResponse($credit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Put(
|
||||
* path="/credits/{credit_id}",
|
||||
* summary="Update a credit",
|
||||
* operationId="updateCredit",
|
||||
* tags={"credit"},
|
||||
* @SWG\Parameter(
|
||||
* in="path",
|
||||
* name="credit_id",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* in="body",
|
||||
* name="credit",
|
||||
* @SWG\Schema(ref="#/definitions/Credit")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Updated credit",
|
||||
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Credit"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param mixed $publicId
|
||||
*/
|
||||
public function update(UpdateCreditRequest $request, $publicId)
|
||||
{
|
||||
if ($request->action) {
|
||||
return $this->handleAction($request);
|
||||
}
|
||||
|
||||
$data = $request->input();
|
||||
$data['public_id'] = $publicId;
|
||||
$credit = $this->creditRepo->save($data, $request->entity());
|
||||
|
||||
return $this->itemResponse($credit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Delete(
|
||||
* path="/credits/{credit_id}",
|
||||
* summary="Delete a credit",
|
||||
* operationId="deleteCredit",
|
||||
* tags={"credit"},
|
||||
* @SWG\Parameter(
|
||||
* in="path",
|
||||
* name="credit_id",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Deleted credit",
|
||||
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Credit"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function destroy(UpdateCreditRequest $request)
|
||||
{
|
||||
$credit = $request->entity();
|
||||
|
||||
$this->creditRepo->delete($credit);
|
||||
|
||||
return $this->itemResponse($credit);
|
||||
}
|
||||
}
|
@ -85,6 +85,18 @@ class CreditController extends BaseController
|
||||
return View::make('credits.edit', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $publicId
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function show($publicId)
|
||||
{
|
||||
Session::reflash();
|
||||
|
||||
return Redirect::to("credits/{$publicId}/edit");
|
||||
}
|
||||
|
||||
public function update(UpdateCreditRequest $request)
|
||||
{
|
||||
$credit = $request->entity();
|
||||
|
@ -79,8 +79,8 @@ class DashboardController extends BaseController
|
||||
'tasks' => $tasks,
|
||||
'showBlueVinePromo' => $showBlueVinePromo,
|
||||
'showWhiteLabelExpired' => $showWhiteLabelExpired,
|
||||
'headerClass' => in_array(\App::getLocale(), ['lt', 'pl', 'cs', 'sl']) ? 'in-large' : 'in-thin',
|
||||
'footerClass' => in_array(\App::getLocale(), ['lt', 'pl', 'cs', 'sl']) ? '' : 'in-thin',
|
||||
'headerClass' => in_array(\App::getLocale(), ['lt', 'pl', 'cs', 'sl', 'tr_TR']) ? 'in-large' : 'in-thin',
|
||||
'footerClass' => in_array(\App::getLocale(), ['lt', 'pl', 'cs', 'sl', 'tr_TR']) ? '' : 'in-thin',
|
||||
];
|
||||
|
||||
if ($showBlueVinePromo) {
|
||||
|
@ -169,6 +169,17 @@ class ExpenseController extends BaseController
|
||||
$data = $request->input();
|
||||
$data['documents'] = $request->file('documents');
|
||||
|
||||
// check for possible duplicate expense
|
||||
$duplcate = Expense::scope()
|
||||
->whereAmount($request->amount)
|
||||
->whereExpenseDate(Utils::toSqlDate($request->expense_date))
|
||||
->orderBy('created_at')
|
||||
->first();
|
||||
if ($duplcate) {
|
||||
Session::flash('warning', trans('texts.duplicate_expense_warning',
|
||||
['link' => link_to($duplcate->present()->url, trans('texts.expense_link'), ['target' => '_blank'])]));
|
||||
}
|
||||
|
||||
$expense = $this->expenseService->save($data);
|
||||
|
||||
Session::flash('message', trans('texts.created_expense'));
|
||||
|
@ -134,4 +134,19 @@ class ImportController extends BaseController
|
||||
return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT);
|
||||
}
|
||||
}
|
||||
|
||||
public function cancelImport()
|
||||
{
|
||||
try {
|
||||
$path = env('FILE_IMPORT_PATH') ?: storage_path() . '/import';
|
||||
foreach ([ENTITY_CLIENT, ENTITY_INVOICE, ENTITY_PAYMENT, ENTITY_QUOTE, ENTITY_PRODUCT] as $entityType) {
|
||||
$fileName = sprintf('%s/%s_%s_%s.csv', $path, Auth::user()->account_id, request()->timestamp, $entityType);
|
||||
\File::delete($fileName);
|
||||
}
|
||||
} catch (Exception $exception) {
|
||||
Utils::logError($exception);
|
||||
}
|
||||
|
||||
return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT);
|
||||
}
|
||||
}
|
||||
|
@ -149,6 +149,7 @@ class InvoiceApiController extends BaseAPIController
|
||||
'country_id',
|
||||
'private_notes',
|
||||
'currency_code',
|
||||
'country_code',
|
||||
] as $field) {
|
||||
if (isset($data[$field])) {
|
||||
$clientData[$field] = $data[$field];
|
||||
@ -185,7 +186,7 @@ class InvoiceApiController extends BaseAPIController
|
||||
$invoice = $this->invoiceService->save($data);
|
||||
$payment = false;
|
||||
|
||||
if ($invoice->isInvoice()) {
|
||||
if ($invoice->isStandard()) {
|
||||
if ($isAutoBill) {
|
||||
$payment = $this->paymentService->autoBillInvoice($invoice);
|
||||
} elseif ($isPaid) {
|
||||
@ -251,6 +252,10 @@ class InvoiceApiController extends BaseAPIController
|
||||
$fields['due_date_sql'] = false;
|
||||
}
|
||||
|
||||
if (isset($data['is_quote']) && filter_var($data['is_quote'], FILTER_VALIDATE_BOOLEAN)) {
|
||||
$fields['invoice_design_id'] = $account->quote_design_id;
|
||||
}
|
||||
|
||||
foreach ($fields as $key => $val) {
|
||||
if (! isset($data[$key])) {
|
||||
$data[$key] = $val;
|
||||
@ -317,7 +322,7 @@ class InvoiceApiController extends BaseAPIController
|
||||
}
|
||||
|
||||
$headers = Utils::getApiHeaders();
|
||||
$response = json_encode(RESULT_SUCCESS, JSON_PRETTY_PRINT);
|
||||
$response = json_encode(['message' => RESULT_SUCCESS], JSON_PRETTY_PRINT);
|
||||
|
||||
return Response::make($response, 200, $headers);
|
||||
}
|
||||
|
@ -302,9 +302,8 @@ class InvoiceController extends BaseController
|
||||
return [
|
||||
'data' => Input::old('data'),
|
||||
'account' => Auth::user()->account->load('country'),
|
||||
'products' => Product::scope()->with('default_tax_rate')->orderBy('product_key')->get(),
|
||||
'products' => Product::scope()->orderBy('product_key')->get(),
|
||||
'taxRateOptions' => $taxRateOptions,
|
||||
'defaultTax' => $account->default_tax_rate,
|
||||
'currencies' => Cache::get('currencies'),
|
||||
'sizes' => Cache::get('sizes'),
|
||||
'invoiceDesigns' => InvoiceDesign::getDesigns(),
|
||||
|
@ -346,8 +346,10 @@ class OnlinePaymentController extends BaseController
|
||||
'frequency_id' => Input::get('frequency_id'),
|
||||
'auto_bill_id' => Input::get('auto_bill_id'),
|
||||
'start_date' => Input::get('start_date', date('Y-m-d')),
|
||||
'tax_rate1' => $account->default_tax_rate ? $account->default_tax_rate->rate : 0,
|
||||
'tax_name1' => $account->default_tax_rate ? $account->default_tax_rate->name : '',
|
||||
'tax_rate1' => $account->tax_rate1,
|
||||
'tax_name1' => $account->tax_name1,
|
||||
'tax_rate2' => $account->tax_rate2,
|
||||
'tax_name2' => $account->tax_name2,
|
||||
'custom_text_value1' => Input::get('custom_invoice1'),
|
||||
'custom_text_value2' => Input::get('custom_invoice2'),
|
||||
'invoice_items' => [[
|
||||
@ -355,8 +357,10 @@ class OnlinePaymentController extends BaseController
|
||||
'notes' => $product->notes,
|
||||
'cost' => $product->cost,
|
||||
'qty' => 1,
|
||||
'tax_rate1' => $product->default_tax_rate ? $product->default_tax_rate->rate : 0,
|
||||
'tax_name1' => $product->default_tax_rate ? $product->default_tax_rate->name : '',
|
||||
'tax_rate1' => $account->tax_rate1,
|
||||
'tax_name1' => $account->tax_name1,
|
||||
'tax_rate2' => $account->tax_rate2,
|
||||
'tax_name2' => $account->tax_name2,
|
||||
'custom_value1' => Input::get('custom_product1') ?: $product->custom_value1,
|
||||
'custom_value2' => Input::get('custom_product2') ?: $product->custom_value2,
|
||||
]],
|
||||
|
@ -6,6 +6,7 @@ use App\Http\Requests\CreatePaymentRequest;
|
||||
use App\Http\Requests\PaymentRequest;
|
||||
use App\Http\Requests\UpdatePaymentRequest;
|
||||
use App\Models\Client;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Ninja\Datatables\PaymentDatatable;
|
||||
@ -136,7 +137,10 @@ class PaymentController extends BaseController
|
||||
if ($payment->invoiceJsonBackup()) {
|
||||
$actions[] = ['url' => url("/invoices/invoice_history/{$payment->invoice->public_id}?payment_id={$payment->public_id}"), 'label' => trans('texts.view_invoice')];
|
||||
}
|
||||
|
||||
$actions[] = ['url' => url("/invoices/{$payment->invoice->public_id}/edit"), 'label' => trans('texts.edit_invoice')];
|
||||
$actions[] = DropdownButton::DIVIDER;
|
||||
$actions[] = ['url' => 'javascript:submitAction("email")', 'label' => trans('texts.email_payment')];
|
||||
|
||||
if ($payment->canBeRefunded()) {
|
||||
$actions[] = ['url' => "javascript:showRefundModal({$payment->public_id}, \"{$payment->getCompletedAmount()}\", \"{$payment->present()->completedAmount}\", \"{$payment->present()->currencySymbol}\")", 'label' => trans('texts.refund_payment')];
|
||||
@ -215,7 +219,7 @@ class PaymentController extends BaseController
|
||||
*/
|
||||
public function update(UpdatePaymentRequest $request)
|
||||
{
|
||||
if (in_array($request->action, ['archive', 'delete', 'restore', 'refund'])) {
|
||||
if (in_array($request->action, ['archive', 'delete', 'restore', 'refund', 'email'])) {
|
||||
return self::bulk();
|
||||
}
|
||||
|
||||
@ -234,11 +238,17 @@ class PaymentController extends BaseController
|
||||
$action = Input::get('action');
|
||||
$amount = Input::get('refund_amount');
|
||||
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
|
||||
$count = $this->paymentService->bulk($ids, $action, ['refund_amount' => $amount]);
|
||||
|
||||
if ($count > 0) {
|
||||
$message = Utils::pluralize($action == 'refund' ? 'refunded_payment' : $action.'d_payment', $count);
|
||||
Session::flash('message', $message);
|
||||
if ($action === 'email') {
|
||||
$payment = Payment::scope($ids)->first();
|
||||
$this->contactMailer->sendPaymentConfirmation($payment);
|
||||
Session::flash('message', trans('texts.emailed_payment'));
|
||||
} else {
|
||||
$count = $this->paymentService->bulk($ids, $action, ['refund_amount' => $amount]);
|
||||
if ($count > 0) {
|
||||
$message = Utils::pluralize($action == 'refund' ? 'refunded_payment' : $action.'d_payment', $count);
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->returnBulk(ENTITY_PAYMENT, $action, $ids);
|
||||
|
@ -83,7 +83,7 @@ class ProductController extends BaseController
|
||||
|
||||
$data = [
|
||||
'account' => $account,
|
||||
'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->whereIsInclusive(false)->get(['id', 'name', 'rate']) : null,
|
||||
'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->whereIsInclusive(false)->get() : null,
|
||||
'product' => $product,
|
||||
'entity' => $product,
|
||||
'method' => 'PUT',
|
||||
|
@ -98,9 +98,8 @@ class QuoteController extends BaseController
|
||||
return [
|
||||
'entityType' => ENTITY_QUOTE,
|
||||
'account' => $account,
|
||||
'products' => Product::scope()->with('default_tax_rate')->orderBy('product_key')->get(),
|
||||
'products' => Product::scope()->orderBy('product_key')->get(),
|
||||
'taxRateOptions' => $account->present()->taxRateOptions,
|
||||
'defaultTax' => $account->default_tax_rate,
|
||||
'countries' => Cache::get('countries'),
|
||||
'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(),
|
||||
'taxRates' => TaxRate::scope()->orderBy('name')->get(),
|
||||
|
@ -253,11 +253,9 @@ class TaskController extends BaseController
|
||||
$action = Input::get('action');
|
||||
$ids = Input::get('public_id') ?: (Input::get('id') ?: Input::get('ids'));
|
||||
|
||||
if ($action == 'stop') {
|
||||
if (in_array($action, ['resume', 'stop'])) {
|
||||
$this->taskRepo->save($ids, ['action' => $action]);
|
||||
Session::flash('message', trans('texts.stopped_task'));
|
||||
|
||||
return Redirect::to('tasks');
|
||||
return Redirect::to('tasks')->withMessage(trans($action == 'stop' ? 'texts.stopped_task' : 'texts.resumed_task'));
|
||||
} elseif ($action == 'invoice' || $action == 'add_to_invoice') {
|
||||
$tasks = Task::scope($ids)->with('client')->orderBy('project_id', 'id')->get();
|
||||
$clientPublicId = false;
|
||||
|
@ -22,7 +22,7 @@ class CreateCreditRequest extends CreditRequest
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'client' => 'required',
|
||||
'client_id' => 'required',
|
||||
'amount' => 'required|positive',
|
||||
];
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ class CreatePaymentAPIRequest extends PaymentRequest
|
||||
]);
|
||||
|
||||
$rules = [
|
||||
'amount' => "required|numeric|between:0.01,{$invoice->balance}",
|
||||
'amount' => 'required|numeric|not_in:0',
|
||||
];
|
||||
|
||||
if ($this->payment_type_id == PAYMENT_TYPE_CREDIT) {
|
||||
|
@ -22,7 +22,7 @@ class UpdateCreditRequest extends CreditRequest
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'amount' => 'required|positive',
|
||||
'amount' => 'positive',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -281,6 +281,7 @@ Route::group([
|
||||
|
||||
Route::post('/export', 'ExportController@doExport');
|
||||
Route::post('/import', 'ImportController@doImport');
|
||||
Route::get('/cancel_import', 'ImportController@cancelImport');
|
||||
Route::post('/import_csv', 'ImportController@doImportCSV');
|
||||
|
||||
Route::get('gateways/create/{show_wepay?}', 'AccountGatewayController@create');
|
||||
@ -329,6 +330,7 @@ Route::group(['middleware' => ['lookup:api', 'api'], 'prefix' => 'api/v1'], func
|
||||
Route::resource('invoices', 'InvoiceApiController');
|
||||
Route::resource('payments', 'PaymentApiController');
|
||||
Route::resource('tasks', 'TaskApiController');
|
||||
Route::resource('credits', 'CreditApiController');
|
||||
Route::post('hooks', 'IntegrationController@subscribe');
|
||||
Route::post('email_invoice', 'InvoiceApiController@emailInvoice');
|
||||
Route::get('user_accounts', 'AccountApiController@getUserAccounts');
|
||||
|
@ -50,6 +50,10 @@ class SendPushNotification extends Job implements ShouldQueue
|
||||
*/
|
||||
public function handle(PushService $pushService)
|
||||
{
|
||||
if (config('queue.default') !== 'sync') {
|
||||
$this->invoice->account->loadLocalizationSettings();
|
||||
}
|
||||
|
||||
$pushService->sendNotification($this->invoice, $this->type);
|
||||
}
|
||||
}
|
||||
|
@ -44,4 +44,16 @@ class HTMLUtils
|
||||
|
||||
return $purifier->purify($html);
|
||||
}
|
||||
|
||||
public static function previousUrl($fallback)
|
||||
{
|
||||
$previous = url()->previous();
|
||||
$current = request()->url();
|
||||
|
||||
if ($previous == $current) {
|
||||
return url($fallback);
|
||||
} else {
|
||||
return $previous;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ class HistoryUtils
|
||||
}
|
||||
|
||||
$icon = '<i class="fa fa-' . EntityModel::getIcon($item->entityType . 's') . '" style="width:24px"></i>';
|
||||
$str .= sprintf('<li style="text-align:right; padding-right:18px;"><a href="%s">%s %s</a></li>', $item->url, $item->name, $icon);
|
||||
$str .= sprintf('<li style="text-align:right; padding-right:18px;"><a href="%s">%s %s</a></li>', $item->url, e($item->name), $icon);
|
||||
}
|
||||
|
||||
return $str;
|
||||
|
@ -382,7 +382,18 @@ class Utils
|
||||
return 'logged';
|
||||
}
|
||||
|
||||
$data = [
|
||||
$data = static::prepareErrorData($context);
|
||||
|
||||
if ($info) {
|
||||
Log::info($error."\n", $data);
|
||||
} else {
|
||||
Log::error($error."\n", $data);
|
||||
}
|
||||
}
|
||||
|
||||
public static function prepareErrorData($context)
|
||||
{
|
||||
return [
|
||||
'context' => $context,
|
||||
'user_id' => Auth::check() ? Auth::user()->id : 0,
|
||||
'account_id' => Auth::check() ? Auth::user()->account_id : 0,
|
||||
@ -394,20 +405,9 @@ class Utils
|
||||
'ip' => Request::getClientIp(),
|
||||
'count' => Session::get('error_count', 0),
|
||||
'is_console' => App::runningInConsole() ? 'yes' : 'no',
|
||||
'is_api' => session('token_id') ? 'yes' : 'no',
|
||||
'db_server' => config('database.default'),
|
||||
];
|
||||
|
||||
if ($info) {
|
||||
Log::info($error."\n", $data);
|
||||
} else {
|
||||
Log::error($error."\n", $data);
|
||||
}
|
||||
|
||||
/*
|
||||
Mail::queue('emails.error', ['message'=>$error.' '.json_encode($data)], function($message)
|
||||
{
|
||||
$message->to($email)->subject($subject);
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
public static function parseFloat($value)
|
||||
|
@ -15,19 +15,26 @@ class AnalyticsListener
|
||||
*/
|
||||
public function trackRevenue(PaymentWasCreated $event)
|
||||
{
|
||||
if (! Utils::isNinja() || ! env('ANALYTICS_KEY')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$payment = $event->payment;
|
||||
$invoice = $payment->invoice;
|
||||
$account = $payment->account;
|
||||
|
||||
if (! $account->isNinjaAccount() && $account->account_key != NINJA_LICENSE_ACCOUNT_KEY) {
|
||||
$analyticsId = false;
|
||||
|
||||
if ($account->isNinjaAccount() || $account->account_key == NINJA_LICENSE_ACCOUNT_KEY) {
|
||||
$analyticsId = env('ANALYTICS_KEY');
|
||||
} else {
|
||||
if (Utils::isNinja()) {
|
||||
$analyticsId = $account->analytics_key;
|
||||
} else {
|
||||
$analyticsId = $account->analytics_key ?: env('ANALYTICS_KEY');
|
||||
}
|
||||
}
|
||||
|
||||
if (! $analyticsId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$analyticsId = env('ANALYTICS_KEY');
|
||||
$client = $payment->client;
|
||||
$amount = $payment->amount;
|
||||
$item = $invoice->invoice_items->last()->product_key;
|
||||
|
@ -35,8 +35,7 @@ class InvoiceListener
|
||||
$invoice = $event->invoice;
|
||||
$account = Auth::user()->account;
|
||||
|
||||
if ($invoice->invoice_design_id
|
||||
&& $account->invoice_design_id != $invoice->invoice_design_id) {
|
||||
if ($invoice->invoice_design_id && $account->invoice_design_id != $invoice->invoice_design_id) {
|
||||
$account->invoice_design_id = $invoice->invoice_design_id;
|
||||
$account->save();
|
||||
}
|
||||
|
@ -1,20 +1,47 @@
|
||||
<?php namespace App\Listeners;
|
||||
|
||||
use App\Ninja\Mailers\UserMailer;
|
||||
use App\Ninja\Mailers\ContactMailer;
|
||||
use App\Events\InvoiceWasEmailed;
|
||||
use App\Events\QuoteWasEmailed;
|
||||
use App\Events\InvoiceInvitationWasViewed;
|
||||
use App\Events\QuoteInvitationWasViewed;
|
||||
use App\Events\QuoteInvitationWasApproved;
|
||||
use App\Events\PaymentWasCreated;
|
||||
use App\Jobs\SendPaymentEmail;
|
||||
use App\Services\PushService;
|
||||
use App\Jobs\SendNotificationEmail;
|
||||
use App\Jobs\SendPushNotification;
|
||||
|
||||
/**
|
||||
* Class NotificationListener
|
||||
*/
|
||||
class NotificationListener
|
||||
{
|
||||
/**
|
||||
* @var UserMailer
|
||||
*/
|
||||
protected $userMailer;
|
||||
/**
|
||||
* @var ContactMailer
|
||||
*/
|
||||
protected $contactMailer;
|
||||
/**
|
||||
* @var PushService
|
||||
*/
|
||||
protected $pushService;
|
||||
|
||||
/**
|
||||
* NotificationListener constructor.
|
||||
* @param UserMailer $userMailer
|
||||
* @param ContactMailer $contactMailer
|
||||
* @param PushService $pushService
|
||||
*/
|
||||
public function __construct(UserMailer $userMailer, ContactMailer $contactMailer, PushService $pushService)
|
||||
{
|
||||
$this->userMailer = $userMailer;
|
||||
$this->contactMailer = $contactMailer;
|
||||
$this->pushService = $pushService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $invoice
|
||||
* @param $type
|
||||
@ -37,7 +64,7 @@ class NotificationListener
|
||||
public function emailedInvoice(InvoiceWasEmailed $event)
|
||||
{
|
||||
$this->sendEmails($event->invoice, 'sent', null, $event->notes);
|
||||
dispatch(new SendPushNotification($event->invoice, 'sent'));
|
||||
$this->pushService->sendNotification($event->invoice, 'sent');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,7 +73,7 @@ class NotificationListener
|
||||
public function emailedQuote(QuoteWasEmailed $event)
|
||||
{
|
||||
$this->sendEmails($event->quote, 'sent', null, $event->notes);
|
||||
dispatch(new SendPushNotification($event->quote, 'sent'));
|
||||
$this->pushService->sendNotification($event->quote, 'sent');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -59,7 +86,7 @@ class NotificationListener
|
||||
}
|
||||
|
||||
$this->sendEmails($event->invoice, 'viewed');
|
||||
dispatch(new SendPushNotification($event->invoice, 'viewed'));
|
||||
$this->pushService->sendNotification($event->invoice, 'viewed');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,7 +99,7 @@ class NotificationListener
|
||||
}
|
||||
|
||||
$this->sendEmails($event->quote, 'viewed');
|
||||
dispatch(new SendPushNotification($event->quote, 'viewed'));
|
||||
$this->pushService->sendNotification($event->quote, 'viewed');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -81,7 +108,7 @@ class NotificationListener
|
||||
public function approvedQuote(QuoteInvitationWasApproved $event)
|
||||
{
|
||||
$this->sendEmails($event->quote, 'approved');
|
||||
dispatch(new SendPushNotification($event->quote, 'approved'));
|
||||
$this->pushService->sendNotification($event->quote, 'approved');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,9 +121,10 @@ class NotificationListener
|
||||
return;
|
||||
}
|
||||
|
||||
$this->contactMailer->sendPaymentConfirmation($event->payment);
|
||||
$this->sendEmails($event->payment->invoice, 'paid', $event->payment);
|
||||
dispatch(new SendPaymentEmail($event->payment));
|
||||
dispatch(new SendPushNotification($event->payment->invoice, 'paid'));
|
||||
|
||||
$this->pushService->sendNotification($event->payment->invoice, 'paid');
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -68,6 +68,7 @@ class Account extends Eloquent
|
||||
'invoice_taxes',
|
||||
'invoice_item_taxes',
|
||||
'invoice_design_id',
|
||||
'quote_design_id',
|
||||
'work_phone',
|
||||
'work_email',
|
||||
'language_id',
|
||||
@ -110,7 +111,10 @@ class Account extends Eloquent
|
||||
'num_days_reminder3',
|
||||
'custom_invoice_text_label1',
|
||||
'custom_invoice_text_label2',
|
||||
'default_tax_rate_id',
|
||||
'tax_name1',
|
||||
'tax_rate1',
|
||||
'tax_name2',
|
||||
'tax_rate2',
|
||||
'recurring_hour',
|
||||
'invoice_number_pattern',
|
||||
'quote_number_pattern',
|
||||
@ -166,6 +170,7 @@ class Account extends Eloquent
|
||||
'custom_contact_label1',
|
||||
'custom_contact_label2',
|
||||
'domain_id',
|
||||
'analytics_key',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -366,14 +371,6 @@ class Account extends Eloquent
|
||||
return $this->belongsTo('App\Models\Industry');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function default_tax_rate()
|
||||
{
|
||||
return $this->belongsTo('App\Models\TaxRate');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
@ -890,6 +887,7 @@ class Account extends Eloquent
|
||||
} else {
|
||||
if ($entityType == ENTITY_QUOTE) {
|
||||
$invoice->invoice_type_id = INVOICE_TYPE_QUOTE;
|
||||
$invoice->invoice_design_id = $this->quote_design_id;
|
||||
}
|
||||
|
||||
if ($this->hasClientNumberPattern($invoice) && ! $clientId) {
|
||||
|
@ -51,6 +51,7 @@ class Client extends EntityModel
|
||||
'website',
|
||||
'invoice_number_counter',
|
||||
'quote_number_counter',
|
||||
'public_notes',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -69,26 +69,32 @@ class EntityModel extends Eloquent
|
||||
$entity->setRelation('user', $user);
|
||||
$entity->setRelation('account', $account);
|
||||
|
||||
if (method_exists($className, 'trashed')) {
|
||||
$lastEntity = $className::whereAccountId($entity->account_id)->withTrashed();
|
||||
} else {
|
||||
$lastEntity = $className::whereAccountId($entity->account_id);
|
||||
}
|
||||
|
||||
if (static::$hasPublicId) {
|
||||
$lastEntity = $lastEntity->orderBy('public_id', 'DESC')
|
||||
->first();
|
||||
|
||||
if ($lastEntity) {
|
||||
$entity->public_id = $lastEntity->public_id + 1;
|
||||
} else {
|
||||
$entity->public_id = 1;
|
||||
}
|
||||
$entity->public_id = static::getNextPublicId($entity->account_id);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
private static function getNextPublicId($accountId)
|
||||
{
|
||||
$className = get_called_class();
|
||||
|
||||
if (method_exists($className, 'trashed')) {
|
||||
$lastEntity = $className::whereAccountId($accountId)->withTrashed();
|
||||
} else {
|
||||
$lastEntity = $className::whereAccountId($accountId);
|
||||
}
|
||||
|
||||
$lastEntity = $lastEntity->orderBy('public_id', 'DESC')->first();
|
||||
|
||||
if ($lastEntity) {
|
||||
return $lastEntity->public_id + 1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $publicId
|
||||
*
|
||||
@ -379,4 +385,21 @@ class EntityModel extends Eloquent
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function save(array $options = [])
|
||||
{
|
||||
try {
|
||||
return parent::save($options);
|
||||
} catch (\Illuminate\Database\QueryException $exception) {
|
||||
// check if public_id has been taken
|
||||
if ($exception->getCode() == 23000 && static::$hasPublicId) {
|
||||
$nextId = static::getNextPublicId($this->account_id);
|
||||
if ($nextId != $this->public_id) {
|
||||
$this->public_id = $nextId;
|
||||
return $this->save($options);
|
||||
}
|
||||
}
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,9 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
'tax_rate1',
|
||||
'tax_name2',
|
||||
'tax_rate2',
|
||||
'private_notes',
|
||||
'last_sent_date',
|
||||
'invoice_design_id',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -430,7 +432,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isInvoice()
|
||||
public function isStandard()
|
||||
{
|
||||
return $this->isType(INVOICE_TYPE_STANDARD) && ! $this->is_recurring;
|
||||
}
|
||||
@ -612,7 +614,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
|
||||
public function canBePaid()
|
||||
{
|
||||
return floatval($this->balance) != 0 && ! $this->is_deleted && $this->isInvoice();
|
||||
return floatval($this->balance) != 0 && ! $this->is_deleted && $this->isStandard();
|
||||
}
|
||||
|
||||
public static function calcStatusLabel($status, $class, $entityType, $quoteInvoiceId)
|
||||
@ -1239,7 +1241,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
/**
|
||||
* @return bool|string
|
||||
*/
|
||||
public function getPDFString()
|
||||
public function getPDFString($decode = true)
|
||||
{
|
||||
if (! env('PHANTOMJS_CLOUD_KEY') && ! env('PHANTOMJS_BIN_PATH')) {
|
||||
return false;
|
||||
@ -1258,14 +1260,12 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
$pdfString = CurlUtils::phantom('GET', $link . '?phantomjs=true&phantomjs_secret=' . env('PHANTOMJS_SECRET'));
|
||||
}
|
||||
|
||||
if (! $pdfString && (Utils::isNinja() || ! env('PHANTOMJS_BIN_PATH'))) {
|
||||
if ($key = env('PHANTOMJS_CLOUD_KEY')) {
|
||||
if (Utils::isNinjaDev()) {
|
||||
$link = env('TEST_LINK');
|
||||
}
|
||||
$url = "http://api.phantomjscloud.com/api/browser/v2/{$key}/?request=%7Burl:%22{$link}?phantomjs=true%22,renderType:%22html%22%7D";
|
||||
$pdfString = CurlUtils::get($url);
|
||||
if (! $pdfString && ($key = env('PHANTOMJS_CLOUD_KEY'))) {
|
||||
if (Utils::isNinjaDev()) {
|
||||
$link = env('TEST_LINK');
|
||||
}
|
||||
$url = "http://api.phantomjscloud.com/api/browser/v2/{$key}/?request=%7Burl:%22{$link}?phantomjs=true%22,renderType:%22html%22%7D";
|
||||
$pdfString = CurlUtils::get($url);
|
||||
}
|
||||
|
||||
$pdfString = strip_tags($pdfString);
|
||||
@ -1279,11 +1279,15 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($pdf = Utils::decodePDF($pdfString)) {
|
||||
return $pdf;
|
||||
if ($decode) {
|
||||
if ($pdf = Utils::decodePDF($pdfString)) {
|
||||
return $pdf;
|
||||
} else {
|
||||
Utils::logError("PhantomJS - Unable to decode: {$pdfString}");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Utils::logError("PhantomJS - Unable to decode: {$pdfString}");
|
||||
return false;
|
||||
return $pdfString;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,11 +83,11 @@ class InvoiceDesign extends Eloquent
|
||||
$design->javascript = $design->pdfmake;
|
||||
$design->pdfmake = null;
|
||||
|
||||
if ($design->id == CUSTOM_DESIGN) {
|
||||
if ($account->custom_design) {
|
||||
$design->javascript = $account->custom_design;
|
||||
if (in_array($design->id, [CUSTOM_DESIGN1, CUSTOM_DESIGN2, CUSTOM_DESIGN3])) {
|
||||
if ($javascript = $account->getCustomDesign($design->id)) {
|
||||
$design->javascript = $javascript;
|
||||
} else {
|
||||
$designs->pop();
|
||||
$designs->forget($design->id - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,13 @@ class Payment extends EntityModel
|
||||
use PresentableTrait;
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'private_notes',
|
||||
];
|
||||
|
||||
public static $statusClasses = [
|
||||
PAYMENT_STATUS_PENDING => 'info',
|
||||
PAYMENT_STATUS_COMPLETED => 'success',
|
||||
|
@ -30,7 +30,10 @@ class Product extends EntityModel
|
||||
'notes',
|
||||
'cost',
|
||||
'qty',
|
||||
'default_tax_rate_id',
|
||||
'tax_name1',
|
||||
'tax_rate1',
|
||||
'tax_name2',
|
||||
'tax_rate2',
|
||||
'custom_value1',
|
||||
'custom_value2',
|
||||
];
|
||||
@ -84,12 +87,4 @@ class Product extends EntityModel
|
||||
{
|
||||
return $this->belongsTo('App\Models\User')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function default_tax_rate()
|
||||
{
|
||||
return $this->belongsTo('App\Models\TaxRate');
|
||||
}
|
||||
}
|
||||
|
@ -292,4 +292,16 @@ trait PresentsInvoice
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getCustomDesign($designId) {
|
||||
if ($designId == CUSTOM_DESIGN1) {
|
||||
return $this->custom_design1;
|
||||
} elseif ($designId == CUSTOM_DESIGN2) {
|
||||
return $this->custom_design2;
|
||||
} elseif ($designId == CUSTOM_DESIGN3) {
|
||||
return $this->custom_design3;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ class User extends Authenticatable
|
||||
*/
|
||||
public function maxInvoiceDesignId()
|
||||
{
|
||||
return $this->hasFeature(FEATURE_MORE_INVOICE_DESIGNS) ? 11 : (Utils::isNinja() ? COUNT_FREE_DESIGNS : COUNT_FREE_DESIGNS_SELF_HOST);
|
||||
return $this->hasFeature(FEATURE_MORE_INVOICE_DESIGNS) ? 13 : COUNT_FREE_DESIGNS;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -122,6 +122,15 @@ class PaymentDatatable extends EntityDatatable
|
||||
return Auth::user()->can('editByOwner', [ENTITY_PAYMENT, $model->user_id]);
|
||||
},
|
||||
],
|
||||
[
|
||||
trans('texts.email_payment'),
|
||||
function ($model) {
|
||||
return "javascript:submitForm_payment('email', {$model->public_id})";
|
||||
},
|
||||
function ($model) {
|
||||
return Auth::user()->can('editByOwner', [ENTITY_PAYMENT, $model->user_id]);
|
||||
},
|
||||
],
|
||||
[
|
||||
trans('texts.refund_payment'),
|
||||
function ($model) {
|
||||
|
@ -20,7 +20,7 @@ class RecurringInvoiceDatatable extends EntityDatatable
|
||||
$frequency = strtolower($model->frequency);
|
||||
$frequency = preg_replace('/\s/', '_', $frequency);
|
||||
|
||||
return link_to("invoices/{$model->public_id}", trans('texts.freq_'.$frequency))->toHtml();
|
||||
return link_to("recurring_invoices/{$model->public_id}/edit", trans('texts.freq_'.$frequency))->toHtml();
|
||||
},
|
||||
],
|
||||
[
|
||||
@ -42,18 +42,26 @@ class RecurringInvoiceDatatable extends EntityDatatable
|
||||
return Utils::fromSqlDate($model->last_sent_date_sql);
|
||||
},
|
||||
],
|
||||
/*
|
||||
[
|
||||
'end_date',
|
||||
function ($model) {
|
||||
return Utils::fromSqlDate($model->end_date_sql);
|
||||
},
|
||||
],
|
||||
*/
|
||||
[
|
||||
'amount',
|
||||
function ($model) {
|
||||
return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id);
|
||||
},
|
||||
],
|
||||
[
|
||||
'private_notes',
|
||||
function ($model) {
|
||||
return $model->private_notes;
|
||||
},
|
||||
],
|
||||
[
|
||||
'status',
|
||||
function ($model) {
|
||||
|
@ -88,6 +88,15 @@ class TaskDatatable extends EntityDatatable
|
||||
return $model->invoice_number && Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->invoice_user_id]);
|
||||
},
|
||||
],
|
||||
[
|
||||
trans('texts.resume_task'),
|
||||
function ($model) {
|
||||
return "javascript:submitForm_task('resume', {$model->public_id})";
|
||||
},
|
||||
function ($model) {
|
||||
return ! $model->is_running && Auth::user()->can('editByOwner', [ENTITY_TASK, $model->user_id]);
|
||||
},
|
||||
],
|
||||
[
|
||||
trans('texts.stop_task'),
|
||||
function ($model) {
|
||||
@ -103,7 +112,7 @@ class TaskDatatable extends EntityDatatable
|
||||
return "javascript:submitForm_task('invoice', {$model->public_id})";
|
||||
},
|
||||
function ($model) {
|
||||
return ! $model->invoice_number && (! $model->deleted_at || $model->deleted_at == '0000-00-00') && Auth::user()->can('create', ENTITY_INVOICE);
|
||||
return ! $model->is_running && ! $model->invoice_number && (! $model->deleted_at || $model->deleted_at == '0000-00-00') && Auth::user()->can('create', ENTITY_INVOICE);
|
||||
},
|
||||
],
|
||||
];
|
||||
|
@ -83,11 +83,13 @@ class InvoiceIntent extends BaseIntent
|
||||
$item['cost'] = $product->cost;
|
||||
$item['notes'] = $product->notes;
|
||||
|
||||
/*
|
||||
if ($taxRate = $product->default_tax_rate) {
|
||||
$item['tax_name1'] = $taxRate->name;
|
||||
$item['tax_rate1'] = $taxRate->rate;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
$invoiceItems[] = $item;
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,8 @@ class BasePaymentDriver
|
||||
protected $customerReferenceParam;
|
||||
protected $transactionReferenceParam;
|
||||
|
||||
public $canRefundPayments = false;
|
||||
|
||||
public function __construct($accountGateway = false, $invitation = false, $gatewayType = false)
|
||||
{
|
||||
$this->accountGateway = $accountGateway;
|
||||
@ -379,7 +381,7 @@ class BasePaymentDriver
|
||||
'description' => trans('texts.' . $invoice->getEntityType()) . " {$invoice->invoice_number}",
|
||||
'transactionId' => $invoice->invoice_number,
|
||||
'transactionType' => 'Purchase',
|
||||
'ip' => Request::ip(),
|
||||
'ip' => Request::getClientIp(),
|
||||
];
|
||||
|
||||
if ($paymentMethod) {
|
||||
@ -613,6 +615,7 @@ class BasePaymentDriver
|
||||
|
||||
public function createPayment($ref = false, $paymentMethod = null)
|
||||
{
|
||||
$account = $this->account();
|
||||
$invitation = $this->invitation;
|
||||
$invoice = $this->invoice();
|
||||
$invoice->markSentIfUnsent();
|
||||
@ -625,7 +628,7 @@ class BasePaymentDriver
|
||||
$payment->client_id = $invoice->client_id;
|
||||
$payment->contact_id = $invitation->contact_id;
|
||||
$payment->transaction_reference = $ref;
|
||||
$payment->payment_date = date_create()->format('Y-m-d');
|
||||
$payment->payment_date = $account->getDateTime()->format('Y-m-d');
|
||||
$payment->ip = Request::ip();
|
||||
|
||||
$payment = $this->creatingPayment($payment, $paymentMethod);
|
||||
|
@ -11,6 +11,7 @@ class BraintreePaymentDriver extends BasePaymentDriver
|
||||
{
|
||||
protected $customerReferenceParam = 'customerId';
|
||||
protected $sourceReferenceParam = 'paymentMethodToken';
|
||||
public $canRefundPayments = true;
|
||||
|
||||
public function gatewayTypes()
|
||||
{
|
||||
|
@ -10,6 +10,7 @@ use Exception;
|
||||
class StripePaymentDriver extends BasePaymentDriver
|
||||
{
|
||||
protected $customerReferenceParam = 'customerReference';
|
||||
public $canRefundPayments = true;
|
||||
|
||||
public function gatewayTypes()
|
||||
{
|
||||
|
@ -10,6 +10,8 @@ use Utils;
|
||||
|
||||
class WePayPaymentDriver extends BasePaymentDriver
|
||||
{
|
||||
public $canRefundPayments = true;
|
||||
|
||||
public function gatewayTypes()
|
||||
{
|
||||
$types = [
|
||||
|
@ -175,4 +175,24 @@ class AccountPresenter extends Presenter
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function customDesigns()
|
||||
{
|
||||
$account = $this->entity;
|
||||
$data = [];
|
||||
|
||||
for ($i=1; $i<=3; $i++) {
|
||||
$label = trans('texts.custom_design' . $i);
|
||||
if (! $account->{'custom_design' . $i}) {
|
||||
$label .= ' - ' . trans('texts.empty');
|
||||
}
|
||||
|
||||
$data[] = [
|
||||
'url' => url('/settings/customize_design?design_id=') . ($i + 10),
|
||||
'label' => $label
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ class EntityPresenter extends Presenter
|
||||
*/
|
||||
public function url()
|
||||
{
|
||||
return url($this->path());
|
||||
return SITE_URL . $this->path();
|
||||
}
|
||||
|
||||
public function path()
|
||||
|
@ -29,6 +29,14 @@ class InvoicePresenter extends EntityPresenter
|
||||
return $account->formatMoney($invoice->amount, $invoice->client);
|
||||
}
|
||||
|
||||
public function balance()
|
||||
{
|
||||
$invoice = $this->entity;
|
||||
$account = $invoice->account;
|
||||
|
||||
return $account->formatMoney($invoice->balance, $invoice->client);
|
||||
}
|
||||
|
||||
public function requestedAmount()
|
||||
{
|
||||
$invoice = $this->entity;
|
||||
|
@ -30,7 +30,7 @@ class InvoiceReport extends AbstractReport
|
||||
->with(['invoices' => function ($query) use ($status) {
|
||||
if ($status == 'draft') {
|
||||
$query->whereIsPublic(false);
|
||||
} elseif ($status == 'unpaid' || $status == 'paid') {
|
||||
} elseif (in_array($status, ['paid', 'unpaid', 'sent'])) {
|
||||
$query->whereIsPublic(true);
|
||||
}
|
||||
$query->invoices()
|
||||
|
@ -30,7 +30,7 @@ class ProductReport extends AbstractReport
|
||||
->with(['invoices' => function ($query) use ($status) {
|
||||
if ($status == 'draft') {
|
||||
$query->whereIsPublic(false);
|
||||
} elseif ($status == 'unpaid' || $status == 'paid') {
|
||||
} elseif (in_array($status, ['paid', 'unpaid', 'sent'])) {
|
||||
$query->whereIsPublic(true);
|
||||
}
|
||||
$query->invoices()
|
||||
|
@ -87,7 +87,6 @@ class ActivityRepository
|
||||
'activities.created_at',
|
||||
'activities.contact_id',
|
||||
'activities.activity_type_id',
|
||||
'activities.is_system',
|
||||
'activities.balance',
|
||||
'activities.adjustment',
|
||||
'activities.notes',
|
||||
|
@ -106,6 +106,17 @@ class ClientRepository extends BaseRepository
|
||||
}
|
||||
}
|
||||
|
||||
// convert country code to id
|
||||
if (isset($data['country_code'])) {
|
||||
$countryCode = strtolower($data['country_code']);
|
||||
$country = Cache::get('countries')->filter(function ($item) use ($countryCode) {
|
||||
return strtolower($item->iso_3166_2) == $countryCode || strtolower($item->iso_3166_3) == $countryCode;
|
||||
})->first();
|
||||
if ($country) {
|
||||
$data['country_id'] = $country->id;
|
||||
}
|
||||
}
|
||||
|
||||
$client->fill($data);
|
||||
$client->save();
|
||||
|
||||
|
@ -69,7 +69,6 @@ class CreditRepository extends BaseRepository
|
||||
->where('credits.client_id', '=', $clientId)
|
||||
->where('clients.deleted_at', '=', null)
|
||||
->where('credits.deleted_at', '=', null)
|
||||
->where('credits.balance', '>', 0)
|
||||
->select(
|
||||
DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'),
|
||||
DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'),
|
||||
@ -102,21 +101,29 @@ class CreditRepository extends BaseRepository
|
||||
$publicId = isset($data['public_id']) ? $data['public_id'] : false;
|
||||
|
||||
if ($credit) {
|
||||
$credit->balance = Utils::parseFloat($input['balance']);
|
||||
// do nothing
|
||||
} elseif ($publicId) {
|
||||
$credit = Credit::scope($publicId)->firstOrFail();
|
||||
$credit->balance = Utils::parseFloat($input['balance']);
|
||||
\Log::warning('Entity not set in credit repo save');
|
||||
} else {
|
||||
$credit = Credit::createNew();
|
||||
$credit->balance = Utils::parseFloat($input['amount']);
|
||||
$credit->client_id = Client::getPrivateId($input['client']);
|
||||
$credit->client_id = Client::getPrivateId($input['client_id']);
|
||||
$credit->credit_date = date('Y-m-d');
|
||||
}
|
||||
|
||||
$credit->fill($input);
|
||||
$credit->credit_date = Utils::toSqlDate($input['credit_date']);
|
||||
$credit->amount = Utils::parseFloat($input['amount']);
|
||||
$credit->private_notes = trim($input['private_notes']);
|
||||
|
||||
if (isset($input['credit_date'])) {
|
||||
$credit->credit_date = Utils::toSqlDate($input['credit_date']);
|
||||
}
|
||||
if (isset($input['amount'])) {
|
||||
$credit->amount = Utils::parseFloat($input['amount']);
|
||||
}
|
||||
if (isset($input['balance'])) {
|
||||
$credit->balance = Utils::parseFloat($input['balance']);
|
||||
}
|
||||
|
||||
$credit->save();
|
||||
|
||||
return $credit;
|
||||
|
@ -177,7 +177,8 @@ class InvoiceRepository extends BaseRepository
|
||||
'invoices.due_date',
|
||||
'invoices.due_date as due_date_sql',
|
||||
'invoices.is_recurring',
|
||||
'invoices.quote_invoice_id'
|
||||
'invoices.quote_invoice_id',
|
||||
'invoices.private_notes'
|
||||
);
|
||||
|
||||
if ($clientPublicId) {
|
||||
@ -411,12 +412,14 @@ class InvoiceRepository extends BaseRepository
|
||||
$invoice->invoice_date = Utils::toSqlDate($data['invoice_date']);
|
||||
}
|
||||
|
||||
/*
|
||||
if (isset($data['invoice_status_id'])) {
|
||||
if ($data['invoice_status_id'] == 0) {
|
||||
$data['invoice_status_id'] = INVOICE_STATUS_DRAFT;
|
||||
}
|
||||
$invoice->invoice_status_id = $data['invoice_status_id'];
|
||||
}
|
||||
*/
|
||||
|
||||
if ($invoice->is_recurring) {
|
||||
if (! $isNew && isset($data['start_date']) && $invoice->start_date && $invoice->start_date != Utils::toSqlDate($data['start_date'])) {
|
||||
@ -469,8 +472,6 @@ class InvoiceRepository extends BaseRepository
|
||||
$invoice->po_number = trim($data['po_number']);
|
||||
}
|
||||
|
||||
$invoice->invoice_design_id = isset($data['invoice_design_id']) ? $data['invoice_design_id'] : $account->invoice_design_id;
|
||||
|
||||
// provide backwards compatibility
|
||||
if (isset($data['tax_name']) && isset($data['tax_rate'])) {
|
||||
$data['tax_name1'] = $data['tax_name'];
|
||||
@ -654,6 +655,10 @@ class InvoiceRepository extends BaseRepository
|
||||
if ($product && (Auth::user()->can('edit', $product))) {
|
||||
$product->notes = ($task || $expense) ? '' : $item['notes'];
|
||||
$product->cost = $expense ? 0 : $item['cost'];
|
||||
$product->tax_name1 = isset($item['tax_name1']) ? $item['tax_name1'] : null;
|
||||
$product->tax_rate1 = isset($item['tax_rate1']) ? $item['tax_rate1'] : 0;
|
||||
$product->tax_name2 = isset($item['tax_name2']) ? $item['tax_name2'] : null;
|
||||
$product->tax_rate2 = isset($item['tax_rate2']) ? $item['tax_rate2'] : 0;
|
||||
$product->custom_value1 = isset($item['custom_value1']) ? $item['custom_value1'] : null;
|
||||
$product->custom_value2 = isset($item['custom_value2']) ? $item['custom_value2'] : null;
|
||||
$product->save();
|
||||
@ -720,7 +725,7 @@ class InvoiceRepository extends BaseRepository
|
||||
}
|
||||
}
|
||||
|
||||
// if no contacts are selected auto-select the first to enusre there's an invitation
|
||||
// if no contacts are selected auto-select the first to ensure there's an invitation
|
||||
if (! count($sendInvoiceIds)) {
|
||||
$sendInvoiceIds[] = $client->contacts[0]->id;
|
||||
}
|
||||
|
@ -189,6 +189,9 @@ class PaymentRepository extends BaseRepository
|
||||
if (isset($input['transaction_reference'])) {
|
||||
$payment->transaction_reference = trim($input['transaction_reference']);
|
||||
}
|
||||
if (isset($input['private_notes'])) {
|
||||
$payment->private_notes = trim($input['private_notes']);
|
||||
}
|
||||
|
||||
if (! $publicId) {
|
||||
$clientId = $input['client_id'];
|
||||
|
@ -23,18 +23,14 @@ class ProductRepository extends BaseRepository
|
||||
public function find($accountId, $filter = null)
|
||||
{
|
||||
$query = DB::table('products')
|
||||
->leftJoin('tax_rates', function ($join) {
|
||||
$join->on('tax_rates.id', '=', 'products.default_tax_rate_id')
|
||||
->whereNull('tax_rates.deleted_at');
|
||||
})
|
||||
->where('products.account_id', '=', $accountId)
|
||||
->select(
|
||||
'products.public_id',
|
||||
'products.product_key',
|
||||
'products.notes',
|
||||
'products.cost',
|
||||
'tax_rates.name as tax_name',
|
||||
'tax_rates.rate as tax_rate',
|
||||
'products.tax_name1 as tax_name',
|
||||
'products.tax_rate1 as tax_rate',
|
||||
'products.deleted_at',
|
||||
'products.is_deleted'
|
||||
);
|
||||
@ -82,9 +78,7 @@ class ProductRepository extends BaseRepository
|
||||
$max = SIMILAR_MIN_THRESHOLD;
|
||||
$productId = 0;
|
||||
|
||||
$products = Product::scope()
|
||||
->with('default_tax_rate')
|
||||
->get();
|
||||
$products = Product::scope()->get();
|
||||
|
||||
foreach ($products as $product) {
|
||||
if (! $product->product_key) {
|
||||
|
@ -171,6 +171,7 @@ class AccountTransformer extends EntityTransformer
|
||||
'invoice_taxes' => (bool) $account->invoice_taxes,
|
||||
'invoice_item_taxes' => (bool) $account->invoice_item_taxes,
|
||||
'invoice_design_id' => (int) $account->invoice_design_id,
|
||||
'quote_design_id' => (int) $account->quote_design_id,
|
||||
'client_view_css' => (string) $account->client_view_css,
|
||||
'work_phone' => $account->work_phone,
|
||||
'work_email' => $account->work_email,
|
||||
@ -213,7 +214,10 @@ class AccountTransformer extends EntityTransformer
|
||||
'num_days_reminder3' => $account->num_days_reminder3,
|
||||
'custom_invoice_text_label1' => $account->custom_invoice_text_label1,
|
||||
'custom_invoice_text_label2' => $account->custom_invoice_text_label2,
|
||||
'default_tax_rate_id' => $account->default_tax_rate_id ? $account->default_tax_rate->public_id : 0,
|
||||
'tax_name1' => $account->tax_name1 ?: '',
|
||||
'tax_rate1' => (float) $account->tax_rate1,
|
||||
'tax_name2' => $account->tax_name2 ?: '',
|
||||
'tax_rate2' => (float) $account->tax_rate2,
|
||||
'recurring_hour' => $account->recurring_hour,
|
||||
'invoice_number_pattern' => $account->invoice_number_pattern,
|
||||
'quote_number_pattern' => $account->quote_number_pattern,
|
||||
|
@ -25,6 +25,7 @@ class ClientTransformer extends EntityTransformer
|
||||
* @SWG\Property(property="country_id", type="integer", example=840)
|
||||
* @SWG\Property(property="work_phone", type="string", example="(212) 555-1212")
|
||||
* @SWG\Property(property="private_notes", type="string", example="Notes...")
|
||||
* @SWG\Property(property="public_notes", type="string", example="Notes...")
|
||||
* @SWG\Property(property="last_login", type="string", format="date-time", example="2016-01-01 12:10:00")
|
||||
* @SWG\Property(property="website", type="string", example="http://www.example.com")
|
||||
* @SWG\Property(property="industry_id", type="integer", example=1)
|
||||
@ -119,6 +120,7 @@ class ClientTransformer extends EntityTransformer
|
||||
'country_id' => (int) $client->country_id,
|
||||
'work_phone' => $client->work_phone,
|
||||
'private_notes' => $client->private_notes,
|
||||
'public_notes' => $client->public_notes,
|
||||
'last_login' => $client->last_login,
|
||||
'website' => $client->website,
|
||||
'industry_id' => (int) $client->industry_id,
|
||||
|
@ -27,6 +27,7 @@ class CreditTransformer extends EntityTransformer
|
||||
'credit_number' => $credit->credit_number,
|
||||
'private_notes' => $credit->private_notes,
|
||||
'public_notes' => $credit->public_notes,
|
||||
'client_id' => $credit->client->public_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,13 @@ class EntityTransformer extends TransformerAbstract
|
||||
|
||||
protected function getTimestamp($date)
|
||||
{
|
||||
return method_exists($date, 'getTimestamp') ? $date->getTimestamp() : null;
|
||||
if (method_exists($date, 'getTimestamp')) {
|
||||
return $date->getTimestamp();
|
||||
} elseif (is_string($date)) {
|
||||
return strtotime($date);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function getDefaultIncludes()
|
||||
|
@ -17,7 +17,8 @@ class InvoiceTransformer extends EntityTransformer
|
||||
* @SWG\Property(property="balance", type="number", format="float", example=10, readOnly=true)
|
||||
* @SWG\Property(property="client_id", type="integer", example=1)
|
||||
* @SWG\Property(property="invoice_number", type="string", example="0001")
|
||||
* @SWG\Property(property="invoice_status_id", type="integer", example=1)
|
||||
* @SWG\Property(property="private_notes", type="string", example="Notes...")
|
||||
* @SWG\Property(property="public_notes", type="string", example="Notes...")
|
||||
*/
|
||||
protected $defaultIncludes = [
|
||||
'invoice_items',
|
||||
@ -89,13 +90,14 @@ class InvoiceTransformer extends EntityTransformer
|
||||
'invoice_status_id' => (int) $invoice->invoice_status_id,
|
||||
'updated_at' => $this->getTimestamp($invoice->updated_at),
|
||||
'archived_at' => $this->getTimestamp($invoice->deleted_at),
|
||||
'invoice_number' => $invoice->invoice_number,
|
||||
'invoice_number' => $invoice->is_recurring ? '' : $invoice->invoice_number,
|
||||
'discount' => (float) $invoice->discount,
|
||||
'po_number' => $invoice->po_number,
|
||||
'invoice_date' => $invoice->invoice_date,
|
||||
'due_date' => $invoice->due_date,
|
||||
'terms' => $invoice->terms,
|
||||
'public_notes' => $invoice->public_notes,
|
||||
'private_notes' => $invoice->private_notes,
|
||||
'is_deleted' => (bool) $invoice->is_deleted,
|
||||
'invoice_type_id' => (int) $invoice->invoice_type_id,
|
||||
'is_recurring' => (bool) $invoice->is_recurring,
|
||||
|
@ -16,6 +16,7 @@ class PaymentTransformer extends EntityTransformer
|
||||
* @SWG\Property(property="id", type="integer", example=1, readOnly=true)
|
||||
* @SWG\Property(property="amount", type="number", format="float", example=10, readOnly=true)
|
||||
* @SWG\Property(property="invoice_id", type="integer", example=1)
|
||||
* @SWG\Property(property="private_notes", type="string", example="Notes...")
|
||||
*/
|
||||
protected $defaultIncludes = [];
|
||||
|
||||
@ -58,6 +59,7 @@ class PaymentTransformer extends EntityTransformer
|
||||
'payment_type_id' => (int) $payment->payment_type_id,
|
||||
'invoice_id' => (int) ($this->invoice ? $this->invoice->public_id : $payment->invoice->public_id),
|
||||
'invoice_number' => $this->invoice ? $this->invoice->invoice_number : $payment->invoice->invoice_number,
|
||||
'private_notes' => $payment->private_notes,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ class ProductTransformer extends EntityTransformer
|
||||
* @SWG\Property(property="notes", type="string", example="Notes...")
|
||||
* @SWG\Property(property="cost", type="number", format="float", example=10.00)
|
||||
* @SWG\Property(property="qty", type="number", format="float", example=1)
|
||||
* @SWG\Property(property="default_tax_rate_id", type="integer", example=1)
|
||||
* @SWG\Property(property="updated_at", type="integer", example=1451160233, readOnly=true)
|
||||
* @SWG\Property(property="archived_at", type="integer", example=1451160233, readOnly=true)
|
||||
*/
|
||||
@ -27,7 +26,10 @@ class ProductTransformer extends EntityTransformer
|
||||
'notes' => $product->notes,
|
||||
'cost' => $product->cost,
|
||||
'qty' => $product->qty,
|
||||
'default_tax_rate_id' => $product->default_tax_rate_id ? $product->default_tax_rate->public_id : 0,
|
||||
'tax_name1' => $product->tax_name1 ?: '',
|
||||
'tax_rate1' => (float) $product->tax_rate1,
|
||||
'tax_name2' => $product->tax_name2 ?: '',
|
||||
'tax_rate2' => (float) $product->tax_rate2,
|
||||
'updated_at' => $this->getTimestamp($product->updated_at),
|
||||
'archived_at' => $this->getTimestamp($product->deleted_at),
|
||||
]);
|
||||
|
@ -99,6 +99,7 @@ class ImportService
|
||||
IMPORT_FRESHBOOKS,
|
||||
IMPORT_HIVEAGE,
|
||||
IMPORT_INVOICEABLE,
|
||||
IMPORT_INVOICEPLANE,
|
||||
IMPORT_NUTCACHE,
|
||||
IMPORT_RONIN,
|
||||
IMPORT_WAVE,
|
||||
|
@ -178,8 +178,11 @@ class PaymentService extends BaseService
|
||||
foreach ($payments as $payment) {
|
||||
if (Auth::user()->can('edit', $payment)) {
|
||||
$amount = ! empty($params['refund_amount']) ? floatval($params['refund_amount']) : null;
|
||||
$paymentDriver = false;
|
||||
if ($accountGateway = $payment->account_gateway) {
|
||||
$paymentDriver = $accountGateway->paymentDriver();
|
||||
}
|
||||
if ($paymentDriver && $paymentDriver->canRefundPayments) {
|
||||
if ($paymentDriver->refundPayment($payment, $amount)) {
|
||||
$successful++;
|
||||
}
|
||||
|
@ -55,6 +55,8 @@ class TemplateService
|
||||
'$contact' => $contact->getDisplayName(),
|
||||
'$firstName' => $contact->first_name,
|
||||
'$amount' => $account->formatMoney($data['amount'], $client),
|
||||
'$total' => $invoice->present()->amount,
|
||||
'$balance' => $invoice->present()->balance,
|
||||
'$invoice' => $invoice->invoice_number,
|
||||
'$quote' => $invoice->invoice_number,
|
||||
'$link' => $invitation->getLink(),
|
||||
|
126
composer.json
126
composer.json
@ -14,83 +14,83 @@
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.5.9",
|
||||
"ext-gmp": "*",
|
||||
"ext-gd": "*",
|
||||
"turbo124/laravel-push-notification": "2.*",
|
||||
"omnipay/mollie": "3.*",
|
||||
"omnipay/2checkout": "dev-master#e9c079c2dde0d7ba461903b3b7bd5caf6dee1248",
|
||||
"omnipay/gocardless": "dev-master",
|
||||
"omnipay/stripe": "dev-master",
|
||||
"doctrine/dbal": "2.5.x",
|
||||
"laravelcollective/bus": "5.2.*",
|
||||
"laravel/framework": "5.2.*",
|
||||
"laravelcollective/html": "5.2.*",
|
||||
"symfony/css-selector": "~3.0",
|
||||
"patricktalmadge/bootstrapper": "5.5.x",
|
||||
"anahkiasen/former": "4.0.*@dev",
|
||||
"chumper/datatable": "dev-develop#04ef2bf",
|
||||
"omnipay/omnipay": "~2.3",
|
||||
"intervention/image": "dev-master",
|
||||
"webpatser/laravel-countries": "dev-master",
|
||||
"lokielse/omnipay-alipay": "~1.4",
|
||||
"digitickets/omnipay-datacash": "~3.0",
|
||||
"mfauveau/omnipay-pacnet": "~2.0",
|
||||
"digitickets/omnipay-realex": "~5.0",
|
||||
"fruitcakestudio/omnipay-sisow": "~2.0",
|
||||
"alfaproject/omnipay-skrill": "dev-master",
|
||||
"omnipay/bitpay": "dev-master",
|
||||
"guzzlehttp/guzzle": "~6.0",
|
||||
"wildbit/laravel-postmark-provider": "3.0",
|
||||
"ext-gmp": "*",
|
||||
"Dwolla/omnipay-dwolla": "dev-master",
|
||||
"laravel/socialite": "~2.0",
|
||||
"simshaun/recurr": "dev-master",
|
||||
"league/fractal": "0.13.*",
|
||||
"agmscode/omnipay-agms": "~1.0",
|
||||
"digitickets/omnipay-barclays-epdq": "~3.0",
|
||||
"cardgate/omnipay-cardgate": "~2.0",
|
||||
"fotografde/omnipay-checkoutcom": "~2.0",
|
||||
"meebio/omnipay-creditcall": "dev-master",
|
||||
"dioscouri/omnipay-cybersource": "dev-master",
|
||||
"dercoder/omnipay-ecopayz": "~1.0",
|
||||
"alfaproject/omnipay-skrill": "dev-master",
|
||||
"anahkiasen/former": "4.0.*@dev",
|
||||
"andreas22/omnipay-fasapay": "1.*",
|
||||
"asgrim/ofxparser": "^1.1",
|
||||
"barracudanetworks/archivestream-php": "^1.0",
|
||||
"barryvdh/laravel-cors": "^0.9.1",
|
||||
"barryvdh/laravel-debugbar": "~2.2",
|
||||
"barryvdh/laravel-ide-helper": "~2.2",
|
||||
"cardgate/omnipay-cardgate": "~2.0",
|
||||
"cerdic/css-tidy": "~v1.5",
|
||||
"chumper/datatable": "dev-develop#04ef2bf",
|
||||
"codedge/laravel-selfupdater": "5.x-dev",
|
||||
"collizo4sky/omnipay-wepay": "^1.3",
|
||||
"delatbabel/omnipay-fatzebra": "dev-master",
|
||||
"vink/omnipay-komoju": "~1.0",
|
||||
"incube8/omnipay-multicards": "dev-master",
|
||||
"descubraomundo/omnipay-pagarme": "dev-master",
|
||||
"dercoder/omnipay-ecopayz": "~1.0",
|
||||
"dercoder/omnipay-paysafecard": "dev-master",
|
||||
"softcommerce/omnipay-paytrace": "~1.0",
|
||||
"meebio/omnipay-secure-trading": "dev-master",
|
||||
"descubraomundo/omnipay-pagarme": "dev-master",
|
||||
"digitickets/omnipay-barclays-epdq": "~3.0",
|
||||
"digitickets/omnipay-datacash": "~3.0",
|
||||
"digitickets/omnipay-realex": "~5.0",
|
||||
"dioscouri/omnipay-cybersource": "dev-master",
|
||||
"doctrine/dbal": "2.5.x",
|
||||
"ezyang/htmlpurifier": "~v4.7",
|
||||
"fotografde/omnipay-checkoutcom": "~2.0",
|
||||
"fruitcakestudio/omnipay-sisow": "~2.0",
|
||||
"fzaninotto/faker": "^1.5",
|
||||
"gatepay/FedACHdir": "dev-master@dev",
|
||||
"google/apiclient": "^1.0",
|
||||
"guzzlehttp/guzzle": "~6.0",
|
||||
"incube8/omnipay-multicards": "dev-master",
|
||||
"intervention/image": "dev-master",
|
||||
"jaybizzle/laravel-crawler-detect": "1.*",
|
||||
"jlapp/swaggervel": "master-dev",
|
||||
"jonnyw/php-phantomjs": "4.*",
|
||||
"justinbusschau/omnipay-secpay": "~2.0",
|
||||
"laracasts/presenter": "dev-master",
|
||||
"jlapp/swaggervel": "master-dev",
|
||||
"maatwebsite/excel": "~2.0",
|
||||
"ezyang/htmlpurifier": "~v4.7",
|
||||
"cerdic/css-tidy": "~v1.5",
|
||||
"asgrim/ofxparser": "^1.1",
|
||||
"laravel/framework": "5.2.*",
|
||||
"laravel/socialite": "~2.0",
|
||||
"laravelcollective/bus": "5.2.*",
|
||||
"laravelcollective/html": "5.2.*",
|
||||
"league/flysystem-aws-s3-v3": "~1.0",
|
||||
"league/flysystem-rackspace": "~1.0",
|
||||
"barracudanetworks/archivestream-php": "^1.0",
|
||||
"league/fractal": "0.13.*",
|
||||
"lokielse/omnipay-alipay": "~1.4",
|
||||
"maatwebsite/excel": "~2.0",
|
||||
"meebio/omnipay-creditcall": "dev-master",
|
||||
"meebio/omnipay-secure-trading": "dev-master",
|
||||
"mfauveau/omnipay-pacnet": "~2.0",
|
||||
"nwidart/laravel-modules": "^1.14",
|
||||
"omnipay/2checkout": "dev-master#e9c079c2dde0d7ba461903b3b7bd5caf6dee1248",
|
||||
"omnipay/bitpay": "dev-master",
|
||||
"omnipay/braintree": "~2.0@dev",
|
||||
"gatepay/FedACHdir": "dev-master@dev",
|
||||
"omnipay/gocardless": "dev-master",
|
||||
"omnipay/mollie": "3.*",
|
||||
"omnipay/omnipay": "~2.3",
|
||||
"omnipay/stripe": "dev-master",
|
||||
"patricktalmadge/bootstrapper": "5.5.x",
|
||||
"predis/predis": "^1.1",
|
||||
"simshaun/recurr": "dev-master",
|
||||
"softcommerce/omnipay-paytrace": "~1.0",
|
||||
"symfony/css-selector": "~3.0",
|
||||
"turbo124/laravel-push-notification": "2.*",
|
||||
"vink/omnipay-komoju": "~1.0",
|
||||
"webpatser/laravel-countries": "dev-master",
|
||||
"websight/l5-google-cloud-storage": "^1.0",
|
||||
"wepay/php-sdk": "^0.2",
|
||||
"barryvdh/laravel-ide-helper": "~2.2",
|
||||
"barryvdh/laravel-debugbar": "~2.2",
|
||||
"fzaninotto/faker": "^1.5",
|
||||
"jaybizzle/laravel-crawler-detect": "1.*",
|
||||
"codedge/laravel-selfupdater": "5.x-dev",
|
||||
"predis/predis": "^1.1",
|
||||
"nwidart/laravel-modules": "^1.14",
|
||||
"jonnyw/php-phantomjs": "4.*",
|
||||
"collizo4sky/omnipay-wepay": "^1.3",
|
||||
"barryvdh/laravel-cors": "^0.9.1",
|
||||
"google/apiclient":"^1.0"
|
||||
"wildbit/laravel-postmark-provider": "3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.0",
|
||||
"phpspec/phpspec": "~2.1",
|
||||
"codeception/codeception": "*",
|
||||
"codeception/c3": "~2.0",
|
||||
"codeception/codeception": "*",
|
||||
"phpspec/phpspec": "~2.1",
|
||||
"phpunit/phpunit": "~4.0",
|
||||
"symfony/dom-crawler": "~3.0"
|
||||
},
|
||||
"autoload": {
|
||||
@ -132,7 +132,9 @@
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"preferred-install": "dist"
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
"optimize-autoloader": true
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
|
711
composer.lock
generated
711
composer.lock
generated
File diff suppressed because it is too large
Load Diff
44
config/pdf.php
Normal file
44
config/pdf.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'phantomjs' => [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| PhantomJS Secret
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This enables the PhantomJS request to bypass client authorization.
|
||||
|
|
||||
*/
|
||||
|
||||
'secret' => env('PHANTOMJS_SECRET'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| PhantomJS Bin Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The path to the local PhantomJS binary.
|
||||
| For example: /usr/local/bin/phantomjs
|
||||
| You can run which phantomjs to determine the value
|
||||
|
|
||||
*/
|
||||
|
||||
'bin_path' => env('PHANTOMJS_BIN_PATH'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| PhantomJS Cloud Key
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Key for the https://phantomjscloud.com service
|
||||
|
|
||||
*/
|
||||
|
||||
'cloud_key' => env('PHANTOMJS_CLOUD_KEY')
|
||||
|
||||
]
|
||||
|
||||
];
|
@ -15,7 +15,7 @@ class AddCustomDesign extends Migration
|
||||
$table->mediumText('custom_design')->nullable();
|
||||
});
|
||||
|
||||
DB::table('invoice_designs')->insert(['id' => CUSTOM_DESIGN, 'name' => 'Custom']);
|
||||
DB::table('invoice_designs')->insert(['id' => CUSTOM_DESIGN1, 'name' => 'Custom']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddDefaultNoteToClient extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('clients', function ($table) {
|
||||
$table->text('public_notes')->nullable();
|
||||
});
|
||||
|
||||
Schema::table('invoices', function ($table) {
|
||||
$table->text('private_notes')->nullable();
|
||||
});
|
||||
|
||||
Schema::table('payments', function ($table) {
|
||||
$table->text('private_notes')->nullable();
|
||||
});
|
||||
|
||||
Schema::table('accounts', function ($table) {
|
||||
$table->string('tax_name1')->nullable();
|
||||
$table->decimal('tax_rate1', 13, 3);
|
||||
$table->string('tax_name2')->nullable();
|
||||
$table->decimal('tax_rate2', 13, 3);
|
||||
});
|
||||
|
||||
Schema::table('products', function ($table) {
|
||||
$table->string('tax_name1')->nullable();
|
||||
$table->decimal('tax_rate1', 13, 3);
|
||||
$table->string('tax_name2')->nullable();
|
||||
$table->decimal('tax_rate2', 13, 3);
|
||||
});
|
||||
|
||||
DB::statement('update products
|
||||
left join tax_rates on tax_rates.id = products.default_tax_rate_id
|
||||
set products.tax_name1 = tax_rates.name, products.tax_rate1 = tax_rates.rate');
|
||||
|
||||
DB::statement('update accounts
|
||||
left join tax_rates on tax_rates.id = accounts.default_tax_rate_id
|
||||
set accounts.tax_name1 = tax_rates.name, accounts.tax_rate1 = tax_rates.rate');
|
||||
|
||||
if (Schema::hasColumn('accounts', 'default_tax_rate_id')) {
|
||||
Schema::table('accounts', function ($table) {
|
||||
$table->dropColumn('default_tax_rate_id');
|
||||
});
|
||||
}
|
||||
|
||||
if (Schema::hasColumn('products', 'default_tax_rate_id')) {
|
||||
Schema::table('products', function ($table) {
|
||||
$table->dropColumn('default_tax_rate_id');
|
||||
});
|
||||
}
|
||||
|
||||
if (Utils::isNinja()) {
|
||||
Schema::table('users', function ($table) {
|
||||
$table->unique(['oauth_user_id', 'oauth_provider_id']);
|
||||
});
|
||||
}
|
||||
|
||||
Schema::table('accounts', function ($table) {
|
||||
$table->unsignedInteger('quote_design_id')->default(1);
|
||||
$table->renameColumn('custom_design', 'custom_design1');
|
||||
$table->mediumText('custom_design2')->nullable();
|
||||
$table->mediumText('custom_design3')->nullable();
|
||||
$table->string('analytics_key')->nullable();
|
||||
});
|
||||
|
||||
DB::statement('update accounts
|
||||
set quote_design_id = invoice_design_id');
|
||||
|
||||
DB::statement('update invoice_designs
|
||||
set name = "Custom1"
|
||||
where id = 11
|
||||
and name = "Custom"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('clients', function ($table) {
|
||||
$table->dropColumn('public_notes');
|
||||
});
|
||||
|
||||
Schema::table('invoices', function ($table) {
|
||||
$table->dropColumn('private_notes');
|
||||
});
|
||||
|
||||
Schema::table('payments', function ($table) {
|
||||
$table->dropColumn('private_notes');
|
||||
});
|
||||
|
||||
Schema::table('accounts', function ($table) {
|
||||
$table->renameColumn('custom_design1', 'custom_design');
|
||||
$table->dropColumn('custom_design2');
|
||||
$table->dropColumn('custom_design3');
|
||||
$table->dropColumn('analytics_key');
|
||||
$table->dropColumn('tax_name1');
|
||||
$table->dropColumn('tax_rate1');
|
||||
$table->dropColumn('tax_name2');
|
||||
$table->dropColumn('tax_rate2');
|
||||
});
|
||||
|
||||
Schema::table('products', function ($table) {
|
||||
$table->dropColumn('tax_name1');
|
||||
$table->dropColumn('tax_rate1');
|
||||
$table->dropColumn('tax_name2');
|
||||
$table->dropColumn('tax_rate2');
|
||||
});
|
||||
}
|
||||
}
|
@ -150,6 +150,10 @@ class CountriesSeeder extends Seeder
|
||||
'SK' => [ // Slovakia
|
||||
'swap_currency_symbol' => true,
|
||||
],
|
||||
'US' => [
|
||||
'thousand_separator' => ',',
|
||||
'decimal_separator' => '.',
|
||||
],
|
||||
'UY' => [
|
||||
'swap_postal_code' => true,
|
||||
],
|
||||
|
@ -20,7 +20,7 @@ class InvoiceDesignsSeeder extends Seeder
|
||||
'Playful',
|
||||
'Photo',
|
||||
];
|
||||
|
||||
|
||||
for ($i = 0; $i < count($designs); $i++) {
|
||||
$design = $designs[$i];
|
||||
$fileName = storage_path() . '/templates/' . strtolower($design) . '.js';
|
||||
@ -38,5 +38,15 @@ class InvoiceDesignsSeeder extends Seeder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= 3; $i++) {
|
||||
$name = 'Custom' . $i;
|
||||
if (! InvoiceDesign::whereName($name)->first()) {
|
||||
InvoiceDesign::create([
|
||||
'id' => $i + 10,
|
||||
'name' => $name,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,9 @@ class LanguageSeeder extends Seeder
|
||||
['name' => 'English - United Kingdom', 'locale' => 'en_UK'],
|
||||
['name' => 'Portuguese - Portugal', 'locale' => 'pt_PT'],
|
||||
['name' => 'Slovenian', 'locale' => 'sl'],
|
||||
['name' => 'Finnish', 'locale' => 'fi'],
|
||||
['name' => 'Romanian', 'locale' => 'ro'],
|
||||
['name' => 'Turkish - Turkey', 'locale' => 'tr_TR'],
|
||||
];
|
||||
|
||||
foreach ($languages as $language) {
|
||||
|
@ -35,6 +35,7 @@ class PaymentTypesSeeder extends Seeder
|
||||
['name' => 'iZettle', 'gateway_type_id' => GATEWAY_TYPE_CREDIT_CARD],
|
||||
['name' => 'Swish', 'gateway_type_id' => GATEWAY_TYPE_BANK_TRANSFER],
|
||||
['name' => 'Venmo'],
|
||||
['name' => 'Money Order'],
|
||||
];
|
||||
|
||||
foreach ($paymentTypes as $paymentType) {
|
||||
|
@ -36,7 +36,7 @@ class UserTableSeeder extends Seeder
|
||||
'invoice_terms' => $faker->text($faker->numberBetween(50, 300)),
|
||||
'work_phone' => $faker->phoneNumber,
|
||||
'work_email' => $faker->safeEmail,
|
||||
'invoice_design_id' => InvoiceDesign::where('id', '<', CUSTOM_DESIGN)->get()->random()->id,
|
||||
'invoice_design_id' => InvoiceDesign::where('id', '<', CUSTOM_DESIGN1)->get()->random()->id,
|
||||
'header_font_id' => min(Font::all()->random()->id, 17),
|
||||
'body_font_id' => min(Font::all()->random()->id, 17),
|
||||
'primary_color' => $faker->hexcolor,
|
||||
|
File diff suppressed because one or more lines are too long
19
docs/api.rst
19
docs/api.rst
@ -74,15 +74,15 @@ Here’s an example of creating a client. Note that email address is a property
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
curl -X POST ninja.dev/api/v1/clients -H "Content-Type:application/json"
|
||||
curl -X POST ninja.dev/api/v1/clients -H "Content-Type:application/json" \
|
||||
-d '{"name":"Client","contact":{"email":"test@example.com"}}' -H "X-Ninja-Token: TOKEN"
|
||||
|
||||
You can also update a client by specifying a value for ‘id’. Next, here’s an example of creating an invoice.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
curl -X POST ninja.dev/api/v1/invoices -H "Content-Type:application/json"
|
||||
-d '{"client_id":"1", "invoice_items":[{"product_key": "ITEM", "notes":"Test", "cost":10, "qty":1}]}'
|
||||
curl -X POST ninja.dev/api/v1/invoices -H "Content-Type:application/json" \
|
||||
-d '{"client_id":"1", "invoice_items":[{"product_key": "ITEM", "notes":"Test", "cost":10, "qty":1}]}' \
|
||||
-H "X-Ninja-Token: TOKEN"
|
||||
|
||||
If the product_key is set and matches an existing record the product fields will be auto-populated. If the email field is set then we’ll search for a matching client. If no matches are found a new client will be created.
|
||||
@ -103,8 +103,15 @@ Updating Data
|
||||
|
||||
.. 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"}]}'
|
||||
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"
|
||||
|
||||
You can archive, delete or restore an entity by setting ``action`` in the request
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
curl -X PUT ninja.dev/api/v1/invoices/1?action=archive \
|
||||
-H "X-Ninja-Token: TOKEN"
|
||||
|
||||
Emailing Invoices
|
||||
@ -114,7 +121,7 @@ To email an invoice use the email_invoice command passing the id of the invoice.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
curl -X POST ninja.dev/api/v1/email_invoice -d '{"id":1}'
|
||||
curl -X POST ninja.dev/api/v1/email_invoice -d '{"id":1}' \
|
||||
-H "Content-Type:application/json" -H "X-Ninja-Token: TOKEN"
|
||||
|
||||
Subscriptions
|
||||
|
@ -57,9 +57,9 @@ author = u'Invoice Ninja'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = u'3.3'
|
||||
version = u'3.4'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = u'3.3.3'
|
||||
release = u'3.4.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
@ -49,19 +49,24 @@ Create an application in either Google, Facebook, GitHub or LinkedIn and then se
|
||||
PhantomJS
|
||||
"""""""""
|
||||
|
||||
We use phantomjscloud.com to attach PDFs to emails sent by background processes. Check for the following line in the .env file to enable this feature or sign up to increase your daily limit.
|
||||
There are two methods to attach PDFs to emails sent by background processes: phantomjscloud.com or local PhantomJS install.
|
||||
|
||||
To use phantomjscloud.com check for the following line in the .env file.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'
|
||||
PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'
|
||||
|
||||
If you require contacts to enter a password to see their invoice you'll need to set a random value for ``PHANTOMJS_SECRET``.
|
||||
To use a local PhantomJS install add ``PHANTOMJS_BIN_PATH=/usr/local/bin/phantomjs``.
|
||||
|
||||
You can install PhantomJS to generate the PDF locally, to enable it add ``PHANTOMJS_BIN_PATH=/usr/local/bin/phantomjs``.
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
We suggest using version >= 2.1.1, users have reported seeing 'Error: 0' with older versions.
|
||||
|
||||
.. TIP:: To determine the path you can run ``which phantomjs`` from the command line.
|
||||
- Check storage/logs/laravel-error.log for relevant errors.
|
||||
- To determine the path you can run ``which phantomjs`` from the command line.
|
||||
- We suggest using PhantomJS version >= 2.1.1, users have reported seeing 'Error: 0' with older versions.
|
||||
- You can use `this script <https://raw.githubusercontent.com/invoiceninja/invoiceninja/develop/resources/test.pjs>`_ to test from the command line, change ``__YOUR_LINK_HERE__`` to a 'View as recipient' link.
|
||||
- If you require contacts to enter a password to see their invoice you'll need to set a random value for ``PHANTOMJS_SECRET``.
|
||||
|
||||
Custom Fonts
|
||||
""""""""""""
|
||||
@ -90,7 +95,7 @@ Follow these steps to add a driver.
|
||||
Google Map
|
||||
""""""""""
|
||||
|
||||
You need to create a Google Maps API key for the Javascript, Geocoding and Embed APIs and then add ``GOOGLE_MAPS_API_KEY=your_key`` to the .env file.
|
||||
You need to create a `Google Maps API <https://developers.google.com/maps/documentation/javascript/get-api-key>`_ key for the Javascript, Geocoding and Embed APIs and then add ``GOOGLE_MAPS_API_KEY=your_key`` to the .env file.
|
||||
|
||||
You can disable the feature by adding ``GOOGLE_MAPS_ENABLED=false`` to the .env file.
|
||||
|
||||
|
@ -21,6 +21,8 @@ Automated Installers
|
||||
|
||||
- Softaculous: `softaculous.com <https://www.softaculous.com/apps/ecommerce/Invoice_Ninja>`_
|
||||
|
||||
.. Tip:: You can use `github.com/turbo124/Plane2Ninja <https://github.com/turbo124/Plane2Ninja>`_ to migrate your data from InvoicePlane.
|
||||
|
||||
Steps to Install
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -29,7 +31,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.
|
||||
|
||||
https://download.invoiceninja.com/ninja-v3.3.1.zip
|
||||
https://download.invoiceninja.com
|
||||
|
||||
.. 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.
|
||||
|
||||
@ -57,7 +59,7 @@ You’ll need to create a new database along with a user to access it. Most host
|
||||
|
||||
CREATE DATABASE ninja;
|
||||
CREATE USER 'ninja'@'localhost' IDENTIFIED BY 'ninja';
|
||||
GRANT ALL PRIVILEGES ON * . * TO 'ninja'@'localhost';
|
||||
GRANT ALL PRIVILEGES ON ninja.* TO 'ninja'@'localhost';
|
||||
|
||||
Step 4: Configure the web server
|
||||
""""""""""""""""""""""""""""""""
|
||||
|
@ -5,6 +5,8 @@ Update
|
||||
|
||||
To update the app you just need to copy over the latest code. The app tracks the current version in a file called version.txt, if it notices a change it loads ``/update`` to run the database migrations.
|
||||
|
||||
If you're moving servers make sure to copy over the .env file.
|
||||
|
||||
If the auto-update fails you can manually run the update with the following commands. Once completed add ``?clear_cache=true`` to the end of the URL to clear the application cache.
|
||||
|
||||
.. code-block:: shell
|
||||
@ -16,6 +18,8 @@ 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``
|
||||
|
||||
.. TIP:: You can see the detailed changes for each release on our `GitHub release notes <https://github.com/invoiceninja/invoiceninja/releases>`_.
|
||||
|
||||
Version 3.2
|
||||
"""""""""""
|
||||
|
||||
|
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
120
resources/assets/css/style.css
vendored
120
resources/assets/css/style.css
vendored
@ -1093,7 +1093,7 @@ div.panel-body div.panel-body {
|
||||
}
|
||||
|
||||
.invoice-table #document-upload{
|
||||
width:500px;
|
||||
width:550px;
|
||||
}
|
||||
|
||||
#document-upload .dropzone{
|
||||
@ -1289,3 +1289,121 @@ div.panel-body div.panel-body {
|
||||
.modal-footer {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#upgrade-modal {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 999999;
|
||||
#background-color: rgba(76,76,76,.99);
|
||||
background-color: rgba(0,0,0,.9);
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 1500px;
|
||||
}
|
||||
|
||||
#upgrade-modal h1 {
|
||||
font-family: 'roboto-thin', 'roboto', Helvetica, arial, sans-serif;
|
||||
font-size: 28px!important;
|
||||
padding: 0 0 25px 0;
|
||||
margin: 0!important;
|
||||
color: #fff;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 20px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
#upgrade-modal h2 {
|
||||
font-family: 'roboto-thin', 'roboto', Helvetica, arial, sans-serif;
|
||||
color: #36c157;
|
||||
font-size: 34px;
|
||||
line-height: 15px;
|
||||
padding-bottom: 4px;
|
||||
margin: 0;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
#upgrade-modal h3 {
|
||||
font-family: 'roboto-thin', 'roboto', Helvetica, arial, sans-serif;
|
||||
margin: 20px 0 25px 0;
|
||||
font-size: 75px;
|
||||
padding: 0 0 8px 0;
|
||||
color: #fff;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
#upgrade-modal h3 span.upgrade_frequency {
|
||||
font-size: 17px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
#upgrade-modal h4 {
|
||||
color: white;
|
||||
}
|
||||
|
||||
#upgrade-modal ul {
|
||||
list-style: none;
|
||||
color: #fff;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
#upgrade-modal .col-md-4 {
|
||||
padding:75px 20px;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
#upgrade-modal .col-md-offset-2 {
|
||||
border-top: 1px solid #343333;
|
||||
border-right: 1px solid #343333;
|
||||
}
|
||||
|
||||
#upgrade-modal .columns {
|
||||
border-top: 1px solid #343333;
|
||||
}
|
||||
|
||||
#upgrade-modal ul {
|
||||
border-top: 1px solid #343333;
|
||||
}
|
||||
|
||||
#upgrade-modal ul li {
|
||||
font-size: 17px;
|
||||
line-height: 35px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
#upgrade-modal p.subhead {
|
||||
font-size: 15px;
|
||||
margin: 5px 0 5px 0;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
font-weight: 400;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#upgrade-modal .btn {
|
||||
width: 260px;
|
||||
padding: 16px 0 16px 0;
|
||||
}
|
||||
|
||||
#upgrade-modal i.fa-close {
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
font-size: 26px !important;
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
#upgrade-modal label.radio-inline {
|
||||
padding: 0px 30px 30px 30px;
|
||||
font-size: 22px;
|
||||
color: #fff;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#upgrade-modal select {
|
||||
vertical-align: top;
|
||||
width: 140px;
|
||||
}
|
||||
|
@ -320,6 +320,7 @@ NINJA.decodeJavascript = function(invoice, javascript)
|
||||
|
||||
var value = getDescendantProp(invoice, field) || ' ';
|
||||
value = doubleDollarSign(value);
|
||||
value = value.replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
||||
javascript = javascript.replace(match, '"'+value+'"');
|
||||
}
|
||||
}
|
||||
@ -502,14 +503,15 @@ NINJA.invoiceLines = function(invoice) {
|
||||
|
||||
var lineTotal = roundToTwo(NINJA.parseFloat(item.cost)) * roundToTwo(NINJA.parseFloat(item.qty));
|
||||
if (account.include_item_taxes_inline == '1') {
|
||||
var taxAmount1 = 0;
|
||||
var taxAmount2 = 0;
|
||||
if (tax1) {
|
||||
lineTotal += lineTotal * tax1 / 100;
|
||||
lineTotal = roundToTwo(lineTotal);
|
||||
taxAmount1 = roundToTwo(lineTotal * tax1 / 100);
|
||||
}
|
||||
if (tax2) {
|
||||
lineTotal += lineTotal * tax2 / 100;
|
||||
lineTotal = roundToTwo(lineTotal);
|
||||
taxAmount2 = roundToTwo(lineTotal * tax2 / 100);
|
||||
}
|
||||
lineTotal += taxAmount1 + taxAmount2;
|
||||
}
|
||||
lineTotal = formatMoneyInvoice(lineTotal, invoice);
|
||||
|
||||
|
@ -81,7 +81,7 @@ $LANG = array(
|
||||
'guest' => 'Guest',
|
||||
'company_details' => 'Company Details',
|
||||
'online_payments' => 'Online Payments',
|
||||
'notifications' => 'Email Notifications',
|
||||
'notifications' => 'Notifications',
|
||||
'import_export' => 'Import | Export',
|
||||
'done' => 'Done',
|
||||
'save' => 'Save',
|
||||
@ -995,8 +995,8 @@ $LANG = array(
|
||||
'quote_issued_to' => 'Quote issued to',
|
||||
'show_currency_code' => 'Currency Code',
|
||||
'trial_message' => 'Your account will receive a free two week trial of our pro plan.',
|
||||
'trial_footer' => 'Your free trial lasts :count more days, :link to upgrade now.',
|
||||
'trial_footer_last_day' => 'This is the last day of your free trial, :link to upgrade now.',
|
||||
'trial_footer' => 'Your free pro plan trial lasts :count more days, :link to upgrade now.',
|
||||
'trial_footer_last_day' => 'This is the last day of your free pro plan trial, :link to upgrade now.',
|
||||
'trial_call_to_action' => 'Start Free Trial',
|
||||
'trial_success' => 'Successfully enabled two week free pro plan trial',
|
||||
'overdue' => 'Overdue',
|
||||
@ -1713,6 +1713,11 @@ $LANG = array(
|
||||
'lang_Albanian' => 'Albanian',
|
||||
'lang_English - United Kingdom' => 'English - United Kingdom',
|
||||
'lang_Slovenian' => 'Slovenian',
|
||||
'lang_Finnish' => 'Finnish',
|
||||
'lang_Romanian' => 'Romanian',
|
||||
'lang_Turkish - Turkey' => 'Turkish - Turkey',
|
||||
'lang_Portuguese - Brazilian' => 'Portuguese - Brazilian',
|
||||
'lang_Portuguese - Portugal' => 'Portuguese - Portugal',
|
||||
|
||||
// Frequencies
|
||||
'freq_weekly' => 'Weekly',
|
||||
@ -1723,24 +1728,6 @@ $LANG = array(
|
||||
'freq_six_months' => 'Six months',
|
||||
'freq_annually' => 'Annually',
|
||||
|
||||
// Payment types
|
||||
'payment_type_Apply Credit' => 'Apply Credit',
|
||||
'payment_type_Bank Transfer' => 'Bank Transfer',
|
||||
'payment_type_Cash' => 'Cash',
|
||||
'payment_type_Debit' => 'Debit',
|
||||
'payment_type_ACH' => 'ACH',
|
||||
'payment_type_Visa Card' => 'Visa Card',
|
||||
'payment_type_MasterCard' => 'MasterCard',
|
||||
'payment_type_American Express' => 'American Express',
|
||||
'payment_type_Discover Card' => 'Discover Card',
|
||||
'payment_type_Diners Card' => 'Diners Card',
|
||||
'payment_type_EuroCard' => 'EuroCard',
|
||||
'payment_type_Nova' => 'Nova',
|
||||
'payment_type_Credit Card Other' => 'Credit Card Other',
|
||||
'payment_type_PayPal' => 'PayPal',
|
||||
'payment_type_Google Wallet' => 'Google Wallet',
|
||||
'payment_type_Check' => 'Check',
|
||||
|
||||
// Industries
|
||||
'industry_Accounting & Legal' => 'Accounting & Legal',
|
||||
'industry_Advertising' => 'Advertising',
|
||||
@ -2096,7 +2083,6 @@ $LANG = array(
|
||||
'view_statement' => 'View Statement',
|
||||
'statement' => 'Statement',
|
||||
'statement_date' => 'Statement Date',
|
||||
'inactivity_logout' => 'Due to inactivity, you have been automatically logged out.',
|
||||
'mark_active' => 'Mark Active',
|
||||
'send_automatically' => 'Send Automatically',
|
||||
'initial_email' => 'Initial Email',
|
||||
@ -2222,6 +2208,7 @@ $LANG = array(
|
||||
'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.',
|
||||
'payment_type_Venmo' => 'Venmo',
|
||||
'payment_type_Money Order' => 'Money Order',
|
||||
'archived_products' => 'Successfully archived :count products',
|
||||
'recommend_on' => 'We recommend <b>enabling</b> this setting.',
|
||||
'recommend_off' => 'We recommend <b>disabling</b> this setting.',
|
||||
@ -2244,6 +2231,42 @@ $LANG = array(
|
||||
'white_label_license_error' => 'Failed to validate the license, check storage/logs/laravel-error.log for more details.',
|
||||
'plan_price' => 'Plan Price',
|
||||
'wrong_confirmation' => 'Incorrect confirmation code',
|
||||
'oauth_taken' => 'The account is already registered',
|
||||
'emailed_payment' => 'Successfully emailed payment',
|
||||
'email_payment' => 'Email Payment',
|
||||
'sent' => 'sent',
|
||||
'invoiceplane_import' => 'Use :link to migrate your data from InvoicePlane.',
|
||||
'duplicate_expense_warning' => 'Warning: This :link may be a duplicate',
|
||||
'expense_link' => 'expense',
|
||||
'resume_task' => 'Resume Task',
|
||||
'resumed_task' => 'Successfully resumed task',
|
||||
'quote_design' => 'Quote Design',
|
||||
'default_design' => 'Default Design',
|
||||
'custom_design1' => 'Custom Design 1',
|
||||
'custom_design2' => 'Custom Design 2',
|
||||
'custom_design3' => 'Custom Design 3',
|
||||
'empty' => 'Empty',
|
||||
'load_design' => 'Load Design',
|
||||
'accepted_card_logos' => 'Accepted Card Logos',
|
||||
'phantomjs_local_and_cloud' => 'Using local PhantomJS, falling back to phantomjscloud.com',
|
||||
'google_analytics' => 'Google Analytics',
|
||||
'analytics_key' => 'Analytics Key',
|
||||
'analytics_key_help' => 'Track payments using :link',
|
||||
'start_date_required' => 'The start date is required',
|
||||
'application_settings' => 'Application Settings',
|
||||
'database_connection' => 'Database Connection',
|
||||
'driver' => 'Driver',
|
||||
'host' => 'Host',
|
||||
'database' => 'Database',
|
||||
'test_connection' => 'Test connection',
|
||||
'from_name' => 'From Name',
|
||||
'from_address' => 'From Address',
|
||||
'port' => 'Port',
|
||||
'encryption' => 'Encryption',
|
||||
'mailgun_domain' => 'Mailgun Domain',
|
||||
'mailgun_private_key' => 'Mailgun Private Key',
|
||||
'send_test_email' => 'Send test email',
|
||||
|
||||
|
||||
);
|
||||
|
||||
|
@ -81,7 +81,7 @@ $LANG = array(
|
||||
'guest' => 'Host',
|
||||
'company_details' => 'Detaily firmy',
|
||||
'online_payments' => 'Online platby',
|
||||
'notifications' => 'Emailové notifikace',
|
||||
'notifications' => 'Notifications',
|
||||
'import_export' => 'Import | Export | Zrušit',
|
||||
'done' => 'Hotovo',
|
||||
'save' => 'Uložit',
|
||||
@ -997,8 +997,8 @@ $LANG = array(
|
||||
'quote_issued_to' => 'Náklad je vystaven',
|
||||
'show_currency_code' => 'Kód měny',
|
||||
'trial_message' => 'Váš účet získá zdarma 2 týdny zkušební verze Profi plánu.',
|
||||
'trial_footer' => 'Vaše zkušební verze trvá :count dnů, :link upradujte nyní.',
|
||||
'trial_footer_last_day' => 'Dnes je poslední den Vašeho zkušebního období , :link upradujte nyní.',
|
||||
'trial_footer' => 'Your free pro plan trial lasts :count more days, :link to upgrade now.',
|
||||
'trial_footer_last_day' => 'This is the last day of your free pro plan trial, :link to upgrade now.',
|
||||
'trial_call_to_action' => 'Vyzkoušet zdarma',
|
||||
'trial_success' => '14-ti denní zkušební lhůta úspěšně nastavena',
|
||||
'overdue' => 'Po termínu',
|
||||
@ -1715,6 +1715,11 @@ $LANG = array(
|
||||
'lang_Albanian' => 'Albanian',
|
||||
'lang_English - United Kingdom' => 'English - United Kingdom',
|
||||
'lang_Slovenian' => 'Slovenian',
|
||||
'lang_Finnish' => 'Finnish',
|
||||
'lang_Romanian' => 'Romanian',
|
||||
'lang_Turkish - Turkey' => 'Turkish - Turkey',
|
||||
'lang_Portuguese - Brazilian' => 'Portuguese - Brazilian',
|
||||
'lang_Portuguese - Portugal' => 'Portuguese - Portugal',
|
||||
|
||||
// Frequencies
|
||||
'freq_weekly' => 'Weekly',
|
||||
@ -1725,24 +1730,6 @@ $LANG = array(
|
||||
'freq_six_months' => 'Six months',
|
||||
'freq_annually' => 'Annually',
|
||||
|
||||
// Payment types
|
||||
'payment_type_Apply Credit' => 'Apply Credit',
|
||||
'payment_type_Bank Transfer' => 'Bank Transfer',
|
||||
'payment_type_Cash' => 'Cash',
|
||||
'payment_type_Debit' => 'Debit',
|
||||
'payment_type_ACH' => 'ACH',
|
||||
'payment_type_Visa Card' => 'Visa Card',
|
||||
'payment_type_MasterCard' => 'MasterCard',
|
||||
'payment_type_American Express' => 'American Express',
|
||||
'payment_type_Discover Card' => 'Discover Card',
|
||||
'payment_type_Diners Card' => 'Diners Card',
|
||||
'payment_type_EuroCard' => 'EuroCard',
|
||||
'payment_type_Nova' => 'Nova',
|
||||
'payment_type_Credit Card Other' => 'Credit Card Other',
|
||||
'payment_type_PayPal' => 'PayPal',
|
||||
'payment_type_Google Wallet' => 'Google Wallet',
|
||||
'payment_type_Check' => 'Check',
|
||||
|
||||
// Industries
|
||||
'industry_Accounting & Legal' => 'Accounting & Legal',
|
||||
'industry_Advertising' => 'Advertising',
|
||||
@ -2098,7 +2085,6 @@ $LANG = array(
|
||||
'view_statement' => 'View Statement',
|
||||
'statement' => 'Statement',
|
||||
'statement_date' => 'Statement Date',
|
||||
'inactivity_logout' => 'Due to inactivity, you have been automatically logged out.',
|
||||
'mark_active' => 'Mark Active',
|
||||
'send_automatically' => 'Send Automatically',
|
||||
'initial_email' => 'Initial Email',
|
||||
@ -2224,6 +2210,7 @@ $LANG = array(
|
||||
'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.',
|
||||
'payment_type_Venmo' => 'Venmo',
|
||||
'payment_type_Money Order' => 'Money Order',
|
||||
'archived_products' => 'Successfully archived :count products',
|
||||
'recommend_on' => 'We recommend <b>enabling</b> this setting.',
|
||||
'recommend_off' => 'We recommend <b>disabling</b> this setting.',
|
||||
@ -2246,6 +2233,42 @@ $LANG = array(
|
||||
'white_label_license_error' => 'Failed to validate the license, check storage/logs/laravel-error.log for more details.',
|
||||
'plan_price' => 'Plan Price',
|
||||
'wrong_confirmation' => 'Incorrect confirmation code',
|
||||
'oauth_taken' => 'The account is already registered',
|
||||
'emailed_payment' => 'Successfully emailed payment',
|
||||
'email_payment' => 'Email Payment',
|
||||
'sent' => 'sent',
|
||||
'invoiceplane_import' => 'Use :link to migrate your data from InvoicePlane.',
|
||||
'duplicate_expense_warning' => 'Warning: This :link may be a duplicate',
|
||||
'expense_link' => 'expense',
|
||||
'resume_task' => 'Resume Task',
|
||||
'resumed_task' => 'Successfully resumed task',
|
||||
'quote_design' => 'Quote Design',
|
||||
'default_design' => 'Default Design',
|
||||
'custom_design1' => 'Custom Design 1',
|
||||
'custom_design2' => 'Custom Design 2',
|
||||
'custom_design3' => 'Custom Design 3',
|
||||
'empty' => 'Empty',
|
||||
'load_design' => 'Load Design',
|
||||
'accepted_card_logos' => 'Accepted Card Logos',
|
||||
'phantomjs_local_and_cloud' => 'Using local PhantomJS, falling back to phantomjscloud.com',
|
||||
'google_analytics' => 'Google Analytics',
|
||||
'analytics_key' => 'Analytics Key',
|
||||
'analytics_key_help' => 'Track payments using :link',
|
||||
'start_date_required' => 'The start date is required',
|
||||
'application_settings' => 'Application Settings',
|
||||
'database_connection' => 'Database Connection',
|
||||
'driver' => 'Driver',
|
||||
'host' => 'Host',
|
||||
'database' => 'Database',
|
||||
'test_connection' => 'Test connection',
|
||||
'from_name' => 'From Name',
|
||||
'from_address' => 'From Address',
|
||||
'port' => 'Port',
|
||||
'encryption' => 'Encryption',
|
||||
'mailgun_domain' => 'Mailgun Domain',
|
||||
'mailgun_private_key' => 'Mailgun Private Key',
|
||||
'send_test_email' => 'Send test email',
|
||||
|
||||
|
||||
);
|
||||
|
||||
|
@ -81,7 +81,7 @@ $LANG = array(
|
||||
'guest' => 'Gæst',
|
||||
'company_details' => 'Firmainformation',
|
||||
'online_payments' => 'On-line betaling',
|
||||
'notifications' => 'Notifikationer',
|
||||
'notifications' => 'Notifications',
|
||||
'import_export' => 'Import/Eksport',
|
||||
'done' => 'Færdig',
|
||||
'save' => 'Gem',
|
||||
@ -995,8 +995,8 @@ $LANG = array(
|
||||
'quote_issued_to' => 'Quote issued to',
|
||||
'show_currency_code' => 'Currency Code',
|
||||
'trial_message' => 'Your account will receive a free two week trial of our pro plan.',
|
||||
'trial_footer' => 'Your free trial lasts :count more days, :link to upgrade now.',
|
||||
'trial_footer_last_day' => 'This is the last day of your free trial, :link to upgrade now.',
|
||||
'trial_footer' => 'Your free pro plan trial lasts :count more days, :link to upgrade now.',
|
||||
'trial_footer_last_day' => 'This is the last day of your free pro plan trial, :link to upgrade now.',
|
||||
'trial_call_to_action' => 'Start Free Trial',
|
||||
'trial_success' => 'Successfully enabled two week free pro plan trial',
|
||||
'overdue' => 'Overdue',
|
||||
@ -1713,6 +1713,11 @@ $LANG = array(
|
||||
'lang_Albanian' => 'Albanian',
|
||||
'lang_English - United Kingdom' => 'English - United Kingdom',
|
||||
'lang_Slovenian' => 'Slovenian',
|
||||
'lang_Finnish' => 'Finnish',
|
||||
'lang_Romanian' => 'Romanian',
|
||||
'lang_Turkish - Turkey' => 'Turkish - Turkey',
|
||||
'lang_Portuguese - Brazilian' => 'Portuguese - Brazilian',
|
||||
'lang_Portuguese - Portugal' => 'Portuguese - Portugal',
|
||||
|
||||
// Frequencies
|
||||
'freq_weekly' => 'Weekly',
|
||||
@ -1723,24 +1728,6 @@ $LANG = array(
|
||||
'freq_six_months' => 'Six months',
|
||||
'freq_annually' => 'Annually',
|
||||
|
||||
// Payment types
|
||||
'payment_type_Apply Credit' => 'Apply Credit',
|
||||
'payment_type_Bank Transfer' => 'Bank Transfer',
|
||||
'payment_type_Cash' => 'Cash',
|
||||
'payment_type_Debit' => 'Debit',
|
||||
'payment_type_ACH' => 'ACH',
|
||||
'payment_type_Visa Card' => 'Visa Card',
|
||||
'payment_type_MasterCard' => 'MasterCard',
|
||||
'payment_type_American Express' => 'American Express',
|
||||
'payment_type_Discover Card' => 'Discover Card',
|
||||
'payment_type_Diners Card' => 'Diners Card',
|
||||
'payment_type_EuroCard' => 'EuroCard',
|
||||
'payment_type_Nova' => 'Nova',
|
||||
'payment_type_Credit Card Other' => 'Credit Card Other',
|
||||
'payment_type_PayPal' => 'PayPal',
|
||||
'payment_type_Google Wallet' => 'Google Wallet',
|
||||
'payment_type_Check' => 'Check',
|
||||
|
||||
// Industries
|
||||
'industry_Accounting & Legal' => 'Accounting & Legal',
|
||||
'industry_Advertising' => 'Advertising',
|
||||
@ -2096,7 +2083,6 @@ $LANG = array(
|
||||
'view_statement' => 'View Statement',
|
||||
'statement' => 'Statement',
|
||||
'statement_date' => 'Statement Date',
|
||||
'inactivity_logout' => 'Due to inactivity, you have been automatically logged out.',
|
||||
'mark_active' => 'Mark Active',
|
||||
'send_automatically' => 'Send Automatically',
|
||||
'initial_email' => 'Initial Email',
|
||||
@ -2222,6 +2208,7 @@ $LANG = array(
|
||||
'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.',
|
||||
'payment_type_Venmo' => 'Venmo',
|
||||
'payment_type_Money Order' => 'Money Order',
|
||||
'archived_products' => 'Successfully archived :count products',
|
||||
'recommend_on' => 'We recommend <b>enabling</b> this setting.',
|
||||
'recommend_off' => 'We recommend <b>disabling</b> this setting.',
|
||||
@ -2244,6 +2231,42 @@ $LANG = array(
|
||||
'white_label_license_error' => 'Failed to validate the license, check storage/logs/laravel-error.log for more details.',
|
||||
'plan_price' => 'Plan Price',
|
||||
'wrong_confirmation' => 'Incorrect confirmation code',
|
||||
'oauth_taken' => 'The account is already registered',
|
||||
'emailed_payment' => 'Successfully emailed payment',
|
||||
'email_payment' => 'Email Payment',
|
||||
'sent' => 'sendt',
|
||||
'invoiceplane_import' => 'Use :link to migrate your data from InvoicePlane.',
|
||||
'duplicate_expense_warning' => 'Warning: This :link may be a duplicate',
|
||||
'expense_link' => 'expense',
|
||||
'resume_task' => 'Resume Task',
|
||||
'resumed_task' => 'Successfully resumed task',
|
||||
'quote_design' => 'Quote Design',
|
||||
'default_design' => 'Default Design',
|
||||
'custom_design1' => 'Custom Design 1',
|
||||
'custom_design2' => 'Custom Design 2',
|
||||
'custom_design3' => 'Custom Design 3',
|
||||
'empty' => 'Empty',
|
||||
'load_design' => 'Load Design',
|
||||
'accepted_card_logos' => 'Accepted Card Logos',
|
||||
'phantomjs_local_and_cloud' => 'Using local PhantomJS, falling back to phantomjscloud.com',
|
||||
'google_analytics' => 'Google Analytics',
|
||||
'analytics_key' => 'Analytics Key',
|
||||
'analytics_key_help' => 'Track payments using :link',
|
||||
'start_date_required' => 'The start date is required',
|
||||
'application_settings' => 'Application Settings',
|
||||
'database_connection' => 'Database Connection',
|
||||
'driver' => 'Driver',
|
||||
'host' => 'Host',
|
||||
'database' => 'Database',
|
||||
'test_connection' => 'Test connection',
|
||||
'from_name' => 'From Name',
|
||||
'from_address' => 'From Address',
|
||||
'port' => 'Port',
|
||||
'encryption' => 'Encryption',
|
||||
'mailgun_domain' => 'Mailgun Domain',
|
||||
'mailgun_private_key' => 'Mailgun Private Key',
|
||||
'send_test_email' => 'Send test email',
|
||||
|
||||
|
||||
);
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user