diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b248db72165..50c1739ad6b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,12 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added - Configuration for first day of the week #950 - StyleCI configuration #929 +- Added expense category ### Changed - Removed `invoiceninja.komodoproject` from Git #932 - `APP_CIPHER` changed from `rinjdael-128` to `AES-256-CBC` #898 -- Improved options when exporting data +- Improved options when exporting data ### Fixed - "Manual entry" untranslatable #562 diff --git a/app/Http/Controllers/ExpenseController.php b/app/Http/Controllers/ExpenseController.php index 39a0b788fc2d..1372cd4d95c5 100644 --- a/app/Http/Controllers/ExpenseController.php +++ b/app/Http/Controllers/ExpenseController.php @@ -10,6 +10,7 @@ use Redirect; use Cache; use App\Models\Vendor; use App\Models\Expense; +use App\Models\ExpenseCategory; use App\Models\Client; use App\Services\ExpenseService; use App\Ninja\Repositories\ExpenseRepository; @@ -73,7 +74,7 @@ class ExpenseController extends BaseController } else { $vendor = null; } - + $data = [ 'vendorPublicId' => Input::old('vendor') ? Input::old('vendor') : $request->vendor_id, 'expense' => null, @@ -94,7 +95,7 @@ class ExpenseController extends BaseController public function edit(ExpenseRequest $request) { $expense = $request->entity(); - + $expense->expense_date = Utils::fromSqlDate($expense->expense_date); $actions = []; @@ -140,7 +141,7 @@ class ExpenseController extends BaseController { $data = $request->input(); $data['documents'] = $request->file('documents'); - + $expense = $this->expenseService->save($data, $request->entity()); Session::flash('message', trans('texts.updated_expense')); @@ -157,7 +158,7 @@ class ExpenseController extends BaseController { $data = $request->input(); $data['documents'] = $request->file('documents'); - + $expense = $this->expenseService->save($data); Session::flash('message', trans('texts.created_expense')); @@ -176,7 +177,7 @@ class ExpenseController extends BaseController $expenses = Expense::scope($ids)->with('client')->get(); $clientPublicId = null; $currencyId = null; - + // 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) { @@ -232,6 +233,7 @@ class ExpenseController extends BaseController 'countries' => Cache::get('countries'), 'customLabel1' => Auth::user()->account->custom_vendor_label1, 'customLabel2' => Auth::user()->account->custom_vendor_label2, + 'categories' => ExpenseCategory::scope()->get() ]; } diff --git a/app/Models/Expense.php b/app/Models/Expense.php index 364b5d509687..1311d319e544 100644 --- a/app/Models/Expense.php +++ b/app/Models/Expense.php @@ -39,8 +39,17 @@ class Expense extends EntityModel 'public_notes', 'bank_id', 'transaction_id', + 'expense_category_id', ]; + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function expense_category() + { + return $this->belongsTo('App\Models\ExpenseCategory'); + } + /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ diff --git a/app/Models/ExpenseCategory.php b/app/Models/ExpenseCategory.php index 5f479a35d89f..535bf6f0d819 100644 --- a/app/Models/ExpenseCategory.php +++ b/app/Models/ExpenseCategory.php @@ -25,4 +25,4 @@ class ExpenseCategory extends EntityModel return $this->belongsTo('App\Models\Expense'); } -} \ No newline at end of file +} diff --git a/app/Services/ExpenseService.php b/app/Services/ExpenseService.php index 78cf275d9749..8d549195d55a 100644 --- a/app/Services/ExpenseService.php +++ b/app/Services/ExpenseService.php @@ -5,6 +5,7 @@ use Utils; use App\Ninja\Repositories\ExpenseRepository; use App\Models\Client; use App\Models\Vendor; +use App\Models\ExpenseCategory; use App\Ninja\Datatables\ExpenseDatatable; /** @@ -57,6 +58,19 @@ class ExpenseService extends BaseService $data['vendor_id'] = Vendor::getPrivateId($data['vendor_id']); } + if ( ! empty($data['category'])) { + $name = trim($data['category']); + $category = ExpenseCategory::scope()->whereName($name)->first(); + if ( ! $category) { + $category = ExpenseCategory::createNew(); + $category->name = $name; + $category->save(); + } + $data['expense_category_id'] = $category->id; + } elseif (isset($data['category'])) { + $data['expense_category_id'] = null; + } + return $this->expenseRepo->save($data, $expense); } diff --git a/public/built.js b/public/built.js index 1febfc104813..6e96fb8e2408 100644 --- a/public/built.js +++ b/public/built.js @@ -30319,6 +30319,34 @@ if (window.ko) { trigger: "hover" } }; + + ko.bindingHandlers.typeahead = { + init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { + var $element = $(element); + var allBindings = allBindingsAccessor(); + + $element.typeahead({ + highlight: true, + minLength: 0, + }, + { + name: 'data', + display: allBindings.key, + limit: 50, + source: searchData(allBindings.items, allBindings.key) + }).on('typeahead:change', function(element, datum, name) { + var value = valueAccessor(); + value(datum); + }); + }, + + update: function (element, valueAccessor) { + var value = ko.utils.unwrapObservable(valueAccessor()); + if (value) { + $(element).typeahead('val', value); + } + } + }; } function getContactDisplayName(contact) @@ -30450,7 +30478,7 @@ function formatAddress(city, state, zip, swap) { str += zip ? zip + ' ' : ''; str += city ? city : ''; str += (city && state) ? ', ' : (city ? ' ' : ''); - str += state; + str += state; } else { str += city ? city : ''; str += (city && state) ? ', ' : (state ? ' ' : ''); @@ -30484,7 +30512,7 @@ function calculateAmounts(invoice) { var hasTaxes = false; var taxes = {}; invoice.has_product_key = false; - + // Bold designs currently breaks w/o the product column if (invoice.invoice_design_id == 2) { invoice.has_product_key = true; @@ -30532,7 +30560,7 @@ function calculateAmounts(invoice) { lineTotal -= roundToTwo(lineTotal * (invoice.discount/100)); } } - + var taxAmount1 = roundToTwo(lineTotal * taxRate1 / 100); if (taxAmount1) { var key = taxName1 + taxRate1; @@ -30609,7 +30637,7 @@ function calculateAmounts(invoice) { invoice.tax_amount1 = taxAmount1; invoice.tax_amount2 = taxAmount2; invoice.item_taxes = taxes; - + if (NINJA.parseFloat(invoice.partial)) { invoice.balance_amount = roundToTwo(invoice.partial); } else { @@ -30934,7 +30962,7 @@ function truncate(string, length){ } }; -// Show/hide the 'Select' option in the datalists +// Show/hide the 'Select' option in the datalists function actionListHandler() { $('tbody tr .tr-action').closest('tr').mouseover(function() { $(this).closest('tr').find('.tr-action').show(); @@ -31000,11 +31028,12 @@ function searchData(data, key, fuzzy) { } cb(matches); } -}; +}; function escapeRegExp(str) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); } + var NINJA = NINJA || {}; NINJA.TEMPLATES = { diff --git a/public/js/script.js b/public/js/script.js index a66c48768bb6..b5a4092e2685 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -425,6 +425,34 @@ if (window.ko) { trigger: "hover" } }; + + ko.bindingHandlers.typeahead = { + init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { + var $element = $(element); + var allBindings = allBindingsAccessor(); + + $element.typeahead({ + highlight: true, + minLength: 0, + }, + { + name: 'data', + display: allBindings.key, + limit: 50, + source: searchData(allBindings.items, allBindings.key) + }).on('typeahead:change', function(element, datum, name) { + var value = valueAccessor(); + value(datum); + }); + }, + + update: function (element, valueAccessor) { + var value = ko.utils.unwrapObservable(valueAccessor()); + if (value) { + $(element).typeahead('val', value); + } + } + }; } function getContactDisplayName(contact) @@ -556,7 +584,7 @@ function formatAddress(city, state, zip, swap) { str += zip ? zip + ' ' : ''; str += city ? city : ''; str += (city && state) ? ', ' : (city ? ' ' : ''); - str += state; + str += state; } else { str += city ? city : ''; str += (city && state) ? ', ' : (state ? ' ' : ''); @@ -590,7 +618,7 @@ function calculateAmounts(invoice) { var hasTaxes = false; var taxes = {}; invoice.has_product_key = false; - + // Bold designs currently breaks w/o the product column if (invoice.invoice_design_id == 2) { invoice.has_product_key = true; @@ -638,7 +666,7 @@ function calculateAmounts(invoice) { lineTotal -= roundToTwo(lineTotal * (invoice.discount/100)); } } - + var taxAmount1 = roundToTwo(lineTotal * taxRate1 / 100); if (taxAmount1) { var key = taxName1 + taxRate1; @@ -715,7 +743,7 @@ function calculateAmounts(invoice) { invoice.tax_amount1 = taxAmount1; invoice.tax_amount2 = taxAmount2; invoice.item_taxes = taxes; - + if (NINJA.parseFloat(invoice.partial)) { invoice.balance_amount = roundToTwo(invoice.partial); } else { @@ -1040,7 +1068,7 @@ function truncate(string, length){ } }; -// Show/hide the 'Select' option in the datalists +// Show/hide the 'Select' option in the datalists function actionListHandler() { $('tbody tr .tr-action').closest('tr').mouseover(function() { $(this).closest('tr').find('.tr-action').show(); @@ -1106,8 +1134,8 @@ function searchData(data, key, fuzzy) { } cb(matches); } -}; +}; function escapeRegExp(str) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); -} \ No newline at end of file +} diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 0d872b9eea45..24ebd5915e76 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -2009,7 +2009,8 @@ $LANG = array( 'vendor_contacts' => 'Vendor Contacts', 'all' => 'All', 'selected' => 'Selected', - + 'category' => 'Category', + ); return $LANG; diff --git a/resources/views/expenses/edit.blade.php b/resources/views/expenses/edit.blade.php index 39f2ce2243e9..c80716fb3ab1 100644 --- a/resources/views/expenses/edit.blade.php +++ b/resources/views/expenses/edit.blade.php @@ -26,6 +26,7 @@ @if ($expense) {!! Former::populate($expense) !!} {!! Former::populateField('should_be_invoiced', intval($expense->should_be_invoiced)) !!} + {!! Former::populateField('category', $expense->expense_category ? $expense->expense_category->name : '') !!} {!! Former::hidden('public_id') !!} @endif @@ -33,11 +34,16 @@