mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-09 20:52:56 +01:00
Allow tasks to be associated with products, and pass that data through to the invoice. (#3205)
This commit is contained in:
parent
7c8d9f5c24
commit
1d1c650648
@ -7,6 +7,7 @@ use App\Http\Requests\TaskRequest;
|
||||
use App\Http\Requests\UpdateTaskRequest;
|
||||
use App\Models\Client;
|
||||
use App\Models\Project;
|
||||
use App\Models\Product;
|
||||
use App\Models\Task;
|
||||
use App\Models\TaskStatus;
|
||||
use App\Ninja\Datatables\TaskDatatable;
|
||||
@ -128,6 +129,7 @@ class TaskController extends BaseController
|
||||
'task' => null,
|
||||
'clientPublicId' => Input::old('client') ? Input::old('client') : ($request->client_id ?: 0),
|
||||
'projectPublicId' => Input::old('project_id') ? Input::old('project_id') : ($request->project_id ?: 0),
|
||||
'productPublicId' => Input::old('product_id') ? Input::old('product_id') : ($request->product_id ?: 0),
|
||||
'method' => 'POST',
|
||||
'url' => 'tasks',
|
||||
'title' => trans('texts.new_task'),
|
||||
@ -205,6 +207,7 @@ class TaskController extends BaseController
|
||||
'entity' => $task,
|
||||
'clientPublicId' => $task->client ? $task->client->public_id : 0,
|
||||
'projectPublicId' => $task->project ? $task->project->public_id : 0,
|
||||
'productPublicId' => $task->product ? $task->product->public_id : 0,
|
||||
'method' => $method,
|
||||
'url' => $url,
|
||||
'title' => trans('texts.edit_task'),
|
||||
@ -241,6 +244,7 @@ class TaskController extends BaseController
|
||||
'clients' => Client::scope()->withActiveOrSelected($task ? $task->client_id : false)->with('contacts')->orderBy('name')->get(),
|
||||
'account' => Auth::user()->account,
|
||||
'projects' => Project::scope()->withActiveOrSelected($task ? $task->project_id : false)->with('client.contacts')->orderBy('name')->get(),
|
||||
'products' => Product::scope()->withActiveOrSelected($task ? $task->product_id : false)->orderBy('product_key')->get(),
|
||||
];
|
||||
}
|
||||
|
||||
@ -330,12 +334,20 @@ class TaskController extends BaseController
|
||||
|
||||
$account = Auth::user()->account;
|
||||
$showProject = $lastProjectId != $task->project_id;
|
||||
$data[] = [
|
||||
$item_data = [
|
||||
'publicId' => $task->public_id,
|
||||
'description' => $task->present()->invoiceDescription($account, $showProject),
|
||||
'duration' => $task->getHours(),
|
||||
'cost' => $task->getRate(),
|
||||
'productKey' => null,
|
||||
];
|
||||
|
||||
if (!empty($task->product_id)) {
|
||||
$item_data['productKey'] = $task->product->product_key;
|
||||
}
|
||||
|
||||
$data[] = $item_data;
|
||||
|
||||
$lastProjectId = $task->project_id;
|
||||
}
|
||||
|
||||
|
@ -81,6 +81,14 @@ class Task extends EntityModel
|
||||
return $this->belongsTo('App\Models\Project')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function product()
|
||||
{
|
||||
return $this->belongsTo('App\Models\Product')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
@ -180,7 +188,9 @@ class Task extends EntityModel
|
||||
{
|
||||
$value = 0;
|
||||
|
||||
if ($this->project && floatval($this->project->task_rate)) {
|
||||
if ($this->product && $this->product->cost) {
|
||||
$value = $this->product->cost;
|
||||
} elseif ($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;
|
||||
|
@ -4,6 +4,7 @@ namespace App\Ninja\Repositories;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Project;
|
||||
use App\Models\Product;
|
||||
use App\Models\Task;
|
||||
use App\Models\TaskStatus;
|
||||
use Auth;
|
||||
@ -25,6 +26,7 @@ class TaskRepository extends BaseRepository
|
||||
->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->leftJoin('invoices', 'invoices.id', '=', 'tasks.invoice_id')
|
||||
->leftJoin('projects', 'projects.id', '=', 'tasks.project_id')
|
||||
->leftJoin('products', 'products.id', '=', 'tasks.product_id')
|
||||
->leftJoin('task_statuses', 'task_statuses.id', '=', 'tasks.task_status_id')
|
||||
->where('tasks.account_id', '=', Auth::user()->account_id)
|
||||
->where(function ($query) { // handle when client isn't set
|
||||
@ -173,6 +175,11 @@ class TaskRepository extends BaseRepository
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($data['product_id'])) {
|
||||
$product = Product::scope($data['product_id'])->firstOrFail();
|
||||
$task->product_id = $product->id;
|
||||
}
|
||||
|
||||
if (isset($data['description'])) {
|
||||
$task->description = trim($data['description']);
|
||||
}
|
||||
|
32
database/migrations/2020_01_11_215020_add_task_products.php
Normal file
32
database/migrations/2020_01_11_215020_add_task_products.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddTaskProducts extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('tasks', function ($table) {
|
||||
$table->integer('product_id')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('tasks', function ($table) {
|
||||
$table->dropColumn('product_id');
|
||||
});
|
||||
}
|
||||
}
|
@ -936,7 +936,8 @@
|
||||
item.notes(task.description);
|
||||
item.qty(task.duration);
|
||||
item.cost(task.cost);
|
||||
item.task_public_id(task.publicId);
|
||||
item.task_public_id(task.publicId);
|
||||
item.product_key(task.productKey);
|
||||
}
|
||||
model.invoice().has_tasks(true);
|
||||
NINJA.formIsChanged = true;
|
||||
|
@ -58,12 +58,23 @@
|
||||
->label('project')
|
||||
->value($task->present()->project) !!}
|
||||
@endif
|
||||
|
||||
@if ($task->product)
|
||||
{!! Former::plaintext()
|
||||
->label('product')
|
||||
->value($task->present()->product) !!}
|
||||
@endif
|
||||
@else
|
||||
{!! Former::select('client')->addOption('', '')->addGroupClass('client-select') !!}
|
||||
{!! Former::select('project_id')
|
||||
->addOption('', '')
|
||||
->addGroupClass('project-select')
|
||||
->label(trans('texts.project')) !!}
|
||||
|
||||
{!! Former::select('product_id')
|
||||
->addOption('', '')
|
||||
->addGroupClass('product-select')
|
||||
->label(trans('texts.product')) !!}
|
||||
@endif
|
||||
|
||||
@include('partials/custom_fields', ['entityType' => ENTITY_TASK])
|
||||
@ -240,6 +251,7 @@
|
||||
|
||||
var clients = {!! $clients !!};
|
||||
var projects = {!! $projects !!};
|
||||
var products = {!! $products !!};
|
||||
|
||||
var timeLabels = {};
|
||||
@foreach (['hour', 'minute', 'second'] as $period)
|
||||
@ -552,9 +564,11 @@
|
||||
// setup clients and project comboboxes
|
||||
var clientId = {{ $clientPublicId }};
|
||||
var projectId = {{ $projectPublicId }};
|
||||
var productId = {{ $productPublicId }};
|
||||
|
||||
var clientMap = {};
|
||||
var projectMap = {};
|
||||
var productMap = {};
|
||||
var projectsForClientMap = {};
|
||||
var projectsForAllClients = [];
|
||||
var $clientSelect = $('select#client');
|
||||
@ -638,7 +652,33 @@
|
||||
}
|
||||
});
|
||||
|
||||
var $productSelect = $('select#product_id').on('change', function(e) {
|
||||
var productId = $('input[name=product_id]').val();
|
||||
if (productId == '-1') {
|
||||
$('input[name=product_name]').val('');
|
||||
}
|
||||
|
||||
$('select#project_id').combobox('refresh');
|
||||
});
|
||||
|
||||
$productSelect.append(new Option('', ''));
|
||||
|
||||
for (var i=0; i<products.length; i++) {
|
||||
var product = products[i];
|
||||
var productName = product.product_key;
|
||||
|
||||
productMap[product.public_id] = product;
|
||||
|
||||
if (!productName) {
|
||||
continue;
|
||||
}
|
||||
$productSelect.append(new Option(productName, product.public_id));
|
||||
}
|
||||
|
||||
$productSelect.trigger('change');
|
||||
|
||||
@include('partials/entity_combobox', ['entityType' => ENTITY_PROJECT])
|
||||
@include('partials/entity_combobox', ['entityType' => ENTITY_PRODUCT])
|
||||
|
||||
if (projectId) {
|
||||
var project = projectMap[projectId];
|
||||
@ -650,6 +690,14 @@
|
||||
$clientSelect.trigger('change');
|
||||
}
|
||||
|
||||
if (productId) {
|
||||
var product = productMap[productId];
|
||||
if (product) {
|
||||
setComboboxValue($('.product-select'), product.public_id, product.product_key);
|
||||
$productSelect.trigger('change');
|
||||
}
|
||||
}
|
||||
|
||||
@if (!$task)
|
||||
var taskType = localStorage.getItem('last:task_type');
|
||||
if (taskType) {
|
||||
|
Loading…
Reference in New Issue
Block a user