diff --git a/app/Console/Commands/CreateTestData.php b/app/Console/Commands/CreateTestData.php index 88d4eebc7bf0..a0d2d5be226e 100644 --- a/app/Console/Commands/CreateTestData.php +++ b/app/Console/Commands/CreateTestData.php @@ -22,6 +22,7 @@ use App\Models\PaymentType; use App\Models\User; use App\Repositories\InvoiceRepository; use App\Utils\Traits\MakesHash; +use Carbon\Carbon; use Faker\Factory; use Illuminate\Console\Command; use Illuminate\Support\Facades\Cache; @@ -232,6 +233,17 @@ class CreateTestData extends Command $this->createClient($company, $user); } + + foreach($company->clients as $client) { + + + $this->createInvoice($client); + $this->createQuote($client); + $this->createExpense($client); + $this->createVendor($client); + + } + } private function createClient($company, $user) @@ -254,16 +266,8 @@ class CreateTestData extends Command 'company_id' => $company->id ]); - $y = $this->count * rand(1, 5); - $this->info("Creating {$y} invoices & quotes"); - for ($x=0; $x<$y; $x++) { - $this->createInvoice($client); - $this->createQuote($client); - $this->createExpense($client); - $this->createVendor($client); - } } private function createExpense($client) @@ -308,7 +312,8 @@ class CreateTestData extends Command $invoice = InvoiceFactory::create($client->company->id, $client->user->id);//stub the company and user_id $invoice->client_id = $client->id; - $invoice->date = $faker->date(); +// $invoice->date = $faker->date(); + $invoice->date = Carbon::now()->subDays(rand(0,90)); $invoice->line_items = $this->buildLineItems(); $invoice->uses_inclusive_taxes = false; diff --git a/app/Http/Controllers/OpenAPI/ExpenseSchema.php b/app/Http/Controllers/OpenAPI/ExpenseSchema.php new file mode 100644 index 000000000000..50f8366eb902 --- /dev/null +++ b/app/Http/Controllers/OpenAPI/ExpenseSchema.php @@ -0,0 +1,43 @@ +input('id')); + + $payment = Payment::whereId($request->input('id'))->first(); + + return $this->itemResponse($payment); + } + } diff --git a/app/Http/Requests/Payment/RefundPaymentRequest.php b/app/Http/Requests/Payment/RefundPaymentRequest.php new file mode 100644 index 000000000000..dae5731d654c --- /dev/null +++ b/app/Http/Requests/Payment/RefundPaymentRequest.php @@ -0,0 +1,59 @@ +user()->isAdmin(); + } + + protected function prepareForValidation() + { + $input = $this->all(); + + if(!isset($input['gateway_refund'])) + $input['gateway_refund'] = false; + + if(isset($input['id'])) + $input['id'] = $this->decodePrimaryKey($input['id']); + + $this->replace($input); + } + + public function rules() + { + $rules = [ + 'id' => 'required', + 'refunded' => 'numeric', + 'date' => 'required', + 'invoices.*.invoice_id' => 'required', + 'invoices.*.refunded' => 'required', + 'invoices' => new ValidRefundableInvoices(), + ]; + + return $rules; + } +} diff --git a/app/Http/ValidationRules/ValidRefundableInvoices.php b/app/Http/ValidationRules/ValidRefundableInvoices.php new file mode 100644 index 000000000000..264f32559382 --- /dev/null +++ b/app/Http/ValidationRules/ValidRefundableInvoices.php @@ -0,0 +1,83 @@ +decodePrimaryKey(request()->input('id')))->first(); + + if($request->has('refunded') && ($request->input('refunded') > $payment->amount)){ + $this->error_msg = "Attempting to refunded more than payment amount, enter a value equal to or lower than the payment amount of ". $payment->amount; + return false; + } + + /*If no invoices has been sent, then we apply the payment to the client account*/ + $invoices = []; + + if (is_array($value)) { + $invoices = Invoice::whereIn('id', array_column($value, 'invoice_id'))->company()->get(); + } + else + return true; + + foreach ($invoices as $invoice) { + if (! $invoice->isRefundable()) { + $this->error_msg = "One or more of these invoices have been paid"; + return false; + } + + + foreach ($value as $val) { + if ($val['invoice_id'] == $invoice->id) { + + if($val['refunded'] > ($invoice->amount - $invoice->balance)) + $this->error_msg = "Attempting to refund more than is possible for an invoice"; + return false; + } + } + + } + + + + return true; + } + + /** + * @return string + */ + public function message() + { + return $this->error_msg; + } +} diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index e103436fd61e..22ea2d6a090d 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -231,6 +231,17 @@ class Invoice extends BaseModel } } + public function isRefundable() : bool + { + if($this->is_deleted){ + return false; + } elseif ($this->balance <= 0) + return false; + + + return true; + } + public static function badgeForStatus(int $status) { switch ($status) { diff --git a/app/Models/Project.php b/app/Models/Project.php new file mode 100644 index 000000000000..78b390e2bf26 --- /dev/null +++ b/app/Models/Project.php @@ -0,0 +1,62 @@ +belongsTo(Company::class); + } + + /** + * @return mixed + */ + public function client() + { + return $this->belongsTo(Client::class)->withTrashed(); + } + + // /** + // * @return \Illuminate\Database\Eloquent\Relations\HasMany + // */ + // public function tasks() + // { + // return $this->hasMany('App\Models\Task'); + // } + + +} + diff --git a/app/Models/QuoteInvitation.php b/app/Models/QuoteInvitation.php index f50826020da2..61cc8dcc4ae5 100644 --- a/app/Models/QuoteInvitation.php +++ b/app/Models/QuoteInvitation.php @@ -12,12 +12,14 @@ namespace App\Models; use App\Models\Quote; +use App\Utils\Traits\Inviteable; use App\Utils\Traits\MakesDates; use Illuminate\Database\Eloquent\Model; class QuoteInvitation extends BaseModel { use MakesDates; + use Inviteable; protected $fillable = [ 'id', diff --git a/routes/api.php b/routes/api.php index a544d2daab72..ef0c4db5fcee 100644 --- a/routes/api.php +++ b/routes/api.php @@ -74,6 +74,8 @@ Route::group(['middleware' => ['api_db','api_secret_check','token_auth','locale' Route::resource('payments', 'PaymentController'); // name = (payments. index / create / show / update / destroy / edit + Route::post('payments/refund', 'PaymentController@refund')->name('payments.refund'); + Route::post('payments/bulk', 'PaymentController@bulk')->name('payments.bulk'); // Route::resource('users', 'UserController')->middleware('password_protected'); // name = (users. index / create / show / update / destroy / edit diff --git a/tests/Feature/ClientApiTest.php b/tests/Feature/ClientApiTest.php index 2b610f595c84..6b5f9c5df533 100644 --- a/tests/Feature/ClientApiTest.php +++ b/tests/Feature/ClientApiTest.php @@ -116,7 +116,7 @@ class ClientApiTest extends TestCase ])->post('/api/v1/clients/bulk?action=archive', $data); $arr = $response->json(); -\Log::error($arr); + $this->assertNotNull($arr['data'][0]['archived_at']); } diff --git a/tests/Feature/PaymentTest.php b/tests/Feature/PaymentTest.php index fcb218948671..9b00fb9999f8 100644 --- a/tests/Feature/PaymentTest.php +++ b/tests/Feature/PaymentTest.php @@ -330,7 +330,7 @@ class PaymentTest extends TestCase $payment_id = $arr['data']['id']; - $payment = Payment::find($this->decodePrimaryKey($payment_id))->first(); + $payment = Payment::whereId($this->decodePrimaryKey($payment_id))->first(); $this->assertNotNull($payment); $this->assertNotNull($payment->invoices()); @@ -405,7 +405,7 @@ class PaymentTest extends TestCase $payment_id = $arr['data']['id']; - $payment = Payment::find($this->decodePrimaryKey($payment_id))->first(); + $payment = Payment::whereId($this->decodePrimaryKey($payment_id))->first(); $this->assertNotNull($payment); $this->assertNotNull($payment->invoices()); @@ -466,7 +466,7 @@ class PaymentTest extends TestCase $payment_id = $arr['data']['id']; - $payment = Payment::find($this->decodePrimaryKey($payment_id))->first(); + $payment = Payment::whereId($this->decodePrimaryKey($payment_id))->first(); $this->assertNotNull($payment); $this->assertNotNull($payment->invoices()); @@ -786,7 +786,7 @@ class PaymentTest extends TestCase $payment_id = $arr['data']['id']; - $payment = Payment::find($this->decodePrimaryKey($payment_id))->first(); + $payment = Payment::whereId($this->decodePrimaryKey($payment_id))->first(); $this->assertEquals($payment->amount, 15); $this->assertEquals($payment->applied, 10); @@ -890,7 +890,7 @@ class PaymentTest extends TestCase $payment_id = $arr['data']['id']; - $payment = Payment::find($this->decodePrimaryKey($payment_id))->first(); + $payment = Payment::whereId($this->decodePrimaryKey($payment_id))->first(); $this->assertEquals($payment->amount, 20); $this->assertEquals($payment->applied, 10); @@ -1005,7 +1005,7 @@ class PaymentTest extends TestCase $this->assertEquals($this->invoice->amount, $arr['data']['amount']); - $payment = Payment::find($this->decodePrimaryKey($payment_id))->first(); + $payment = Payment::whereId($this->decodePrimaryKey($payment_id))->first(); $this->assertNotNull($payment); $this->assertNotNull($payment->invoices()); @@ -1066,4 +1066,81 @@ class PaymentTest extends TestCase } + + public function testBasicRefundValidation() + { + $client = ClientFactory::create($this->company->id, $this->user->id); + $client->save(); + + $this->invoice = InvoiceFactory::create($this->company->id,$this->user->id);//stub the company and user_id + $this->invoice->client_id = $client->id; + $this->invoice->status_id = Invoice::STATUS_SENT; + + $this->invoice->line_items = $this->buildLineItems(); + $this->invoice->uses_inclusive_Taxes = false; + + $this->invoice->save(); + + $this->invoice_calc = new InvoiceSum($this->invoice); + $this->invoice_calc->build(); + + $this->invoice = $this->invoice_calc->getInvoice(); + $this->invoice->save(); + + $data = [ + 'amount' => 50, + 'client_id' => $client->hashed_id, + // 'invoices' => [ + // [ + // 'invoice_id' => $this->invoice->hashed_id, + // 'amount' => $this->invoice->amount + // ], + // ], + 'date' => '2020/12/12', + + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/payments', $data); + + + $arr = $response->json(); + $response->assertStatus(200); + + $payment_id = $arr['data']['id']; + + $this->assertEquals(50, $arr['data']['amount']); + + $payment = Payment::whereId($this->decodePrimaryKey($payment_id))->first(); + + $this->assertNotNull($payment); + // $this->assertNotNull($payment->invoices()); + // $this->assertEquals(1, $payment->invoices()->count()); + + + $data = [ + 'id' => $this->encodePrimaryKey($payment->id), + 'refunded' => 50, + // 'invoices' => [ + // [ + // 'invoice_id' => $this->invoice->hashed_id, + // 'amount' => $this->invoice->amount + // ], + // ], + 'date' => '2020/12/12', + ]; + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/payments/refund', $data); + + $arr = $response->json(); + + $response->assertStatus(200); + } + }