diff --git a/app/Constants.php b/app/Constants.php
index 799a4c1bb524..39522f175529 100644
--- a/app/Constants.php
+++ b/app/Constants.php
@@ -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);
diff --git a/app/Http/Controllers/ExpenseController.php b/app/Http/Controllers/ExpenseController.php
index d0be38fc8491..39ef8de542fb 100644
--- a/app/Http/Controllers/ExpenseController.php
+++ b/app/Http/Controllers/ExpenseController.php
@@ -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')];
diff --git a/app/Models/Account.php b/app/Models/Account.php
index 879cc445dae9..82ef3564c5d6 100644
--- a/app/Models/Account.php
+++ b/app/Models/Account.php
@@ -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
*/
diff --git a/app/Models/Expense.php b/app/Models/Expense.php
index 109c461b5d8a..a14625ea8390 100644
--- a/app/Models/Expense.php
+++ b/app/Models/Expense.php
@@ -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);
}
}
diff --git a/app/Ninja/Datatables/ExpenseDatatable.php b/app/Ninja/Datatables/ExpenseDatatable.php
index d29408ab31e8..30ccffab4fd5 100644
--- a/app/Ninja/Datatables/ExpenseDatatable.php
+++ b/app/Ninja/Datatables/ExpenseDatatable.php
@@ -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 "
$label
";
diff --git a/app/Ninja/Presenters/ExpensePresenter.php b/app/Ninja/Presenters/ExpensePresenter.php
index 9f8ac4c5506b..100a0de8cded 100644
--- a/app/Ninja/Presenters/ExpensePresenter.php
+++ b/app/Ninja/Presenters/ExpensePresenter.php
@@ -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');
diff --git a/app/Ninja/Repositories/ExpenseRepository.php b/app/Ninja/Repositories/ExpenseRepository.php
index 6a322e153d79..ba539eb254a7 100644
--- a/app/Ninja/Repositories/ExpenseRepository.php
+++ b/app/Ninja/Repositories/ExpenseRepository.php
@@ -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;
diff --git a/app/Providers/ComposerServiceProvider.php b/app/Providers/ComposerServiceProvider.php
index f20211cee4bc..ef2512c796f0 100644
--- a/app/Providers/ComposerServiceProvider.php
+++ b/app/Providers/ComposerServiceProvider.php
@@ -20,6 +20,7 @@ class ComposerServiceProvider extends ServiceProvider
'vendors.edit',
'payments.edit',
'invoices.edit',
+ 'expenses.edit',
'accounts.localization',
'payments.credit_card',
],
diff --git a/database/migrations/2017_04_16_101744_add_custom_contact_fields.php b/database/migrations/2017_04_16_101744_add_custom_contact_fields.php
index 69971fdc1c08..0f4b1a67b1d4 100644
--- a/database/migrations/2017_04_16_101744_add_custom_contact_fields.php
+++ b/database/migrations/2017_04_16_101744_add_custom_contact_fields.php
@@ -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');
+ });
}
}
diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php
index 451ecdde235f..163a285565ac 100644
--- a/resources/lang/en/texts.php
+++ b/resources/lang/en/texts.php
@@ -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',
);
diff --git a/resources/views/expenses/edit.blade.php b/resources/views/expenses/edit.blade.php
index e9d205bb7bb5..3bfcb346a914 100644
--- a/resources/views/expenses/edit.blade.php
+++ b/resources/views/expenses/edit.blade.php
@@ -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
+
+
+ {!! 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('') !!}
+
+ {!! Former::text('transaction_reference') !!}
+
+ @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' }});
diff --git a/resources/views/export/expenses.blade.php b/resources/views/export/expenses.blade.php
index d1668eb6f78a..6de84218b9c0 100644
--- a/resources/views/export/expenses.blade.php
+++ b/resources/views/export/expenses.blade.php
@@ -7,8 +7,11 @@
{{ trans('texts.expense_date') }} |
{{ trans('texts.amount') }} |
{{ trans('texts.category') }} |
+ {{ trans('texts.status') }} |
{{ trans('texts.public_notes') }} |
{{ trans('texts.private_notes') }} |
+ {{ trans('texts.payment_date') }} |
+ {{ trans('texts.transaction_reference') }} |
@foreach ($expenses as $expense)
@@ -21,7 +24,10 @@
{{ $expense->present()->expense_date }} |
{{ $expense->present()->amount }} |
{{ $expense->present()->category }} |
+ {{ $expense->statusLabel() }} |
{{ $expense->public_notes }} |
{{ $expense->private_notes }} |
+ {{ $expense->present()->payment_date }} |
+ {{ $expense->transaction_reference }} |
@endforeach