diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php index 16dc60adad5a..4091667352e4 100644 --- a/app/Http/Controllers/TaskController.php +++ b/app/Http/Controllers/TaskController.php @@ -262,7 +262,7 @@ class TaskController extends BaseController $this->taskRepo->save($ids, ['action' => $action]); return Redirect::to('tasks')->withMessage(trans($action == 'stop' ? 'texts.stopped_task' : 'texts.resumed_task')); } elseif ($action == 'invoice' || $action == 'add_to_invoice') { - $tasks = Task::scope($ids)->with('client')->orderBy('project_id', 'id')->get(); + $tasks = Task::scope($ids)->with('account', 'client', 'project')->orderBy('project_id', 'id')->get(); $clientPublicId = false; $data = []; @@ -294,6 +294,7 @@ class TaskController extends BaseController 'publicId' => $task->public_id, 'description' => $task->present()->invoiceDescription($account, $showProject), 'duration' => $task->getHours(), + 'cost' => $task->getRate(), ]; $lastProjectId = $task->project_id; } diff --git a/app/Models/Account.php b/app/Models/Account.php index 2f28c19a3d59..db5ec2968a81 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -176,6 +176,7 @@ class Account extends Eloquent 'credit_number_counter', 'credit_number_prefix', 'credit_number_pattern', + 'task_rate', ]; /** diff --git a/app/Models/Client.php b/app/Models/Client.php index 4ff71cee43f0..3213e3d17026 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -52,6 +52,7 @@ class Client extends EntityModel 'invoice_number_counter', 'quote_number_counter', 'public_notes', + 'task_rate', ]; diff --git a/app/Models/Project.php b/app/Models/Project.php index 834aef98b00e..72bd1d10c1b7 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -24,6 +24,7 @@ class Project extends EntityModel */ protected $fillable = [ 'name', + 'task_rate', ]; /** diff --git a/app/Models/Task.php b/app/Models/Task.php index 139627f46a24..ddf7dc67ff7e 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -145,6 +145,24 @@ class Task extends EntityModel return self::calcDuration($this); } + /** + * @return float + */ + public function getRate() + { + $value = 0; + + if ($this->project && floatval($this->project->task_rate)) { + $value = $this->project->task_rate; + } elseif ($this->client && floatval($this->client->task_rate)) { + $value = $this->client->task_rate; + } else { + $value = $this->account->task_rate; + } + + return Utils::roundSignificant($value); + } + /** * @return int */ diff --git a/app/Ninja/Datatables/ProjectDatatable.php b/app/Ninja/Datatables/ProjectDatatable.php index 2235481ad83e..6165028e45aa 100644 --- a/app/Ninja/Datatables/ProjectDatatable.php +++ b/app/Ninja/Datatables/ProjectDatatable.php @@ -38,6 +38,12 @@ class ProjectDatatable extends EntityDatatable } }, ], + [ + 'task_rate', + function ($model) { + return floatval($model->task_rate) ? Utils::roundSignificant($model->task_rate) : ''; + } + ], ]; } diff --git a/app/Ninja/Repositories/ProjectRepository.php b/app/Ninja/Repositories/ProjectRepository.php index 8e1bedb5b14e..8aa704efca71 100644 --- a/app/Ninja/Repositories/ProjectRepository.php +++ b/app/Ninja/Repositories/ProjectRepository.php @@ -35,6 +35,7 @@ class ProjectRepository extends BaseRepository 'projects.public_id', 'projects.user_id', 'projects.deleted_at', + 'projects.task_rate', 'projects.is_deleted', DB::raw("COALESCE(NULLIF(clients.name,''), NULLIF(CONCAT(contacts.first_name, ' ', contacts.last_name),''), NULLIF(contacts.email,'')) client_name"), 'clients.user_id as client_user_id', diff --git a/app/Ninja/Transformers/AccountTransformer.php b/app/Ninja/Transformers/AccountTransformer.php index f34f466705c4..dbf53acf9485 100644 --- a/app/Ninja/Transformers/AccountTransformer.php +++ b/app/Ninja/Transformers/AccountTransformer.php @@ -274,6 +274,7 @@ class AccountTransformer extends EntityTransformer 'reset_counter_date' => $account->reset_counter_date, 'custom_contact_label1' => $account->custom_contact_label1, 'custom_contact_label2' => $account->custom_contact_label2, + 'task_rate' => (float) $account->task_rate, ]; } } diff --git a/app/Ninja/Transformers/ClientTransformer.php b/app/Ninja/Transformers/ClientTransformer.php index 6e1d6a23f662..e6814a287f5e 100644 --- a/app/Ninja/Transformers/ClientTransformer.php +++ b/app/Ninja/Transformers/ClientTransformer.php @@ -37,6 +37,7 @@ class ClientTransformer extends EntityTransformer * @SWG\Property(property="vat_number", type="string", example="123456") * @SWG\Property(property="id_number", type="string", example="123456") * @SWG\Property(property="language_id", type="integer", example=1) + * @SWG\Property(property="task_rate", type="number", format="float", example=10) */ protected $defaultIncludes = [ 'contacts', @@ -135,6 +136,7 @@ class ClientTransformer extends EntityTransformer 'custom_value2' => $client->custom_value2, 'invoice_number_counter' => (int) $client->invoice_number_counter, 'quote_number_counter' => (int) $client->quote_number_counter, + 'task_rate' => (float) $client->task_rate, ]); } } diff --git a/app/Ninja/Transformers/ProjectTransformer.php b/app/Ninja/Transformers/ProjectTransformer.php index 6e6dbcb2bb84..dbff6390a40a 100644 --- a/app/Ninja/Transformers/ProjectTransformer.php +++ b/app/Ninja/Transformers/ProjectTransformer.php @@ -16,6 +16,7 @@ class ProjectTransformer extends EntityTransformer * @SWG\Property(property="updated_at", type="integer", example=1451160233, readOnly=true) * @SWG\Property(property="archived_at", type="integer", example=1451160233, readOnly=true) * @SWG\Property(property="is_deleted", type="boolean", example=false, readOnly=true) + * @SWG\Property(property="task_rate", type="number", format="float", example=10) */ public function transform(Project $project) { @@ -26,6 +27,7 @@ class ProjectTransformer extends EntityTransformer 'updated_at' => $this->getTimestamp($project->updated_at), 'archived_at' => $this->getTimestamp($project->deleted_at), 'is_deleted' => (bool) $project->is_deleted, + 'task_rate' => (float) $project->task_rate, ]); } } diff --git a/database/migrations/2017_10_17_083846_add_default_rates.php b/database/migrations/2017_10_17_083846_add_default_rates.php new file mode 100644 index 000000000000..fbc3d4dffc53 --- /dev/null +++ b/database/migrations/2017_10_17_083846_add_default_rates.php @@ -0,0 +1,47 @@ +decimal('task_rate', 12, 4); + }); + + Schema::table('clients', function ($table) { + $table->decimal('task_rate', 12, 4); + }); + + Schema::table('projects', function ($table) { + $table->decimal('task_rate', 12, 4); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('accounts', function ($table) { + $table->dropColumn('task_rate'); + }); + + Schema::table('clients', function ($table) { + $table->dropColumn('task_rate'); + }); + + Schema::table('projects', function ($table) { + $table->dropColumn('task_rate'); + }); + } +} diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 608a395b54b5..cf975ebd7c36 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -2483,6 +2483,9 @@ $LANG = array( 'clear' => 'Clear', 'warn_payment_gateway' => 'Note: to accept online payments :link to add a payment gateway.', 'setup_desktop_app' => 'Setup the desktop app', + 'task_rate' => 'Task Rate', + 'task_rate_help' => 'Set the default rate for invoiced tasks.', + ); return $LANG; diff --git a/resources/views/accounts/details.blade.php b/resources/views/accounts/details.blade.php index 30010c9c8ff8..44caa554b966 100644 --- a/resources/views/accounts/details.blade.php +++ b/resources/views/accounts/details.blade.php @@ -19,6 +19,7 @@ ]) !!} {{ Former::populate($account) }} + {{ Former::populateField('task_rate', floatval($account->task_rate) ? Utils::roundSignificant($account->task_rate) : '') }} @include('accounts.nav', ['selected' => ACCOUNT_COMPANY_DETAILS]) @@ -99,6 +100,11 @@ ->fromQuery(\App\Models\PaymentTerm::getSelectOptions(), 'name', 'num_days') ->help(trans('texts.payment_terms_help') . ' | ' . link_to('/settings/payment_terms', trans('texts.customize_options'))) !!} + @if ($account->isModuleEnabled(ENTITY_TASK)) + {!! Former::text('task_rate') + ->help('task_rate_help')!!} + @endif + diff --git a/resources/views/clients/edit.blade.php b/resources/views/clients/edit.blade.php index ad05536cf3c3..289793590564 100644 --- a/resources/views/clients/edit.blade.php +++ b/resources/views/clients/edit.blade.php @@ -23,6 +23,7 @@ @if ($client) {!! Former::populate($client) !!} + {!! Former::populateField('task_rate', floatval($client->task_rate) ? Utils::roundSignificant($client->task_rate) : '') !!} {!! Former::hidden('public_id') !!} @else {!! Former::populateField('invoice_number_counter', 1) !!} @@ -155,6 +156,10 @@ ->fromQuery(\App\Models\PaymentTerm::getSelectOptions(), 'name', 'num_days') ->placeholder($account->present()->paymentTerms) ->help(trans('texts.payment_terms_help')) !!} + @if ($account->isModuleEnabled(ENTITY_TASK)) + {!! Former::text('task_rate') + ->help('task_rate_help') !!} + @endif {!! Former::select('size_id')->addOption('','') ->fromQuery($sizes, 'name', 'id') !!} {!! Former::select('industry_id')->addOption('','') diff --git a/resources/views/clients/show.blade.php b/resources/views/clients/show.blade.php index 29957aa5b68b..e26731d2bfac 100644 --- a/resources/views/clients/show.blade.php +++ b/resources/views/clients/show.blade.php @@ -111,6 +111,8 @@ {{ $client->country->name }}
@endif +
+ @if ($client->account->custom_client_label1 && $client->custom_value1) {{ $client->account->custom_client_label1 . ': ' . $client->custom_value1 }}
@endif @@ -122,6 +124,10 @@ {{ $client->work_phone }} @endif + @if (floatval($client->task_rate)) +

{{ trans('texts.task_rate') }}: {{ Utils::roundSignificant($client->task_rate) }}

+ @endif + @if ($client->public_notes)

{{ $client->public_notes }}

@endif diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index f96c1bf9321c..ffff300bba08 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -893,6 +893,7 @@ var item = model.invoice().addItem(true); item.notes(task.description); item.qty(task.duration); + item.cost(task.cost); item.task_public_id(task.publicId); } model.invoice().has_tasks(true); diff --git a/resources/views/invoices/knockout.blade.php b/resources/views/invoices/knockout.blade.php index 2fe3bf6b3202..95eeeed13e2f 100644 --- a/resources/views/invoices/knockout.blade.php +++ b/resources/views/invoices/knockout.blade.php @@ -1013,7 +1013,9 @@ ko.bindingHandlers.productTypeahead = { model.notes(datum.notes); } if (datum.cost) { - model.cost(roundSignificant(datum.cost, 2)); + if (! model.cost() || ! model.task_public_id()) { + model.cost(roundSignificant(datum.cost, 2)); + } } if (!model.qty()) { model.qty(1); diff --git a/resources/views/projects/edit.blade.php b/resources/views/projects/edit.blade.php index e30b119ca0f7..9407388a0894 100644 --- a/resources/views/projects/edit.blade.php +++ b/resources/views/projects/edit.blade.php @@ -12,6 +12,7 @@ @if ($project) {!! Former::populate($project) !!} + {!! Former::populateField('task_rate', floatval($project->task_rate) ? Utils::roundSignificant($project->task_rate) : '') !!} @endif @@ -39,6 +40,8 @@ {!! Former::text('name') !!} + {!! Former::text('task_rate') + ->help('task_rate_help') !!}