Additional payment logic

This commit is contained in:
David Bomba 2023-07-14 17:09:23 +10:00
parent f82b21343c
commit 1127f3ea5f
8 changed files with 248 additions and 8 deletions

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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();

View File

@ -109,6 +109,8 @@ class DeletePayment
$paymentable_invoice->service()
->updatePaidToDate($net_deletable * -1)
->save();
$paymentable_invoice->delete();
}
});
}

View File

@ -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);
}

View 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);
}
}

View File

@ -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}"));
}
}