mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Additional payment logic
This commit is contained in:
parent
f82b21343c
commit
1127f3ea5f
@ -288,6 +288,33 @@ abstract class QueryFilters
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Builder
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function without_deleted_clients(): Builder
|
||||
{
|
||||
return $this->builder->where(function ($query) {
|
||||
$query->whereHas('client', function ($sub_query) {
|
||||
$sub_query->where('is_deleted', 0);
|
||||
})->orWhere('client_id', null);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Builder
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function without_deleted_vendors(): Builder
|
||||
{
|
||||
return $this->builder->where(function ($query) {
|
||||
$query->whereHas('vendor', function ($sub_query) {
|
||||
$sub_query->where('is_deleted', 0);
|
||||
})->orWhere('vendor_id', null);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public function with(string $value = ''): Builder
|
||||
{
|
||||
if (strlen($value) == 0) {
|
||||
|
@ -69,13 +69,16 @@ class ValidCreditsRules implements Rule
|
||||
|
||||
if (! $cred) {
|
||||
$this->error_msg = ctrans('texts.credit_not_found');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($cred->client_id != $this->input['client_id']) {
|
||||
$this->error_msg = ctrans('texts.invoices_dont_match_client');
|
||||
return false;
|
||||
}
|
||||
|
||||
if($cred->balance < $credit['amount']) {
|
||||
$this->error_msg = ctrans('texts.insufficient_credit_balance');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,6 @@ class HandleRestore extends AbstractService
|
||||
//cannot restore an invoice with a deleted payment
|
||||
foreach ($this->invoice->payments as $payment) {
|
||||
if (($this->invoice->paid_to_date == 0) && $payment->is_deleted) {
|
||||
$this->invoice->delete(); //set it back to deleted so that it can be restored from repository
|
||||
return $this->invoice;
|
||||
}
|
||||
}
|
||||
@ -81,8 +80,8 @@ class HandleRestore extends AbstractService
|
||||
Paymentable::query()
|
||||
->withTrashed()
|
||||
->where('payment_id', $payment->id)
|
||||
->where('paymentable_type', '=', 'invoices')
|
||||
->where('paymentable_id', $this->invoice->id)
|
||||
// ->where('paymentable_type', '=', 'invoices')
|
||||
// ->where('paymentable_id', $this->invoice->id)
|
||||
->update(['deleted_at' => null]);
|
||||
});
|
||||
|
||||
@ -102,6 +101,12 @@ class HandleRestore extends AbstractService
|
||||
->where('paymentable_type', '=', 'invoices')
|
||||
->where('paymentable_id', $this->invoice->id)
|
||||
->sum(DB::raw('refunded'));
|
||||
|
||||
//14/07/2023 - do not include credits in the payment amount
|
||||
$this->adjustment_amount -= $payment->paymentables
|
||||
->where('paymentable_type', '=', 'App\Models\Credit')
|
||||
->sum(DB::raw('amount'));
|
||||
|
||||
}
|
||||
|
||||
$this->total_payments = $this->invoice->payments->sum('amount') - $this->invoice->payments->sum('refunded');
|
||||
@ -130,11 +135,16 @@ class HandleRestore extends AbstractService
|
||||
->where('paymentable_id', $this->invoice->id)
|
||||
->sum(DB::raw('refunded'));
|
||||
|
||||
$payment_adjustment -= $payment->paymentables
|
||||
->where('paymentable_type', '=', 'App\Models\Credit')
|
||||
->sum(DB::raw('amount'));
|
||||
|
||||
$payment->amount += $payment_adjustment;
|
||||
$payment->applied += $payment_adjustment;
|
||||
$payment->is_deleted = false;
|
||||
$payment->restore();
|
||||
$payment->saveQuietly();
|
||||
|
||||
});
|
||||
|
||||
return $this;
|
||||
|
@ -11,11 +11,12 @@
|
||||
|
||||
namespace App\Services\Invoice;
|
||||
|
||||
use App\Jobs\Inventory\AdjustProductInventory;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Services\AbstractService;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use App\Jobs\Inventory\AdjustProductInventory;
|
||||
|
||||
class MarkInvoiceDeleted extends AbstractService
|
||||
{
|
||||
@ -94,6 +95,11 @@ class MarkInvoiceDeleted extends AbstractService
|
||||
->where('paymentable_id', $this->invoice->id)
|
||||
->sum(DB::raw('refunded'));
|
||||
|
||||
//14-07-2023 - Do not include credits in the payment adjustment.
|
||||
$payment_adjustment -= $payment->paymentables
|
||||
->where('paymentable_type', '=', 'App\Models\Credit')
|
||||
->sum(DB::raw('amount'));
|
||||
|
||||
$payment->amount -= $payment_adjustment;
|
||||
$payment->applied -= $payment_adjustment;
|
||||
$payment->save();
|
||||
|
@ -109,6 +109,8 @@ class DeletePayment
|
||||
$paymentable_invoice->service()
|
||||
->updatePaidToDate($net_deletable * -1)
|
||||
->save();
|
||||
$paymentable_invoice->delete();
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ class ExportCompanyTest extends TestCase
|
||||
|
||||
public function testCompanyExport()
|
||||
{
|
||||
$res = (new CompanyExport($this->company, $this->company->users->first()))->handle();
|
||||
$res = (new CompanyExport($this->company, $this->company->users->first(), '123'))->handle();
|
||||
|
||||
$this->assertTrue($res);
|
||||
}
|
||||
|
192
tests/Feature/PaymentV2Test.php
Normal file
192
tests/Feature/PaymentV2Test.php
Normal file
@ -0,0 +1,192 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Foundation\Testing\WithoutEvents;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @covers App\Http\Controllers\PaymentController
|
||||
*/
|
||||
class PaymentV2Test extends TestCase
|
||||
{
|
||||
use MakesHash;
|
||||
use DatabaseTransactions;
|
||||
use MockAccountData;
|
||||
use WithoutEvents;
|
||||
|
||||
public $faker;
|
||||
|
||||
protected function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Session::start();
|
||||
|
||||
$this->faker = \Faker\Factory::create();
|
||||
|
||||
Model::reguard();
|
||||
|
||||
$this->makeTestData();
|
||||
$this->withoutExceptionHandling();
|
||||
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class
|
||||
);
|
||||
}
|
||||
|
||||
public function testStorePaymentWithCreditsThenDeletingInvoices()
|
||||
{
|
||||
$client = Client::factory()->create(['company_id' =>$this->company->id, 'user_id' => $this->user->id, 'balance' => 20, 'paid_to_date' => 0]);
|
||||
ClientContact::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $client->id,
|
||||
'company_id' => $this->company->id,
|
||||
'is_primary' => 1,
|
||||
]);
|
||||
|
||||
$invoice = Invoice::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $client->id,
|
||||
'status_id' => Invoice::STATUS_SENT,
|
||||
'uses_inclusive_taxes' => false,
|
||||
'amount' => 20,
|
||||
'balance' => 20,
|
||||
'discount' => 0,
|
||||
'number' => uniqid("st", true),
|
||||
'line_items' => []
|
||||
]);
|
||||
|
||||
$this->assertEquals(20, $client->balance);
|
||||
$this->assertEquals(0, $client->paid_to_date);
|
||||
$this->assertEquals(20, $invoice->amount);
|
||||
$this->assertEquals(20, $invoice->balance);
|
||||
|
||||
$credit = Credit::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $client->id,
|
||||
'status_id' => Invoice::STATUS_SENT,
|
||||
'uses_inclusive_taxes' => false,
|
||||
'amount' => 20,
|
||||
'balance' => 20,
|
||||
'discount' => 0,
|
||||
'number' => uniqid("st", true),
|
||||
'line_items' => []
|
||||
]);
|
||||
|
||||
$this->assertEquals(20, $credit->amount);
|
||||
$this->assertEquals(20, $credit->balance);
|
||||
|
||||
$data = [
|
||||
'client_id' => $client->hashed_id,
|
||||
'invoices' => [
|
||||
[
|
||||
'invoice_id' => $invoice->hashed_id,
|
||||
'amount' => 20,
|
||||
],
|
||||
],
|
||||
'credits' => [
|
||||
[
|
||||
'credit_id' => $credit->hashed_id,
|
||||
'amount' => 20,
|
||||
],
|
||||
],
|
||||
'date' => '2020/12/12',
|
||||
|
||||
];
|
||||
|
||||
$response = null;
|
||||
|
||||
try {
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/payments?include=invoices', $data);
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
nlog($message);
|
||||
$this->assertNotNull($message);
|
||||
}
|
||||
|
||||
$arr = $response->json();
|
||||
$response->assertStatus(200);
|
||||
|
||||
$payment_id = $arr['data']['id'];
|
||||
|
||||
$payment = Payment::find($this->decodePrimaryKey($payment_id));
|
||||
|
||||
$this->assertNotNull($payment);
|
||||
$this->assertNotNull($payment->invoices());
|
||||
$this->assertEquals(1, $payment->invoices()->count());
|
||||
$this->assertEquals(0, $payment->amount);
|
||||
$this->assertEquals(0, $client->fresh()->balance);
|
||||
$this->assertEquals(20, $client->fresh()->paid_to_date);
|
||||
|
||||
$data = [
|
||||
'action' => 'delete',
|
||||
'ids' => [
|
||||
$invoice->hashed_id,
|
||||
],
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/invoices/bulk', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$invoice = $invoice->fresh();
|
||||
$payment = $payment->fresh();
|
||||
|
||||
$this->assertEquals(true, $invoice->is_deleted);
|
||||
$this->assertEquals(0, $payment->amount);
|
||||
$this->assertEquals(0, $client->fresh()->balance);
|
||||
$this->assertEquals(0, $client->fresh()->paid_to_date);
|
||||
|
||||
$data = [
|
||||
'action' => 'restore',
|
||||
'ids' => [
|
||||
$invoice->hashed_id,
|
||||
],
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/invoices/bulk', $data);
|
||||
|
||||
$invoice = $invoice->fresh();
|
||||
$this->assertEquals(false, $invoice->is_deleted);
|
||||
|
||||
$payment = $payment->fresh();
|
||||
|
||||
$this->assertEquals(0, $payment->amount);
|
||||
$this->assertEquals(20, $client->fresh()->paid_to_date);
|
||||
|
||||
}
|
||||
}
|
@ -33,7 +33,7 @@ class PaymentTypeTest extends TestCase
|
||||
$payment_type_class = new PaymentType;
|
||||
|
||||
foreach($payment_type_class->type_names as $type)
|
||||
{nlog($type);
|
||||
{
|
||||
$this->assertTrue(Lang::has("texts.{$type}"));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user