1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-20 08:21:34 +02:00

Expense API Endpoints

This commit is contained in:
David Bomba 2020-09-23 10:46:35 +10:00
parent 14e024cb72
commit 81ef541d2e
11 changed files with 565 additions and 12 deletions

View File

@ -11,7 +11,14 @@
namespace App\Http\Controllers;
use App\Factory\ExpenseFactory;
use App\Filters\ExpenseFilters;
use App\Http\Requests\Expense\CreateExpenseRequest;
use App\Http\Requests\Expense\DestroyExpenseRequest;
use App\Http\Requests\Expense\EditExpenseRequest;
use App\Http\Requests\Expense\ShowExpenseRequest;
use App\Http\Requests\Expense\StoreExpenseRequest;
use App\Http\Requests\Expense\UpdateExpenseRequest;
use App\Jobs\Entity\ActionEntity;
use App\Jobs\Util\ProcessBulk;
use App\Jobs\Util\UploadAvatar;
@ -266,7 +273,7 @@ class ExpenseController extends BaseController
return $request->disallowUpdate();
}
$expense = $this->client_repo->save($request->all(), $expense);
$expense = $this->expense_repo->save($request->all(), $expense);
$this->uploadLogo($request->file('company_logo'), $expense->company, $expense);
@ -359,11 +366,7 @@ class ExpenseController extends BaseController
*/
public function store(StoreExpenseRequest $request)
{
$expense = $this->client_repo->save($request->all(), ExpenseFactory::create(auth()->user()->company()->id, auth()->user()->id));
$expense->load('contacts', 'primary_contact');
$this->uploadLogo($request->file('company_logo'), $expense->company, $expense);
$expense = $this->expense_repo->save($request->all(), ExpenseFactory::create(auth()->user()->company()->id, auth()->user()->id));
return $this->itemResponse($expense);
}
@ -485,7 +488,7 @@ class ExpenseController extends BaseController
$expenses->each(function ($expense, $key) use ($action) {
if (auth()->user()->can('edit', $expense)) {
$this->client_repo->{$action}($expense);
$this->expense_repo->{$action}($expense);
}
});

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Requests\Expense;
use App\Models\Expense;
use App\Utils\Traits\BulkOptions;
use Illuminate\Foundation\Http\FormRequest;
class BulkExpenseRequest extends FormRequest
{
use BulkOptions;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
if (! $this->has('action')) {
return false;
}
if (! in_array($this->action, $this->getBulkOptions(), true)) {
return false;
}
return auth()->user()->can(auth()->user()->isAdmin(), Expense::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = $this->getGlobalRules();
/* We don't require IDs on bulk storing. */
if ($this->action !== self::$STORE_METHOD) {
$rules['ids'] = ['required'];
}
return $rules;
}
}

View File

@ -0,0 +1,28 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Expense;
use App\Http\Requests\Request;
use App\Models\Expense;
class CreateExpenseRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('create', Expense::class);
}
}

View File

@ -0,0 +1,28 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Expense;
use App\Http\Requests\Request;
use App\Models\Expense;
class DestroyExpenseRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->expense);
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Expense;
use App\Http\Requests\Request;
use App\Models\Expense;
class EditExpenseRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->expense);
}
// public function prepareForValidation()
// {
// $input = $this->all();
// //$input['id'] = $this->encodePrimaryKey($input['id']);
// $this->replace($input);
// }
}

View File

@ -0,0 +1,28 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Expense;
use App\Http\Requests\Request;
use App\Models\Expense;
class ShowExpenseRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('view', $this->expense);
}
}

View File

@ -0,0 +1,76 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Expense;
use App\DataMapper\ExpenseSettings;
use App\Http\Requests\Request;
use App\Http\ValidationRules\Expense\UniqueExpenseNumberRule;
use App\Http\ValidationRules\ValidExpenseGroupSettingsRule;
use App\Models\Expense;
use App\Utils\Traits\MakesHash;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Rule;
class StoreExpenseRequest extends Request
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('create', Expense::class);
}
public function rules()
{
/* Ensure we have a client name, and that all emails are unique*/
//$rules['name'] = 'required|min:1';
$rules['id_number'] = 'unique:expenses,id_number,'.$this->id.',id,company_id,'.$this->company_id;
//$rules['settings'] = new ValidExpenseGroupSettingsRule();
$rules['contacts.*.email'] = 'nullable|distinct';
$rules['number'] = new UniqueExpenseNumberRule($this->all());
// $contacts = request('contacts');
// if (is_array($contacts)) {
// for ($i = 0; $i < count($contacts); $i++) {
// //$rules['contacts.' . $i . '.email'] = 'nullable|email|distinct';
// }
// }
return $rules;
}
protected function prepareForValidation()
{
// $input = $this->all();
// $this->replace($input);
}
public function messages()
{
return [
'unique' => ctrans('validation.unique', ['attribute' => 'email']),
//'required' => trans('validation.required', ['attribute' => 'email']),
'contacts.*.email.required' => ctrans('validation.email', ['attribute' => 'email']),
];
}
}

View File

@ -0,0 +1,77 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Expense;
use App\Http\Requests\Request;
use App\Http\ValidationRules\IsDeletedRule;
use App\Http\ValidationRules\ValidExpenseGroupSettingsRule;
use App\Utils\Traits\ChecksEntityStatus;
use App\Utils\Traits\MakesHash;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Rule;
class UpdateExpenseRequest extends Request
{
use MakesHash;
use ChecksEntityStatus;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->expense);
}
public function rules()
{
/* Ensure we have a client name, and that all emails are unique*/
$rules['country_id'] = 'integer|nullable';
//$rules['id_number'] = 'unique:clients,id_number,,id,company_id,' . auth()->user()->company()->id;
$rules['contacts.*.email'] = 'nullable|distinct';
if ($this->input('number')) {
$rules['number'] = 'unique:expenses,number,'.$this->id.',id,company_id,'.$this->expense->company_id;
}
$contacts = request('contacts');
if (is_array($contacts)) {
// for ($i = 0; $i < count($contacts); $i++) {
// // $rules['contacts.' . $i . '.email'] = 'nullable|email|unique:client_contacts,email,' . isset($contacts[$i]['id'].',company_id,'.$this->company_id);
// //$rules['contacts.' . $i . '.email'] = 'nullable|email';
// }
}
return $rules;
}
public function messages()
{
return [
'unique' => ctrans('validation.unique', ['attribute' => 'email']),
'email' => ctrans('validation.email', ['attribute' => 'email']),
'name.required' => ctrans('validation.required', ['attribute' => 'name']),
'required' => ctrans('validation.required', ['attribute' => 'email']),
];
}
protected function prepareForValidation()
{
$input = $this->all();
$this->replace($input);
}
}

View File

@ -0,0 +1,69 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\ValidationRules\Expense;
use App\Libraries\MultiDB;
use App\Models\Expense;
use App\Models\User;
use Illuminate\Contracts\Validation\Rule;
/**
* Class UniqueExpenseNumberRule.
*/
class UniqueExpenseNumberRule implements Rule
{
public $input;
public function __construct($input)
{
$this->input = $input;
}
/**
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
return $this->checkIfExpenseNumberUnique(); //if it exists, return false!
}
/**
* @return string
*/
public function message()
{
return 'Expense number already taken';
}
/**
* @param $email
*
* //off,when_sent,when_paid
*
* @return bool
*/
private function checkIfExpenseNumberUnique() : bool
{
$expense = Expense::where('client_id', $this->input['client_id'])
->where('number', $this->input['number'])
->withTrashed()
->exists();
if ($expense) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,152 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace Tests\Feature;
use App\DataMapper\DefaultSettings;
use App\Models\Account;
use App\Models\Expense;
use App\Models\ExpenseContact;
use App\Models\Company;
use App\Models\User;
use App\Utils\Traits\MakesHash;
use Faker\Factory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session;
use Tests\MockAccountData;
use Tests\TestCase;
/**
* @test
* @covers App\Http\Controllers\ExpenseController
*/
class ExpenseApiTest extends TestCase
{
use MakesHash;
use DatabaseTransactions;
use MockAccountData;
public function setUp() :void
{
parent::setUp();
$this->makeTestData();
Session::start();
$this->faker = \Faker\Factory::create();
Model::reguard();
}
public function testExpensePost()
{
$data = [
'public_notes' => $this->faker->firstName,
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/expenses', $data);
$response->assertStatus(200);
}
public function testExpensePut()
{
$data = [
'public_notes' => $this->faker->firstName,
'id_number' => 'Coolio',
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->put('/api/v1/expenses/'.$this->encodePrimaryKey($this->expense->id), $data);
$response->assertStatus(200);
}
public function testExpenseGet()
{
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->get('/api/v1/expenses/'.$this->encodePrimaryKey($this->expense->id));
$response->assertStatus(200);
}
public function testExpenseNotArchived()
{
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->get('/api/v1/expenses/'.$this->encodePrimaryKey($this->expense->id));
$arr = $response->json();
$this->assertEquals(0, $arr['data']['archived_at']);
}
public function testExpenseArchived()
{
$data = [
'ids' => [$this->encodePrimaryKey($this->expense->id)],
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/expenses/bulk?action=archive', $data);
$arr = $response->json();
$this->assertNotNull($arr['data'][0]['archived_at']);
}
public function testExpenseRestored()
{
$data = [
'ids' => [$this->encodePrimaryKey($this->expense->id)],
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/expenses/bulk?action=restore', $data);
$arr = $response->json();
$this->assertEquals(0, $arr['data'][0]['archived_at']);
}
public function testExpenseDeleted()
{
$data = [
'ids' => [$this->encodePrimaryKey($this->expense->id)],
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/expenses/bulk?action=delete', $data);
$arr = $response->json();
$this->assertTrue($arr['data'][0]['is_deleted']);
}
}

View File

@ -67,6 +67,8 @@ trait MockAccountData
public $vendor;
public $expense;
public function makeTestData()
{
@ -151,7 +153,7 @@ trait MockAccountData
$this->client = factory(\App\Models\Client::class)->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
]);
]);
$contact = factory(\App\Models\ClientContact::class)->create([
'user_id' => $this->user->id,
@ -159,14 +161,14 @@ trait MockAccountData
'company_id' => $this->company->id,
'is_primary' => 1,
'send_email' => true,
]);
]);
$contact2 = factory(\App\Models\ClientContact::class)->create([
'user_id' => $this->user->id,
'client_id' => $this->client->id,
'company_id' => $this->company->id,
'send_email' => true,
]);
]);
$this->vendor = factory(\App\Models\Vendor::class)->create([
'user_id' => $this->user->id,
@ -179,14 +181,19 @@ trait MockAccountData
'company_id' => $this->company->id,
'is_primary' => 1,
'send_email' => true,
]);
]);
$vendor_contact2 = factory(\App\Models\VendorContact::class)->create([
'user_id' => $this->user->id,
'vendor_id' => $this->vendor->id,
'company_id' => $this->company->id,
'send_email' => true,
]);
]);
$this->expense = factory(\App\Models\Expense::class)->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
]);
// $rels = collect($contact, $contact2);
// $this->client->setRelation('contacts', $rels);