mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-09-19 16:01:34 +02:00
Support marking expenses as paid #1400
This commit is contained in:
parent
432c8e94a8
commit
6351086c4e
@ -200,8 +200,11 @@ if (! defined('APP_NAME')) {
|
||||
define('TASK_STATUS_PAID', 4);
|
||||
|
||||
define('EXPENSE_STATUS_LOGGED', 1);
|
||||
define('EXPENSE_STATUS_INVOICED', 2);
|
||||
define('EXPENSE_STATUS_PAID', 3);
|
||||
define('EXPENSE_STATUS_PENDING', 2);
|
||||
define('EXPENSE_STATUS_INVOICED', 3);
|
||||
define('EXPENSE_STATUS_BILLED', 4);
|
||||
define('EXPENSE_STATUS_PAID', 5);
|
||||
define('EXPENSE_STATUS_UNPAID', 6);
|
||||
|
||||
define('CUSTOM_DESIGN', 11);
|
||||
|
||||
|
@ -98,8 +98,6 @@ class ExpenseController extends BaseController
|
||||
{
|
||||
$expense = $request->entity();
|
||||
|
||||
$expense->expense_date = Utils::fromSqlDate($expense->expense_date);
|
||||
|
||||
$actions = [];
|
||||
if ($expense->invoice) {
|
||||
$actions[] = ['url' => URL::to("invoices/{$expense->invoice->public_id}/edit"), 'label' => trans('texts.view_invoice')];
|
||||
|
@ -371,6 +371,14 @@ class Account extends Eloquent
|
||||
return $this->belongsTo('App\Models\TaxRate');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function payment_type()
|
||||
{
|
||||
return $this->belongsTo('App\Models\PaymentType');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
|
@ -47,6 +47,9 @@ class Expense extends EntityModel
|
||||
'tax_name1',
|
||||
'tax_rate2',
|
||||
'tax_name2',
|
||||
'payment_date',
|
||||
'payment_type_id',
|
||||
'transaction_reference',
|
||||
];
|
||||
|
||||
public static function getImportColumns()
|
||||
@ -129,6 +132,14 @@ class Expense extends EntityModel
|
||||
return $this->hasMany('App\Models\Document')->orderBy('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function payment_type()
|
||||
{
|
||||
return $this->belongsTo('App\Models\PaymentType');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
@ -175,6 +186,14 @@ class Expense extends EntityModel
|
||||
return $this->invoice_currency_id != $this->expense_currency_id || $this->exchange_rate != 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isPaid()
|
||||
{
|
||||
return $this->payment_date || $this->payment_type_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
@ -221,19 +240,23 @@ class Expense extends EntityModel
|
||||
{
|
||||
$statuses = [];
|
||||
$statuses[EXPENSE_STATUS_LOGGED] = trans('texts.logged');
|
||||
$statuses[EXPENSE_STATUS_PENDING] = trans('texts.pending');
|
||||
$statuses[EXPENSE_STATUS_INVOICED] = trans('texts.invoiced');
|
||||
$statuses[EXPENSE_STATUS_BILLED] = trans('texts.billed');
|
||||
$statuses[EXPENSE_STATUS_PAID] = trans('texts.paid');
|
||||
$statuses[EXPENSE_STATUS_UNPAID] = trans('texts.unpaid');
|
||||
|
||||
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
public static function calcStatusLabel($shouldBeInvoiced, $invoiceId, $balance)
|
||||
public static function calcStatusLabel($shouldBeInvoiced, $invoiceId, $balance, $paymentDate)
|
||||
{
|
||||
if ($invoiceId) {
|
||||
if (floatval($balance) > 0) {
|
||||
$label = 'invoiced';
|
||||
} else {
|
||||
$label = 'paid';
|
||||
$label = 'billed';
|
||||
}
|
||||
} elseif ($shouldBeInvoiced) {
|
||||
$label = 'pending';
|
||||
@ -241,7 +264,13 @@ class Expense extends EntityModel
|
||||
$label = 'logged';
|
||||
}
|
||||
|
||||
return trans("texts.{$label}");
|
||||
$label = trans("texts.{$label}");
|
||||
|
||||
if ($paymentDate) {
|
||||
$label = trans('texts.paid') . ' | ' . $label;
|
||||
}
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
public static function calcStatusClass($shouldBeInvoiced, $invoiceId, $balance)
|
||||
@ -270,7 +299,7 @@ class Expense extends EntityModel
|
||||
{
|
||||
$balance = $this->invoice ? $this->invoice->balance : 0;
|
||||
|
||||
return static::calcStatusLabel($this->should_be_invoiced, $this->invoice_id, $balance);
|
||||
return static::calcStatusLabel($this->should_be_invoiced, $this->invoice_id, $balance, $this->payment_date);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,7 +90,7 @@ class ExpenseDatatable extends EntityDatatable
|
||||
[
|
||||
'status',
|
||||
function ($model) {
|
||||
return self::getStatusLabel($model->invoice_id, $model->should_be_invoiced, $model->balance);
|
||||
return self::getStatusLabel($model->invoice_id, $model->should_be_invoiced, $model->balance, $model->payment_date);
|
||||
},
|
||||
],
|
||||
];
|
||||
@ -129,9 +129,9 @@ class ExpenseDatatable extends EntityDatatable
|
||||
];
|
||||
}
|
||||
|
||||
private function getStatusLabel($invoiceId, $shouldBeInvoiced, $balance)
|
||||
private function getStatusLabel($invoiceId, $shouldBeInvoiced, $balance, $paymentDate)
|
||||
{
|
||||
$label = Expense::calcStatusLabel($shouldBeInvoiced, $invoiceId, $balance);
|
||||
$label = Expense::calcStatusLabel($shouldBeInvoiced, $invoiceId, $balance, $paymentDate);
|
||||
$class = Expense::calcStatusClass($shouldBeInvoiced, $invoiceId, $balance);
|
||||
|
||||
return "<h4><div class=\"label label-{$class}\">$label</div></h4>";
|
||||
|
@ -26,6 +26,14 @@ class ExpensePresenter extends EntityPresenter
|
||||
return Utils::fromSqlDate($this->entity->expense_date);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime|string
|
||||
*/
|
||||
public function payment_date()
|
||||
{
|
||||
return Utils::fromSqlDate($this->entity->payment_date);
|
||||
}
|
||||
|
||||
public function month()
|
||||
{
|
||||
return Carbon::parse($this->entity->payment_date)->format('Y m');
|
||||
|
@ -81,6 +81,7 @@ class ExpenseRepository extends BaseRepository
|
||||
'expenses.user_id',
|
||||
'expenses.tax_rate1',
|
||||
'expenses.tax_rate2',
|
||||
'expenses.payment_date',
|
||||
'expense_categories.name as category',
|
||||
'expense_categories.user_id as category_user_id',
|
||||
'expense_categories.public_id as category_public_id',
|
||||
@ -112,14 +113,24 @@ class ExpenseRepository extends BaseRepository
|
||||
}
|
||||
if (in_array(EXPENSE_STATUS_INVOICED, $statuses)) {
|
||||
$query->orWhere('expenses.invoice_id', '>', 0);
|
||||
if (! in_array(EXPENSE_STATUS_PAID, $statuses)) {
|
||||
if (! in_array(EXPENSE_STATUS_BILLED, $statuses)) {
|
||||
$query->where('invoices.balance', '>', 0);
|
||||
}
|
||||
}
|
||||
if (in_array(EXPENSE_STATUS_PAID, $statuses)) {
|
||||
if (in_array(EXPENSE_STATUS_BILLED, $statuses)) {
|
||||
$query->orWhere('invoices.balance', '=', 0)
|
||||
->where('expenses.invoice_id', '>', 0);
|
||||
}
|
||||
if (in_array(EXPENSE_STATUS_PAID, $statuses)) {
|
||||
$query->orWhereNotNull('expenses.payment_date');
|
||||
}
|
||||
if (in_array(EXPENSE_STATUS_UNPAID, $statuses)) {
|
||||
$query->orWhereNull('expenses.payment_date');
|
||||
}
|
||||
if (in_array(EXPENSE_STATUS_PENDING, $statuses)) {
|
||||
$query->orWhere('expenses.should_be_invoiced', '=', 1)
|
||||
->whereNull('expenses.invoice_id');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -161,6 +172,9 @@ class ExpenseRepository extends BaseRepository
|
||||
if (isset($input['expense_date'])) {
|
||||
$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;
|
||||
|
||||
|
@ -20,6 +20,7 @@ class ComposerServiceProvider extends ServiceProvider
|
||||
'vendors.edit',
|
||||
'payments.edit',
|
||||
'invoices.edit',
|
||||
'expenses.edit',
|
||||
'accounts.localization',
|
||||
'payments.credit_card',
|
||||
],
|
||||
|
@ -21,7 +21,7 @@ class AddCustomContactFields extends Migration
|
||||
$table->string('custom_value1')->nullable();
|
||||
$table->string('custom_value2')->nullable();
|
||||
});
|
||||
|
||||
|
||||
Schema::table('payment_methods', function ($table) {
|
||||
$table->unsignedInteger('account_gateway_token_id')->nullable()->change();
|
||||
$table->dropForeign('payment_methods_account_gateway_token_id_foreign');
|
||||
@ -38,6 +38,13 @@ class AddCustomContactFields extends Migration
|
||||
Schema::table('payments', function ($table) {
|
||||
$table->foreign('payment_method_id')->references('id')->on('payment_methods')->onDelete('cascade');
|
||||
});
|
||||
|
||||
Schema::table('expenses', function($table) {
|
||||
$table->unsignedInteger('payment_type_id')->nullable();
|
||||
$table->date('payment_date')->nullable();
|
||||
$table->string('transaction_reference')->nullable();
|
||||
$table->foreign('payment_type_id')->references('id')->on('payment_types');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,5 +63,11 @@ class AddCustomContactFields extends Migration
|
||||
$table->dropColumn('custom_value1');
|
||||
$table->dropColumn('custom_value2');
|
||||
});
|
||||
|
||||
Schema::table('expenses', function($table) {
|
||||
$table->dropColumn('payment_type_id');
|
||||
$table->dropColumn('payment_date');
|
||||
$table->dropColumn('transaction_reference');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -2481,6 +2481,8 @@ $LANG = array(
|
||||
'custom_contact_fields_help' => 'Add a field when creating a contact and display the label and value on the PDF.',
|
||||
'datatable_info' => 'Showing :start to :end of :total entries',
|
||||
'credit_total' => 'Credit Total',
|
||||
'mark_billable' => 'Mark Billable',
|
||||
'billed' => 'Billed',
|
||||
|
||||
);
|
||||
|
||||
|
@ -78,12 +78,37 @@
|
||||
|
||||
@if (!$expense || ($expense && !$expense->invoice_id))
|
||||
{!! Former::checkbox('should_be_invoiced')
|
||||
->text(trans('texts.billable'))
|
||||
->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_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'))
|
||||
@ -302,7 +327,7 @@
|
||||
setComboboxValue($('.expense-category-select'), category.public_id, category.name);
|
||||
}
|
||||
|
||||
$('#expense_date').datepicker('update', '{{ $expense ? $expense->expense_date : 'new Date()' }}');
|
||||
$('#expense_date').datepicker('update', '{{ $expense ? Utils::fromSqlDate($expense->expense_date) : 'new Date()' }}');
|
||||
|
||||
$('.expense_date .input-group-addon').click(function() {
|
||||
toggleDatePicker('expense_date');
|
||||
@ -349,6 +374,23 @@
|
||||
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
|
||||
|
||||
// Initialize document upload
|
||||
dropzone = new Dropzone('#document-upload', {
|
||||
url:{!! json_encode(url('documents')) !!},
|
||||
@ -406,6 +448,7 @@
|
||||
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' }});
|
||||
|
||||
|
@ -7,8 +7,11 @@
|
||||
<td>{{ trans('texts.expense_date') }}</td>
|
||||
<td>{{ trans('texts.amount') }}</td>
|
||||
<td>{{ trans('texts.category') }}</td>
|
||||
<td>{{ trans('texts.status') }}</td>
|
||||
<td>{{ trans('texts.public_notes') }}</td>
|
||||
<td>{{ trans('texts.private_notes') }}</td>
|
||||
<td>{{ trans('texts.payment_date') }}</td>
|
||||
<td>{{ trans('texts.transaction_reference') }}</td>
|
||||
</tr>
|
||||
|
||||
@foreach ($expenses as $expense)
|
||||
@ -21,7 +24,10 @@
|
||||
<td>{{ $expense->present()->expense_date }}</td>
|
||||
<td>{{ $expense->present()->amount }}</td>
|
||||
<td>{{ $expense->present()->category }}</td>
|
||||
<td>{{ $expense->statusLabel() }}</td>
|
||||
<td>{{ $expense->public_notes }}</td>
|
||||
<td>{{ $expense->private_notes }}</td>
|
||||
<td>{{ $expense->present()->payment_date }}</td>
|
||||
<td>{{ $expense->transaction_reference }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
|
Loading…
Reference in New Issue
Block a user