diff --git a/app/Factory/RecurringExpenseToExpenseFactory.php b/app/Factory/RecurringExpenseToExpenseFactory.php index 069a18410677..921130ac9118 100644 --- a/app/Factory/RecurringExpenseToExpenseFactory.php +++ b/app/Factory/RecurringExpenseToExpenseFactory.php @@ -65,6 +65,7 @@ class RecurringExpenseToExpenseFactory $expense->tax_amount3 = $recurring_expense->tax_amount3 ?: 0; $expense->uses_inclusive_taxes = $recurring_expense->uses_inclusive_taxes; $expense->calculate_tax_by_amount = $recurring_expense->calculate_tax_by_amount; + $expense->invoice_currency_id = $recurring_expense->invoice_currency_id; return $expense; } diff --git a/app/Http/Requests/RecurringExpense/StoreRecurringExpenseRequest.php b/app/Http/Requests/RecurringExpense/StoreRecurringExpenseRequest.php index 7b9e582a58d1..3c4e884ce241 100644 --- a/app/Http/Requests/RecurringExpense/StoreRecurringExpenseRequest.php +++ b/app/Http/Requests/RecurringExpense/StoreRecurringExpenseRequest.php @@ -27,27 +27,35 @@ class StoreRecurringExpenseRequest extends Request */ public function authorize() : bool { - return auth()->user()->can('create', RecurringExpense::class); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->can('create', RecurringExpense::class); } public function rules() { + + /** @var \App\Models\User $user */ + $user = auth()->user(); + $rules = []; if ($this->number) { - $rules['number'] = Rule::unique('recurring_expenses')->where('company_id', auth()->user()->company()->id); + $rules['number'] = Rule::unique('recurring_expenses')->where('company_id', $user->company()->id); } if (! empty($this->client_id)) { - $rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id; + $rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.$user->company()->id; } - $rules['category_id'] = 'bail|nullable|sometimes|exists:expense_categories,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; + $rules['category_id'] = 'bail|nullable|sometimes|exists:expense_categories,id,company_id,'.$user->company()->id.',is_deleted,0'; $rules['frequency_id'] = 'required|integer|digits_between:1,12'; $rules['tax_amount1'] = 'numeric'; $rules['tax_amount2'] = 'numeric'; $rules['tax_amount3'] = 'numeric'; - + $rules['currency_id'] = 'bail|required|integer|exists:currencies,id'; + if ($this->file('documents') && is_array($this->file('documents'))) { $rules['documents.*'] = $this->file_validation; } elseif ($this->file('documents')) { @@ -65,6 +73,10 @@ class StoreRecurringExpenseRequest extends Request public function prepareForValidation() { + + /** @var \App\Models\User $user */ + $user = auth()->user(); + $input = $this->all(); $input = $this->decodePrimaryKeys($input); @@ -74,7 +86,7 @@ class StoreRecurringExpenseRequest extends Request } if (! array_key_exists('currency_id', $input) || strlen($input['currency_id']) == 0) { - $input['currency_id'] = (string) auth()->user()->company()->settings->currency_id; + $input['currency_id'] = (string) $user->company()->settings->currency_id; } if (array_key_exists('color', $input) && is_null($input['color'])) { diff --git a/database/factories/RecurringExpenseFactory.php b/database/factories/RecurringExpenseFactory.php index 68b109451aee..be6ed11a9672 100644 --- a/database/factories/RecurringExpenseFactory.php +++ b/database/factories/RecurringExpenseFactory.php @@ -35,6 +35,12 @@ class RecurringExpenseFactory extends Factory 'private_notes' => $this->faker->text(50), 'transaction_reference' => $this->faker->text(5), 'invoice_id' => null, + 'tax_rate1' => 0, + 'tax_rate2' => 0, + 'tax_rate3' => 0, + 'tax_name1' => '', + 'tax_name2' => '', + 'tax_name3' => '', ]; } } diff --git a/tests/Feature/RecurringExpenseApiTest.php b/tests/Feature/RecurringExpenseApiTest.php index e0848ba4901d..3c863c047e18 100644 --- a/tests/Feature/RecurringExpenseApiTest.php +++ b/tests/Feature/RecurringExpenseApiTest.php @@ -11,14 +11,16 @@ namespace Tests\Feature; -use App\Models\RecurringInvoice; -use App\Utils\Traits\MakesHash; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Testing\DatabaseTransactions; -use Illuminate\Support\Facades\Session; -use Illuminate\Validation\ValidationException; -use Tests\MockAccountData; use Tests\TestCase; +use Tests\MockAccountData; +use Illuminate\Support\Str; +use App\Utils\Traits\MakesHash; +use App\Models\RecurringInvoice; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Session; +use App\Jobs\Cron\RecurringExpensesCron; +use Illuminate\Validation\ValidationException; +use Illuminate\Foundation\Testing\DatabaseTransactions; /** * @test @@ -30,6 +32,8 @@ class RecurringExpenseApiTest extends TestCase use DatabaseTransactions; use MockAccountData; + public $faker; + protected function setUp() :void { parent::setUp(); @@ -43,6 +47,175 @@ class RecurringExpenseApiTest extends TestCase Model::reguard(); } + public function testRecurringExpenseGenerationWithCurrencyConversion() + { + $r = \App\Models\RecurringExpense::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'amount' => 100, + 'number' => Str::random(10), + 'frequency_id' => 5, + 'remaining_cycles' => -1, + 'status_id' => \App\Models\RecurringInvoice::STATUS_ACTIVE, + 'date' => now()->format('Y-m-d'), + 'currency_id' => 1, + 'next_send_date' => now(), + 'next_send_date_client' => now(), + 'invoice_currency_id' => 2, + 'foreign_amount' => 50, + ]); + + (new RecurringExpensesCron())->handle(); + + $expense = \App\Models\Expense::where('recurring_expense_id', $r->id)->orderBy('id','desc')->first(); + + $this->assertEquals($r->amount, $expense->amount); + $this->assertEquals($r->currency_id, $expense->currency_id); + $this->assertEquals($r->invoice_currency_id, $expense->invoice_currency_id); + $this->assertEquals($r->foreign_amount, $expense->foreign_amount); + + } + + public function testRecurringExpenseGenerationNullForeignCurrency() + { + $r = \App\Models\RecurringExpense::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'amount' => 100, + 'number' => Str::random(10), + 'frequency_id' => 5, + 'remaining_cycles' => -1, + 'status_id' => \App\Models\RecurringInvoice::STATUS_ACTIVE, + 'date' => now()->format('Y-m-d'), + 'currency_id' => 1, + 'next_send_date' => now(), + 'next_send_date_client' => now(), + 'invoice_currency_id' => null + ]); + + (new RecurringExpensesCron())->handle(); + + $expense = \App\Models\Expense::where('recurring_expense_id', $r->id)->orderBy('id','desc')->first(); + + $this->assertEquals($r->amount, $expense->amount); + $this->assertEquals($r->currency_id, $expense->currency_id); + $this->assertEquals($r->invoice_currency_id, $expense->invoice_currency_id); + + } + + public function testRecurringExpenseGeneration() + { + $r = \App\Models\RecurringExpense::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'amount' => 100, + 'number' => Str::random(10), + 'frequency_id' => 5, + 'remaining_cycles' => -1, + 'status_id' => \App\Models\RecurringInvoice::STATUS_ACTIVE, + 'date' => now()->format('Y-m-d'), + 'currency_id' => 1, + 'next_send_date' => now(), + 'next_send_date_client' => now(), + ]); + + (new RecurringExpensesCron())->handle(); + + $expense = \App\Models\Expense::where('recurring_expense_id', $r->id)->orderBy('id','desc')->first(); + + $this->assertEquals($r->amount, $expense->amount); + $this->assertEquals($r->currency_id, $expense->currency_id); + + } + + public function testRecurringExpenseValidation() + { + $data = [ + 'amount' => 10, + 'client_id' => $this->client->hashed_id, + 'number' => '123321', + 'frequency_id' => 5, + 'remaining_cycles' =>5, + 'currency_id' => 34545435425 + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/recurring_expenses?start=true', $data); + + $response->assertStatus(422); + + } + + public function testRecurringExpenseValidation2() + { + $data = [ + 'amount' => 10, + 'client_id' => $this->client->hashed_id, + 'number' => '123321', + 'frequency_id' => 5, + 'remaining_cycles' =>5, + 'currency_id' => 1 + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/recurring_expenses?start=true', $data); + + $response->assertStatus(200); + + } + + public function testRecurringExpenseValidation3() + { + $data = [ + 'amount' => 10, + 'client_id' => $this->client->hashed_id, + 'number' => '123321', + 'frequency_id' => 5, + 'remaining_cycles' =>5, + 'currency_id' => null + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/recurring_expenses?start=true', $data); + + $data = $response->json(); + + $response->assertStatus(200); + + $this->assertEquals(1, $data['data']['currency_id']); + + } + + public function testRecurringExpenseValidation4() + { + $data = [ + 'amount' => 10, + 'client_id' => $this->client->hashed_id, + 'number' => '123321', + 'frequency_id' => 5, + 'remaining_cycles' =>5, + 'currency_id' => "" + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/recurring_expenses?start=true', $data); + + $data = $response->json(); + + $response->assertStatus(200); + + $this->assertEquals(1, $data['data']['currency_id']); + + } + public function testRecurringExpenseGetFiltered() { $response = $this->withHeaders([