mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Inovice tasks lockijng
This commit is contained in:
parent
b1fea68fc7
commit
85c0dbe0e4
@ -182,7 +182,7 @@ class Handler extends ExceptionHandler
|
||||
} elseif ($exception instanceof FatalThrowableError && $request->expectsJson()) {
|
||||
return response()->json(['message'=>'Fatal error'], 500);
|
||||
} elseif ($exception instanceof AuthorizationException) {
|
||||
return response()->json(['message'=>'You are not authorized to view or perform this action'], 401);
|
||||
return response()->json(['message'=> $exception->getMessage()], 401);
|
||||
} elseif ($exception instanceof TokenMismatchException) {
|
||||
return redirect()
|
||||
->back()
|
||||
|
@ -15,6 +15,7 @@ use App\Http\Requests\Request;
|
||||
use App\Models\Project;
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateTaskRequest extends Request
|
||||
@ -29,6 +30,10 @@ class UpdateTaskRequest extends Request
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
//prevent locked tasks from updating
|
||||
if($this->task->invoice_lock && $this->task->invoice_id)
|
||||
return false;
|
||||
|
||||
return auth()->user()->can('edit', $this->task);
|
||||
}
|
||||
|
||||
@ -87,4 +92,11 @@ class UpdateTaskRequest extends Request
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
|
||||
protected function failedAuthorization()
|
||||
{
|
||||
throw new AuthorizationException(ctrans('texts.task_update_authorization_error'));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -123,6 +123,7 @@ class Company extends BaseModel
|
||||
'enabled_expense_tax_rates',
|
||||
'invoice_task_project',
|
||||
'report_include_deleted',
|
||||
'invoice_task_lock',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
|
@ -40,6 +40,7 @@ class Task extends BaseModel
|
||||
'number',
|
||||
'is_date_based',
|
||||
'status_order',
|
||||
'invoice_lock'
|
||||
];
|
||||
|
||||
protected $touches = [];
|
||||
|
@ -186,6 +186,7 @@ class CompanyTransformer extends EntityTransformer
|
||||
'enabled_expense_tax_rates' => (int) $company->enabled_expense_tax_rates,
|
||||
'invoice_task_project' => (bool) $company->invoice_task_project,
|
||||
'report_include_deleted' => (bool) $company->report_include_deleted,
|
||||
'invoice_task_lock' => (bool) $company->invoice_task_lock,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,6 @@ class TaskTransformer extends EntityTransformer
|
||||
'user_id' => (string) $this->encodePrimaryKey($task->user_id),
|
||||
'assigned_user_id' => (string) $this->encodePrimaryKey($task->assigned_user_id),
|
||||
'number' => (string) $task->number ?: '',
|
||||
// 'start_time' => (int) $task->start_time,
|
||||
'description' => (string) $task->description ?: '',
|
||||
'duration' => (int) $task->duration ?: 0,
|
||||
'rate' => (float) $task->rate ?: 0,
|
||||
@ -93,6 +92,7 @@ class TaskTransformer extends EntityTransformer
|
||||
'status_sort_order' => (int) $task->status_sort_order, //deprecated 5.0.34
|
||||
'is_date_based' => (bool) $task->is_date_based,
|
||||
'status_order' => is_null($task->status_order) ? null : (int) $task->status_order,
|
||||
'invoice_lock' => (bool) $task->invoice_lock,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('tasks', function (Blueprint $table)
|
||||
{
|
||||
$table->boolean('invoice_lock')->default(false);
|
||||
});
|
||||
|
||||
Schema::table('companies', function (Blueprint $table)
|
||||
{
|
||||
$table->boolean('invoice_task_lock')->default(false);
|
||||
});
|
||||
|
||||
Schema::table('bank_transactions', function (Blueprint $table)
|
||||
{
|
||||
$table->bigInteger('bank_rule_id')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
@ -4843,6 +4843,11 @@ $LANG = array(
|
||||
'refresh_accounts' => 'Refresh Accounts',
|
||||
'upgrade_to_connect_bank_account' => 'Upgrade to Enterprise to connect your bank account',
|
||||
'click_here_to_connect_bank_account' => 'Click here to connect your bank account',
|
||||
'task_update_authorization_error' => 'Insufficient permissions, or task may be locked',
|
||||
'cash_vs_accrual' => 'Accrual accounting',
|
||||
'cash_vs_accrual_help' => 'Turn on for accrual reporting, turn off for cash basis reporting.',
|
||||
'expense_paid_report' => 'Expensed reporting',
|
||||
'expense_paid_report_help' => 'Turn on for reporting all expense, turn off for reporting only paid expenses',
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Task;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
@ -42,6 +43,90 @@ class TaskApiTest extends TestCase
|
||||
Model::reguard();
|
||||
}
|
||||
|
||||
|
||||
public function testTaskLockingGate()
|
||||
{
|
||||
$data = [
|
||||
'timelog' => [[1,2],[3,4]],
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/tasks', $data);
|
||||
|
||||
$arr = $response->json();
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/tasks/' . $arr['data']['id'], $data);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$task = Task::find($this->decodePrimaryKey($arr['data']['id']));
|
||||
$task->invoice_id = $this->invoice->id;
|
||||
$task->save();
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/tasks/' . $arr['data']['id'], $data);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$task = Task::find($this->decodePrimaryKey($arr['data']['id']));
|
||||
$task->invoice_lock =true;
|
||||
$task->invoice_id = $this->invoice->id;
|
||||
$task->save();
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/tasks/' . $arr['data']['id'], $data);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$response->assertStatus(401);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testTaskLocking()
|
||||
{
|
||||
$data = [
|
||||
'timelog' => [[1,2],[3,4]],
|
||||
'invoice_lock' => true
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/tasks', $data);
|
||||
|
||||
$arr = $response->json();
|
||||
$response->assertStatus(200);
|
||||
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/tasks/' . $arr['data']['id'], $data);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public function testTimeLogValidation()
|
||||
{
|
||||
$data = [
|
||||
@ -75,9 +160,10 @@ class TaskApiTest extends TestCase
|
||||
$arr = $response->json();
|
||||
$response->assertStatus(200);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function testTimeLogValidation2()
|
||||
{
|
||||
$data = [
|
||||
|
Loading…
x
Reference in New Issue
Block a user