1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-19 16:01:34 +02:00

Working on recurring expenses

This commit is contained in:
Hillel Coren 2017-06-26 07:16:29 +03:00
parent 29ae0c3f76
commit 4a55e26117
17 changed files with 944 additions and 147 deletions

View File

@ -37,6 +37,7 @@ if (! defined('APP_NAME')) {
define('ENTITY_BANK_SUBACCOUNT', 'bank_subaccount');
define('ENTITY_EXPENSE_CATEGORY', 'expense_category');
define('ENTITY_PROJECT', 'project');
define('ENTITY_RECURRING_EXPENSE', 'recurring_expense');
define('INVOICE_TYPE_STANDARD', 1);
define('INVOICE_TYPE_QUOTE', 2);

View File

@ -266,6 +266,7 @@ class ExpenseController extends BaseController
'customLabel2' => Auth::user()->account->custom_vendor_label2,
'categories' => ExpenseCategory::whereAccountId(Auth::user()->account_id)->withArchived()->orderBy('name')->get(),
'taxRates' => TaxRate::scope()->whereIsInclusive(false)->orderBy('name')->get(),
'isRecurring' => false,
];
}

View File

@ -0,0 +1,164 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\CreateRecurringExpenseRequest;
use App\Http\Requests\RecurringExpenseRequest;
use App\Http\Requests\UpdateRecurringExpenseRequest;
use App\Models\Client;
use App\Models\ExpenseCategory;
use App\Models\TaxRate;
use App\Models\Vendor;
use App\Ninja\Datatables\RecurringExpenseDatatable;
use App\Ninja\Repositories\RecurringExpenseRepository;
use App\Services\RecurringExpenseService;
use Auth;
use Input;
use Session;
use View;
use Cache;
class RecurringExpenseController extends BaseController
{
protected $recurringExpenseRepo;
protected $recurringExpenseService;
protected $entityType = ENTITY_RECURRING_EXPENSE;
public function __construct(RecurringExpenseRepository $recurringExpenseRepo, RecurringExpenseService $recurringExpenseService)
{
$this->recurringExpenseRepo = $recurringExpenseRepo;
$this->recurringExpenseService = $recurringExpenseService;
}
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
return View::make('list_wrapper', [
'entityType' => ENTITY_RECURRING_EXPENSE,
'datatable' => new RecurringExpenseDatatable(),
'title' => trans('texts.recurring_expenses'),
]);
}
public function getDatatable($expensePublicId = null)
{
$search = Input::get('sSearch');
$userId = Auth::user()->filterId();
return $this->recurringExpenseService->getDatatable($search, $userId);
}
public function create(RecurringExpenseRequest $request)
{
if ($request->vendor_id != 0) {
$vendor = Vendor::scope($request->vendor_id)->with('vendor_contacts')->firstOrFail();
} else {
$vendor = null;
}
$data = [
'vendorPublicId' => Input::old('vendor') ? Input::old('vendor') : $request->vendor_id,
'expense' => null,
'method' => 'POST',
'url' => 'recurring_expenses',
'title' => trans('texts.new_expense'),
'vendors' => Vendor::scope()->with('vendor_contacts')->orderBy('name')->get(),
'vendor' => $vendor,
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
'clientPublicId' => $request->client_id,
'categoryPublicId' => $request->category_id,
];
$data = array_merge($data, self::getViewModel());
return View::make('expenses.edit', $data);
}
public function edit(RecurringExpenseRequest $request)
{
$expense = $request->entity();
$actions = [];
if (! $expense->trashed()) {
$actions[] = ['url' => 'javascript:submitAction("archive")', 'label' => trans('texts.archive_expense')];
$actions[] = ['url' => 'javascript:onDeleteClick()', 'label' => trans('texts.delete_expense')];
} else {
$actions[] = ['url' => 'javascript:submitAction("restore")', 'label' => trans('texts.restore_expense')];
}
$data = [
'vendor' => null,
'expense' => $expense,
'entity' => $expense,
'method' => 'PUT',
'url' => 'recurring_expenses/'.$expense->public_id,
'title' => 'Edit Expense',
'actions' => $actions,
'vendors' => Vendor::scope()->with('vendor_contacts')->orderBy('name')->get(),
'vendorPublicId' => $expense->vendor ? $expense->vendor->public_id : null,
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
'clientPublicId' => $expense->client ? $expense->client->public_id : null,
'categoryPublicId' => $expense->expense_category ? $expense->expense_category->public_id : null,
];
$data = array_merge($data, self::getViewModel());
return View::make('expenses.edit', $data);
}
private static function getViewModel()
{
return [
'data' => Input::old('data'),
'account' => Auth::user()->account,
'sizes' => Cache::get('sizes'),
'paymentTerms' => Cache::get('paymentTerms'),
'industries' => Cache::get('industries'),
'currencies' => Cache::get('currencies'),
'languages' => Cache::get('languages'),
'countries' => Cache::get('countries'),
'customLabel1' => Auth::user()->account->custom_vendor_label1,
'customLabel2' => Auth::user()->account->custom_vendor_label2,
'categories' => ExpenseCategory::whereAccountId(Auth::user()->account_id)->withArchived()->orderBy('name')->get(),
'taxRates' => TaxRate::scope()->whereIsInclusive(false)->orderBy('name')->get(),
'isRecurring' => true,
];
}
public function store(CreateRecurringExpenseRequest $request)
{
$recurringExpense = $this->recurringExpenseService->save($request->input());
Session::flash('message', trans('texts.created_recurring_expense'));
return redirect()->to($recurringExpense->getRoute());
}
public function update(UpdateRecurringExpenseRequest $request)
{
$recurringExpense = $this->recurringExpenseService->save($request->input(), $request->entity());
Session::flash('message', trans('texts.updated_recurring_expense'));
return redirect()->to($recurringExpense->getRoute());
}
public function bulk()
{
$action = Input::get('action');
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
$count = $this->recurringExpenseService->bulk($ids, $action);
if ($count > 0) {
$field = $count == 1 ? "{$action}d_recurring_expense" : "{$action}d_recurring_expenses";
$message = trans("texts.$field", ['count' => $count]);
Session::flash('message', $message);
}
return redirect()->to('/recurring_expenses');
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
class CreateRecurringExpenseRequest extends RecurringExpenseRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->can('create', ENTITY_RECURRING_EXPENSE);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'amount' => 'numeric',
];
}
}

View File

@ -14,7 +14,7 @@ class ExpenseRequest extends EntityRequest
$expense = parent::entity();
// eager load the documents
if ($expense && ! $expense->relationLoaded('documents')) {
if ($expense && method_exists($expense, 'documents') && ! $expense->relationLoaded('documents')) {
$expense->load('documents');
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Http\Requests;
class RecurringExpenseRequest extends ExpenseRequest
{
protected $entityType = ENTITY_RECURRING_EXPENSE;
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
class UpdateRecurringExpenseRequest extends RecurringExpenseRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->can('edit', $this->entity());
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'amount' => 'numeric',
];
}
}

View File

@ -179,6 +179,14 @@ Route::group(['middleware' => ['lookup:user', 'auth:user']], function () {
Route::post('invoices/bulk', 'InvoiceController@bulk');
Route::post('recurring_invoices/bulk', 'InvoiceController@bulk');
Route::get('recurring_expenses', 'RecurringExpenseController@index');
Route::get('api/recurring_expenses', 'RecurringExpenseController@getDatatable');
Route::get('recurring_expenses/create/{vendor_id?}/{client_id?}/{category_id?}', 'RecurringExpenseController@create');
Route::post('recurring_expenses', 'RecurringExpenseController@store');
Route::put('recurring_expenses/{recurring_expenses}', 'RecurringExpenseController@update');
Route::get('recurring_expenses/{recurring_expenses}/edit', 'RecurringExpenseController@edit');
Route::post('recurring_expenses/bulk', 'RecurringExpenseController@bulk');
Route::get('documents/{documents}/{filename?}', 'DocumentController@get');
Route::get('documents/js/{documents}/{filename}', 'DocumentController@getVFSJS');
Route::get('documents/preview/{documents}/{filename?}', 'DocumentController@getPreview');

View File

@ -0,0 +1,148 @@
<?php
namespace App\Models;
//use App\Events\ExpenseWasCreated;
//use App\Events\ExpenseWasUpdated;
use Illuminate\Database\Eloquent\SoftDeletes;
use Laracasts\Presenter\PresentableTrait;
use Utils;
/**
* Class Expense.
*/
class RecurringExpense extends EntityModel
{
// Expenses
use SoftDeletes;
use PresentableTrait;
/**
* @var array
*/
protected $dates = ['deleted_at'];
/**
* @var string
*/
protected $presenter = 'App\Ninja\Presenters\ExpensePresenter';
/**
* @var array
*/
protected $fillable = [
'client_id',
'vendor_id',
'expense_currency_id',
//'invoice_currency_id',
//'exchange_rate',
'amount',
'private_notes',
'public_notes',
'expense_category_id',
'tax_rate1',
'tax_name1',
'tax_rate2',
'tax_name2',
];
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function expense_category()
{
return $this->belongsTo('App\Models\ExpenseCategory')->withTrashed();
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function account()
{
return $this->belongsTo('App\Models\Account');
}
/**
* @return mixed
*/
public function user()
{
return $this->belongsTo('App\Models\User')->withTrashed();
}
/**
* @return mixed
*/
public function vendor()
{
return $this->belongsTo('App\Models\Vendor')->withTrashed();
}
/**
* @return mixed
*/
public function client()
{
return $this->belongsTo('App\Models\Client')->withTrashed();
}
/**
* @return mixed
*/
public function getName()
{
if ($this->public_notes) {
return Utils::truncateString($this->public_notes, 16);
} else {
return '#' . $this->public_id;
}
}
/**
* @return mixed
*/
public function getDisplayName()
{
return $this->getName();
}
/**
* @return string
*/
public function getRoute()
{
return "/recurring_expenses/{$this->public_id}/edit";
}
/**
* @return mixed
*/
public function getEntityType()
{
return ENTITY_RECURRING_EXPENSE;
}
public function amountWithTax()
{
return Utils::calculateTaxes($this->amount, $this->tax_rate1, $this->tax_rate2);
}
}
RecurringExpense::creating(function ($expense) {
$expense->setNullValues();
});
RecurringExpense::created(function ($expense) {
//event(new ExpenseWasCreated($expense));
});
RecurringExpense::updating(function ($expense) {
$expense->setNullValues();
});
RecurringExpense::updated(function ($expense) {
//event(new ExpenseWasUpdated($expense));
});
RecurringExpense::deleting(function ($expense) {
$expense->setNullValues();
});

View File

@ -0,0 +1,112 @@
<?php
namespace App\Ninja\Datatables;
use App\Models\Expense;
use Auth;
use URL;
use Utils;
class RecurringExpenseDatatable extends EntityDatatable
{
public $entityType = ENTITY_RECURRING_EXPENSE;
public $sortCol = 3;
public function columns()
{
return [
[
'vendor_name',
function ($model) {
if ($model->vendor_public_id) {
if (! Auth::user()->can('viewByOwner', [ENTITY_VENDOR, $model->vendor_user_id])) {
return $model->vendor_name;
}
return link_to("vendors/{$model->vendor_public_id}", $model->vendor_name)->toHtml();
} else {
return '';
}
},
! $this->hideClient,
],
[
'client_name',
function ($model) {
if ($model->client_public_id) {
if (! Auth::user()->can('viewByOwner', [ENTITY_CLIENT, $model->client_user_id])) {
return Utils::getClientDisplayName($model);
}
return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml();
} else {
return '';
}
},
! $this->hideClient,
],
/*
[
'expense_date',
function ($model) {
if (! Auth::user()->can('viewByOwner', [ENTITY_EXPENSE, $model->user_id])) {
return Utils::fromSqlDate($model->expense_date_sql);
}
return link_to("expenses/{$model->public_id}/edit", Utils::fromSqlDate($model->expense_date_sql))->toHtml();
},
],
*/
[
'amount',
function ($model) {
$amount = Utils::calculateTaxes($model->amount, $model->tax_rate1, $model->tax_rate2);
$str = Utils::formatMoney($amount, $model->expense_currency_id);
/*
// show both the amount and the converted amount
if ($model->exchange_rate != 1) {
$converted = round($amount * $model->exchange_rate, 2);
$str .= ' | ' . Utils::formatMoney($converted, $model->invoice_currency_id);
}
*/
return $str;
},
],
[
'category',
function ($model) {
$category = $model->category != null ? substr($model->category, 0, 100) : '';
if (! Auth::user()->can('editByOwner', [ENTITY_EXPENSE_CATEGORY, $model->category_user_id])) {
return $category;
}
return $model->category_public_id ? link_to("expense_categories/{$model->category_public_id}/edit", $category)->toHtml() : '';
},
],
[
'public_notes',
function ($model) {
return $model->public_notes != null ? substr($model->public_notes, 0, 100) : '';
},
],
];
}
public function actions()
{
return [
[
trans('texts.edit_recurring_expense'),
function ($model) {
return URL::to("recurring_expenses/{$model->public_id}/edit");
},
function ($model) {
return Auth::user()->can('editByOwner', [ENTITY_RECURRING_EXPENSE, $model->user_id]);
},
],
];
}
}

View File

@ -0,0 +1,141 @@
<?php
namespace App\Ninja\Repositories;
use App\Models\RecurringExpense;
use App\Models\Vendor;
use Auth;
use DB;
use Utils;
class RecurringExpenseRepository extends BaseRepository
{
// Expenses
public function getClassName()
{
return 'App\Models\RecurringExpense';
}
public function all()
{
return RecurringExpense::scope()
->with('user')
->withTrashed()
->where('is_deleted', '=', false)
->get();
}
public function find($filter = null)
{
$accountid = \Auth::user()->account_id;
$query = DB::table('recurring_expenses')
->join('accounts', 'accounts.id', '=', 'recurring_expenses.account_id')
->leftjoin('clients', 'clients.id', '=', 'recurring_expenses.client_id')
->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
->leftjoin('vendors', 'vendors.id', '=', 'recurring_expenses.vendor_id')
->leftJoin('expense_categories', 'recurring_expenses.expense_category_id', '=', 'expense_categories.id')
->where('recurring_expenses.account_id', '=', $accountid)
->where('contacts.deleted_at', '=', null)
->where('vendors.deleted_at', '=', null)
->where('clients.deleted_at', '=', null)
->where(function ($query) { // handle when client isn't set
$query->where('contacts.is_primary', '=', true)
->orWhere('contacts.is_primary', '=', null);
})
->select(
'recurring_expenses.account_id',
'recurring_expenses.amount',
'recurring_expenses.deleted_at',
'recurring_expenses.id',
'recurring_expenses.is_deleted',
'recurring_expenses.private_notes',
'recurring_expenses.public_id',
'recurring_expenses.public_notes',
'recurring_expenses.should_be_invoiced',
'recurring_expenses.vendor_id',
'recurring_expenses.expense_currency_id',
'recurring_expenses.invoice_currency_id',
'recurring_expenses.user_id',
'recurring_expenses.tax_rate1',
'recurring_expenses.tax_rate2',
'expense_categories.name as category',
'expense_categories.user_id as category_user_id',
'expense_categories.public_id as category_public_id',
'vendors.name as vendor_name',
'vendors.public_id as vendor_public_id',
'vendors.user_id as vendor_user_id',
DB::raw("COALESCE(NULLIF(clients.name,''), NULLIF(CONCAT(contacts.first_name, ' ', contacts.last_name),''), NULLIF(contacts.email,'')) client_name"),
'clients.public_id as client_public_id',
'clients.user_id as client_user_id',
'contacts.first_name',
'contacts.email',
'contacts.last_name',
'clients.country_id as client_country_id'
);
$this->applyFilters($query, ENTITY_RECURRING_EXPENSE);
if ($filter) {
$query->where(function ($query) use ($filter) {
$query->where('recurring_expenses.public_notes', 'like', '%'.$filter.'%')
->orWhere('clients.name', 'like', '%'.$filter.'%')
->orWhere('vendors.name', 'like', '%'.$filter.'%')
->orWhere('expense_categories.name', 'like', '%'.$filter.'%');
;
});
}
return $query;
}
public function save($input, $expense = null)
{
$publicId = isset($input['public_id']) ? $input['public_id'] : false;
if ($expense) {
// do nothing
} elseif ($publicId) {
$expense = RecurringExpense::scope($publicId)->firstOrFail();
if (Utils::isNinjaDev()) {
\Log::warning('Entity not set in expense repo save');
}
} else {
$expense = RecurringExpense::createNew();
}
if ($expense->is_deleted) {
return $expense;
}
// First auto fill
$expense->fill($input);
/*
if (isset($input['expense_date'])) {
$expense->expense_date = Utils::toSqlDate($input['expense_date']);
}
if (isset($input['payment_date'])) {
$expense->payment_date = Utils::toSqlDate($input['payment_date']);
}
$expense->should_be_invoiced = isset($input['should_be_invoiced']) && floatval($input['should_be_invoiced']) || $expense->client_id ? true : false;
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);
if (isset($input['amount'])) {
$expense->amount = round(Utils::parseFloat($input['amount']), 2);
}
*/
$expense->save();
return $expense;
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Policies;
use App\Models\User;
class RecurringExpensePolicy extends EntityPolicy
{
/**
* @param User $user
* @param mixed $item
*
* @return bool
*/
public static function create(User $user, $item)
{
if (! parent::create($user, $item)) {
return false;
}
return $user->hasFeature(FEATURE_EXPENSES);
}
}

View File

@ -18,6 +18,7 @@ class AuthServiceProvider extends ServiceProvider
\App\Models\Credit::class => \App\Policies\CreditPolicy::class,
\App\Models\Document::class => \App\Policies\DocumentPolicy::class,
\App\Models\Expense::class => \App\Policies\ExpensePolicy::class,
\App\Models\RecurringExpense::class => \App\Policies\RecurringExpensePolicy::class,
\App\Models\ExpenseCategory::class => \App\Policies\ExpenseCategoryPolicy::class,
\App\Models\Invoice::class => \App\Policies\InvoicePolicy::class,
\App\Models\Payment::class => \App\Policies\PaymentPolicy::class,

View File

@ -0,0 +1,82 @@
<?php
namespace App\Services;
use Utils;
use App\Models\Client;
use App\Models\Vendor;
use App\Ninja\Datatables\RecurringExpenseDatatable;
use App\Ninja\Repositories\RecurringExpenseRepository;
/**
* Class RecurringExpenseService.
*/
class RecurringExpenseService extends BaseService
{
/**
* @var RecurringExpenseRepository
*/
protected $recurringExpenseRepo;
/**
* @var DatatableService
*/
protected $datatableService;
/**
* CreditService constructor.
*
* @param RecurringExpenseRepository $creditRepo
* @param DatatableService $datatableService
*/
public function __construct(RecurringExpenseRepository $recurringExpenseRepo, DatatableService $datatableService)
{
$this->recurringExpenseRepo = $recurringExpenseRepo;
$this->datatableService = $datatableService;
}
/**
* @return CreditRepository
*/
protected function getRepo()
{
return $this->recurringExpenseRepo;
}
/**
* @param $data
* @param mixed $recurringExpense
*
* @return mixed|null
*/
public function save($data, $recurringExpense = false)
{
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->recurringExpenseRepo->save($data, $recurringExpense);
}
/**
* @param $clientPublicId
* @param $search
* @param mixed $userId
*
* @return \Illuminate\Http\JsonResponse
*/
public function getDatatable($search, $userId)
{
$query = $this->recurringExpenseRepo->find($search);
if (! Utils::hasPermission('view_all')) {
$query->where('recurring_expenses.user_id', '=', Auth::user()->id);
}
return $this->datatableService->createDatatable(new RecurringExpenseDatatable(), $query);
}
}

View File

@ -38,7 +38,7 @@ class UpdateDarkMode extends Migration
$table->decimal('amount', 13, 2);
$table->text('private_notes');
$table->text('public_notes');
$table->unsignedInteger('invoice_currency_id')->nullable(false);
$table->unsignedInteger('invoice_currency_id')->nullable()->index();
$table->unsignedInteger('expense_currency_id')->nullable()->index();
$table->boolean('should_be_invoiced')->default(true);
$table->unsignedInteger('expense_category_id')->nullable()->index();
@ -47,6 +47,11 @@ class UpdateDarkMode extends Migration
$table->string('tax_name2')->nullable();
$table->decimal('tax_rate2', 13, 3);
$table->unsignedInteger('frequency_id');
$table->date('start_date')->nullable();
$table->date('end_date')->nullable();
$table->timestamp('last_created_date')->nullable();
// Relations
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
@ -59,6 +64,10 @@ class UpdateDarkMode extends Migration
$table->unique(['account_id', 'public_id']);
});
Schema::table('expenses', function ($table) {
$table->unsignedInteger('recurring_expense_id')->nullable();
});
}
/**
@ -69,5 +78,9 @@ class UpdateDarkMode extends Migration
public function down()
{
Schema::drop('recurring_expenses');
Schema::table('expenses', function ($table) {
$table->dropColumn('recurring_expense_id');
});
}
}

View File

@ -2272,6 +2272,22 @@ $LANG = array(
'update_payment_details' => 'Update payment details',
'updated_payment_details' => 'Successfully updated payment details',
'update_credit_card' => 'Update Credit Card',
'recurring_expenses' => 'Recurring Expenses',
'recurring_expense' => 'Recurring Expense',
'new_recurring_expense' => 'New Recurring Expense',
'edit_recurring_expense' => 'Edit Recurring Expense',
'archive_recurring_expense' => 'Archive Recurring Expense',
'list_recurring_expense' => 'List Recurring Expenses',
'updated_recurring_expense' => 'Successfully updated recurring expense',
'created_recurring_expense' => 'Successfully created recurring expense',
'archived_recurring_expense' => 'Successfully archived recurring expense',
'archived_recurring_expense' => 'Successfully archived :count recurring expenses',
'restore_recurring_expense' => 'Restore Recurring Expense',
'restored_recurring_expense' => 'Successfully restored recurring expense',
'delete_recurring_expense' => 'Delete Recurring Expense',
'deleted_recurring_expense' => 'Successfully deleted project',
'deleted_recurring_expense' => 'Successfully deleted :count projects',
);

View File

@ -76,74 +76,6 @@
->addGroupClass('client-select') !!}
@endif
@if (!$expense || ($expense && !$expense->invoice_id))
{!! Former::checkbox('should_be_invoiced')
->text(trans('texts.mark_billable'))
->data_bind('checked: should_be_invoiced()')
->label(' ')
->value(1) !!}
@endif
@if (! $expense || ! $expense->transaction_id)
@if (! $expense || ! $expense->isPaid())
{!! Former::checkbox('mark_paid')
->data_bind('checked: mark_paid')
->text(trans('texts.mark_expense_paid'))
->label(' ')
->value(1) !!}
@endif
<div style="display:none" data-bind="visible: mark_paid">
{!! Former::select('payment_type_id')
->addOption('','')
->fromQuery($paymentTypes, 'name', 'id')
->addGroupClass('payment-type-select') !!}
{!! Former::text('payment_date')
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT))
->addGroupClass('payment_date')
->append('<i class="glyphicon glyphicon-calendar"></i>') !!}
{!! Former::text('transaction_reference') !!}
</div>
@endif
@if (!$expense || ($expense && ! $expense->isExchanged()))
{!! Former::checkbox('convert_currency')
->text(trans('texts.convert_currency'))
->data_bind('checked: convert_currency')
->label(' ')
->value(1) !!}
@endif
<div style="display:none" data-bind="visible: enableExchangeRate">
<br/>
<span style="display:none" data-bind="visible: !client_id()">
{!! Former::select('invoice_currency_id')->addOption('','')
->label(trans('texts.invoice_currency'))
->data_placeholder(Utils::getFromCache($account->getCurrencyId(), 'currencies')->name)
->data_bind('combobox: invoice_currency_id, disable: true')
->fromQuery($currencies, 'name', 'id') !!}
</span>
<span style="display:none;" data-bind="visible: client_id">
{!! Former::plaintext('')
->value('<span data-bind="html: invoiceCurrencyName"></span>')
->style('min-height:46px')
->label(trans('texts.invoice_currency')) !!}
</span>
{!! Former::text('exchange_rate')
->data_bind("value: exchange_rate, enable: enableExchangeRate, valueUpdate: 'afterkeydown'") !!}
{!! Former::text('invoice_amount')
->addGroupClass('converted-amount')
->data_bind("value: convertedAmount, enable: enableExchangeRate")
->append('<span data-bind="html: invoiceCurrencyCode"></span>') !!}
</div>
@if (count($taxRates))
@if (!$expense || ($expense && (!$expense->tax_name1 && !$expense->tax_name2)))
{!! Former::checkbox('apply_taxes')
@ -159,22 +91,110 @@
@include('partials.tax_rates')
</div>
@if ($account->hasFeature(FEATURE_DOCUMENTS))
{!! Former::checkbox('invoice_documents')
->text(trans('texts.add_documents_to_invoice'))
->onchange('onInvoiceDocumentsChange()')
->data_bind('checked: invoice_documents')
@if (!$expense || ($expense && !$expense->invoice_id))
{!! Former::checkbox('should_be_invoiced')
->text(trans('texts.mark_billable'))
->data_bind('checked: should_be_invoiced()')
->label(' ')
->value(1) !!}
@endif
@if ($isRecurring)
{!! Former::select('frequency_id')
->label('frequency')
->options(\App\Models\Frequency::selectOptions())
->data_bind("value: frequency_id") !!}
{!! Former::text('start_date')
->data_bind("datePicker: start_date, valueUpdate: 'afterkeydown'")
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))
->appendIcon('calendar')
->addGroupClass('start_date') !!}
{!! Former::text('end_date')
->data_bind("datePicker: end_date, valueUpdate: 'afterkeydown'")
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))
->appendIcon('calendar')
->addGroupClass('end_date') !!}
@else
@if ((! $expense || ! $expense->transaction_id))
@if (! $expense || ! $expense->isPaid())
{!! Former::checkbox('mark_paid')
->data_bind('checked: mark_paid')
->text(trans('texts.mark_expense_paid'))
->label(' ')
->value(1) !!}
@endif
<div style="display:none" data-bind="visible: mark_paid">
{!! Former::select('payment_type_id')
->addOption('','')
->fromQuery($paymentTypes, 'name', 'id')
->addGroupClass('payment-type-select') !!}
{!! Former::text('payment_date')
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT))
->addGroupClass('payment_date')
->append('<i class="glyphicon glyphicon-calendar"></i>') !!}
{!! Former::text('transaction_reference') !!}
</div>
@endif
@if (!$expense || ($expense && ! $expense->isExchanged()))
{!! Former::checkbox('convert_currency')
->text(trans('texts.convert_currency'))
->data_bind('checked: convert_currency')
->label(' ')
->value(1) !!}
@endif
<div style="display:none" data-bind="visible: enableExchangeRate">
<br/>
<span style="display:none" data-bind="visible: !client_id()">
{!! Former::select('invoice_currency_id')->addOption('','')
->label(trans('texts.invoice_currency'))
->data_placeholder(Utils::getFromCache($account->getCurrencyId(), 'currencies')->name)
->data_bind('combobox: invoice_currency_id, disable: true')
->fromQuery($currencies, 'name', 'id') !!}
</span>
<span style="display:none;" data-bind="visible: client_id">
{!! Former::plaintext('')
->value('<span data-bind="html: invoiceCurrencyName"></span>')
->style('min-height:46px')
->label(trans('texts.invoice_currency')) !!}
</span>
{!! Former::text('exchange_rate')
->data_bind("value: exchange_rate, enable: enableExchangeRate, valueUpdate: 'afterkeydown'") !!}
{!! Former::text('invoice_amount')
->addGroupClass('converted-amount')
->data_bind("value: convertedAmount, enable: enableExchangeRate")
->append('<span data-bind="html: invoiceCurrencyCode"></span>') !!}
</div>
@if ($account->hasFeature(FEATURE_DOCUMENTS))
{!! Former::checkbox('invoice_documents')
->text(trans('texts.add_documents_to_invoice'))
->onchange('onInvoiceDocumentsChange()')
->data_bind('checked: invoice_documents')
->label(' ')
->value(1) !!}
@endif
@endif
</div>
<div class="col-md-6">
{!! Former::textarea('public_notes')->rows(6) !!}
{!! Former::textarea('private_notes')->rows(6) !!}
@if ($account->hasFeature(FEATURE_DOCUMENTS))
@if (! $isRecurring && $account->hasFeature(FEATURE_DOCUMENTS))
<div class="form-group">
<label for="public_notes" class="control-label col-lg-4 col-sm-4">
{{trans('texts.documents')}}
@ -351,78 +371,78 @@
$('#amount').focus();
@endif
@if (Auth::user()->account->hasFeature(FEATURE_DOCUMENTS))
$('.main-form').submit(function(){
if($('#document-upload .fallback input').val())$(this).attr('enctype', 'multipart/form-data')
else $(this).removeAttr('enctype')
})
@if (! $isRecurring && Auth::user()->account->hasFeature(FEATURE_DOCUMENTS))
$('.main-form').submit(function(){
if($('#document-upload .fallback input').val())$(this).attr('enctype', 'multipart/form-data')
else $(this).removeAttr('enctype')
})
$('#payment_type_id').combobox();
$('#mark_paid').click(function(event) {
if ($('#mark_paid').is(':checked')) {
$('#payment_date').datepicker('update', new Date());
@if ($account->payment_type_id)
setComboboxValue($('.payment-type-select'), {{ $account->payment_type_id }}, "{{ trans('texts.payment_type_' . $account->payment_type->name) }}");
@endif
} else {
$('#payment_date').datepicker('update', false);
setComboboxValue($('.payment-type-select'), '', '');
}
})
@if ($expense && $expense->payment_date)
$('#payment_date').datepicker('update', '{{ Utils::fromSqlDate($expense->payment_date) }}');
@endif
$('.payment_date .input-group-addon').click(function() {
toggleDatePicker('payment_date');
});
// Initialize document upload
dropzone = new Dropzone('#document-upload', {
url:{!! json_encode(url('documents')) !!},
params:{
_token:"{{ Session::getToken() }}"
},
acceptedFiles:{!! json_encode(implode(',',\App\Models\Document::$allowedMimes)) !!},
addRemoveLinks:true,
dictRemoveFileConfirmation:"{{trans('texts.are_you_sure')}}",
@foreach(['default_message', 'fallback_message', 'fallback_text', 'file_too_big', 'invalid_file_type', 'response_error', 'cancel_upload', 'cancel_upload_confirmation', 'remove_file'] as $key)
"dict{{strval($key)}}":"{{trans('texts.dropzone_'.Utils::toClassCase($key))}}",
@endforeach
maxFilesize:{{floatval(MAX_DOCUMENT_SIZE/1000)}},
});
if(dropzone instanceof Dropzone){
dropzone.on("addedfile",handleDocumentAdded);
dropzone.on("removedfile",handleDocumentRemoved);
dropzone.on("success",handleDocumentUploaded);
dropzone.on("canceled",handleDocumentCanceled);
dropzone.on("error",handleDocumentError);
for (var i=0; i<model.documents().length; i++) {
var document = model.documents()[i];
var mockFile = {
name:document.name(),
size:document.size(),
type:document.type(),
public_id:document.public_id(),
status:Dropzone.SUCCESS,
accepted:true,
url:document.url(),
mock:true,
index:i
};
dropzone.emit('addedfile', mockFile);
dropzone.emit('complete', mockFile);
if(document.preview_url()){
dropzone.emit('thumbnail', mockFile, document.preview_url()||document.url());
$('#payment_type_id').combobox();
$('#mark_paid').click(function(event) {
if ($('#mark_paid').is(':checked')) {
$('#payment_date').datepicker('update', new Date());
@if ($account->payment_type_id)
setComboboxValue($('.payment-type-select'), {{ $account->payment_type_id }}, "{{ trans('texts.payment_type_' . $account->payment_type->name) }}");
@endif
} else {
$('#payment_date').datepicker('update', false);
setComboboxValue($('.payment-type-select'), '', '');
}
else if(document.type()=='jpeg' || document.type()=='png' || document.type()=='svg'){
dropzone.emit('thumbnail', mockFile, document.url());
})
@if ($expense && $expense->payment_date)
$('#payment_date').datepicker('update', '{{ Utils::fromSqlDate($expense->payment_date) }}');
@endif
$('.payment_date .input-group-addon').click(function() {
toggleDatePicker('payment_date');
});
// Initialize document upload
dropzone = new Dropzone('#document-upload', {
url:{!! json_encode(url('documents')) !!},
params:{
_token:"{{ Session::getToken() }}"
},
acceptedFiles:{!! json_encode(implode(',',\App\Models\Document::$allowedMimes)) !!},
addRemoveLinks:true,
dictRemoveFileConfirmation:"{{trans('texts.are_you_sure')}}",
@foreach(['default_message', 'fallback_message', 'fallback_text', 'file_too_big', 'invalid_file_type', 'response_error', 'cancel_upload', 'cancel_upload_confirmation', 'remove_file'] as $key)
"dict{{strval($key)}}":"{{trans('texts.dropzone_'.Utils::toClassCase($key))}}",
@endforeach
maxFilesize:{{floatval(MAX_DOCUMENT_SIZE/1000)}},
});
if(dropzone instanceof Dropzone){
dropzone.on("addedfile",handleDocumentAdded);
dropzone.on("removedfile",handleDocumentRemoved);
dropzone.on("success",handleDocumentUploaded);
dropzone.on("canceled",handleDocumentCanceled);
dropzone.on("error",handleDocumentError);
for (var i=0; i<model.documents().length; i++) {
var document = model.documents()[i];
var mockFile = {
name:document.name(),
size:document.size(),
type:document.type(),
public_id:document.public_id(),
status:Dropzone.SUCCESS,
accepted:true,
url:document.url(),
mock:true,
index:i
};
dropzone.emit('addedfile', mockFile);
dropzone.emit('complete', mockFile);
if(document.preview_url()){
dropzone.emit('thumbnail', mockFile, document.preview_url()||document.url());
}
else if(document.type()=='jpeg' || document.type()=='png' || document.type()=='svg'){
dropzone.emit('thumbnail', mockFile, document.url());
}
dropzone.files.push(mockFile);
}
dropzone.files.push(mockFile);
}
}
@endif
});
@ -435,10 +455,13 @@
self.amount = ko.observable();
self.exchange_rate = ko.observable(1);
self.should_be_invoiced = ko.observable();
self.mark_paid = ko.observable({{ $expense && $expense->isPaid() ? 'true' : 'false' }});
self.convert_currency = ko.observable({{ ($expense && $expense->isExchanged()) ? 'true' : 'false' }});
self.apply_taxes = ko.observable({{ ($expense && ($expense->tax_name1 || $expense->tax_name2)) ? 'true' : 'false' }});
@if (! $isRecurring)
self.convert_currency = ko.observable({{ ($expense && $expense->isExchanged()) ? 'true' : 'false' }});
self.mark_paid = ko.observable({{ $expense && $expense->isPaid() ? 'true' : 'false' }});
@endif
var invoiceDocuments = false;
if (isStorageSupported()) {
invoiceDocuments = localStorage.getItem('last:invoice_documents');
@ -489,7 +512,7 @@
});
self.enableExchangeRate = ko.computed(function() {
if (self.convert_currency()) {
if (self.convert_currency && self.convert_currency()) {
return true;
}
var expenseCurrencyId = self.expense_currency_id() || self.account_currency_id();