Merge pull request #1 from invoiceninja/master

Actualizacion desde proyecto original
This commit is contained in:
ridgarou 2016-02-09 10:56:16 +01:00
commit 0766f77713
153 changed files with 13615 additions and 10446 deletions

View File

@ -101,6 +101,7 @@ module.exports = function(grunt) {
'public/vendor/jspdf/dist/jspdf.min.js',
'public/vendor/moment/min/moment.min.js',
'public/vendor/moment-timezone/builds/moment-timezone-with-data.min.js',
'public/vendor/stacktrace-js/dist/stacktrace-with-polyfills.min.js',
//'public/vendor/moment-duration-format/lib/moment-duration-format.js',
//'public/vendor/handsontable/dist/jquery.handsontable.full.min.js',
//'public/vendor/pdfmake/build/pdfmake.min.js',
@ -111,7 +112,7 @@ module.exports = function(grunt) {
'public/js/script.js',
'public/js/pdf.pdfmake.js',
],
dest: 'public/js/built.js',
dest: 'public/built.js',
nonull: true
},
js_public: {
@ -126,7 +127,7 @@ module.exports = function(grunt) {
'public/js/bootstrap-combobox.js',
],
dest: 'public/js/built.public.js',
dest: 'public/built.public.js',
nonull: true
},
css: {
@ -171,7 +172,7 @@ module.exports = function(grunt) {
'public/js/pdfmake.min.js',
'public/js/vfs_fonts.js',
],
dest: 'public/js/pdf.built.js',
dest: 'public/pdf.built.js',
nonull: true
}
}

View File

@ -1,5 +1,5 @@
Attribution Assurance License
Copyright (c) 2015 by Hillel Coren
Copyright (c) 2016 by Hillel Coren
http://www.hillelcoren.com
All Rights Reserved

View File

@ -18,13 +18,15 @@ class TestOFX extends Command
public function fire()
{
$this->info(date('Y-m-d').' Running TestOFX...');
/*
$bankId = env('TEST_BANK_ID');
$username = env('TEST_BANK_USERNAME');
$password = env('TEST_BANK_PASSWORD');
$data = $this->bankAccountService->loadBankAccounts($bankId, $username, $password, false);
print "<pre>".print_r($data, 1)."</pre>";
echo json_encode($data);
*/
}
}

View File

@ -4,6 +4,7 @@ use Auth;
use Utils;
use Response;
use Input;
use Validator;
use Cache;
use App\Models\Client;
use App\Models\Account;
@ -18,6 +19,8 @@ use App\Ninja\Transformers\UserAccountTransformer;
use App\Http\Controllers\BaseAPIController;
use Swagger\Annotations as SWG;
use App\Http\Requests\UpdateAccountRequest;
class AccountApiController extends BaseAPIController
{
protected $accountRepo;
@ -101,4 +104,15 @@ class AccountApiController extends BaseAPIController
{
return $this->processLogin($request);
}
public function update(UpdateAccountRequest $request)
{
$account = Auth::user()->account;
$this->accountRepo->save($request->input(), $account);
$transformer = new AccountTransformer(null, $request->serializer);
$account = $this->createItem($account, $transformer, 'account');
return $this->response($account);
}
}

View File

@ -29,6 +29,8 @@ use App\Events\UserLoggedIn;
use App\Events\UserSettingsChanged;
use App\Services\AuthService;
use App\Http\Requests\UpdateAccountRequest;
class AccountController extends BaseController
{
protected $accountRepo;
@ -611,11 +613,12 @@ class AccountController extends BaseController
$iframeURL = rtrim($iframeURL, "/");
$subdomain = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', substr(strtolower(Input::get('subdomain')), 0, MAX_SUBDOMAIN_LENGTH));
if ($iframeURL || !$subdomain || in_array($subdomain, ['www', 'app', 'mail', 'admin', 'blog', 'user', 'contact', 'payment', 'payments', 'billing', 'invoice', 'business', 'owner'])) {
if ($iframeURL) {
$subdomain = null;
}
if ($subdomain) {
$rules['subdomain'] = "unique:accounts,subdomain,{$user->account_id},id";
$exclude = ['www', 'app', 'mail', 'admin', 'blog', 'user', 'contact', 'payment', 'payments', 'billing', 'invoice', 'business', 'owner', 'info', 'ninja'];
$rules['subdomain'] = "unique:accounts,subdomain,{$user->account_id},id|not_in:" . implode(',', $exclude);
}
$validator = Validator::make(Input::all(), $rules);
@ -704,7 +707,9 @@ class AccountController extends BaseController
$account->quote_number_prefix = null;
}
if (!$account->share_counter && $account->invoice_number_prefix == $account->quote_number_prefix) {
if (!$account->share_counter
&& $account->invoice_number_prefix == $account->quote_number_prefix
&& $account->invoice_number_pattern == $account->quote_number_pattern) {
Session::flash('error', trans('texts.invalid_counter'));
return Redirect::to('settings/'.ACCOUNT_INVOICE_SETTINGS)->withInput();
@ -724,6 +729,8 @@ class AccountController extends BaseController
$account = Auth::user()->account;
$account->hide_quantity = Input::get('hide_quantity') ? true : false;
$account->hide_paid_to_date = Input::get('hide_paid_to_date') ? true : false;
$account->all_pages_header = Input::get('all_pages_header') ? true : false;
$account->all_pages_footer = Input::get('all_pages_footer') ? true : false;
$account->header_font_id = Input::get('header_font_id');
$account->body_font_id = Input::get('body_font_id');
$account->primary_color = Input::get('primary_color');
@ -762,80 +769,52 @@ class AccountController extends BaseController
return Redirect::to('settings/'.ACCOUNT_NOTIFICATIONS);
}
private function saveDetails()
public function updateDetails(UpdateAccountRequest $request)
{
$rules = array(
'name' => 'required',
'logo' => 'sometimes|max:'.MAX_LOGO_FILE_SIZE.'|mimes:jpeg,gif,png',
);
$account = Auth::user()->account;
$this->accountRepo->save($request->input(), $account);
$validator = Validator::make(Input::all(), $rules);
/* Logo image file */
if ($file = Input::file('logo')) {
$path = Input::file('logo')->getRealPath();
File::delete('logo/'.$account->account_key.'.jpg');
File::delete('logo/'.$account->account_key.'.png');
if ($validator->fails()) {
return Redirect::to('settings/'.ACCOUNT_COMPANY_DETAILS)
->withErrors($validator)
->withInput();
} else {
$account = Auth::user()->account;
$account->name = trim(Input::get('name'));
$account->id_number = trim(Input::get('id_number'));
$account->vat_number = trim(Input::get('vat_number'));
$account->work_email = trim(Input::get('work_email'));
$account->website = trim(Input::get('website'));
$account->work_phone = trim(Input::get('work_phone'));
$account->address1 = trim(Input::get('address1'));
$account->address2 = trim(Input::get('address2'));
$account->city = trim(Input::get('city'));
$account->state = trim(Input::get('state'));
$account->postal_code = trim(Input::get('postal_code'));
$account->country_id = Input::get('country_id') ? Input::get('country_id') : null;
$account->size_id = Input::get('size_id') ? Input::get('size_id') : null;
$account->industry_id = Input::get('industry_id') ? Input::get('industry_id') : null;
$account->email_footer = Input::get('email_footer');
$account->save();
$mimeType = $file->getMimeType();
/* Logo image file */
if ($file = Input::file('logo')) {
$path = Input::file('logo')->getRealPath();
File::delete('logo/'.$account->account_key.'.jpg');
File::delete('logo/'.$account->account_key.'.png');
$mimeType = $file->getMimeType();
if ($mimeType == 'image/jpeg') {
$path = 'logo/'.$account->account_key.'.jpg';
$file->move('logo/', $account->account_key.'.jpg');
} elseif ($mimeType == 'image/png') {
$path = 'logo/'.$account->account_key.'.png';
$file->move('logo/', $account->account_key.'.png');
} else {
if (extension_loaded('fileinfo')) {
$image = Image::make($path);
$image->resize(200, 120, function ($constraint) {
$constraint->aspectRatio();
});
$path = 'logo/'.$account->account_key.'.jpg';
Image::canvas($image->width(), $image->height(), '#FFFFFF')
->insert($image)->save($path);
} else {
Session::flash('warning', 'Warning: To support gifs the fileinfo PHP extension needs to be enabled.');
}
}
// make sure image isn't interlaced
if ($mimeType == 'image/jpeg') {
$path = 'logo/'.$account->account_key.'.jpg';
$file->move('logo/', $account->account_key.'.jpg');
} elseif ($mimeType == 'image/png') {
$path = 'logo/'.$account->account_key.'.png';
$file->move('logo/', $account->account_key.'.png');
} else {
if (extension_loaded('fileinfo')) {
$img = Image::make($path);
$img->interlace(false);
$img->save();
$image = Image::make($path);
$image->resize(200, 120, function ($constraint) {
$constraint->aspectRatio();
});
$path = 'logo/'.$account->account_key.'.jpg';
Image::canvas($image->width(), $image->height(), '#FFFFFF')
->insert($image)->save($path);
} else {
Session::flash('warning', 'Warning: To support gifs the fileinfo PHP extension needs to be enabled.');
}
}
event(new UserSettingsChanged());
Session::flash('message', trans('texts.updated_settings'));
return Redirect::to('settings/'.ACCOUNT_COMPANY_DETAILS);
// make sure image isn't interlaced
if (extension_loaded('fileinfo')) {
$img = Image::make($path);
$img->interlace(false);
$img->save();
}
}
event(new UserSettingsChanged());
Session::flash('message', trans('texts.updated_settings'));
return Redirect::to('settings/'.ACCOUNT_COMPANY_DETAILS);
}
private function saveUserDetails()
@ -882,6 +861,7 @@ class AccountController extends BaseController
$account->currency_id = Input::get('currency_id') ? Input::get('currency_id') : 1; // US Dollar
$account->language_id = Input::get('language_id') ? Input::get('language_id') : 1; // English
$account->military_time = Input::get('military_time') ? true : false;
$account->show_currency_code = Input::get('show_currency_code') ? true : false;
$account->save();
event(new UserSettingsChanged());

View File

@ -89,6 +89,10 @@ class AccountGatewayController extends BaseController
->orderBy('name')->get();
$data['hiddenFields'] = Gateway::$hiddenFields;
if ( ! \Request::secure() && ! Utils::isNinjaDev()) {
Session::flash('warning', trans('texts.enable_https'));
}
return View::make('accounts.account_gateway', $data);
}
@ -195,6 +199,8 @@ class AccountGatewayController extends BaseController
if ($gatewayId == GATEWAY_DWOLLA) {
$optional = array_merge($optional, ['key', 'secret']);
} elseif ($gatewayId == GATEWAY_STRIPE) {
$rules['publishable_key'] = 'required';
}
foreach ($fields as $field => $details) {

View File

@ -206,15 +206,15 @@ class AppController extends BaseController
Config::set('mail.from.address', $email);
Config::set('mail.from.name', $fromName);
$data = [
'text' => 'Test email',
];
try {
$this->mailer->sendTo($email, $email, $fromName, 'Test email', 'contact', $data);
$response = $this->mailer->sendTo($email, $email, $fromName, 'Test email', 'contact', $data);
return 'Sent';
return $response === true ? 'Sent' : $response;
} catch (Exception $e) {
return $e->getMessage();
}
@ -224,6 +224,7 @@ class AppController extends BaseController
{
if (!Utils::isNinjaProd() && !Utils::isDatabaseSetup()) {
try {
set_time_limit(60 * 5); // shouldn't take this long but just in case
Artisan::call('migrate', array('--force' => true));
if (Industry::count() == 0) {
Artisan::call('db:seed', array('--force' => true));
@ -241,12 +242,19 @@ class AppController extends BaseController
{
if (!Utils::isNinjaProd()) {
try {
set_time_limit(60 * 5);
Cache::flush();
Session::flush();
Artisan::call('optimize', array('--force' => true));
Artisan::call('migrate', array('--force' => true));
Artisan::call('db:seed', array('--force' => true, '--class' => 'PaymentLibrariesSeeder'));
Artisan::call('db:seed', array('--force' => true, '--class' => 'FontsSeeder'));
foreach ([
'PaymentLibraries',
'Fonts',
'Banks',
'InvoiceStatus'
] as $seeder) {
Artisan::call('db:seed', array('--force' => true, '--class' => "{$seeder}Seeder"));
}
Artisan::call('optimize', array('--force' => true));
Event::fire(new UserSettingsChanged());
Session::flash('message', trans('texts.processed_updates'));
} catch (Exception $e) {
@ -271,4 +279,36 @@ class AppController extends BaseController
return RESULT_SUCCESS;
}
}
public function stats()
{
if (Input::get('password') != env('RESELLER_PASSWORD')) {
sleep(3);
return '';
}
if (Utils::getResllerType() == RESELLER_REVENUE_SHARE) {
$payments = DB::table('accounts')
->leftJoin('payments', 'payments.account_id', '=', 'accounts.id')
->leftJoin('clients', 'clients.id', '=', 'payments.client_id')
->where('accounts.account_key', '=', NINJA_ACCOUNT_KEY)
->where('payments.is_deleted', '=', false)
->get([
'clients.public_id as client_id',
'payments.public_id as payment_id',
'payments.payment_date',
'payments.amount'
]);
} else {
$payments = DB::table('accounts')
->leftJoin('payments', 'payments.account_id', '=', 'accounts.id')
->leftJoin('clients', 'clients.id', '=', 'payments.client_id')
->where('accounts.account_key', '=', NINJA_ACCOUNT_KEY)
->where('payments.is_deleted', '=', false)
->groupBy('clients.id')
->count();
}
return json_encode($payments);
}
}

View File

@ -1,6 +1,5 @@
<?php namespace App\Http\Controllers;
use Crypt;
use Cache;
use Auth;
use Datatable;
@ -11,23 +10,27 @@ use Session;
use View;
use Validator;
use stdClass;
use Crypt;
use URL;
use Utils;
use App\Models\Gateway;
use App\Models\Account;
use App\Models\BankAccount;
use App\Ninja\Repositories\AccountRepository;
use App\Ninja\Repositories\BankAccountRepository;
use App\Services\BankAccountService;
use App\Http\Requests\CreateBankAccountRequest;
class BankAccountController extends BaseController
{
protected $bankAccountService;
protected $bankAccountRepo;
public function __construct(BankAccountService $bankAccountService)
public function __construct(BankAccountService $bankAccountService, BankAccountRepository $bankAccountRepo)
{
parent::__construct();
$this->bankAccountService = $bankAccountService;
$this->bankAccountRepo = $bankAccountRepo;
}
public function index()
@ -43,11 +46,8 @@ class BankAccountController extends BaseController
public function edit($publicId)
{
$bankAccount = BankAccount::scope($publicId)->firstOrFail();
$bankAccount->username = str_repeat('*', 16);
$data = [
'url' => 'bank_accounts/' . $publicId,
'method' => 'PUT',
'title' => trans('texts.edit_bank_account'),
'banks' => Cache::get('banks'),
'bankAccount' => $bankAccount,
@ -61,11 +61,6 @@ class BankAccountController extends BaseController
return $this->save($publicId);
}
public function store()
{
return $this->save();
}
/**
* Displays the form for account creation
*
@ -73,9 +68,6 @@ class BankAccountController extends BaseController
public function create()
{
$data = [
'url' => 'bank_accounts',
'method' => 'POST',
'title' => trans('texts.add_bank_account'),
'banks' => Cache::get('banks'),
'bankAccount' => null,
];
@ -94,59 +86,40 @@ class BankAccountController extends BaseController
return Redirect::to('settings/' . ACCOUNT_BANKS);
}
/**
* Stores new account
*
*/
public function save($bankAccountPublicId = false)
public function validateAccount()
{
$account = Auth::user()->account;
$bankId = Input::get('bank_id');
$username = Input::get('bank_username');
$rules = [
'bank_id' => $bankAccountPublicId ? '' : 'required',
'bank_username' => 'required',
];
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return Redirect::to('bank_accounts/create')
->withErrors($validator)
->withInput();
$publicId = Input::get('public_id');
$username = trim(Input::get('bank_username'));
$password = trim(Input::get('bank_password'));
if ($publicId) {
$bankAccount = BankAccount::scope($publicId)->firstOrFail();
if ($username != $bankAccount->username) {
// TODO update username
}
$username = Crypt::decrypt($username);
$bankId = $bankAccount->bank_id;
} else {
if ($bankAccountPublicId) {
$bankAccount = BankAccount::scope($bankAccountPublicId)->firstOrFail();
} else {
$bankAccount = BankAccount::createNew();
$bankAccount->bank_id = $bankId;
}
if ($username != str_repeat('*', strlen($username))) {
$bankAccount->username = Crypt::encrypt(trim($username));
}
if ($bankAccountPublicId) {
$bankAccount->save();
$message = trans('texts.updated_bank_account');
} else {
$account->bank_accounts()->save($bankAccount);
$message = trans('texts.created_bank_account');
}
Session::flash('message', $message);
return Redirect::to("bank_accounts/{$bankAccount->public_id}/edit");
$bankId = Input::get('bank_id');
}
return json_encode($this->bankAccountService->loadBankAccounts($bankId, $username, $password, $publicId));
}
public function test()
public function store(CreateBankAccountRequest $request)
{
$bankId = Input::get('bank_id');
$username = Input::get('bank_username');
$password = Input::get('bank_password');
$bankAccount = $this->bankAccountRepo->save(Input::all());
return json_encode($this->bankAccountService->loadBankAccounts($bankId, $username, $password, false));
$bankId = Input::get('bank_id');
$username = trim(Input::get('bank_username'));
$password = trim(Input::get('bank_password'));
return json_encode($this->bankAccountService->loadBankAccounts($bankId, $username, $password, true));
}
public function importExpenses($bankId)
{
return $this->bankAccountService->importExpenses($bankId, Input::all());
}
}

View File

@ -107,6 +107,17 @@ class BaseAPIController extends Controller
return Response::make($response, 200, $headers);
}
protected function errorResponse($response)
{
$error['error'] = $response;
$error = json_encode($error, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders();
return Response::make($error, 400, $headers);
}
protected function getIncluded()
{
$data = ['user'];

View File

@ -9,16 +9,20 @@ use App\Ninja\Repositories\ClientRepository;
use App\Http\Requests\CreateClientRequest;
use App\Http\Controllers\BaseAPIController;
use App\Ninja\Transformers\ClientTransformer;
use App\Services\ClientService;
use App\Http\Requests\UpdateClientRequest;
class ClientApiController extends BaseAPIController
{
protected $clientRepo;
protected $clientService;
public function __construct(ClientRepository $clientRepo)
public function __construct(ClientRepository $clientRepo, ClientService $clientService)
{
parent::__construct();
$this->clientRepo = $clientRepo;
$this->clientService = $clientService;
}
public function ping()
@ -47,12 +51,23 @@ class ClientApiController extends BaseAPIController
public function index()
{
$clients = Client::scope()
->with($this->getIncluded())
->orderBy('created_at', 'desc')
->paginate();
->with($this->getIncluded())
->orderBy('created_at', 'desc')->withTrashed();
// Filter by email
if (Input::has('email')) {
$email = Input::get('email');
$clients = $clients->whereHas('contacts', function ($query) use ($email) {
$query->where('email', $email);
});
}
$clients = $clients->paginate();
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$paginator = Client::scope()->paginate();
$paginator = Client::scope()->withTrashed()->paginate();
$data = $this->createCollection($clients, $transformer, ENTITY_CLIENT, $paginator);
@ -83,14 +98,105 @@ class ClientApiController extends BaseAPIController
public function store(CreateClientRequest $request)
{
$client = $this->clientRepo->save($request->input());
$client = Client::scope($client->public_id)
->with('country', 'contacts', 'industry', 'size', 'currency')
->first();
->with('country', 'contacts', 'industry', 'size', 'currency')
->first();
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($client, $transformer, ENTITY_CLIENT);
return $this->response($data);
}
/**
* @SWG\Put(
* path="/clients/{client_id}",
* tags={"client"},
* summary="Update a client",
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/Client")
* ),
* @SWG\Response(
* response=200,
* description="Update client",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Client"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function update(UpdateClientRequest $request, $publicId)
{
if ($request->action == ACTION_ARCHIVE) {
$client = Client::scope($publicId)->firstOrFail();
$this->clientRepo->archive($client);
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($client, $transformer, ENTITY_CLIENT);
return $this->response($data);
}
$data = $request->input();
$data['public_id'] = $publicId;
$this->clientRepo->save($data);
$client = Client::scope($publicId)
->with('country', 'contacts', 'industry', 'size', 'currency')
->first();
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($client, $transformer, ENTITY_CLIENT);
return $this->response($data);
}
/**
* @SWG\Delete(
* path="/clients/{client_id}",
* tags={"client"},
* summary="Delete a client",
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/Client")
* ),
* @SWG\Response(
* response=200,
* description="Delete client",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Client"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function destroy($publicId)
{
$client = Client::scope($publicId)->withTrashed()->first();
$this->clientRepo->delete($client);
$client = Client::scope($publicId)
->with('country', 'contacts', 'industry', 'size', 'currency')
->withTrashed()
->first();
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($client, $transformer, ENTITY_CLIENT);
return $this->response($data);
}
}

View File

@ -112,7 +112,7 @@ class DashboardController extends BaseController
->where('payments.account_id', '=', Auth::user()->account_id)
->where('payments.is_deleted', '=', false)
->where('invoices.is_deleted', '=', false)
->where('clients.deleted_at', '=', null)
->where('clients.is_deleted', '=', false)
->where('contacts.deleted_at', '=', null)
->where('contacts.is_primary', '=', true)
->select(['payments.payment_date', 'payments.amount', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id'])

View File

@ -48,6 +48,7 @@ class ExpenseController extends BaseController
'columns' => Utils::trans([
'checkbox',
'vendor',
'client',
'expense_date',
'amount',
'public_notes',
@ -59,7 +60,7 @@ class ExpenseController extends BaseController
public function getDatatable($expensePublicId = null)
{
return $this->expenseService->getDatatable($expensePublicId, Input::get('sSearch'));
return $this->expenseService->getDatatable(Input::get('sSearch'));
}
public function getDatatableVendor($vendorPublicId = null)
@ -152,7 +153,7 @@ class ExpenseController extends BaseController
*/
public function update(UpdateExpenseRequest $request)
{
$expense = $this->expenseRepo->save($request->input());
$expense = $this->expenseService->save($request->input());
Session::flash('message', trans('texts.updated_expense'));
@ -166,7 +167,7 @@ class ExpenseController extends BaseController
public function store(CreateExpenseRequest $request)
{
$expense = $this->expenseRepo->save($request->input());
$expense = $this->expenseService->save($request->input());
Session::flash('message', trans('texts.created_expense'));
@ -181,22 +182,30 @@ class ExpenseController extends BaseController
switch($action)
{
case 'invoice':
$expenses = Expense::scope($ids)->get();
$expenses = Expense::scope($ids)->with('client')->get();
$clientPublicId = null;
$data = [];
$currencyId = null;
$data = [];
// Validate that either all expenses do not have a client or if there is a client, it is the same client
foreach ($expenses as $expense)
{
if ($expense->client_id) {
if ($expense->client) {
if (!$clientPublicId) {
$clientPublicId = $expense->client_id;
} elseif ($clientPublicId != $expense->client_id) {
$clientPublicId = $expense->client->public_id;
} elseif ($clientPublicId != $expense->client->public_id) {
Session::flash('error', trans('texts.expense_error_multiple_clients'));
return Redirect::to('expenses');
}
}
if (!$currencyId) {
$currencyId = $expense->invoice_currency_id;
} elseif ($currencyId != $expense->invoice_currency_id && $expense->invoice_currency_id) {
Session::flash('error', trans('texts.expense_error_multiple_currencies'));
return Redirect::to('expenses');
}
if ($expense->invoice_id) {
Session::flash('error', trans('texts.expense_error_invoiced'));
return Redirect::to('expenses');
@ -211,7 +220,9 @@ class ExpenseController extends BaseController
];
}
return Redirect::to("invoices/create/{$clientPublicId}")->with('expenses', $data);
return Redirect::to("invoices/create/{$clientPublicId}")
->with('expenseCurrencyId', $currencyId)
->with('expenses', $data);
break;
default:

View File

@ -185,6 +185,10 @@ class InvoiceApiController extends BaseAPIController
'partial' => 0
];
if (!isset($data['invoice_status_id']) || $data['invoice_status_id'] == 0) {
$data['invoice_status_id'] = INVOICE_STATUS_DRAFT;
}
if (!isset($data['invoice_date'])) {
$fields['invoice_date_sql'] = date_create()->format('Y-m-d');
}
@ -250,20 +254,14 @@ class InvoiceApiController extends BaseAPIController
$data = Input::all();
$error = null;
if (!isset($data['id'])) {
$error = trans('validation.required', ['attribute' => 'id']);
} else {
$invoice = Invoice::scope($data['id'])->first();
if (!$invoice) {
$error = trans('validation.not_in', ['attribute' => 'id']);
} else {
$this->mailer->sendInvoice($invoice);
}
}
$invoice = Invoice::scope($data['id'])->firstOrFail();
if ($error) {
$response = json_encode($error, JSON_PRETTY_PRINT);
} else {
$this->mailer->sendInvoice($invoice);
if($error) {
$response['error'] = "There was an error sending the invoice";
}
else {
$response = json_encode(RESULT_SUCCESS, JSON_PRETTY_PRINT);
}
@ -271,6 +269,7 @@ class InvoiceApiController extends BaseAPIController
return Response::make($response, $error ? 400 : 200, $headers);
}
/**
* @SWG\Put(
* path="/invoices",
@ -297,11 +296,25 @@ class InvoiceApiController extends BaseAPIController
if ($request->action == ACTION_ARCHIVE) {
$invoice = Invoice::scope($publicId)->firstOrFail();
$this->invoiceRepo->archive($invoice);
/*
$response = json_encode(RESULT_SUCCESS, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders();
return Response::make($response, 200, $headers);
*/
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
return $this->response($data);
}
else if ($request->action == ACTION_CONVERT) {
$quote = Invoice::scope($publicId)->firstOrFail();
$invoice = $this->invoiceRepo->cloneInvoice($quote, $quote->id);
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
return $this->response($data);
}
else if ($request->action == ACTION_RESTORE) {
$invoice = Invoice::scope($publicId)->withTrashed()->firstOrFail();
$this->invoiceRepo->restore($invoice);
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');

View File

@ -296,7 +296,7 @@ class InvoiceController extends BaseController
return [
'data' => Input::old('data'),
'account' => Auth::user()->account->load('country'),
'products' => Product::scope()->with('default_tax_rate')->orderBy('id')->get(),
'products' => Product::scope()->with('default_tax_rate')->orderBy('product_key')->get(),
'taxRates' => TaxRate::scope()->orderBy('name')->get(),
'currencies' => Cache::get('currencies'),
'languages' => Cache::get('languages'),
@ -320,6 +320,7 @@ class InvoiceController extends BaseController
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null,
'expenses' => Session::get('expenses') ? json_encode(Session::get('expenses')) : null,
'expenseCurrencyId' => Session::get('expenseCurrencyId') ?: null,
];
}
@ -333,7 +334,7 @@ class InvoiceController extends BaseController
{
$action = Input::get('action');
$entityType = Input::get('entityType');
$invoice = $this->invoiceService->save($request->input());
$entityType = $invoice->getEntityType();
$message = trans("texts.created_{$entityType}");

View File

@ -1,6 +1,8 @@
<?php namespace App\Http\Controllers;
use App\Ninja\Mailers\ContactMailer;
use Auth;
use Illuminate\Http\Request;
use Input;
use Utils;
use Response;
@ -9,16 +11,19 @@ use App\Models\Invoice;
use App\Ninja\Repositories\PaymentRepository;
use App\Http\Controllers\BaseAPIController;
use App\Ninja\Transformers\PaymentTransformer;
use App\Ninja\Transformers\InvoiceTransformer;
class PaymentApiController extends BaseAPIController
{
protected $paymentRepo;
public function __construct(PaymentRepository $paymentRepo)
public function __construct(PaymentRepository $paymentRepo, ContactMailer $contactMailer)
{
parent::__construct();
$this->paymentRepo = $paymentRepo;
$this->contactMailer = $contactMailer;
}
/**
@ -41,7 +46,7 @@ class PaymentApiController extends BaseAPIController
{
$paginator = Payment::scope();
$payments = Payment::scope()
->with('client.contacts', 'invitation', 'user', 'invoice');
->with('client.contacts', 'invitation', 'user', 'invoice')->withTrashed();
if ($clientPublicId = Input::get('client_id')) {
$filter = function($query) use ($clientPublicId) {
@ -60,6 +65,63 @@ class PaymentApiController extends BaseAPIController
return $this->response($data);
}
/**
* @SWG\Put(
* path="/payments/{payment_id",
* summary="Update a payment",
* tags={"payment"},
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/Payment")
* ),
* @SWG\Response(
* response=200,
* description="Update payment",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Payment"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function update(Request $request, $publicId)
{
$data = Input::all();
$data['public_id'] = $publicId;
$error = false;
if ($request->action == ACTION_ARCHIVE) {
$payment = Payment::scope($publicId)->withTrashed()->firstOrFail();
$this->paymentRepo->archive($payment);
$invoice = Invoice::scope($data['invoice_id'])->with('client')->with(['payments' => function($query) {
$query->withTrashed();
}])->first();
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
return $this->response($data);
}
$payment = $this->paymentRepo->save($data);
if ($error) {
return $error;
}
$invoice = Invoice::scope($data['invoice_id'])->with('client', 'invoice_items', 'invitations')->with(['payments' => function($query) {
$query->withTrashed();
}])->withTrashed()->first();
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
return $this->response($data);
}
/**
* @SWG\Post(
* path="/payments",
@ -107,13 +169,58 @@ class PaymentApiController extends BaseAPIController
return $error;
}
$payment = $this->paymentRepo->save($data);
$payment = Payment::scope($payment->public_id)->with('client', 'contact', 'user', 'invoice')->first();
$transformer = new PaymentTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($payment, $transformer, 'payment');
if (Input::get('email_receipt')) {
$this->contactMailer->sendPaymentConfirmation($payment);
}
$invoice = Invoice::scope($invoice->public_id)->with('client', 'invoice_items', 'invitations')->with(['payments' => function($query) {
$query->withTrashed();
}])->first();
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
return $this->response($data);
}
/**
* @SWG\Delete(
* path="/payments/{payment_id}",
* summary="Delete a payment",
* tags={"payment"},
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/Payment")
* ),
* @SWG\Response(
* response=200,
* description="Delete payment",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Payment"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function destroy($publicId)
{
$payment = Payment::scope($publicId)->withTrashed()->first();
$invoiceId = $payment->invoice->public_id;
$this->paymentRepo->delete($payment);
$invoice = Invoice::scope($invoiceId)->with('client', 'invoice_items', 'invitations')->with(['payments' => function($query) {
$query->withTrashed();
}])->first();
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
return $this->response($data);
}
}

View File

@ -0,0 +1,103 @@
<?php namespace App\Http\Controllers;
use App\Ninja\Repositories\ProductRepository;
use App\Ninja\Transformers\ProductTransformer;
use Auth;
use Str;
use DB;
use Datatable;
use Utils;
use URL;
use View;
use Input;
use Session;
use Redirect;
use App\Models\Product;
use App\Models\TaxRate;
use App\Services\ProductService;
class ProductApiController extends BaseAPIController
{
protected $productService;
protected $productRepo;
public function __construct(ProductService $productService, ProductRepository $productRepo)
{
parent::__construct();
$this->productService = $productService;
$this->productRepo = $productRepo;
}
public function index()
{
$products = Product::scope()->withTrashed();
$products = $products->paginate();
$paginator = Product::scope()->withTrashed()->paginate();
$transformer = new ProductTransformer(\Auth::user()->account, $this->serializer);
$data = $this->createCollection($products, $transformer, 'products', $paginator);
return $this->response($data);
}
public function getDatatable()
{
return $this->productService->getDatatable(Auth::user()->account_id);
}
public function store()
{
return $this->save();
}
public function update(\Illuminate\Http\Request $request, $publicId)
{
if ($request->action == ACTION_ARCHIVE) {
$product = Product::scope($publicId)->withTrashed()->firstOrFail();
$this->productRepo->archive($product);
$transformer = new ProductTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($product, $transformer, 'products');
return $this->response($data);
}
else
return $this->save($publicId);
}
public function destroy($publicId)
{
//stub
}
private function save($productPublicId = false)
{
if ($productPublicId) {
$product = Product::scope($productPublicId)->firstOrFail();
} else {
$product = Product::createNew();
}
$product->product_key = trim(Input::get('product_key'));
$product->notes = trim(Input::get('notes'));
$product->cost = trim(Input::get('cost'));
//$product->default_tax_rate_id = Input::get('default_tax_rate_id');
$product->save();
$transformer = new ProductTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($product, $transformer, 'products');
return $this->response($data);
}
}

View File

@ -145,6 +145,7 @@ class TaskController extends BaseController
'actions' => $actions,
'timezone' => Auth::user()->account->timezone ? Auth::user()->account->timezone->name : DEFAULT_TIMEZONE,
'datetimeFormat' => Auth::user()->account->getMomentDateTimeFormat(),
//'entityStatus' => $task->present()->status,
];
$data = array_merge($data, self::getViewModel());

View File

@ -0,0 +1,68 @@
<?php namespace App\Http\Controllers;
use App\Services\TaxRateService;
use App\Ninja\Repositories\TaxRateRepository;
use App\Ninja\Transformers\TaxRateTransformer;
use Auth;
use App\Models\TaxRate;
use App\Http\Requests\CreateTaxRateRequest;
use App\Http\Requests\UpdateTaxRateRequest;
class TaxRateApiController extends BaseAPIController
{
protected $taxRateService;
protected $taxRateRepo;
public function __construct(TaxRateService $taxRateService, TaxRateRepository $taxRateRepo)
{
parent::__construct();
$this->taxRateService = $taxRateService;
$this->taxRateRepo = $taxRateRepo;
}
public function index()
{
$taxRates = TaxRate::scope()->withTrashed();
$taxRates = $taxRates->paginate();
$paginator = TaxRate::scope()->withTrashed()->paginate();
$transformer = new TaxRateTransformer(Auth::user()->account, $this->serializer);
$data = $this->createCollection($taxRates, $transformer, 'tax_rates', $paginator);
return $this->response($data);
}
public function store(CreateTaxRateRequest $request)
{
return $this->save($request);
}
public function update(UpdateTaxRateRequest $request, $taxRatePublicId)
{
$taxRate = TaxRate::scope($taxRatePublicId)->firstOrFail();
if ($request->action == ACTION_ARCHIVE) {
$this->taxRateRepo->archive($taxRate);
$transformer = new TaxRateTransformer(Auth::user()->account, $request->serializer);
$data = $this->createItem($taxRate, $transformer, 'tax_rates');
return $this->response($data);
} else {
return $this->save($request, $taxRate);
}
}
private function save($request, $taxRate = false)
{
$taxRate = $this->taxRateRepo->save($request->input(), $taxRate);
$transformer = new TaxRateTransformer(\Auth::user()->account, $request->serializer);
$data = $this->createItem($taxRate, $transformer, 'tax_rates');
return $this->response($data);
}
}

View File

@ -13,16 +13,22 @@ use Redirect;
use App\Models\TaxRate;
use App\Services\TaxRateService;
use App\Ninja\Repositories\TaxRateRepository;
use App\Http\Requests\CreateTaxRateRequest;
use App\Http\Requests\UpdateTaxRateRequest;
class TaxRateController extends BaseController
{
protected $taxRateService;
protected $taxRateRepo;
public function __construct(TaxRateService $taxRateService)
public function __construct(TaxRateService $taxRateService, TaxRateRepository $taxRateRepo)
{
parent::__construct();
$this->taxRateService = $taxRateService;
$this->taxRateRepo = $taxRateRepo;
}
public function index()
@ -59,34 +65,25 @@ class TaxRateController extends BaseController
return View::make('accounts.tax_rate', $data);
}
public function store()
public function store(CreateTaxRateRequest $request)
{
return $this->save();
}
public function update($publicId)
{
return $this->save($publicId);
}
private function save($publicId = false)
{
if ($publicId) {
$taxRate = TaxRate::scope($publicId)->firstOrFail();
} else {
$taxRate = TaxRate::createNew();
}
$taxRate->name = trim(Input::get('name'));
$taxRate->rate = Utils::parseFloat(Input::get('rate'));
$taxRate->save();
$message = $publicId ? trans('texts.updated_tax_rate') : trans('texts.created_tax_rate');
Session::flash('message', $message);
$this->taxRateRepo->save($request->input());
Session::flash('message', trans('texts.created_tax_rate'));
return Redirect::to('settings/' . ACCOUNT_TAX_RATES);
}
public function update(UpdateTaxRateRequest $request, $publicId)
{
$taxRate = TaxRate::scope($publicId)->firstOrFail();
$this->taxRateRepo->save($request->input(), $taxRate);
Session::flash('message', trans('texts.updated_tax_rate'));
return Redirect::to('settings/' . ACCOUNT_TAX_RATES);
}
public function bulk()
{
$action = Input::get('bulk_action');

View File

@ -0,0 +1,76 @@
<?php namespace App\Http\Controllers;
use App\Services\UserService;
use App\Ninja\Repositories\UserRepository;
use App\Ninja\Transformers\UserTransformer;
use Auth;
use App\Models\User;
use App\Http\Requests\CreateUserRequest;
use App\Http\Requests\UpdateUserRequest;
class UserApiController extends BaseAPIController
{
protected $userService;
protected $userRepo;
public function __construct(UserService $userService, UserRepository $userRepo)
{
parent::__construct();
$this->userService = $userService;
$this->userRepo = $userRepo;
}
public function index()
{
$user = Auth::user();
$users = User::whereAccountId($user->account_id)->withTrashed();
$users = $users->paginate();
$paginator = User::whereAccountId($user->account_id)->withTrashed()->paginate();
$transformer = new UserTransformer(Auth::user()->account, $this->serializer);
$data = $this->createCollection($users, $transformer, 'users', $paginator);
return $this->response($data);
}
/*
public function store(CreateUserRequest $request)
{
return $this->save($request);
}
*/
public function update(UpdateUserRequest $request, $userPublicId)
{
/*
// temporary fix for ids starting at 0
$userPublicId -= 1;
$user = User::scope($userPublicId)->firstOrFail();
*/
$user = Auth::user();
if ($request->action == ACTION_ARCHIVE) {
$this->userRepo->archive($user);
$transformer = new UserTransformer(Auth::user()->account, $request->serializer);
$data = $this->createItem($user, $transformer, 'users');
return $this->response($data);
} else {
return $this->save($request, $user);
}
}
private function save($request, $user = false)
{
$user = $this->userRepo->save($request->input(), $user);
$transformer = new UserTransformer(\Auth::user()->account, $request->serializer);
$data = $this->createItem($user, $transformer, 'users');
return $this->response($data);
}
}

View File

@ -55,7 +55,8 @@ class VendorController extends BaseController
'columns' => Utils::trans([
'checkbox',
'vendor',
'contact',
'city',
'phone',
'email',
'date_created',
''

View File

@ -7,7 +7,9 @@ class VerifyCsrfToken extends BaseVerifier {
private $openRoutes = [
'signup/register',
'api/v1/*',
'api/v1/login',
'api/v1/clients/*',
'api/v1/clients',
'api/v1/invoices/*',
'api/v1/invoices',
@ -18,6 +20,7 @@ class VerifyCsrfToken extends BaseVerifier {
'api/v1/hooks',
'hook/email_opened',
'hook/email_bounced',
'reseller_stats',
];
/**

View File

@ -0,0 +1,32 @@
<?php namespace app\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
class CreateBankAccountRequest extends Request
{
// Expenses
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'bank_id' => 'required',
'bank_username' => 'required',
'bank_password' => 'required',
];
}
}

View File

@ -0,0 +1,31 @@
<?php namespace app\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
class CreateTaxRateRequest extends Request
{
// Expenses
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required',
'rate' => 'required',
];
}
}

View File

@ -23,10 +23,11 @@ class CreateVendorRequest extends Request
public function rules()
{
return [
'vendorcontacts' => 'valid_contacts',
'name' => 'required',
];
}
/*
public function validator($factory)
{
// support submiting the form with a single contact record
@ -41,4 +42,5 @@ class CreateVendorRequest extends Request
$this->input(), $this->container->call([$this, 'rules']), $this->messages()
);
}
*/
}

View File

@ -0,0 +1,31 @@
<?php namespace app\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
class UpdateAccountRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required',
'logo' => 'sometimes|max:'.MAX_LOGO_FILE_SIZE.'|mimes:jpeg,gif,png',
];
}
}
//

View File

@ -0,0 +1,31 @@
<?php namespace app\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
class UpdateTaxRateRequest extends Request
{
// Expenses
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required',
'rate' => 'required',
];
}
}

View File

@ -0,0 +1,33 @@
<?php namespace app\Http\Requests;
use Auth;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
class UpdateUserRequest extends Request
{
// Expenses
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'email' => 'email|required|unique:users,email,' . Auth::user()->id . ',id',
'first_name' => 'required',
'last_name' => 'required',
];
}
}

View File

@ -23,7 +23,7 @@ class UpdateVendorRequest extends Request
public function rules()
{
return [
'vendor_contacts' => 'valid_contacts',
'name' => 'required',
];
}
}

View File

@ -1,5 +1,6 @@
<?php
/*
|--------------------------------------------------------------------------
| Application Routes
@ -82,6 +83,10 @@ if (Utils::isNinja()) {
Route::get('/demo', 'AccountController@demo');
}
if (Utils::isReseller()) {
Route::post('/reseller_stats', 'AppController@stats');
}
Route::group(['middleware' => 'auth'], function() {
Route::get('dashboard', 'DashboardController@index');
Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible');
@ -116,6 +121,7 @@ Route::group(['middleware' => 'auth'], function() {
Route::post('settings/charts_and_reports', 'ReportController@showReports');
Route::post('settings/cancel_account', 'AccountController@cancelAccount');
Route::post('settings/company_details', 'AccountController@updateDetails');
Route::get('settings/{section?}', 'AccountController@showSection');
Route::post('settings/{section?}', 'AccountController@doSection');
@ -140,7 +146,8 @@ Route::group(['middleware' => 'auth'], function() {
Route::resource('bank_accounts', 'BankAccountController');
Route::get('api/bank_accounts', array('as'=>'api.bank_accounts', 'uses'=>'BankAccountController@getDatatable'));
Route::post('bank_accounts/bulk', 'BankAccountController@bulk');
Route::post('bank_accounts/test', 'BankAccountController@test');
Route::post('bank_accounts/validate', 'BankAccountController@validateAccount');
Route::post('bank_accounts/import_expenses/{bank_id}', 'BankAccountController@importExpenses');
Route::resource('clients', 'ClientController');
Route::get('api/clients', array('as'=>'api.clients', 'uses'=>'ClientController@getDatatable'));
@ -209,10 +216,11 @@ Route::group(['middleware' => 'auth'], function() {
// Route groups for API
Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
{
Route::resource('ping', 'ClientApiController@ping');
Route::get('ping', 'ClientApiController@ping');
Route::post('login', 'AccountApiController@login');
Route::get('static', 'AccountApiController@getStaticData');
Route::get('accounts', 'AccountApiController@show');
Route::put('accounts', 'AccountApiController@update');
Route::resource('clients', 'ClientApiController');
Route::get('quotes', 'QuoteApiController@index');
Route::resource('quotes', 'QuoteApiController');
@ -224,7 +232,10 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
Route::resource('tasks', 'TaskApiController');
Route::post('hooks', 'IntegrationController@subscribe');
Route::post('email_invoice', 'InvoiceApiController@emailInvoice');
Route::get('user_accounts','AccountApiController@getUserAccounts');
Route::get('user_accounts', 'AccountApiController@getUserAccounts');
Route::resource('products', 'ProductApiController');
Route::resource('tax_rates', 'TaxRateApiController');
Route::resource('users', 'UserApiController');
// Vendor
Route::resource('vendors', 'VendorApiController');
@ -283,17 +294,18 @@ if (!defined('CONTACT_EMAIL')) {
define('ENTITY_QUOTE', 'quote');
define('ENTITY_TASK', 'task');
define('ENTITY_ACCOUNT_GATEWAY', 'account_gateway');
define('ENTITY_BANK_ACCOUNT', 'bank_account');
define('ENTITY_USER', 'user');
define('ENTITY_TOKEN', 'token');
define('ENTITY_TAX_RATE', 'tax_rate');
define('ENTITY_PRODUCT', 'product');
define('ENTITY_ACTIVITY', 'activity');
define('ENTITY_VENDOR','vendor');
define('ENTITY_VENDOR_ACTIVITY','vendor_activity');
define('ENTITY_VENDOR', 'vendor');
define('ENTITY_VENDOR_ACTIVITY', 'vendor_activity');
define('ENTITY_EXPENSE', 'expense');
define('ENTITY_PAYMENT_TERM','payment_term');
define('ENTITY_EXPENSE_ACTIVITY','expense_activity');
define('ENTITY_PAYMENT_TERM', 'payment_term');
define('ENTITY_EXPENSE_ACTIVITY', 'expense_activity');
define('ENTITY_BANK_ACCOUNT', 'bank_account');
define('ENTITY_BANK_SUBACCOUNT', 'bank_subaccount');
define('PERSON_CONTACT', 'contact');
define('PERSON_USER', 'user');
@ -309,6 +321,7 @@ if (!defined('CONTACT_EMAIL')) {
define('ACCOUNT_IMPORT_EXPORT', 'import_export');
define('ACCOUNT_PAYMENTS', 'online_payments');
define('ACCOUNT_BANKS', 'bank_accounts');
define('ACCOUNT_IMPORT_EXPENSES', 'import_expenses');
define('ACCOUNT_MAP', 'import_map');
define('ACCOUNT_EXPORT', 'export');
define('ACCOUNT_TAX_RATES', 'tax_rates');
@ -329,6 +342,7 @@ if (!defined('CONTACT_EMAIL')) {
define('ACTION_RESTORE', 'restore');
define('ACTION_ARCHIVE', 'archive');
define('ACTION_CONVERT', 'convert');
define('ACTION_DELETE', 'delete');
define('ACTIVITY_TYPE_CREATE_CLIENT', 1);
@ -512,6 +526,7 @@ if (!defined('CONTACT_EMAIL')) {
define('PHP_DATE_FORMATS', 'http://php.net/manual/en/function.date.php');
define('REFERRAL_PROGRAM_URL', 'https://www.invoiceninja.com/referral-program/');
define('EMAIL_MARKUP_URL', 'https://developers.google.com/gmail/markup');
define('OFX_HOME_URL', 'http://www.ofxhome.com/index.php/home/directory/all');
define('COUNT_FREE_DESIGNS', 4);
define('COUNT_FREE_DESIGNS_SELF_HOST', 5); // include the custom design
@ -577,6 +592,9 @@ if (!defined('CONTACT_EMAIL')) {
define('BANK_LIBRARY_OFX', 1);
define('RESELLER_REVENUE_SHARE', 'A');
define('RESELLER_LIMITED_USERS', 'B');
$creditCards = [
1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'],
2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'],

View File

@ -24,8 +24,9 @@ class OFX
curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-Type: application/x-ofx'));
curl_setopt($c, CURLOPT_POSTFIELDS, $this->request);
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
//curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false);
$this->response = curl_exec($c);
//print_r($this->response);
//\Log::info(print_r($this->response, true));
curl_close($c);
$tmp = explode('<OFX>', $this->response);
$this->responseHeader = $tmp[0];

View File

@ -58,6 +58,10 @@ class Utils
public static function isNinjaProd()
{
if (Utils::isReseller()) {
return true;
}
return isset($_ENV['NINJA_PROD']) && $_ENV['NINJA_PROD'] == 'true';
}
@ -71,6 +75,16 @@ class Utils
return Utils::isNinjaProd() || (isset($_ENV['REQUIRE_HTTPS']) && $_ENV['REQUIRE_HTTPS'] == 'true');
}
public static function isReseller()
{
return Utils::getResllerType() ? true : false;
}
public static function getResllerType()
{
return isset($_ENV['RESELLER_TYPE']) ? $_ENV['RESELLER_TYPE'] : false;
}
public static function isOAuthEnabled()
{
$providers = [
@ -210,7 +224,7 @@ class Utils
$count = Session::get('error_count', 0);
Session::put('error_count', ++$count);
if ($count > 100) {
if ($count > 200) {
return 'logged';
}

View File

@ -21,6 +21,30 @@ class Account extends Eloquent
protected $dates = ['deleted_at'];
protected $hidden = ['ip'];
protected $fillable = [
'name',
'id_number',
'vat_number',
'work_email',
'website',
'work_phone',
'address1',
'address2',
'city',
'state',
'postal_code',
'country_id',
'size_id',
'industry_id',
'email_footer',
'timezone_id',
'date_format_id',
'datetime_format_id',
'currency_id',
'language_id',
'military_time',
];
public static $basicSettings = [
ACCOUNT_COMPANY_DETAILS,
ACCOUNT_USER_DETAILS,
@ -140,6 +164,24 @@ class Account extends Eloquent
return $this->belongsTo('App\Models\TaxRate');
}
public function setIndustryIdAttribute($value)
{
$this->attributes['industry_id'] = $value ?: null;
}
public function setCountryIdAttribute($value)
{
$this->attributes['country_id'] = $value ?: null;
}
public function setSizeIdAttribute($value)
{
$this->attributes['size_id'] = $value ?: null;
}
public function isGatewayConfigured($gatewayId = 0)
{
$this->load('account_gateways');
@ -248,6 +290,8 @@ class Account extends Eloquent
$countryId = false;
}
$hideSymbol = $this->show_currency_code || $hideSymbol;
return Utils::formatMoney($amount, $currencyId, $countryId, $hideSymbol);
}
@ -604,6 +648,7 @@ class Account extends Eloquent
'quote_number',
'total',
'invoice_issued_to',
'quote_issued_to',
//'date',
'rate',
'hours',

View File

@ -19,5 +19,9 @@ class BankAccount extends EntityModel
return $this->belongsTo('App\Models\Bank');
}
public function bank_subaccounts()
{
return $this->hasMany('App\Models\BankSubaccount');
}
}

View File

@ -0,0 +1,23 @@
<?php namespace App\Models;
use Crypt;
use App\Models\Bank;
use Illuminate\Database\Eloquent\SoftDeletes;
class BankSubaccount extends EntityModel
{
use SoftDeletes;
protected $dates = ['deleted_at'];
public function getEntityType()
{
return ENTITY_BANK_SUBACCOUNT;
}
public function bank_account()
{
return $this->belongsTo('App\Models\BankAccount');
}
}

View File

@ -136,6 +136,12 @@ class Client extends EntityModel
return $this->belongsTo('App\Models\Industry');
}
public function credits()
{
return $this->hasMany('App\Models\Credit');
}
public function addContact($data, $isPrimary = false)
{
$publicId = isset($data['public_id']) ? $data['public_id'] : false;

View File

@ -18,12 +18,15 @@ class Expense extends EntityModel
protected $fillable = [
'client_id',
'vendor_id',
'currency_id',
'expense_currency_id',
'invoice_currency_id',
'amount',
'foreign_amount',
'exchange_rate',
'private_notes',
'public_notes',
'bank_id',
'transaction_id',
];
public function account()
{

View File

@ -429,6 +429,8 @@ class Invoice extends EntityModel implements BalanceAffecting
'secondary_color',
'hide_quantity',
'hide_paid_to_date',
'all_pages_header',
'all_pages_footer',
'custom_invoice_label1',
'custom_invoice_label2',
'pdf_email_attachment',

View File

@ -7,6 +7,11 @@ class TaxRate extends EntityModel
use SoftDeletes;
protected $dates = ['deleted_at'];
protected $fillable = [
'name',
'rate'
];
public function getEntityType()
{
return ENTITY_TAX_RATE;

View File

@ -29,7 +29,13 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*
* @var array
*/
protected $fillable = ['first_name', 'last_name', 'email', 'password'];
protected $fillable = [
'first_name',
'last_name',
'email',
'password',
'phone',
];
/**
* The attributes excluded from the model's JSON form.
@ -51,6 +57,11 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return $this->belongsTo('App\Models\Theme');
}
public function setEmailAttribute($value)
{
$this->attributes['email'] = $this->attributes['username'] = $value;
}
public function getName()
{
return $this->getDisplayName();

View File

@ -29,6 +29,7 @@ class Vendor extends EntityModel
'private_notes',
'currency_id',
'website',
'transaction_name',
];
public static $fieldName = 'name';

View File

@ -19,13 +19,14 @@ class ContactMailer extends Mailer
public static $variableFields = [
'footer',
'account',
'dueDate',
'invoiceDate',
'client',
'amount',
'contact',
'firstName',
'invoice',
'quote',
'dueDate',
'viewLink',
'viewButton',
'paymentLink',
@ -234,13 +235,14 @@ class ContactMailer extends Mailer
'$footer' => $account->getEmailFooter(),
'$client' => $client->getDisplayName(),
'$account' => $account->getDisplayName(),
'$dueDate' => $account->formatDate($invoice->due_date),
'$invoiceDate' => $account->formatDate($invoice->invoice_date),
'$contact' => $invitation->contact->getDisplayName(),
'$firstName' => $invitation->contact->first_name,
'$amount' => $account->formatMoney($data['amount'], $client),
'$invoice' => $invoice->invoice_number,
'$quote' => $invoice->invoice_number,
'$link' => $invitation->getLink(),
'$dueDate' => $account->formatDate($invoice->due_date),
'$viewLink' => $invitation->getLink(),
'$viewButton' => HTML::emailViewButton($invitation->getLink(), $invoice->getEntityType()),
'$paymentLink' => $invitation->getLink('payment'),

View File

@ -40,8 +40,8 @@ class UserMailer extends Mailer
return;
}
$entityType = $notificationType == 'approved' ? ENTITY_QUOTE : ENTITY_INVOICE;
$view = "{$entityType}_{$notificationType}";
$entityType = $invoice->getEntityType();
$view = ($notificationType == 'approved' ? ENTITY_QUOTE : ENTITY_INVOICE) . "_{$notificationType}";
$account = $user->account;
$client = $invoice->client;

View File

@ -9,4 +9,23 @@ class ClientPresenter extends Presenter {
{
return $this->entity->country ? $this->entity->country->name : '';
}
public function status()
{
$class = $text = '';
if ($this->entity->is_deleted) {
$class = 'danger';
$text = trans('texts.deleted');
} elseif ($this->entity->trashed()) {
$class = 'warning';
$text = trans('texts.archived');
} else {
$class = 'success';
$text = trans('texts.active');
}
return "<span class=\"label label-{$class}\">{$text}</span>";
}
}

View File

@ -36,4 +36,23 @@ class TaskPresenter extends Presenter {
return implode("\n", $times);
}
public function status()
{
$class = $text = '';
if ($this->entity->is_deleted) {
$class = 'danger';
$text = trans('texts.deleted');
} elseif ($this->entity->trashed()) {
$class = 'warning';
$text = trans('texts.archived');
} else {
$class = 'success';
$text = trans('texts.active');
}
return "<span class=\"label label-{$class}\">{$text}</span>";
}
}

View File

@ -516,4 +516,10 @@ class AccountRepository
return $userAccount ? $userAccount->id : false;
}
public function save($data, $account)
{
$account->fill($data);
$account->save();
}
}

View File

@ -1,9 +1,11 @@
<?php namespace App\Ninja\Repositories;
use DB;
use Crypt;
use Utils;
use Session;
use App\Models\BankAccount;
use App\Models\BankSubaccount;
use App\Ninja\Repositories\BaseRepository;
class BankAccountRepository extends BaseRepository
@ -19,6 +21,34 @@ class BankAccountRepository extends BaseRepository
->join('banks', 'banks.id', '=', 'bank_accounts.bank_id')
->where('bank_accounts.deleted_at', '=', null)
->where('bank_accounts.account_id', '=', $accountId)
->select('bank_accounts.public_id', 'banks.name as bank_name', 'bank_accounts.deleted_at', 'banks.bank_library_id');
->select(
'bank_accounts.public_id',
'banks.name as bank_name',
'bank_accounts.deleted_at',
'banks.bank_library_id'
);
}
public function save($input)
{
$bankAccount = BankAccount::createNew();
$bankAccount->bank_id = $input['bank_id'];
$bankAccount->username = Crypt::encrypt(trim($input['bank_username']));
$account = \Auth::user()->account;
$account->bank_accounts()->save($bankAccount);
foreach ($input['bank_accounts'] as $data) {
if ( ! isset($data['include']) || ! filter_var($data['include'], FILTER_VALIDATE_BOOLEAN)) {
continue;
}
$subaccount = BankSubaccount::createNew();
$subaccount->account_name = trim($data['account_name']);
$subaccount->account_number = trim($data['hashed_account_number']);
$bankAccount->bank_subaccounts()->save($subaccount);
}
return $bankAccount;
}
}

View File

@ -26,11 +26,12 @@ class ExpenseRepository extends BaseRepository
public function findVendor($vendorPublicId)
{
$vendorId = Vendor::getPrivateId($vendorPublicId);
$accountid = \Auth::user()->account_id;
$query = DB::table('expenses')
->join('accounts', 'accounts.id', '=', 'expenses.account_id')
->where('expenses.account_id', '=', $accountid)
->where('expenses.vendor_id', '=', $vendorPublicId)
->where('expenses.vendor_id', '=', $vendorId)
->select(
'expenses.id',
'expenses.expense_date',
@ -51,12 +52,20 @@ class ExpenseRepository extends BaseRepository
$query = DB::table('expenses')
->join('accounts', 'accounts.id', '=', 'expenses.account_id')
->leftjoin('clients', 'clients.id', '=', 'expenses.client_id')
->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
->leftjoin('vendors', 'vendors.id', '=', 'expenses.vendor_id')
->leftJoin('invoices', 'invoices.id', '=', 'expenses.invoice_id')
->where('expenses.account_id', '=', $accountid)
->select('expenses.account_id',
->where('contacts.deleted_at', '=', null)
->where('vendors.deleted_at', '=', null)
->where('clients.deleted_at', '=', null)
->where(function ($query) {
$query->where('contacts.is_primary', '=', true)
->orWhere('contacts.is_primary', '=', null);
})
->select(
'expenses.account_id',
'expenses.amount',
'expenses.currency_id',
'expenses.deleted_at',
'expenses.exchange_rate',
'expenses.expense_date',
@ -68,11 +77,16 @@ class ExpenseRepository extends BaseRepository
'expenses.public_notes',
'expenses.should_be_invoiced',
'expenses.vendor_id',
'expenses.expense_currency_id',
'expenses.invoice_currency_id',
'invoices.public_id as invoice_public_id',
'vendors.name as vendor_name',
'vendors.public_id as vendor_public_id',
'accounts.country_id as account_country_id',
'accounts.currency_id as account_currency_id',
'clients.name as client_name',
'clients.public_id as client_public_id',
'contacts.first_name',
'contacts.email',
'contacts.last_name',
'clients.country_id as client_country_id'
);
@ -84,7 +98,9 @@ class ExpenseRepository extends BaseRepository
if ($filter) {
$query->where(function ($query) use ($filter) {
$query->where('expenses.public_notes', 'like', '%'.$filter.'%');
$query->where('expenses.public_notes', 'like', '%'.$filter.'%')
->orWhere('clients.name', 'like', '%'.$filter.'%')
->orWhere('vendors.name', 'like', '%'.$filter.'%');
});
}
@ -105,10 +121,20 @@ class ExpenseRepository extends BaseRepository
$expense->fill($input);
$expense->expense_date = Utils::toSqlDate($input['expense_date']);
$expense->private_notes = trim($input['private_notes']);
if (isset($input['private_notes'])) {
$expense->private_notes = trim($input['private_notes']);
}
$expense->public_notes = trim($input['public_notes']);
$expense->should_be_invoiced = isset($input['should_be_invoiced']) || $expense->client_id ? true : false;
if ( ! $expense->expense_currency_id) {
$expense->expense_currency_id = \Auth::user()->account->getCurrencyId();
}
if ( ! $expense->invoice_currency_id) {
$expense->invoice_currency_id = \Auth::user()->account->getCurrencyId();
}
$rate = isset($input['exchange_rate']) ? Utils::parseFloat($input['exchange_rate']) : 1;
$expense->exchange_rate = round($rate, 4);
$expense->amount = round(Utils::parseFloat($input['amount']), 2);

View File

@ -244,6 +244,13 @@ 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 ($invoice->start_date && $invoice->start_date != Utils::toSqlDate($data['start_date'])) {
$invoice->last_sent_date = null;
@ -395,6 +402,7 @@ class InvoiceRepository extends BaseRepository
continue;
}
$task = false;
if (isset($item['task_public_id']) && $item['task_public_id']) {
$task = Task::scope($item['task_public_id'])->where('invoice_id', '=', null)->firstOrFail();
$task->invoice_id = $invoice->id;
@ -402,6 +410,7 @@ class InvoiceRepository extends BaseRepository
$task->save();
}
$expense = false;
if (isset($item['expense_public_id']) && $item['expense_public_id']) {
$expense = Expense::scope($item['expense_public_id'])->where('invoice_id', '=', null)->firstOrFail();
$expense->invoice_id = $invoice->id;
@ -417,11 +426,8 @@ class InvoiceRepository extends BaseRepository
$product = Product::createNew();
$product->product_key = trim($item['product_key']);
}
$product->notes = $invoice->has_tasks ? '' : $item['notes'];
$product->notes = $invoice->has_expenses ? '' : $item['notes'];
$product->cost = $item['cost'];
$product->notes = ($task || $expense) ? '' : $item['notes'];
$product->cost = $expense ? 0 : $item['cost'];
$product->save();
}
}
@ -667,6 +673,8 @@ class InvoiceRepository extends BaseRepository
$sql = implode(' OR ', $dates);
$invoices = Invoice::whereAccountId($account->id)
->where('balance', '>', 0)
->where('is_quote', '=', false)
->where('is_recurring', '=', false)
->whereRaw('('.$sql.')')
->get();

View File

@ -23,7 +23,23 @@ class TaskRepository
})
->where('contacts.deleted_at', '=', null)
->where('clients.deleted_at', '=', null)
->select('tasks.public_id', 'clients.name as client_name', 'clients.public_id as client_public_id', 'contacts.first_name', 'contacts.email', 'contacts.last_name', 'invoices.invoice_status_id', 'tasks.description', 'tasks.is_deleted', 'tasks.deleted_at', 'invoices.invoice_number', 'invoices.public_id as invoice_public_id', 'tasks.is_running', 'tasks.time_log', 'tasks.created_at');
->select(
'tasks.public_id',
'clients.name as client_name',
'clients.public_id as client_public_id',
'contacts.first_name',
'contacts.email',
'contacts.last_name',
'invoices.invoice_status_id',
'tasks.description',
'tasks.is_deleted',
'tasks.deleted_at',
'invoices.invoice_number',
'invoices.public_id as invoice_public_id',
'tasks.is_running',
'tasks.time_log',
'tasks.created_at'
);
if ($clientPublicId) {
$query->where('clients.public_id', '=', $clientPublicId);

View File

@ -20,6 +20,22 @@ class TaxRateRepository extends BaseRepository
->select('tax_rates.public_id', 'tax_rates.name', 'tax_rates.rate', 'tax_rates.deleted_at');
}
public function save($data, $taxRate = false)
{
if ( ! $taxRate) {
if (isset($data['public_id'])) {
$taxRate = TaxRate::scope($data['public_id'])->firstOrFail();
} else {
$taxRate = TaxRate::createNew();
}
}
$taxRate->fill($data);
$taxRate->save();
return $taxRate;
}
/*
public function save($taxRates)
{

View File

@ -26,4 +26,13 @@ class UserRepository extends BaseRepository
return $query;
}
public function save($data, $user)
{
$user->fill($data);
$user->save();
return $user;
}
}

View File

@ -39,6 +39,7 @@ class VendorRepository extends BaseRepository
'vendor_contacts.last_name',
'vendors.created_at',
'vendors.work_phone',
'vendors.city',
'vendor_contacts.email',
'vendors.deleted_at',
'vendors.is_deleted'
@ -73,10 +74,6 @@ class VendorRepository extends BaseRepository
$vendor->fill($data);
$vendor->save();
if ( ! isset($data['vendorcontact']) && ! isset($data['vendorcontacts'])) {
return $vendor;
}
$first = true;
$vendorcontacts = isset($data['vendorcontact']) ? [$data['vendorcontact']] : $data['vendorcontacts'];

View File

@ -43,6 +43,7 @@ class ClientTransformer extends EntityTransformer
protected $availableIncludes = [
'contacts',
'invoices',
'credits',
];
public function includeContacts(Client $client)
@ -57,6 +58,12 @@ class ClientTransformer extends EntityTransformer
return $this->includeCollection($client->invoices, $transformer, ENTITY_INVOICE);
}
public function includeCredits(Client $client)
{
$transformer = new CreditTransformer($this->account, $this->serializer);
return $this->includeCollection($client->credits, $transformer, ENTITY_CREDIT);
}
public function transform(Client $client)
{
return [

View File

@ -19,6 +19,7 @@ class ContactTransformer extends EntityTransformer
'phone' => $contact->phone,
'last_login' => $contact->last_login,
'account_key' => $this->account->account_key,
'send_invoice' => (bool) $contact->send_invoice,
];
}
}

View File

@ -0,0 +1,24 @@
<?php namespace App\Ninja\Transformers;
use App\Models\Account;
use App\Models\Credit;
use League\Fractal;
class CreditTransformer extends EntityTransformer
{
public function transform(Credit $credit)
{
return [
'id' => (int) $credit->public_id,
'amount' => (float) $credit->amount,
'balance' => (float) $credit->balance,
'updated_at' => $this->getTimestamp($credit->updated_at),
'archived_at' => $this->getTimestamp($credit->deleted_at),
'is_deleted' => (bool) $credit->is_deleted,
'account_key' => $this->account->account_key,
'credit_date' => $credit->credit_date,
'credit_number' => $credit->credit_number,
'private_notes' => $credit->private_notes,
];
}
}

View File

@ -0,0 +1,21 @@
<?php namespace App\Ninja\Transformers;
use App\Models\Account;
use App\Models\Contact;
use App\Models\Invitation;
use League\Fractal;
class InvitationTransformer extends EntityTransformer
{
public function transform(Invitation $invitation)
{
return [
'id' => (int) $invitation->public_id,
'key' => $invitation->getName(),
'status' => $invitation->getStatus(),
'link' => $invitation->getLink(),
'sent_date' => $invitation->sent_date,
'viewed_date' => $invitation->sent_date,
];
}
}

View File

@ -25,12 +25,22 @@ class InvoiceTransformer extends EntityTransformer
'payments'
];
protected $availableIncludes = [
'invitations',
];
public function includeInvoiceItems(Invoice $invoice)
{
$transformer = new InvoiceItemTransformer($this->account, $this->serializer);
return $this->includeCollection($invoice->invoice_items, $transformer, ENTITY_INVOICE_ITEMS);
}
public function includeInvitations(Invoice $invoice)
{
$transformer = new InvitationTransformer($this->account, $this->serializer);
return $this->includeCollection($invoice->invitations, $transformer, ENTITY_INVITATION);
}
public function includePayments(Invoice $invoice)
{
$transformer = new PaymentTransformer($this->account, $this->serializer);
@ -78,6 +88,7 @@ class InvoiceTransformer extends EntityTransformer
'custom_taxes1' => (bool) $invoice->custom_taxes1,
'custom_taxes2' => (bool) $invoice->custom_taxes2,
'has_expenses' => (bool) $invoice->has_expenses,
'quote_invoice_id' => (int) $invoice->quote_invoice_id,
];
}
}

View File

@ -16,6 +16,7 @@ class ProductTransformer extends EntityTransformer
'account_key' =>$this->account->account_key,
'default_tax_rate_id' =>$product->default_tax_rate_id,
'updated_at' =>$this->getTimestamp($product->updated_at),
'archived_at' => $this->getTimestamp($product->deleted_at),
];
}
}

View File

@ -13,7 +13,7 @@ class TaxRateTransformer extends EntityTransformer
/**
* @SWG\Property(property="id", type="integer", example=1, readOnly=true)
* @SWG\Property(property="name", type="string", example="GST")
* @SWG\Property(property="account_key", type="string", example="34erfdf33fdff" readOnly=true)
* @SWG\Property(property="account_key", type="string", example="asimplestring", readOnly=true)
* @SWG\Property(property="rate", type="float", example=17.5)
* @SWG\Property(property="updated_at", type="date-time", example="2016-01-01 12:10:00")
* @SWG\Property(property="archived_at", type="date-time", example="2016-01-01 12:10:00")

View File

@ -17,7 +17,7 @@ class UserTransformer extends EntityTransformer
'updated_at' => $this->getTimestamp($user->updated_at),
'deleted_at' => $this->getTimestamp($user->deleted_at),
'phone' => $user->phone,
'username' => $user->username,
//'username' => $user->username,
'registered' => (bool) $user->registered,
'confirmed' => (bool) $user->confirmed,
'oauth_user_id' => $user->oauth_user_id,

View File

@ -93,7 +93,7 @@ class AppServiceProvider extends ServiceProvider {
->render();
});
HTML::macro('breadcrumbs', function() {
HTML::macro('breadcrumbs', function($status = false) {
$str = '<ol class="breadcrumb">';
// Get the breadcrumbs by exploding the current path.
@ -128,6 +128,11 @@ class AppServiceProvider extends ServiceProvider {
$str .= '<li>'.link_to($crumb, $name).'</li>';
}
}
if ($status) {
$str .= '&nbsp;&nbsp;&nbsp;&nbsp;' . $status;
}
return $str . '</ol>';
});

View File

@ -3,21 +3,30 @@
use stdClass;
use Utils;
use URL;
use Hash;
use App\Models\Gateway;
use App\Models\BankSubaccount;
use App\Models\Vendor;
use App\Models\Expense;
use App\Services\BaseService;
use App\Ninja\Repositories\BankAccountRepository;
use App\Ninja\Repositories\ExpenseRepository;
use App\Ninja\Repositories\VendorRepository;
use App\Libraries\Finance;
use App\Libraries\Login;
class BankAccountService extends BaseService
{
protected $bankAccountRepo;
protected $expenseRepo;
protected $vendorRepo;
protected $datatableService;
public function __construct(BankAccountRepository $bankAccountRepo, DatatableService $datatableService)
public function __construct(BankAccountRepository $bankAccountRepo, ExpenseRepository $expenseRepo, VendorRepository $vendorRepo, DatatableService $datatableService)
{
$this->bankAccountRepo = $bankAccountRepo;
$this->vendorRepo = $vendorRepo;
$this->expenseRepo = $expenseRepo;
$this->datatableService = $datatableService;
}
@ -26,22 +35,31 @@ class BankAccountService extends BaseService
return $this->bankAccountRepo;
}
/*
public function save()
{
return null;
}
*/
public function loadBankAccounts($bankId, $username, $password, $includeTransactions = true)
{
if ( ! $bankId || ! $username || ! $password) {
return false;
}
$expenses = Expense::scope()
->whereBankId($bankId)
->where('transaction_id', '!=', '')
->withTrashed()
->get(['transaction_id'])
->toArray();
$expenses = array_flip(array_map(function($val) {
return $val['transaction_id'];
}, $expenses));
$bankAccounts = BankSubaccount::scope()
->whereHas('bank_account', function($query) use ($bankId) {
$query->where('bank_id', '=', $bankId);
})
->get();
$bank = Utils::getFromCache($bankId, 'banks');
$data = [];
// load OFX trnansactions
try {
$finance = new Finance();
$finance->banks[$bankId] = $bank->getOFXBank($finance);
@ -52,21 +70,137 @@ class BankAccountService extends BaseService
$login->setup();
foreach ($login->accounts as $account) {
$account->setup($includeTransactions);
$obj = new stdClass;
$obj->account_number = Utils::maskAccountNumber($account->id);
$obj->type = $account->type;
$obj->balance = Utils::formatMoney($account->ledgerBalance, CURRENCY_DOLLAR);
$data[] = $obj;
if ($account = $this->parseBankAccount($account, $bankAccounts, $expenses, $includeTransactions)) {
$data[] = $account;
}
}
}
}
return $data;
} catch (\Exception $e) {
return false;
}
}
private function parseBankAccount($account, $bankAccounts, $expenses, $includeTransactions)
{
$obj = new stdClass;
$obj->account_name = '';
// look up bank account name
foreach ($bankAccounts as $bankAccount) {
if (Hash::check($account->id, $bankAccount->account_number)) {
$obj->account_name = $bankAccount->account_name;
}
}
// if we can't find a match skip the account
if (count($bankAccounts) && ! $obj->account_name) {
return false;
}
$obj->masked_account_number = Utils::maskAccountNumber($account->id);
$obj->hashed_account_number = bcrypt($account->id);
$obj->type = $account->type;
$obj->balance = Utils::formatMoney($account->ledgerBalance, CURRENCY_DOLLAR);
if ($includeTransactions) {
$ofxParser = new \OfxParser\Parser;
$ofx = $ofxParser->loadFromString($account->response);
$obj->start_date = $ofx->BankAccount->Statement->startDate;
$obj->end_date = $ofx->BankAccount->Statement->endDate;
$obj->transactions = [];
foreach ($ofx->BankAccount->Statement->transactions as $transaction) {
// ensure transactions aren't imported as expenses twice
if (isset($expenses[$transaction->uniqueId])) {
continue;
}
if ($transaction->amount >= 0) {
continue;
}
$transaction->vendor = $this->prepareValue(substr($transaction->name, 0, 20));
$transaction->info = $this->prepareValue(substr($transaction->name, 20));
$transaction->memo = $this->prepareValue($transaction->memo);
$transaction->date = \Auth::user()->account->formatDate($transaction->date);
$transaction->amount *= -1;
$obj->transactions[] = $transaction;
}
}
return $obj;
}
private function prepareValue($value) {
return ucwords(strtolower(trim($value)));
}
public function importExpenses($bankId, $input) {
$countVendors = 0;
$countExpenses = 0;
// create a vendor map
$vendorMap = [];
$vendors = Vendor::scope()
->withTrashed()
->get(['id', 'name', 'transaction_name']);
foreach ($vendors as $vendor) {
$vendorMap[strtolower($vendor->name)] = $vendor;
$vendorMap[strtolower($vendor->transaction_name)] = $vendor;
}
foreach ($input as $transaction) {
$vendorName = $transaction['vendor'];
$key = strtolower($vendorName);
$info = $transaction['info'];
// find vendor otherwise create it
if (isset($vendorMap[$key])) {
$vendor = $vendorMap[$key];
} else {
$field = $this->determineInfoField($info);
$vendor = $this->vendorRepo->save([
$field => $info,
'name' => $vendorName,
'transaction_name' => $transaction['vendor_orig'],
'vendorcontact' => []
]);
$vendorMap[$key] = $vendor;
$vendorMap[$transaction['vendor_orig']] = $vendor;
$countVendors++;
}
// create the expense record
$this->expenseRepo->save([
'vendor_id' => $vendor->id,
'amount' => $transaction['amount'],
'public_notes' => $transaction['memo'],
'expense_date' => $transaction['date'],
'transaction_id' => $transaction['id'],
'bank_id' => $bankId,
'should_be_invoiced' => true,
]);
$countExpenses++;
}
return trans('texts.imported_expenses', [
'count_vendors' => $countVendors,
'count_expenses' => $countExpenses
]);
}
private function determineInfoField($value) {
if (preg_match("/^[0-9\-\(\)\.]+$/", $value)) {
return 'work_phone';
} elseif (strpos($value, '.') !== false) {
return 'private_notes';
} else {
return 'city';
}
}
public function getDatatable($accountId)
{
$query = $this->bankAccountRepo->find($accountId);

View File

@ -5,7 +5,8 @@ use Utils;
use URL;
use App\Services\BaseService;
use App\Ninja\Repositories\ExpenseRepository;
use App\Models\Client;
use App\Models\Vendor;
class ExpenseService extends BaseService
{
@ -26,6 +27,14 @@ class ExpenseService extends BaseService
public function save($data)
{
if (isset($data['client_id']) && $data['client_id']) {
$data['client_id'] = Client::getPrivateId($data['client_id']);
}
if (isset($data['vendor_id']) && $data['vendor_id']) {
$data['vendor_id'] = Vendor::getPrivateId($data['vendor_id']);
}
return $this->expenseRepo->save($data);
}
@ -53,27 +62,41 @@ class ExpenseService extends BaseService
'vendor_name',
function ($model)
{
if($model->vendor_public_id) {
if ($model->vendor_public_id) {
return link_to("vendors/{$model->vendor_public_id}", $model->vendor_name);
} else {
return 'No vendor' ;
return '';
}
}
],
[
'client_name',
function ($model)
{
if ($model->client_public_id) {
return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model));
} else {
return '';
}
}
],
[
'expense_date',
function ($model) {
return Utils::fromSqlDate($model->expense_date);
return link_to("expenses/{$model->public_id}/edit", Utils::fromSqlDate($model->expense_date));
}
],
[
'amount',
function ($model) {
$str = Utils::formatMoney($model->amount, $model->account_currency_id, $model->account_country_id, true);
// show both the amount and the converted amount
if ($model->exchange_rate != 1) {
$str .= ' | ' . Utils::formatMoney(round($model->amount * $model->exchange_rate,2), $model->currency_id, $model->client_country_id, true);
$converted = round($model->amount * $model->exchange_rate, 2);
return Utils::formatMoney($model->amount, $model->expense_currency_id) . ' | ' .
Utils::formatMoney($converted, $model->invoice_currency_id);
} else {
return Utils::formatMoney($model->amount, $model->expense_currency_id);
}
return $str;
}
],
[
@ -97,7 +120,7 @@ class ExpenseService extends BaseService
[
'expense_date',
function ($model) {
return $model->expense_date;
return Utils::dateToString($model->expense_date);
}
],
[

View File

@ -218,7 +218,9 @@ class PaymentService extends BaseService
$account = Account::with('users')->find($invoice->client->public_id);
if ($account->pro_plan_paid && $account->pro_plan_paid != '0000-00-00') {
$date = DateTime::createFromFormat('Y-m-d', $account->pro_plan_paid);
$account->pro_plan_paid = $date->modify('+1 year')->format('Y-m-d');
$date->modify('+1 year');
$date = max($date, date_create());
$account->pro_plan_paid = $date->format('Y-m-d');
} else {
$account->pro_plan_paid = date_create()->format('Y-m-d');
}

View File

@ -50,9 +50,15 @@ class VendorService extends BaseService
}
],
[
'first_name',
'city',
function ($model) {
return link_to("vendors/{$model->public_id}", $model->first_name.' '.$model->last_name);
return $model->city;
}
],
[
'work_phone',
function ($model) {
return $model->work_phone;
}
],
[

View File

@ -24,7 +24,8 @@
"jsoneditor": "*",
"moment-timezone": "~0.4.0",
"quill": "~0.20.0",
"datetimepicker": "~2.4.5"
"datetimepicker": "~2.4.5",
"stacktrace-js": "~1.0.1"
},
"resolutions": {
"jquery": "~1.11"

View File

@ -7,16 +7,13 @@
{
"name": "Hillel Coren",
"email": "hillelcoren@gmail.com"
},
{
"name": "Jeramy Simpson",
"email": "jeramy.n.simpson@gmail.com"
}
],
"require": {
"omnipay/mollie": "dev-master#22956c1a62a9662afa5f5d119723b413770ac525",
"omnipay/2checkout": "dev-master#e9c079c2dde0d7ba461903b3b7bd5caf6dee1248",
"omnipay/gocardless": "dev-master",
"omnipay/stripe": "2.3.0",
"laravel/framework": "5.0.*",
"patricktalmadge/bootstrapper": "5.5.x",
"anahkiasen/former": "4.0.*@dev",
@ -25,7 +22,7 @@
"omnipay/omnipay": "~2.3.0",
"intervention/image": "dev-master",
"webpatser/laravel-countries": "dev-master",
"barryvdh/laravel-ide-helper": "^2.1",
"barryvdh/laravel-ide-helper": "dev-master",
"doctrine/dbal": "2.5.x",
"jsanc623/phpbenchtime": "2.x",
"lokielse/omnipay-alipay": "dev-master",
@ -66,7 +63,8 @@
"jlapp/swaggervel": "master-dev",
"maatwebsite/excel": "~2.0",
"ezyang/htmlpurifier": "~v4.7",
"cerdic/css-tidy": "~v1.5"
"cerdic/css-tidy": "~v1.5",
"asgrim/ofxparser": "^1.1"
},
"require-dev": {
"phpunit/phpunit": "~4.0",

388
composer.lock generated
View File

@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "9ed916108c17ac2634ffc4b9676098d1",
"content-hash": "e5177cc53f079158f3180a7e4ef0ded6",
"hash": "6e219bb4f5ffaf8423177bd6fccc89f8",
"content-hash": "4778ab164bfb93c4af1bb51c4320ea4c",
"packages": [
{
"name": "agmscode/omnipay-agms",
@ -123,12 +123,12 @@
"source": {
"type": "git",
"url": "https://github.com/alfaproject/omnipay-skrill.git",
"reference": "2fa2ba8083fd5289366660f8de1b46b5f49ac052"
"reference": "41a7a03c5b90d496720e288bebc157d898837ccd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/alfaproject/omnipay-skrill/zipball/41a7a03c5b90d496720e288bebc157d898837ccd",
"reference": "2fa2ba8083fd5289366660f8de1b46b5f49ac052",
"reference": "41a7a03c5b90d496720e288bebc157d898837ccd",
"shasum": ""
},
"require": {
@ -169,7 +169,7 @@
"payment",
"skrill"
],
"time": "2014-02-25 13:40:07"
"time": "2016-01-13 16:33:07"
},
{
"name": "anahkiasen/former",
@ -323,6 +323,58 @@
],
"time": "2015-03-19 21:32:19"
},
{
"name": "asgrim/ofxparser",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/asgrim/ofxparser.git",
"reference": "7652efea77a1c5dda007f9764d8061f870248ea1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/asgrim/ofxparser/zipball/7652efea77a1c5dda007f9764d8061f870248ea1",
"reference": "7652efea77a1c5dda007f9764d8061f870248ea1",
"shasum": ""
},
"require": {
"php": ">=5.4"
},
"type": "library",
"autoload": {
"psr-0": {
"OfxParser": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Guillaume Bailleul",
"email": "contact@guillaume-bailleul.fr",
"homepage": "http://www.guillaume-bailleul.fr"
},
{
"name": "James Titcumb",
"email": "hello@jamestitcumb.com",
"homepage": "http://www.jamestitcumb.com/"
},
{
"name": "Oliver Lowe",
"email": "mrtriangle@gmail.com"
}
],
"description": "Simple OFX file parser",
"keywords": [
"finance",
"ofx",
"open financial exchange",
"parser"
],
"time": "2015-12-11 11:08:57"
},
{
"name": "barryvdh/laravel-debugbar",
"version": "v2.0.6",
@ -379,25 +431,25 @@
},
{
"name": "barryvdh/laravel-ide-helper",
"version": "v2.0.6",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-ide-helper.git",
"reference": "037386153630a7515a1542f29410d8c267651689"
"reference": "aa8f772a46c35ecdcb7119f38772ac2ec952a941"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/037386153630a7515a1542f29410d8c267651689",
"reference": "037386153630a7515a1542f29410d8c267651689",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/aa8f772a46c35ecdcb7119f38772ac2ec952a941",
"reference": "aa8f772a46c35ecdcb7119f38772ac2ec952a941",
"shasum": ""
},
"require": {
"illuminate/console": "5.0.x|5.1.x",
"illuminate/filesystem": "5.0.x|5.1.x",
"illuminate/support": "5.0.x|5.1.x",
"illuminate/console": "5.0.x|5.1.x|5.2.x",
"illuminate/filesystem": "5.0.x|5.1.x|5.2.x",
"illuminate/support": "5.0.x|5.1.x|5.2.x",
"php": ">=5.4.0",
"phpdocumentor/reflection-docblock": "2.0.4",
"symfony/class-loader": "~2.3"
"symfony/class-loader": "~2.3|~3.0"
},
"require-dev": {
"doctrine/dbal": "~2.3"
@ -408,7 +460,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
"dev-master": "2.1-dev"
}
},
"autoload": {
@ -438,7 +490,7 @@
"phpstorm",
"sublime"
],
"time": "2015-06-25 08:58:59"
"time": "2016-01-22 13:33:15"
},
{
"name": "cardgate/omnipay-cardgate",
@ -1272,33 +1324,33 @@
},
{
"name": "doctrine/cache",
"version": "v1.5.4",
"version": "v1.6.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/cache.git",
"reference": "47cdc76ceb95cc591d9c79a36dc3794975b5d136"
"reference": "f8af318d14bdb0eff0336795b428b547bd39ccb6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/cache/zipball/47cdc76ceb95cc591d9c79a36dc3794975b5d136",
"reference": "47cdc76ceb95cc591d9c79a36dc3794975b5d136",
"url": "https://api.github.com/repos/doctrine/cache/zipball/f8af318d14bdb0eff0336795b428b547bd39ccb6",
"reference": "f8af318d14bdb0eff0336795b428b547bd39ccb6",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
"php": "~5.5|~7.0"
},
"conflict": {
"doctrine/common": ">2.2,<2.4"
},
"require-dev": {
"phpunit/phpunit": ">=3.7",
"phpunit/phpunit": "~4.8|~5.0",
"predis/predis": "~1.0",
"satooshi/php-coveralls": "~0.6"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.5.x-dev"
"dev-master": "1.6.x-dev"
}
},
"autoload": {
@ -1338,7 +1390,7 @@
"cache",
"caching"
],
"time": "2015-12-19 05:03:47"
"time": "2015-12-31 16:37:02"
},
{
"name": "doctrine/collections",
@ -1481,16 +1533,16 @@
},
{
"name": "doctrine/dbal",
"version": "v2.5.3",
"version": "v2.5.4",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
"reference": "2fbcea96eae34a53183377cdbb0b9bec33974648"
"reference": "abbdfd1cff43a7b99d027af3be709bc8fc7d4769"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/2fbcea96eae34a53183377cdbb0b9bec33974648",
"reference": "2fbcea96eae34a53183377cdbb0b9bec33974648",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/abbdfd1cff43a7b99d027af3be709bc8fc7d4769",
"reference": "abbdfd1cff43a7b99d027af3be709bc8fc7d4769",
"shasum": ""
},
"require": {
@ -1548,7 +1600,7 @@
"persistence",
"queryobject"
],
"time": "2015-12-25 16:28:24"
"time": "2016-01-05 22:11:12"
},
{
"name": "doctrine/inflector",
@ -2091,16 +2143,16 @@
},
{
"name": "guzzlehttp/psr7",
"version": "1.2.1",
"version": "1.2.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "4d0bdbe1206df7440219ce14c972aa57cc5e4982"
"reference": "f5d04bdd2881ac89abde1fb78cc234bce24327bb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/4d0bdbe1206df7440219ce14c972aa57cc5e4982",
"reference": "4d0bdbe1206df7440219ce14c972aa57cc5e4982",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/f5d04bdd2881ac89abde1fb78cc234bce24327bb",
"reference": "f5d04bdd2881ac89abde1fb78cc234bce24327bb",
"shasum": ""
},
"require": {
@ -2145,7 +2197,7 @@
"stream",
"uri"
],
"time": "2015-11-03 01:34:55"
"time": "2016-01-23 01:23:02"
},
{
"name": "illuminate/html",
@ -2254,12 +2306,12 @@
"source": {
"type": "git",
"url": "https://github.com/Intervention/image.git",
"reference": "e6acb1609ce89f2c1ec7864fbbda0a20a3eeca70"
"reference": "86dfe2f2a95aa9eed58faf1cc1dae6534a6ce931"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Intervention/image/zipball/86dfe2f2a95aa9eed58faf1cc1dae6534a6ce931",
"reference": "e6acb1609ce89f2c1ec7864fbbda0a20a3eeca70",
"reference": "86dfe2f2a95aa9eed58faf1cc1dae6534a6ce931",
"shasum": ""
},
"require": {
@ -2308,7 +2360,7 @@
"thumbnail",
"watermark"
],
"time": "2015-12-04 17:09:36"
"time": "2016-01-10 11:20:02"
},
{
"name": "ircmaxell/password-compat",
@ -3232,12 +3284,12 @@
"source": {
"type": "git",
"url": "https://github.com/lokielse/omnipay-alipay.git",
"reference": "cbfbee089e0a84a58c73e9d3794894b81a6a82d6"
"reference": "f39ce21a5cbfe5c7cd4108d264b398dbd42be05b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/lokielse/omnipay-alipay/zipball/cbfbee089e0a84a58c73e9d3794894b81a6a82d6",
"reference": "cbfbee089e0a84a58c73e9d3794894b81a6a82d6",
"url": "https://api.github.com/repos/lokielse/omnipay-alipay/zipball/f39ce21a5cbfe5c7cd4108d264b398dbd42be05b",
"reference": "f39ce21a5cbfe5c7cd4108d264b398dbd42be05b",
"shasum": ""
},
"require": {
@ -3273,7 +3325,7 @@
"payment",
"purchase"
],
"time": "2015-10-07 09:33:48"
"time": "2016-01-19 15:08:12"
},
{
"name": "maatwebsite/excel",
@ -3463,12 +3515,12 @@
"source": {
"type": "git",
"url": "https://github.com/meebio/omnipay-secure-trading.git",
"reference": "42f97ee5ad1d28605550d816fc1893919e19e502"
"reference": "992224a3c8dd834ee18f6f253a77ecb4c87c1c1a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/meebio/omnipay-secure-trading/zipball/992224a3c8dd834ee18f6f253a77ecb4c87c1c1a",
"reference": "42f97ee5ad1d28605550d816fc1893919e19e502",
"reference": "992224a3c8dd834ee18f6f253a77ecb4c87c1c1a",
"shasum": ""
},
"require": {
@ -3513,7 +3565,7 @@
"secure trading",
"securetrading"
],
"time": "2015-12-01 10:03:20"
"time": "2016-01-05 09:26:36"
},
{
"name": "mfauveau/omnipay-pacnet",
@ -4363,16 +4415,16 @@
},
{
"name": "omnipay/firstdata",
"version": "v2.2.0",
"version": "v2.3.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/omnipay-firstdata.git",
"reference": "0853bba0ee313f5557eb1c696d3ce5538dbd4aca"
"reference": "e33826821db88d90886cad6c81a29452d3cf91a2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/omnipay-firstdata/zipball/0853bba0ee313f5557eb1c696d3ce5538dbd4aca",
"reference": "0853bba0ee313f5557eb1c696d3ce5538dbd4aca",
"url": "https://api.github.com/repos/thephpleague/omnipay-firstdata/zipball/e33826821db88d90886cad6c81a29452d3cf91a2",
"reference": "e33826821db88d90886cad6c81a29452d3cf91a2",
"shasum": ""
},
"require": {
@ -4417,7 +4469,7 @@
"pay",
"payment"
],
"time": "2015-07-28 17:50:44"
"time": "2016-01-14 06:24:28"
},
{
"name": "omnipay/gocardless",
@ -5112,16 +5164,16 @@
},
{
"name": "omnipay/paypal",
"version": "2.5.0",
"version": "v2.5.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/omnipay-paypal.git",
"reference": "67efe5a927dec13fc7520e29bc44f15fd3a728e9"
"reference": "b546d24241725061d44e60516f0fbce202336963"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/omnipay-paypal/zipball/67efe5a927dec13fc7520e29bc44f15fd3a728e9",
"reference": "67efe5a927dec13fc7520e29bc44f15fd3a728e9",
"url": "https://api.github.com/repos/thephpleague/omnipay-paypal/zipball/b546d24241725061d44e60516f0fbce202336963",
"reference": "b546d24241725061d44e60516f0fbce202336963",
"shasum": ""
},
"require": {
@ -5166,20 +5218,20 @@
"paypal",
"purchase"
],
"time": "2015-11-11 21:48:00"
"time": "2016-01-13 07:03:27"
},
{
"name": "omnipay/pin",
"version": "v2.1.0",
"version": "v2.2.1",
"source": {
"type": "git",
"url": "https://github.com/omnipay/pin.git",
"reference": "04e778e9689882d4c40419263014068b69b93168"
"url": "https://github.com/thephpleague/omnipay-pin.git",
"reference": "c2252e41f3674267b2bbe79eaeec73b6b1e4ee58"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/omnipay/pin/zipball/04e778e9689882d4c40419263014068b69b93168",
"reference": "04e778e9689882d4c40419263014068b69b93168",
"url": "https://api.github.com/repos/thephpleague/omnipay-pin/zipball/c2252e41f3674267b2bbe79eaeec73b6b1e4ee58",
"reference": "c2252e41f3674267b2bbe79eaeec73b6b1e4ee58",
"shasum": ""
},
"require": {
@ -5210,11 +5262,11 @@
},
{
"name": "Omnipay Contributors",
"homepage": "https://github.com/omnipay/pin/contributors"
"homepage": "https://github.com/thephpleague/omnipay-pin/contributors"
}
],
"description": "Pin Payments driver for the Omnipay payment processing library",
"homepage": "https://github.com/omnipay/pin",
"homepage": "https://github.com/thephpleague/omnipay-pin",
"keywords": [
"gateway",
"merchant",
@ -5223,20 +5275,20 @@
"payment",
"pin"
],
"time": "2014-04-14 11:26:15"
"time": "2016-01-13 07:00:17"
},
{
"name": "omnipay/sagepay",
"version": "v2.2.0",
"version": "2.3.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/omnipay-sagepay.git",
"reference": "899507095428fa54276ba5ca89f11fd7f8fd78ab"
"reference": "4208d23b33b2f8a59176e44ad22d304c461ecb62"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/omnipay-sagepay/zipball/899507095428fa54276ba5ca89f11fd7f8fd78ab",
"reference": "899507095428fa54276ba5ca89f11fd7f8fd78ab",
"url": "https://api.github.com/repos/thephpleague/omnipay-sagepay/zipball/4208d23b33b2f8a59176e44ad22d304c461ecb62",
"reference": "4208d23b33b2f8a59176e44ad22d304c461ecb62",
"shasum": ""
},
"require": {
@ -5282,7 +5334,7 @@
"sage pay",
"sagepay"
],
"time": "2015-04-02 17:46:20"
"time": "2016-01-12 12:43:31"
},
{
"name": "omnipay/securepay",
@ -5512,6 +5564,54 @@
],
"time": "2014-09-17 00:37:18"
},
{
"name": "paragonie/random_compat",
"version": "1.1.5",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "dd8998b7c846f6909f4e7a5f67fabebfc412a4f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/dd8998b7c846f6909f4e7a5f67fabebfc412a4f7",
"reference": "dd8998b7c846f6909f4e7a5f67fabebfc412a4f7",
"shasum": ""
},
"require": {
"php": ">=5.2.0"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"type": "library",
"autoload": {
"files": [
"lib/random.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"pseudorandom",
"random"
],
"time": "2016-01-06 13:31:20"
},
{
"name": "patricktalmadge/bootstrapper",
"version": "5.5.3",
@ -6058,28 +6158,28 @@
},
{
"name": "symfony/class-loader",
"version": "v2.8.1",
"version": "v3.0.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/class-loader.git",
"reference": "ec74b0a279cf3a9bd36172b3e3061591d380ce6c"
"reference": "6294f616bb9888ba2e13c8bfdcc4d306c1c95da7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/class-loader/zipball/ec74b0a279cf3a9bd36172b3e3061591d380ce6c",
"reference": "ec74b0a279cf3a9bd36172b3e3061591d380ce6c",
"url": "https://api.github.com/repos/symfony/class-loader/zipball/6294f616bb9888ba2e13c8bfdcc4d306c1c95da7",
"reference": "6294f616bb9888ba2e13c8bfdcc4d306c1c95da7",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
"php": ">=5.5.9"
},
"require-dev": {
"symfony/finder": "~2.0,>=2.0.5|~3.0.0"
"symfony/finder": "~2.8|~3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.8-dev"
"dev-master": "3.0-dev"
}
},
"autoload": {
@ -6106,11 +6206,11 @@
],
"description": "Symfony ClassLoader Component",
"homepage": "https://symfony.com",
"time": "2015-12-05 17:37:59"
"time": "2015-12-05 17:45:07"
},
{
"name": "symfony/console",
"version": "v2.6.12",
"version": "v2.6.13",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
@ -6168,16 +6268,16 @@
},
{
"name": "symfony/css-selector",
"version": "v2.8.1",
"version": "v2.8.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
"reference": "eaa3320e32f09a01dc432c6efbe8051aee59cfef"
"reference": "ac06d8173bd80790536c0a4a634a7d705b91f54f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/eaa3320e32f09a01dc432c6efbe8051aee59cfef",
"reference": "eaa3320e32f09a01dc432c6efbe8051aee59cfef",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/ac06d8173bd80790536c0a4a634a7d705b91f54f",
"reference": "ac06d8173bd80790536c0a4a634a7d705b91f54f",
"shasum": ""
},
"require": {
@ -6217,11 +6317,11 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
"time": "2015-12-05 17:37:59"
"time": "2016-01-03 15:33:41"
},
{
"name": "symfony/debug",
"version": "v2.6.12",
"version": "v2.6.13",
"target-dir": "Symfony/Component/Debug",
"source": {
"type": "git",
@ -6282,16 +6382,16 @@
},
{
"name": "symfony/event-dispatcher",
"version": "v2.8.1",
"version": "v2.8.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc"
"reference": "ee278f7c851533e58ca307f66305ccb9188aceda"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a5eb815363c0388e83247e7e9853e5dbc14999cc",
"reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ee278f7c851533e58ca307f66305ccb9188aceda",
"reference": "ee278f7c851533e58ca307f66305ccb9188aceda",
"shasum": ""
},
"require": {
@ -6338,20 +6438,20 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2015-10-30 20:15:42"
"time": "2016-01-13 10:28:07"
},
{
"name": "symfony/filesystem",
"version": "v2.8.1",
"version": "v2.8.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "a7ad724530a764d70c168d321ac226ba3d2f10fc"
"reference": "637b64d0ee10f44ae98dbad651b1ecdf35a11e8c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/a7ad724530a764d70c168d321ac226ba3d2f10fc",
"reference": "a7ad724530a764d70c168d321ac226ba3d2f10fc",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/637b64d0ee10f44ae98dbad651b1ecdf35a11e8c",
"reference": "637b64d0ee10f44ae98dbad651b1ecdf35a11e8c",
"shasum": ""
},
"require": {
@ -6387,11 +6487,11 @@
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
"time": "2015-12-22 10:25:57"
"time": "2016-01-13 10:28:07"
},
{
"name": "symfony/finder",
"version": "v2.6.12",
"version": "v2.6.13",
"target-dir": "Symfony/Component/Finder",
"source": {
"type": "git",
@ -6441,7 +6541,7 @@
},
{
"name": "symfony/http-foundation",
"version": "v2.6.12",
"version": "v2.6.13",
"target-dir": "Symfony/Component/HttpFoundation",
"source": {
"type": "git",
@ -6495,17 +6595,17 @@
},
{
"name": "symfony/http-kernel",
"version": "v2.6.12",
"version": "v2.6.13",
"target-dir": "Symfony/Component/HttpKernel",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
"reference": "498866a8ca0bcbcd3f3824b1520fa568ff7ca3b6"
"reference": "cdd991d304fed833514dc44d6aafcf19397c26cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/498866a8ca0bcbcd3f3824b1520fa568ff7ca3b6",
"reference": "498866a8ca0bcbcd3f3824b1520fa568ff7ca3b6",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/cdd991d304fed833514dc44d6aafcf19397c26cb",
"reference": "cdd991d304fed833514dc44d6aafcf19397c26cb",
"shasum": ""
},
"require": {
@ -6569,7 +6669,7 @@
],
"description": "Symfony HttpKernel Component",
"homepage": "https://symfony.com",
"time": "2015-11-23 11:37:53"
"time": "2016-01-14 10:11:16"
},
{
"name": "symfony/polyfill-php56",
@ -6681,7 +6781,7 @@
},
{
"name": "symfony/process",
"version": "v2.6.12",
"version": "v2.6.13",
"target-dir": "Symfony/Component/Process",
"source": {
"type": "git",
@ -6731,7 +6831,7 @@
},
{
"name": "symfony/routing",
"version": "v2.6.12",
"version": "v2.6.13",
"target-dir": "Symfony/Component/Routing",
"source": {
"type": "git",
@ -6800,20 +6900,21 @@
},
{
"name": "symfony/security-core",
"version": "v2.6.12",
"version": "v2.6.13",
"target-dir": "Symfony/Component/Security/Core",
"source": {
"type": "git",
"url": "https://github.com/symfony/security-core.git",
"reference": "05f58bb3814e8a853332dc448e3b7addaa87679c"
"reference": "813cf2aaacccbbe1a4705aef8d4ac0d79d993a76"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/security-core/zipball/05f58bb3814e8a853332dc448e3b7addaa87679c",
"reference": "05f58bb3814e8a853332dc448e3b7addaa87679c",
"url": "https://api.github.com/repos/symfony/security-core/zipball/813cf2aaacccbbe1a4705aef8d4ac0d79d993a76",
"reference": "813cf2aaacccbbe1a4705aef8d4ac0d79d993a76",
"shasum": ""
},
"require": {
"paragonie/random_compat": "~1.0",
"php": ">=5.3.3"
},
"require-dev": {
@ -6860,11 +6961,11 @@
],
"description": "Symfony Security Component - Core Library",
"homepage": "https://symfony.com",
"time": "2015-07-22 10:08:40"
"time": "2016-01-14 09:04:34"
},
{
"name": "symfony/translation",
"version": "v2.6.12",
"version": "v2.6.13",
"target-dir": "Symfony/Component/Translation",
"source": {
"type": "git",
@ -6923,7 +7024,7 @@
},
{
"name": "symfony/var-dumper",
"version": "v2.6.12",
"version": "v2.6.13",
"target-dir": "Symfony/Component/VarDumper",
"source": {
"type": "git",
@ -7030,16 +7131,16 @@
},
{
"name": "true/punycode",
"version": "v2.0.1",
"version": "v2.0.2",
"source": {
"type": "git",
"url": "https://github.com/true/php-punycode.git",
"reference": "b672918d992b84f8016bbe353a42516928393c63"
"reference": "74fa01d4de396c40e239794123b3874cb594a30c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/true/php-punycode/zipball/b672918d992b84f8016bbe353a42516928393c63",
"reference": "b672918d992b84f8016bbe353a42516928393c63",
"url": "https://api.github.com/repos/true/php-punycode/zipball/74fa01d4de396c40e239794123b3874cb594a30c",
"reference": "74fa01d4de396c40e239794123b3874cb594a30c",
"shasum": ""
},
"require": {
@ -7072,7 +7173,7 @@
"idna",
"punycode"
],
"time": "2015-09-01 14:53:31"
"time": "2016-01-07 17:12:58"
},
{
"name": "twbs/bootstrap",
@ -7347,16 +7448,16 @@
},
{
"name": "zircote/swagger-php",
"version": "2.0.4",
"version": "2.0.5",
"source": {
"type": "git",
"url": "https://github.com/zircote/swagger-php.git",
"reference": "be5d96e56c23cbe52c5bc5e267851323d95c57cd"
"reference": "c19af4edcc13c00e82fabeee926335b1fe1d92e9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zircote/swagger-php/zipball/be5d96e56c23cbe52c5bc5e267851323d95c57cd",
"reference": "be5d96e56c23cbe52c5bc5e267851323d95c57cd",
"url": "https://api.github.com/repos/zircote/swagger-php/zipball/c19af4edcc13c00e82fabeee926335b1fe1d92e9",
"reference": "c19af4edcc13c00e82fabeee926335b1fe1d92e9",
"shasum": ""
},
"require": {
@ -7403,7 +7504,7 @@
"rest",
"service discovery"
],
"time": "2015-11-13 13:50:11"
"time": "2016-01-15 09:39:28"
}
],
"packages-dev": [
@ -7593,16 +7694,16 @@
},
{
"name": "facebook/webdriver",
"version": "1.1.0",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/facebook/php-webdriver.git",
"reference": "518a9e0635e69777f07e41619cdf5d82fae284b6"
"reference": "1c98108ba3eb435b681655764de11502a0653705"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facebook/php-webdriver/zipball/518a9e0635e69777f07e41619cdf5d82fae284b6",
"reference": "518a9e0635e69777f07e41619cdf5d82fae284b6",
"url": "https://api.github.com/repos/facebook/php-webdriver/zipball/1c98108ba3eb435b681655764de11502a0653705",
"reference": "1c98108ba3eb435b681655764de11502a0653705",
"shasum": ""
},
"require": {
@ -7632,7 +7733,7 @@
"selenium",
"webdriver"
],
"time": "2015-12-08 17:04:30"
"time": "2015-12-31 15:58:49"
},
{
"name": "fzaninotto/faker",
@ -7722,16 +7823,16 @@
},
{
"name": "phpspec/phpspec",
"version": "2.4.0",
"version": "2.4.1",
"source": {
"type": "git",
"url": "https://github.com/phpspec/phpspec.git",
"reference": "1d3938e6d9ffb1bd4805ea8ddac62ea48767f358"
"reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/phpspec/zipball/1d3938e6d9ffb1bd4805ea8ddac62ea48767f358",
"reference": "1d3938e6d9ffb1bd4805ea8ddac62ea48767f358",
"url": "https://api.github.com/repos/phpspec/phpspec/zipball/5528ce1e93a1efa090c9404aba3395c329b4e6ed",
"reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed",
"shasum": ""
},
"require": {
@ -7796,7 +7897,7 @@
"testing",
"tests"
],
"time": "2015-11-29 02:03:49"
"time": "2016-01-01 10:17:54"
},
{
"name": "phpspec/prophecy",
@ -8599,16 +8700,16 @@
},
{
"name": "symfony/browser-kit",
"version": "v2.8.1",
"version": "v2.8.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
"reference": "dd2cfb20fabd4efca14cf3b2345d40b3dd5e9aca"
"reference": "a93dffaf763182acad12a4c42c7efc372899891e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/dd2cfb20fabd4efca14cf3b2345d40b3dd5e9aca",
"reference": "dd2cfb20fabd4efca14cf3b2345d40b3dd5e9aca",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/a93dffaf763182acad12a4c42c7efc372899891e",
"reference": "a93dffaf763182acad12a4c42c7efc372899891e",
"shasum": ""
},
"require": {
@ -8652,20 +8753,20 @@
],
"description": "Symfony BrowserKit Component",
"homepage": "https://symfony.com",
"time": "2015-12-26 13:37:56"
"time": "2016-01-12 17:46:01"
},
{
"name": "symfony/dom-crawler",
"version": "v2.8.1",
"version": "v2.8.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
"reference": "a2712aff8b250d9601ad6bd23a2ff82a12730e8e"
"reference": "650d37aacb1fa0dcc24cced483169852b3a0594e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/a2712aff8b250d9601ad6bd23a2ff82a12730e8e",
"reference": "a2712aff8b250d9601ad6bd23a2ff82a12730e8e",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/650d37aacb1fa0dcc24cced483169852b3a0594e",
"reference": "650d37aacb1fa0dcc24cced483169852b3a0594e",
"shasum": ""
},
"require": {
@ -8708,7 +8809,7 @@
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
"time": "2015-12-23 17:16:29"
"time": "2016-01-03 15:33:41"
},
{
"name": "symfony/polyfill-mbstring",
@ -8771,16 +8872,16 @@
},
{
"name": "symfony/yaml",
"version": "v2.8.1",
"version": "v2.8.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966"
"reference": "34c8a4b51e751e7ea869b8262f883d008a2b81b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966",
"reference": "ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966",
"url": "https://api.github.com/repos/symfony/yaml/zipball/34c8a4b51e751e7ea869b8262f883d008a2b81b8",
"reference": "34c8a4b51e751e7ea869b8262f883d008a2b81b8",
"shasum": ""
},
"require": {
@ -8816,7 +8917,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2015-12-26 13:37:56"
"time": "2016-01-13 10:28:07"
}
],
"aliases": [],
@ -8829,6 +8930,7 @@
"chumper/datatable": 20,
"intervention/image": 20,
"webpatser/laravel-countries": 20,
"barryvdh/laravel-ide-helper": 20,
"lokielse/omnipay-alipay": 20,
"alfaproject/omnipay-neteller": 20,
"alfaproject/omnipay-skrill": 20,
@ -8851,4 +8953,4 @@
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
}

View File

@ -96,12 +96,11 @@ class CreateVendorsTable extends Migration
$table->softDeletes();
$table->unsignedInteger('user_id');
$table->unsignedInteger('account_id');
$table->unsignedInteger('public_id')->index();
//$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
//$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->unsignedInteger('public_id')->index();
$table->unique(array('account_id', 'public_id'));
//$table->unique(array('account_id', 'public_id'));
});
// Update public id
@ -112,12 +111,16 @@ class CreateVendorsTable extends Migration
$i = 1;
foreach ($paymentTerms as $pTerm) {
$data = ['public_id' => $i++];
DB::table('paymet_terms')->where('id', $pTerm->id)->update($data);
DB::table('payment_terms')->where('id', $pTerm->id)->update($data);
}
Schema::table('invoices', function (Blueprint $table) {
$table->boolean('has_expenses')->default(false);
});
Schema::table('payment_terms', function (Blueprint $table) {
$table->unique(array('account_id', 'public_id'));
});
}
/**

View File

@ -11,8 +11,6 @@ class AddInvoiceFontSupport extends Migration
*/
public function up()
{
Schema::dropIfExists('fonts');
Schema::create('fonts', function ($t) {
$t->increments('id');
@ -30,18 +28,20 @@ class AddInvoiceFontSupport extends Migration
});
// Create fonts
$seeder = new FontsSeeder();
$seeder->run();
//$seeder = new FontsSeeder();
//$seeder->run();
Schema::table('accounts', function ($table) {
$table->unsignedInteger('header_font_id')->default(1);
$table->unsignedInteger('body_font_id')->default(1);
});
/*
Schema::table('accounts', function ($table) {
$table->foreign('header_font_id')->references('id')->on('fonts');
$table->foreign('body_font_id')->references('id')->on('fonts');
});
*/
}
/**
@ -53,14 +53,14 @@ class AddInvoiceFontSupport extends Migration
{
if (Schema::hasColumn('accounts', 'header_font_id')) {
Schema::table('accounts', function ($table) {
$table->dropForeign('accounts_header_font_id_foreign');
//$table->dropForeign('accounts_header_font_id_foreign');
$table->dropColumn('header_font_id');
});
}
if (Schema::hasColumn('accounts', 'body_font_id')) {
Schema::table('accounts', function ($table) {
$table->dropForeign('accounts_body_font_id_foreign');
//$table->dropForeign('accounts_body_font_id_foreign');
$table->dropColumn('body_font_id');
});
}

View File

@ -18,10 +18,12 @@ class AddQuoteToInvoiceOption extends Migration
});
// we need to create the last status to resolve a foreign key constraint
DB::table('invoice_statuses')->insert([
'id' => 6,
'name' => 'Paid'
]);
if (DB::table('invoice_statuses')->count() == 5) {
DB::table('invoice_statuses')->insert([
'id' => 6,
'name' => 'Paid'
]);
}
DB::table('invoices')
->whereIn('invoice_status_id', [4, 5])

View File

@ -0,0 +1,69 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddBankSubaccounts extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('bank_subaccounts', function($table)
{
$table->increments('id');
$table->unsignedInteger('account_id');
$table->unsignedInteger('user_id');
$table->unsignedInteger('bank_account_id');
$table->string('account_name');
$table->string('account_number');
$table->timestamps();
$table->softDeletes();
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('bank_account_id')->references('id')->on('bank_accounts')->onDelete('cascade');
$table->unsignedInteger('public_id')->index();
$table->unique(['account_id', 'public_id']);
});
Schema::table('expenses', function($table)
{
$table->string('transaction_id');
$table->unsignedInteger('bank_id');
});
Schema::table('vendors', function($table)
{
$table->string('transaction_name');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('bank_subaccounts');
Schema::table('expenses', function($table)
{
$table->dropColumn('transaction_id');
$table->dropColumn('bank_id');
});
Schema::table('vendors', function($table)
{
$table->dropColumn('transaction_name');
});
}
}

View File

@ -0,0 +1,64 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddHeaderFooterOption extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function($table)
{
$table->boolean('all_pages_footer');
$table->boolean('all_pages_header');
$table->boolean('show_currency_code');
$table->date('pro_plan_trial')->nullable();
});
Schema::table('gateways', function($table)
{
$table->boolean('is_offsite');
$table->boolean('is_secure');
});
Schema::table('expenses', function($table)
{
$table->string('transaction_id')->nullable()->change();
$table->unsignedInteger('bank_id')->nullable()->change();
});
Schema::table('vendors', function($table)
{
$table->string('transaction_name')->nullable()->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accounts', function($table)
{
$table->dropColumn('all_pages_footer');
$table->dropColumn('all_pages_header');
$table->dropColumn('show_currency_code');
$table->dropColumn('pro_plan_trial');
});
Schema::table('gateways', function($table)
{
$table->dropColumn('is_offsite');
$table->dropColumn('is_secure');
});
}
}

View File

@ -0,0 +1,48 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddSourceCurrencyToExpenses extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('expenses', function (Blueprint $table) {
$table->dropColumn('foreign_amount');
$table->unsignedInteger('currency_id')->nullable(false)->change();
$table->renameColumn('currency_id', 'invoice_currency_id');
$table->unsignedInteger('expense_currency_id');
});
Schema::table('expenses', function (Blueprint $table) {
// set account value so we're able to create foreign constraint
DB::statement('update expenses e
left join accounts a on a.id = e.account_id
set e.expense_currency_id = COALESCE(a.currency_id, 1)');
$table->foreign('invoice_currency_id')->references('id')->on('currencies');
$table->foreign('expense_currency_id')->references('id')->on('currencies');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('expenses', function($table) {
});
}
}

File diff suppressed because one or more lines are too long

View File

@ -18,7 +18,6 @@ class DatabaseSeeder extends Seeder
$this->call('PaymentLibrariesSeeder');
$this->call('FontsSeeder');
$this->call('BanksSeeder');
$this->call('FontsSeeder');
$this->call('InvoiceStatusSeeder');
}
}

View File

@ -224,6 +224,7 @@ class FontsSeeder extends Seeder
foreach ($fonts as $font) {
if (!DB::table('fonts')->where('name', '=', $font['name'])->get()) {
$font['is_early_access'] = false;
Font::create($font);
}
}

View File

@ -125,6 +125,7 @@ class PaymentLibrariesSeeder extends Seeder
['name' => 'Ghanaian Cedi', 'code' => 'GHS', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['name' => 'Bulgarian Lev', 'code' => 'BGN', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ' ', 'decimal_separator' => '.'],
['name' => 'Aruban Florin', 'code' => 'AWG', 'symbol' => 'Afl. ', 'precision' => '2', 'thousand_separator' => ' ', 'decimal_separator' => '.'],
['name' => 'Turkish Lira', 'code' => 'TRY', 'symbol' => 'TL ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
];
foreach ($currencies as $currency) {
@ -151,7 +152,7 @@ class PaymentLibrariesSeeder extends Seeder
['format' => 'M j, Y', 'picker_format' => 'M d, yyyy', 'label' => 'Mar 10, 2013'],
['format' => 'F j, Y', 'picker_format' => 'MM d, yyyy', 'label' => 'March 10, 2013'],
['format' => 'D M j, Y', 'picker_format' => 'D MM d, yyyy', 'label' => 'Mon March 10, 2013'],
['format' => 'Y-M-d', 'picker_format' => 'yyyy-M-dd', 'label' => '2013-03-10'],
['format' => 'Y-m-d', 'picker_format' => 'yyyy-mm-dd', 'label' => '2013-03-10'],
['format' => 'd-m-Y', 'picker_format' => 'dd-mm-yyyy', 'label' => '20-03-2013'],
['format' => 'm/d/Y', 'picker_format' => 'mm/dd/yyyy', 'label' => '03/20/2013']
];
@ -207,7 +208,7 @@ class PaymentLibrariesSeeder extends Seeder
'label' => 'Mon March 10th, 2013 6:15 pm'
],
[
'format' => 'Y-M-d g:i a',
'format' => 'Y-m-d g:i a',
'format_moment' => 'YYYY-MMM-DD h:mm:ss a',
'label' => '2013-03-10 6:15 pm'
],

View File

@ -1,187 +0,0 @@
<?php
/**
* Adds data to invitation for a user/client so that the
* public/payment/{aaabbb} page is accessable (aaabbb is the invitation_key)
**/
class SecurePaymentFormSeeder extends Seeder
{
public function run()
{
Eloquent::unguard();
//Delete table content
DB::table('invitations')->delete();
DB::table('invoices')->delete();
DB::table('contacts')->delete();
DB::table('clients')->delete();
DB::table('account_gateways')->delete();
//To reset the auto increment
$statement = "
ALTER TABLE invitations AUTO_INCREMENT = 1;
ALTER TABLE invoices AUTO_INCREMENT = 1;
ALTER TABLE contacts AUTO_INCREMENT = 1;
ALTER TABLE clients AUTO_INCREMENT = 1;
ALTER TABLE account_gateways AUTO_INCREMENT = 1;
";
DB::unprepared($statement);
//$firstName = 'Oscar';
// $lastName = 'Thompson';
// $firstName_2 = 'Philip';
// $lastName_2 = 'Jonsson';
//
// $user = AccountGateway::create(array(
// 'account_id' => 1,
// 'user_id' => 1,
// 'gateway_id' => 4,
// 'config' => '{"bla":"vla","bli":"cla"}',
// 'public_id' => 1,
// 'accepted_credit_cards' => 8
// ));
//
// $user2 = AccountGateway::create(array(
// 'account_id' => 2,
// 'user_id' => 2,
// 'gateway_id' => 5,
// 'config' => '{"bla":"vla","bli":"cla"}',
// 'public_id' => 2,
// 'accepted_credit_cards' => 7
// ));
//
// $client = Client::create(array(
// 'user_id' => 1,
// 'account_id' => 1,
// 'currency_id' => 1,
// 'name' => $firstName.' '.$lastName,
// 'address1' => '2119 Howe Course',
// 'address2' => '2118 Howe Course',
// 'city' => 'West Chazport',
// 'state' => 'Utah',
// 'postal_code' => '31572',
// 'country_id' => 752,
// 'work_phone' => '012-345678',
// 'private_notes' => 'bla bla bla bla bla bla bla',
// 'balance' => 10.4,
// 'paid_to_date' => 10.2,
// 'website' => 'awebsite.com',
// 'industry_id' => 8,
// 'is_deleted' => 0,
// 'payment_terms' => 2,
// 'public_id' => 1,
// 'custom_value1' => $firstName,
// 'custom_value2' => $firstName
// ));
//
// $client2 = Client::create(array(
// 'user_id' => 2,
// 'account_id' => 2,
// 'currency_id' => 1,
// 'name' => $firstName_2.' '.$lastName_2,
// 'address1' => '1118 Muma Road',
// 'address2' => '1118 Muma Road',
// 'city' => 'New Orleans',
// 'state' => 'Arizona',
// 'postal_code' => '31572',
// 'country_id' => 752,
// 'work_phone' => '012-345678',
// 'private_notes' => 'bla bla bla bla bla bla bla',
// 'balance' => 10.4,
// 'paid_to_date' => 10.2,
// 'website' => 'bodosite.com',
// 'industry_id' => 8,
// 'is_deleted' => 0,
// 'payment_terms' => 2,
// 'public_id' => 1,
// 'custom_value1' => $firstName_2,
// 'custom_value2' => $firstName_2
// ));
//
// $contact = Contact::create(array(
// 'account_id' => 1,
// 'user_id' => 1,
// 'client_id' => 1,
// 'is_primary' => 0,
// 'send_invoice' => 0,
// 'first_name' => $firstName,
// 'last_name' => $lastName,
// 'email' => 'an@email.com',
// 'phone' => '012-345678',
// 'public_id' => 1
// ));
//
// $contact2 = Contact::create(array(
// 'account_id' => 2,
// 'user_id' => 2,
// 'client_id' => 2,
// 'is_primary' => 0,
// 'send_invoice' => 0,
// 'first_name' => $firstName_2,
// 'last_name' => $lastName_2,
// 'email' => 'the@email.com',
// 'phone' => '012-345678',
// 'public_id' => 2
// ));
//
// $invoice = Invoice::create(array(
// 'client_id' => 1,
// 'user_id' => 1,
// 'account_id' => 1,
// 'invoice_number' => 1,
// 'discount' => 0.4,
// 'po_number' => $firstName,
// 'terms' => 'bla bla bla bla bla bla bla',
// 'public_notes' => 'bla bla bla bla bla bla bla',
// 'is_deleted' => 0,
// 'is_recurring' => 0,
// 'frequency_id' => 1,
// 'tax_name' => 'moms',
// 'tax_rate' => 33.0,
// 'amount' => 10.0,
// 'balance' => 8.0,
// 'public_id' => 1,
// 'is_quote' => 0
// ));
//
// $invoice2 = Invoice::create(array(
// 'client_id' => 2,
// 'user_id' => 2,
// 'account_id' => 2,
// 'invoice_number' => 2,
// 'discount' => 0.4,
// 'po_number' => $firstName_2,
// 'terms' => 'bla bla bla bla bla bla bla',
// 'public_notes' => 'bla bla bla bla bla bla bla',
// 'is_deleted' => 0,
// 'is_recurring' => 0,
// 'frequency_id' => 1,
// 'tax_name' => 'moms',
// 'tax_rate' => 33.0,
// 'amount' => 10.0,
// 'balance' => 8.0,
// 'public_id' => 2,
// 'is_quote' => 0
// ));
//
// $invitation = Invitation::create(array(
// 'account_id' => 1,
// 'user_id' => 1,
// 'contact_id' => 1,
// 'invoice_id' => 1,
// 'invitation_key' => 'aaabbb',
// 'transaction_reference' => 'bla bla bla bla bla bla bla',
// 'public_id' => 1
// ));
//
// $invitation2 = Invitation::create(array(
// 'account_id' => 2,
// 'user_id' => 2,
// 'contact_id' => 2,
// 'invoice_id' => 2,
// 'invitation_key' => 'cccddd',
// 'transaction_reference' => 'bla bla bla bla bla bla bla',
// 'public_id' => 2
// ));
}
}

File diff suppressed because one or more lines are too long

View File

@ -3372,6 +3372,10 @@ ul.user-accounts a:hover div.remove {
font-size: .9em;
}
td.right {
text-align: right;
}
/* Show selected section in settings nav */
.list-group-item.selected:before {
position: absolute;

View File

@ -1021,6 +1021,10 @@ ul.user-accounts a:hover div.remove {
font-size: .9em;
}
td.right {
text-align: right;
}
/* Show selected section in settings nav */
.list-group-item.selected:before {
position: absolute;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -54,15 +54,16 @@ function GetPdfMake(invoice, javascript, callback) {
}
}
// only show the header on the first page
// and the footer on the last page
if (key === 'header') {
return function(page, pages) {
return page === 1 ? val : '';
}
} else if (invoice.is_pro && key === 'footer') {
return function(page, pages) {
return page === pages ? val : '';
// determine whether or not to show the header/footer
if (invoice.is_pro) {
if (key === 'header') {
return function(page, pages) {
return page === 1 || account.all_pages_header ? val : '';
}
} else if (key === 'footer') {
return function(page, pages) {
return page === pages || account.all_pages_footer ? val : '';
}
}
}
@ -190,8 +191,8 @@ NINJA.decodeJavascript = function(invoice, javascript)
if (match.indexOf('?') < 0 || value) {
if (invoice.partial && field == 'balance_due') {
field = 'amount_due';
} else if (invoice.is_quote && field == 'your_invoice') {
field = 'your_quote';
} else if (invoice.is_quote) {
field = field.replace('invoice', 'quote');
}
var label = invoiceLabels[field];
if (match.indexOf('UC') >= 0) {
@ -334,9 +335,6 @@ NINJA.invoiceLines = function(invoice) {
}
var lineTotal = roundToTwo(NINJA.parseFloat(item.cost)) * roundToTwo(NINJA.parseFloat(item.qty));
if (showItemTaxes && tax) {
lineTotal += lineTotal * tax / 100;
}
lineTotal = formatMoneyInvoice(lineTotal, invoice);
rowStyle = (i % 2 == 0) ? 'odd' : 'even';

View File

@ -5,6 +5,8 @@ var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Const
var isEdge = navigator.userAgent.indexOf('Edge/') >= 0;
var isChrome = !!window.chrome && !isOpera && !isEdge; // Chrome 1+
var isChromium = isChrome && navigator.userAgent.indexOf('Chromium') >= 0;
// https://code.google.com/p/chromium/issues/detail?id=574648
var isChrome48 = isChrome && navigator.userAgent.indexOf('Chrome/48') >= 0;
var isIE = /*@cc_on!@*/false || !!document.documentMode; // At least IE6
var refreshTimer;

View File

@ -13,7 +13,7 @@
### Reseller Program
There are two options:
* 10% of revenue
* $1,000 for a site limited to 1,000 accounts
* $1,000 for a site limited to 1,000 users
### Installation Options
* [Self-Host Zip](https://www.invoiceninja.com/knowledgebase/self-host/) - Free
@ -95,3 +95,5 @@ There are two options:
* [thephpleague/fractal](https://github.com/thephpleague/fractal) - Output complex, flexible, AJAX/RESTful data structures
* [ezyang/htmlpurifier](https://github.com/ezyang/htmlpurifier) - Standards compliant HTML filter written in PHP
* [cerdic/css-tidy](https://github.com/Cerdic/CSSTidy) - CSSTidy is a CSS minifier
* [asgrim/ofxparser](https://github.com/asgrim/ofxparser) - OFX File Parser
* [stacktracejs/stacktrace.js](https://github.com/stacktracejs/stacktrace.js) - Framework-agnostic, micro-library for getting stack traces in all web browsers

View File

@ -1,4 +1,4 @@
<?php
<?php
return array(
@ -17,4 +17,4 @@ return array(
'next' => 'Næste &raquo;',
);
);

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,7 @@ return array(
"numeric" => ":attribute skal være imellem :min - :max.",
"file" => ":attribute skal være imellem :min - :max kilobytes.",
"string" => ":attribute skal være imellem :min - :max tegn.",
"array" => ":attribute skal indeholde mellem :min - :max elementer."
"array" => ":attribute skal indeholde mellem :min - :max elementer.",
),
"boolean" => ":attribute skal være sandt eller falsk",
"confirmed" => ":attribute er ikke det samme som bekræftelsesfeltet.",
@ -44,14 +44,14 @@ return array(
"numeric" => ":attribute skal være højest :max.",
"file" => ":attribute skal være højest :max kilobytes.",
"string" => ":attribute skal være højest :max tegn.",
"array" => ":attribute må ikke indeholde mere end :max elementer."
"array" => ":attribute må ikke indeholde mere end :max elementer.",
),
"mimes" => ":attribute skal være en fil af typen: :values.",
"min" => array(
"numeric" => ":attribute skal være mindst :min.",
"file" => ":attribute skal være mindst :min kilobytes.",
"string" => ":attribute skal være mindst :min tegn.",
"array" => ":attribute skal indeholde mindst :min elementer."
"array" => ":attribute skal indeholde mindst :min elementer.",
),
"not_in" => "Den valgte :attribute er ugyldig.",
"numeric" => ":attribute skal være et tal.",
@ -67,7 +67,7 @@ return array(
"numeric" => ":attribute skal være :size.",
"file" => ":attribute skal være :size kilobytes.",
"string" => ":attribute skal være :size tegn lang.",
"array" => ":attribute skal indeholde :size elementer."
"array" => ":attribute skal indeholde :size elementer.",
),
"timezone" => "The :attribute must be a valid zone.",
"unique" => ":attribute er allerede taget.",
@ -80,7 +80,7 @@ return array(
"has_counter" => 'The value must contain {$counter}',
"valid_contacts" => "All of the contacts must have either an email or name",
"valid_invoice_items" => "The invoice exceeds the maximum amount",
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines

View File

@ -1,4 +1,4 @@
<?php
<?php
return array(

View File

@ -59,7 +59,7 @@ return array(
'learn_more' => 'Mehr erfahren',
'manage_rates' => 'Steuersätze verwalten',
'note_to_client' => 'Notiz an den Kunden',
'invoice_terms' => 'Zahlungsbedingungen',
'invoice_terms' => 'Rechnungsbedingungen',
'save_as_default_terms' => 'Als Standardbedingungen speichern',
'download_pdf' => 'PDF herunterladen',
'pay_now' => 'Jetzt bezahlen',
@ -271,8 +271,7 @@ return array(
'notification_invoice_sent' => 'Dem Kunden :client wurde die Rechnung :invoice über :amount versendet.',
'notification_invoice_viewed' => 'Der Kunde :client hat sich die Rechnung :invoice über :amount angesehen.',
'reset_password' => 'Du kannst dein Passwort zurücksetzen, indem du auf den folgenden Link klickst:',
'reset_password_footer' => 'Wenn du das Zurücksetzen des Passworts nicht beantragt hast, benachrichtige bitte unseren Support: ' . CONTACT_EMAIL,
'reset_password_footer' => 'Wenn du das Zurücksetzen des Passworts nicht beantragt hast, benachrichtige bitte unseren Support: '.CONTACT_EMAIL,
// Payment page
'secure_payment' => 'Sichere Zahlung',
@ -300,7 +299,7 @@ return array(
'logout' => 'Ausloggen',
'sign_up_to_save' => 'Melde dich an, um deine Arbeit zu speichern',
'agree_to_terms' =>'Ich akzeptiere die InvoiceNinja :terms',
'agree_to_terms' => 'Ich akzeptiere die InvoiceNinja :terms',
'terms_of_service' => 'Service-Bedingungen',
'email_taken' => 'Diese E-Mail-Adresse ist bereits registriert',
'working' => 'Wird bearbeitet',
@ -420,7 +419,7 @@ return array(
'active' => 'Aktiv',
'pending' => 'Ausstehend',
'deleted_user' => 'Benutzer erfolgreich gelöscht',
'limit_users' => 'Entschuldige, das würde das Limit von ' . MAX_NUM_USERS . ' Benutzern überschreiten',
'limit_users' => 'Entschuldige, das würde das Limit von '.MAX_NUM_USERS.' Benutzern überschreiten',
'confirm_email_invoice' => 'Bist du sicher, dass du diese Rechnung per E-Mail versenden möchtest?',
'confirm_email_quote' => 'Bist du sicher, dass du dieses Angebot per E-Mail versenden möchtest',
@ -990,5 +989,127 @@ return array(
'schedule' => 'Schedule',
'email_designs' => 'Email Designs',
'assigned_when_sent' => 'Assigned when sent',
'white_label_custom_css' => ':link for $'.WHITE_LABEL_PRICE.' to enable custom styling and help support our project.',
'white_label_purchase_link' => 'Purchase a white label license',
// Expense / vendor
'expense' => 'Expense',
'expenses' => 'Expenses',
'new_expense' => 'Enter Expense',
'enter_expense' => 'Enter Expense',
'vendors' => 'Vendors',
'new_vendor' => 'New Vendor',
'payment_terms_net' => 'Net',
'vendor' => 'Vendor',
'edit_vendor' => 'Edit Vendor',
'archive_vendor' => 'Archive Vendor',
'delete_vendor' => 'Delete Vendor',
'view_vendor' => 'View Vendor',
'deleted_expense' => 'Successfully deleted expense',
'archived_expense' => 'Successfully archived expense',
'deleted_expenses' => 'Successfully deleted expenses',
'archived_expenses' => 'Successfully archived expenses',
// Expenses
'expense_amount' => 'Expense Amount',
'expense_balance' => 'Expense Balance',
'expense_date' => 'Expense Date',
'expense_should_be_invoiced' => 'Should this expense be invoiced?',
'public_notes' => 'Public Notes',
'invoice_amount' => 'Invoice Amount',
'exchange_rate' => 'Exchange Rate',
'yes' => 'Yes',
'no' => 'No',
'should_be_invoiced' => 'Should be invoiced',
'view_expense' => 'View expense # :expense',
'edit_expense' => 'Edit Expense',
'archive_expense' => 'Archive Expense',
'delete_expense' => 'Delete Expense',
'view_expense_num' => 'Expense # :expense',
'updated_expense' => 'Successfully updated expense',
'created_expense' => 'Successfully created expense',
'enter_expense' => 'Enter Expense',
'view' => 'View',
'restore_expense' => 'Restore Expense',
'invoice_expense' => 'Invoice Expense',
'expense_error_multiple_clients' => 'The expenses can\'t belong to different clients',
'expense_error_invoiced' => 'Expense has already been invoiced',
'convert_currency' => 'Convert currency',
// Payment terms
'num_days' => 'Number of days',
'create_payment_term' => 'Create Payment Term',
'edit_payment_terms' => 'Edit Payment Term',
'edit_payment_term' => 'Edit Payment Term',
'archive_payment_term' => 'Archive Payment Term',
// recurring due dates
'recurring_due_dates' => 'Recurring Invoice Due Dates',
'recurring_due_date_help' => '<p>Automatically sets a due date for the invoice.</p>
<p>Invoices on a monthly or yearly cycle set to be due on or before the day they are created will be due the next month. Invoices set to be due on the 29th or 30th in months that don\'t have that day will be due the last day of the month.</p>
<p>Invoices on a weekly cycle set to be due on the day of the week they are created will be due the next week.</p>
<p>For example:</p>
<ul>
<li>Today is the 15th, due date is 1st of the month. The due date should likely be the 1st of the next month.</li>
<li>Today is the 15th, due date is the last day of the month. The due date will be the last day of the this month.
</li>
<li>Today is the 15th, due date is the 15th day of the month. The due date will be the 15th day of <strong>next</strong> month.
</li>
<li>Today is the Friday, due date is the 1st Friday after. The due date will be next Friday, not today.
</li>
</ul>',
'due' => 'Due',
'next_due_on' => 'Due Next: :date',
'use_client_terms' => 'Use client terms',
'day_of_month' => ':ordinal day of month',
'last_day_of_month' => 'Last day of month',
'day_of_week_after' => ':ordinal :day after',
'sunday' => 'Sunday',
'monday' => 'Monday',
'tuesday' => 'Tuesday',
'wednesday' => 'Wednesday',
'thursday' => 'Thursday',
'friday' => 'Friday',
'saturday' => 'Saturday',
// Fonts
'header_font_id' => 'Header Font',
'body_font_id' => 'Body Font',
'color_font_help' => 'Note: the primary color and fonts are also used in the client portal and custom email designs.',
'live_preview' => 'Live Preview',
'invalid_mail_config' => 'Unable to send email, please check that the mail settings are correct.',
'invoice_message_button' => 'To view your invoice for :amount, click the button below.',
'quote_message_button' => 'To view your quote for :amount, click the button below.',
'payment_message_button' => 'Thank you for your payment of :amount.',
'payment_type_direct_debit' => 'Direct Debit',
'bank_accounts' => 'Bank Accounts',
'add_bank_account' => 'Add Bank Account',
'setup_account' => 'Setup Account',
'import_expenses' => 'Import Expenses',
'bank_id' => 'bank',
'integration_type' => 'Integration Type',
'updated_bank_account' => 'Successfully updated bank account',
'edit_bank_account' => 'Edit Bank Account',
'archive_bank_account' => 'Archive Bank Account',
'archived_bank_account' => 'Successfully archived bank account',
'created_bank_account' => 'Successfully created bank account',
'validate_bank_account' => 'Validate Bank Account',
'bank_accounts_help' => 'Connect a bank account to automatically import expenses and create vendors. Supports American Express and <a href="'.OFX_HOME_URL.'" target="_blank">400+ US banks.</a>',
'bank_password_help' => 'Note: your password is transmitted securely and never stored on our servers.',
'bank_password_warning' => 'Warning: your password may be transmitted in plain text, consider enabling HTTPS.',
'username' => 'Username',
'account_number' => 'Account Number',
'account_name' => 'Account Name',
'bank_account_error' => 'Failed to retreive account details, please check your credentials.',
'status_approved' => 'Approved',
'quote_settings' => 'Quote Settings',
'auto_convert_quote' => 'Auto convert quote',
'auto_convert_quote_help' => 'Automatically convert a quote to an invoice when approved by a client.',
'validate' => 'Validate',
'info' => 'Info',
'imported_expenses' => 'Successfully created :count_vendors vendor(s) and :count_expenses expense(s)',
);

View File

@ -25,7 +25,7 @@ return array(
"numeric" => ":attribute muss zwischen :min & :max liegen.",
"file" => ":attribute muss zwischen :min & :max Kilobytes groß sein.",
"string" => ":attribute muss zwischen :min & :max Zeichen lang sein.",
"array" => ":attribute muss zwischen :min & :max Elemente haben."
"array" => ":attribute muss zwischen :min & :max Elemente haben.",
),
"confirmed" => ":attribute stimmt nicht mit der Bestätigung überein.",
"date" => ":attribute muss ein gültiges Datum sein.",
@ -43,14 +43,14 @@ return array(
"numeric" => ":attribute darf maximal :max sein.",
"file" => ":attribute darf maximal :max Kilobytes groß sein.",
"string" => ":attribute darf maximal :max Zeichen haben.",
"array" => ":attribute darf nicht mehr als :max Elemente haben."
"array" => ":attribute darf nicht mehr als :max Elemente haben.",
),
"mimes" => ":attribute muss den Dateityp :values haben.",
"min" => array(
"numeric" => ":attribute muss mindestens :min sein.",
"file" => ":attribute muss mindestens :min Kilobytes groß sein.",
"string" => ":attribute muss mindestens :min Zeichen lang sein.",
"array" => ":attribute muss mindestens :min Elemente haben."
"array" => ":attribute muss mindestens :min Elemente haben.",
),
"not_in" => "Der gewählte Wert für :attribute ist ungültig.",
"numeric" => ":attribute muss eine Zahl sein.",
@ -66,7 +66,7 @@ return array(
"numeric" => ":attribute muss gleich :size sein.",
"file" => ":attribute muss :size Kilobyte groß sein.",
"string" => ":attribute muss :size Zeichen lang sein.",
"array" => ":attribute muss genau :size Elemente haben."
"array" => ":attribute muss genau :size Elemente haben.",
),
"unique" => ":attribute ist schon vergeben.",
"url" => "Das Format von :attribute ist ungültig.",
@ -78,7 +78,7 @@ return array(
"has_counter" => 'Der Wert muss {$counter} beinhalten',
"valid_contacts" => "Alle Kontake müssen entweder einen Namen oder eine E-Mail Adresse haben",
"valid_invoice_items" => "Die Rechnung übersteigt den maximalen Betrag",
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines

View File

@ -1,4 +1,4 @@
<?php
<?php
return array(

View File

@ -263,7 +263,6 @@ return array(
'deleted_vendor' => 'Successfully deleted vendor',
'deleted_vendors' => 'Successfully deleted :count vendors',
// Emails
'confirmation_subject' => 'Invoice Ninja Account Confirmation',
'confirmation_header' => 'Account Confirmation',
@ -284,8 +283,7 @@ return array(
'notification_invoice_sent' => 'The following client :client was emailed Invoice :invoice for :amount.',
'notification_invoice_viewed' => 'The following client :client viewed Invoice :invoice for :amount.',
'reset_password' => 'You can reset your account password by clicking the following button:',
'reset_password_footer' => 'If you did not request this password reset please email our support: ' . CONTACT_EMAIL,
'reset_password_footer' => 'If you did not request this password reset please email our support: '.CONTACT_EMAIL,
// Payment page
'secure_payment' => 'Secure Payment',
@ -313,7 +311,7 @@ return array(
'logout' => 'Log Out',
'sign_up_to_save' => 'Sign up to save your work',
'agree_to_terms' =>'I agree to the Invoice Ninja :terms',
'agree_to_terms' => 'I agree to the Invoice Ninja :terms',
'terms_of_service' => 'Terms of Service',
'email_taken' => 'The email address is already registered',
'working' => 'Working',
@ -433,7 +431,7 @@ return array(
'active' => 'Active',
'pending' => 'Pending',
'deleted_user' => 'Successfully deleted user',
'limit_users' => 'Sorry, this will exceed the limit of ' . MAX_NUM_USERS . ' users',
'limit_users' => 'Sorry, this will exceed the limit of '.MAX_NUM_USERS.' users',
'confirm_email_invoice' => 'Are you sure you want to email this invoice?',
'confirm_email_quote' => 'Are you sure you want to email this quote?',
@ -1019,7 +1017,7 @@ return array(
'new_expense' => 'Enter Expense',
'enter_expense' => 'Enter Expense',
'vendors' => 'Vendors',
'new_vendor' => 'Create Vendor',
'new_vendor' => 'New Vendor',
'payment_terms_net' => 'Net',
'vendor' => 'Vendor',
'edit_vendor' => 'Edit Vendor',
@ -1037,7 +1035,7 @@ return array(
'expense_date' => 'Expense Date',
'expense_should_be_invoiced' => 'Should this expense be invoiced?',
'public_notes' => 'Public Notes',
'converted_amount' => 'Converted Amount',
'invoice_amount' => 'Invoice Amount',
'exchange_rate' => 'Exchange Rate',
'yes' => 'Yes',
'no' => 'No',
@ -1053,8 +1051,8 @@ return array(
'view' => 'View',
'restore_expense' => 'Restore Expense',
'invoice_expense' => 'Invoice Expense',
'expense_error_multiple_clients' =>'The expenses can\'t belong to different clients',
'expense_error_invoiced' => 'Expense have already been invoiced',
'expense_error_multiple_clients' => 'The expenses can\'t belong to different clients',
'expense_error_invoiced' => 'Expense has already been invoiced',
'convert_currency' => 'Convert currency',
// Payment terms
@ -1107,6 +1105,8 @@ return array(
'payment_type_direct_debit' => 'Direct Debit',
'bank_accounts' => 'Bank Accounts',
'add_bank_account' => 'Add Bank Account',
'setup_account' => 'Setup Account',
'import_expenses' => 'Import Expenses',
'bank_id' => 'bank',
'integration_type' => 'Integration Type',
'updated_bank_account' => 'Successfully updated bank account',
@ -1114,17 +1114,34 @@ return array(
'archive_bank_account' => 'Archive Bank Account',
'archived_bank_account' => 'Successfully archived bank account',
'created_bank_account' => 'Successfully created bank account',
'test' => 'Test',
'test_bank_account' => 'Test Bank Account',
'validate_bank_account' => 'Validate Bank Account',
'bank_accounts_help' => 'Connect a bank account to automatically import expenses and create vendors. Supports American Express and <a href="'.OFX_HOME_URL.'" target="_blank">400+ US banks.</a>',
'bank_password_help' => 'Note: your password is transmitted securely and never stored on our servers.',
'bank_password_warning' => 'Warning: your password may be transmitted in plain text, consider enabling HTTPS.',
'username' => 'Username',
'account_number' => 'Account Number',
'account_name' => 'Account Name',
'bank_account_error' => 'Failed to retreive account details, please check your credentials.',
'status_approved' => 'Approved',
'quote_settings' => 'Quote Settings',
'auto_convert_quote' => 'Auto convert quote',
'auto_convert_quote_help' => 'Automatically convert a quote to an invoice when approved by a client.',
'validate' => 'Validate',
'info' => 'Info',
'imported_expenses' => 'Successfully created :count_vendors vendor(s) and :count_expenses expense(s)',
'iframe_url_help3' => 'Note: if you plan on accepting credit cards details we strongly recommend enabling HTTPS on your site.',
'expense_error_multiple_currencies' => 'The expenses can\'t have different currencies.',
'expense_error_mismatch_currencies' => 'The client\'s currency does not match the expense currency.',
'trello_roadmap' => 'Trello Roadmap',
'header_footer' => 'Header/Footer',
'first_page' => 'first page',
'all_pages' => 'all pages',
'last_page' => 'last page',
'all_pages_header' => 'Show header on',
'all_pages_footer' => 'Show footer on',
'invoice_currency' => 'Invoice Currency',
'enable_https' => 'We strongly recommend using HTTPS to accept credit card details online.',
'quote_issued_to' => 'Quote issued to',
'show_currency_code' => 'Currency Code',
);

View File

@ -68,7 +68,7 @@ return array(
),
"unique" => "The :attribute has already been taken.",
"url" => "The :attribute format is invalid.",
"positive" => "The :attribute must be greater than zero.",
"has_credit" => "The client does not have enough credit.",
"notmasked" => "The values are masked",

File diff suppressed because it is too large Load Diff

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