Working on expenses

This commit is contained in:
Hillel Coren 2016-01-21 21:15:30 +02:00
parent 3bd636fedc
commit b24a6109ba
16 changed files with 263 additions and 120 deletions

View File

@ -359,8 +359,6 @@ class AccountController extends BaseController
$client->postal_code = trans('texts.postal_code');
$client->work_phone = trans('texts.work_phone');
$client->work_email = trans('texts.work_id');
$client->id_number = trans('texts.id_number');
$client->vat_number = trans('texts.var_number');
$invoice->invoice_number = '0000';
$invoice->invoice_date = Utils::fromSqlDate(date('Y-m-d'));

View File

@ -104,8 +104,10 @@ class ClientController extends BaseController
}
array_push($actionLinks,
\DropdownButton::DIVIDER,
['label' => trans('texts.enter_payment'), 'url' => '/payments/create/'.$client->public_id],
['label' => trans('texts.enter_credit'), 'url' => '/credits/create/'.$client->public_id]
['label' => trans('texts.enter_credit'), 'url' => '/credits/create/'.$client->public_id],
['label' => trans('texts.enter_expense'), 'url' => '/expenses/create/0/'.$client->public_id]
);
$data = array(

View File

@ -67,7 +67,7 @@ class ExpenseController extends BaseController
return $this->expenseService->getDatatableVendor($vendorPublicId);
}
public function create($vendorPublicId = 0)
public function create($vendorPublicId = null, $clientPublicId = null)
{
if($vendorPublicId != 0) {
$vendor = Vendor::scope($vendorPublicId)->with('vendorcontacts')->firstOrFail();
@ -83,7 +83,7 @@ class ExpenseController extends BaseController
'vendors' => Vendor::scope()->with('vendorcontacts')->orderBy('name')->get(),
'vendor' => $vendor,
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
'clientPublicId' => null,
'clientPublicId' => $clientPublicId,
);
$data = array_merge($data, self::getViewModel());

View File

@ -200,7 +200,7 @@ Route::group(['middleware' => 'auth'], function() {
// Expense
Route::resource('expenses', 'ExpenseController');
Route::get('expenses/create/{vendor_id?}', 'ExpenseController@create');
Route::get('expenses/create/{vendor_id?}/{client_id?}', 'ExpenseController@create');
Route::get('api/expense', array('as'=>'api.expenses', 'uses'=>'ExpenseController@getDatatable'));
Route::get('api/expenseVendor/{id}', array('as'=>'api.expense', 'uses'=>'ExpenseController@getDatatableVendor'));
Route::post('expenses/bulk', 'ExpenseController@bulk');

View File

@ -251,6 +251,11 @@ class Account extends Eloquent
return Utils::formatMoney($amount, $currencyId, $countryId, $hideSymbol);
}
public function getCurrencyId()
{
return $this->currency_id ?: DEFAULT_CURRENCY;
}
public function formatDate($date)
{
$date = $this->getDateTime($date);

View File

@ -37,6 +37,11 @@ class Expense extends EntityModel
return $this->belongsTo('App\Models\Vendor')->withTrashed();
}
public function client()
{
return $this->belongsTo('App\Models\Client')->withTrashed();
}
public function getName()
{
if($this->expense_number)

View File

@ -14,4 +14,11 @@ class AccountPresenter extends Presenter {
{
return Utils::addHttp($this->entity->website);
}
public function currencyCode()
{
$currencyId = $this->entity->getCurrencyId();
$currency = Utils::getFromCache($currencyId, 'currencies');
return $currency->code;
}
}

View File

@ -1,4 +1,4 @@
<?php namespace App\Ninja\Repositories;
<?php namespace app\Ninja\Repositories;
use DB;
use Utils;
@ -30,14 +30,15 @@ class ExpenseRepository extends BaseRepository
$query = DB::table('expenses')
->join('accounts', 'accounts.id', '=', 'expenses.account_id')
->where('expenses.account_id', '=', $accountid)
->where('expenses.vendor_id','=',$vendorPublicId)
->where('expenses.vendor_id', '=', $vendorPublicId)
->select('expenses.id',
'expenses.expense_date',
'expenses.amount',
'expenses.public_notes',
'expenses.public_id',
'expenses.deleted_at','expenses.should_be_invoiced','expenses.created_at');
return $query;
'expenses.deleted_at', 'expenses.should_be_invoiced', 'expenses.created_at');
return $query;
}
public function find($filter = null)
@ -45,11 +46,11 @@ class ExpenseRepository extends BaseRepository
$accountid = \Auth::user()->account_id;
$query = DB::table('expenses')
->join('accounts', 'accounts.id', '=', 'expenses.account_id')
->leftjoin('vendors','vendors.public_id','=', 'expenses.vendor_id')
->leftjoin('vendors', 'vendors.public_id', '=', 'expenses.vendor_id')
->where('expenses.account_id', '=', $accountid)
->select('expenses.account_id',
'expenses.amount',
'expenses.foreign_amount',
'expenses.converted_amount',
'expenses.currency_id',
'expenses.deleted_at',
'expenses.exchange_rate',
@ -94,40 +95,46 @@ class ExpenseRepository extends BaseRepository
$expense->fill($input);
// We can have an expense without a vendor
if(isset($input['vendor'])) {
$expense->vendor_id = $input['vendor'];
if (isset($input['vendor_id'])) {
$expense->vendor_id = $input['vendor_id'];
}
$expense->expense_date = Utils::toSqlDate($input['expense_date']);
$expense->amount = Utils::parseFloat($input['amount']);
if(isset($input['foreign_amount']))
$expense->foreign_amount = Utils::parseFloat($input['foreign_amount']);
if (isset($input['converted_amount'])) {
$expense->converted_amount = Utils::parseFloat($input['converted_amount']);
}
$expense->private_notes = trim($input['private_notes']);
$expense->public_notes = trim($input['public_notes']);
if(isset($input['exchange_rate']))
if (isset($input['exchange_rate'])) {
$expense->exchange_rate = Utils::parseFloat($input['exchange_rate']);
else
} else {
$expense->exchange_rate = 100;
}
if($expense->exchange_rate == 0)
if ($expense->exchange_rate == 0) {
$expense->exchange_rate = 100;
}
// set the currency
if(isset($input['currency_id']))
if (isset($input['currency_id'])) {
$expense->currency_id = $input['currency_id'];
}
if($expense->currency_id == 0)
if ($expense->currency_id == 0) {
$expense->currency_id = Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY);
}
// Calculate the amount cur
$expense->foreign_amount = ($expense->amount / 100) * $expense->exchange_rate;
$expense->converted_amount = ($expense->amount / 100) * $expense->exchange_rate;
$expense->should_be_invoiced = isset($input['should_be_invoiced']) ? true : false;
if(isset($input['client'])) {
$expense->client_id = $input['client'];
$expense->should_be_invoiced = isset($input['should_be_invoiced']) || $expense->client_id ? true : false;
if (isset($input['client_id'])) {
$expense->client_id = $input['client_id'];
}
$expense->save();
@ -156,5 +163,4 @@ class ExpenseRepository extends BaseRepository
return count($tasks);
}
}

View File

@ -125,6 +125,12 @@ class ClientService extends BaseService
function ($model) {
return URL::to("credits/create/{$model->public_id}");
}
],
[
trans('texts.enter_expense'),
function ($model) {
return URL::to("expenses/create/0/{$model->public_id}");
}
]
];
}

View File

@ -79,7 +79,7 @@ class CreateVendorsTable extends Migration
$table->date('expense_date')->nullable();
$table->text('private_notes');
$table->text('public_notes');
$table->integer('currency_id', false, true)->nullable();
$table->unsignedInteger('currency_id')->nullable();
$table->boolean('should_be_invoiced')->default(true);
// Relations
@ -96,10 +96,9 @@ class CreateVendorsTable extends Migration
$table->softDeletes();
$table->unsignedInteger('user_id');
$table->unsignedInteger('account_id');
$table->integer('public_id')->default(0);
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
//$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
//$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->unsignedInteger('public_id')->index();
$table->unique(array('account_id', 'public_id'));
@ -112,7 +111,7 @@ class CreateVendorsTable extends Migration
->get();
$i = 1;
foreach ($paymentTerms as $pTerm) {
$data = ['public_id' => $i];
$data = ['public_id' => $i++];
DB::table('paymet_terms')->where('id', $pTerm->id)->update($data);
}

View File

@ -97,13 +97,13 @@ class ConstantsSeeder extends Seeder
Size::create(array('name' => '101 - 500'));
Size::create(array('name' => '500+'));
PaymentTerm::create(array('num_days' => 7, 'name' => 'Net 7'));
PaymentTerm::create(array('num_days' => 10, 'name' => 'Net 10'));
PaymentTerm::create(array('num_days' => 14, 'name' => 'Net 14'));
PaymentTerm::create(array('num_days' => 15, 'name' => 'Net 15'));
PaymentTerm::create(array('num_days' => 30, 'name' => 'Net 30'));
PaymentTerm::create(array('num_days' => 60, 'name' => 'Net 60'));
PaymentTerm::create(array('num_days' => 90, 'name' => 'Net 90'));
PaymentTerm::create(array('num_days' => 7, 'name' => 'Net 7', 'public_id' => 1));
PaymentTerm::create(array('num_days' => 10, 'name' => 'Net 10', 'public_id' => 2));
PaymentTerm::create(array('num_days' => 14, 'name' => 'Net 14', 'public_id' => 3));
PaymentTerm::create(array('num_days' => 15, 'name' => 'Net 15', 'public_id' => 4));
PaymentTerm::create(array('num_days' => 30, 'name' => 'Net 30', 'public_id' => 5));
PaymentTerm::create(array('num_days' => 60, 'name' => 'Net 60', 'public_id' => 6));
PaymentTerm::create(array('num_days' => 90, 'name' => 'Net 90', 'public_id' => 7));
PaymentLibrary::create(['name' => 'Omnipay']);
PaymentLibrary::create(['name' => 'PHP-Payments [Deprecated]']);

View File

@ -30234,6 +30234,44 @@ if (window.ko) {
}
};
ko.bindingHandlers.combobox = {
init: function (element, valueAccessor, allBindingsAccessor) {
var options = allBindingsAccessor().dropdownOptions|| {};
var value = ko.utils.unwrapObservable(valueAccessor());
var id = (value && value.public_id) ? value.public_id() : (value && value.id) ? value.id() : value ? value : false;
if (id) $(element).val(id);
$(element).combobox(options);
ko.utils.registerEventHandler(element, "change", function () {
var value = valueAccessor();
value($(element).val());
});
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
var id = (value && value.public_id) ? value.public_id() : (value && value.id) ? value.id() : value ? value : false;
if (id) {
$(element).val(id);
$(element).combobox('refresh');
} else {
$(element).combobox('clearTarget');
$(element).combobox('clearElement');
}
}
};
ko.bindingHandlers.fadeVisible = {
init: function(element, valueAccessor) {
// Initially set the element to be instantly visible/hidden depending on the value
var value = valueAccessor();
$(element).toggle(ko.unwrap(value)); // Use "unwrapObservable" so we can handle values that may or may not be observable
},
update: function(element, valueAccessor) {
// Whenever the value subsequently changes, slowly fade the element in or out
var value = valueAccessor();
ko.unwrap(value) ? $(element).fadeIn() : $(element).fadeOut();
}
};
ko.bindingHandlers.datePicker = {
init: function (element, valueAccessor, allBindingsAccessor) {
@ -30298,17 +30336,6 @@ function getClientDisplayName(client)
return '';
}
function getVendorDisplayName(vendor)
{
var contact = vendor.contacts ? vendor.vendorcontacts[0] : false;
if (vendor.name) {
return vendor.name;
} else if (contact) {
return getContactDisplayName(contact);
}
return '';
}
function populateInvoiceComboboxes(clientId, invoiceId) {
var clientMap = {};
var invoiceMap = {};

View File

@ -356,6 +356,31 @@ if (window.ko) {
}
};
ko.bindingHandlers.combobox = {
init: function (element, valueAccessor, allBindingsAccessor) {
var options = allBindingsAccessor().dropdownOptions|| {};
var value = ko.utils.unwrapObservable(valueAccessor());
var id = (value && value.public_id) ? value.public_id() : (value && value.id) ? value.id() : value ? value : false;
if (id) $(element).val(id);
$(element).combobox(options);
ko.utils.registerEventHandler(element, "change", function () {
var value = valueAccessor();
value($(element).val());
});
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
var id = (value && value.public_id) ? value.public_id() : (value && value.id) ? value.id() : value ? value : false;
if (id) {
$(element).val(id);
$(element).combobox('refresh');
} else {
$(element).combobox('clearTarget');
$(element).combobox('clearElement');
}
}
};
ko.bindingHandlers.datePicker = {
init: function (element, valueAccessor, allBindingsAccessor) {

View File

@ -1016,7 +1016,8 @@ return array(
// Expense / vendor
'expense' => 'Expense',
'expenses' => 'Expenses',
'new_expense' => 'Create Expense',
'new_expense' => 'Enter Expense',
'enter_expense' => 'Enter Expense',
'vendors' => 'Vendors',
'new_vendor' => 'Create Vendor',
'payment_terms_net' => 'Net',
@ -1027,6 +1028,8 @@ return array(
'view_vendor' => 'View Vendor',
'deleted_expense' => 'Successfully deleted expense',
'archived_expense' => 'Successfully archived expense',
'deleted_expenses' => 'Successfully deleted expenses',
'archived_expenses' => 'Successfully archived expenses',
// Expenses
'expense_amount' => 'Expense Amount',
@ -1034,7 +1037,7 @@ return array(
'expense_date' => 'Expense Date',
'expense_should_be_invoiced' => 'Should this expense be invoiced?',
'public_notes' => 'Public Notes',
'foreign_amount' => 'Foreign Amount',
'converted_amount' => 'Converted Amount',
'exchange_rate' => 'Exchange Rate',
'yes' => 'Yes',
'no' => 'No',
@ -1053,6 +1056,7 @@ return array(
'expense_error_multiple_clients' =>'The expenses can\'t belong to different clients',
'expense_error_invoiced' => 'Expense have already been invoiced',
'expense_error_should_not_be_invoiced' => 'Expense maked not to be invoiced',
'convert_currency' => 'Convert currency',
// Payment terms
'num_days' => 'Number of days',

View File

@ -166,10 +166,11 @@
$('#testModal').on('shown.bs.modal', function() {
$('#bank_password').focus();
});
$('#bank_id').focus();
});
// Here's my data model
var ViewModel = function() {
var self = this;
self.bank_id = ko.observable({{ $bankAccount ? $bankAccount->bank_id : 0 }});
@ -186,8 +187,6 @@
self.disableDoTest = ko.computed(function() {
return !self.bank_id() || !self.bank_username() || !self.bank_password();
}, self);
$('#bank_id').focus();
};
window.model = new ViewModel();

View File

@ -3,14 +3,7 @@
@section('head')
@parent
<style type="text/css">
.input-group-addon div.checkbox {
display: inline;
}
div.client-select > div > div > span.input-group-addon {
padding-right: 30px;
}
</style>
@include('money_script')
@stop
@section('content')
@ -27,31 +20,58 @@
<div class="panel-body">
<div class="row">
<div class="col-md-6">
{!! Former::select('vendor')->addOption('', '')
{!! Former::select('vendor_id')->addOption('', '')
->data_bind('combobox: vendor_id')
->label(trans('texts.vendor'))
->addGroupClass('vendor-select') !!}
{!! Former::select('client')
->addOption('', '')
->addGroupClass('client-select')
->append(Former::checkbox('should_be_invoiced')->raw() .
trans('texts.invoice')) !!}
{!! Former::text('amount')
->label(trans('texts.amount'))
->addGroupClass('amount')
->append($account->present()->currencyCode) !!}
{!! Former::text('expense_date')
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))
->addGroupClass('expense_date')->label(trans('texts.expense_date'))
->addGroupClass('expense_date')
->label(trans('texts.date'))
->append('<i class="glyphicon glyphicon-calendar"></i>') !!}
{!! Former::select('currency_id')->addOption('','')
->placeholder($account->currency ? $account->currency->name : '')
->fromQuery($currencies, 'name', 'id') !!}
{!! Former::text('amount')->label(trans('texts.expense_amount')) !!}
<!--
{!! Former::text('foreign_amount') !!}
{!! Former::text('exchange_rate') !!}
-->
{!! Former::select('client_id')
->addOption('', '')
->label(trans('texts.client'))
->data_bind('combobox: client_id')
->addGroupClass('client-select') !!}
{!! Former::checkbox('should_be_invoiced')
->text(trans('texts.should_be_invoiced'))
->data_bind('checked: should_be_invoiced() || client_id(), enable: !client_id()')
->label(' ') !!}<br/>
<span style="display:none" data-bind="visible: !client_id()">
{!! Former::select('currency_id')->addOption('','')
->data_bind('combobox: currency_id, disable: true')
->fromQuery($currencies, 'name', 'id') !!}
</span>
<span style="display:none;" data-bind="visible: client_id">
{!! Former::plaintext('test')
->value('<span data-bind="html: currencyName"></span>')
->style('min-height:46px')
->label(trans('texts.currency_id')) !!}
</span>
{!! Former::text('exchange_rate')
->data_bind('enable: enableExchangeRate') !!}
{!! Former::text('converted_amount')
->addGroupClass('converted-amount')
->data_bind('enable: enableExchangeRate')
->append('<span data-bind="html: currencyCode"></span>') !!}
</div>
<div class="col-md-6">
{!! Former::textarea('public_notes')->rows(5) !!}
{!! Former::textarea('private_notes')->rows(5) !!}
{!! Former::textarea('public_notes')->rows(9) !!}
{!! Former::textarea('private_notes')->rows(9) !!}
</div>
</div>
</div>
@ -64,52 +84,92 @@
{!! Former::close() !!}
<script type="text/javascript">
<script type="text/javascript">
var vendors = {!! $vendors !!};
var clients = {!! $clients !!};
var vendors = {!! $vendors !!};
var clients = {!! $clients !!};
$(function() {
var $vendorSelect = $('select#vendor');
for (var i = 0; i < vendors.length; i++) {
var vendor = vendors[i];
$vendorSelect.append(new Option(getVendorDisplayName(vendor), vendor.public_id));
}
if ({{ $vendorPublicId ? 'true' : 'false' }}) {
$vendorSelect.val({{ $vendorPublicId }});
}
$vendorSelect.combobox();
$('#currency_id').combobox();
$('#expense_date').datepicker('update', new Date());
@if (!$vendorPublicId)
$('.vendor-select input.form-control').focus();
@else
$('#amount').focus();
@endif
$('.expense_date .input-group-addon').click(function() {
toggleDatePicker('expense_date');
});
var $clientSelect = $('select#client');
var clientMap = {};
for (var i=0; i<clients.length; i++) {
var client = clients[i];
$clientSelect.append(new Option(getClientDisplayName(client), client.public_id));
clientMap[client.public_id] = client;
}
if ({{ $clientPublicId ? 'true' : 'false' }}) {
$clientSelect.val({{ $clientPublicId }});
function onClientChange() {
var clientId = $('select#client_id').val();
var client = clientMap[clientId];
if (client) {
model.currency_id(client.currency_id);
}
}
$clientSelect.combobox();
});
$(function() {
var $vendorSelect = $('select#vendor_id');
for (var i = 0; i < vendors.length; i++) {
var vendor = vendors[i];
$vendorSelect.append(new Option(getClientDisplayName(vendor), vendor.public_id));
}
$vendorSelect.combobox();
$('#expense_date').datepicker('update', new Date());
$('.expense_date .input-group-addon').click(function() {
toggleDatePicker('expense_date');
});
var $clientSelect = $('select#client_id');
for (var i=0; i<clients.length; i++) {
var client = clients[i];
$clientSelect.append(new Option(getClientDisplayName(client), client.public_id));
}
$clientSelect.combobox().change(function() {
onClientChange();
});
window.model = new ViewModel();
ko.applyBindings(model);
@if (!$expense && $clientPublicId)
onClientChange();
@endif
@if (!$vendorPublicId)
$('.vendor-select input.form-control').focus();
@else
$('#amount').focus();
@endif
});
var ViewModel = function() {
var self = this;
self.client_id = ko.observable({{ $clientPublicId }});
self.vendor_id = ko.observable({{ $vendorPublicId }});
self.currency_id = ko.observable({{ $expense && $expense->currency_id ? $expense->currency_id : null }});
self.should_be_invoiced = ko.observable({{ $expense && ($expense->should_be_invoiced || $expense->client_id) ? 'true' : 'false' }});
self.account_currency_id = ko.observable({{ $account->getCurrencyId() }});
self.currencyCode = ko.computed(function() {
var currencyId = self.currency_id() || self.account_currency_id();
var currency = currencyMap[currencyId];
return currency.code;
});
self.currencyName = ko.computed(function() {
var currencyId = self.currency_id() || self.account_currency_id();
var currency = currencyMap[currencyId];
return currency.name;
});
self.enableExchangeRate = ko.computed(function() {
if (!self.currency_id()) {
return false;
}
return self.currency_id() != self.account_currency_id();
})
};
</script>
</script>
@stop