1
0
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:
Hillel Coren 2017-04-19 17:18:24 +03:00
parent 432c8e94a8
commit 6351086c4e
12 changed files with 141 additions and 16 deletions

View File

@ -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);

View File

@ -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')];

View File

@ -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
*/

View File

@ -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);
}
}

View File

@ -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>";

View File

@ -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');

View File

@ -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;

View File

@ -20,6 +20,7 @@ class ComposerServiceProvider extends ServiceProvider
'vendors.edit',
'payments.edit',
'invoices.edit',
'expenses.edit',
'accounts.localization',
'payments.credit_card',
],

View File

@ -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');
});
}
}

View File

@ -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',
);

View File

@ -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' }});

View File

@ -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