Fixes for deletion of payments with unapplied amounts

This commit is contained in:
David Bomba 2025-08-19 14:28:35 +10:00
parent 866a5e0977
commit 22e1350a75
2 changed files with 133 additions and 7 deletions

View File

@ -139,10 +139,12 @@ class DeletePayment
// 2025-03-26 - If we are deleting a negative payment, then there is an edge case where the paid to date will be reduced further down. // 2025-03-26 - If we are deleting a negative payment, then there is an edge case where the paid to date will be reduced further down.
// for this scenario, we skip the update to the client paid to date at this point. // for this scenario, we skip the update to the client paid to date at this point.
//2025-08-19 - if there is an unapplied amount, we need to subtract it from the paid to date.
$this->payment $this->payment
->client ->client
->service() ->service()
->updateBalanceAndPaidToDate($net_deletable, ($net_deletable * -1) > 0 ? 0 : ($net_deletable * -1)) // if negative, set to 0, the paid to date will be reduced further down. ->updateBalanceAndPaidToDate($net_deletable, ($net_deletable * -1) > 0 ? 0 : ($net_deletable * -1 - ($this->payment->amount - $this->payment->applied))) // if negative, set to 0, the paid to date will be reduced further down.
->save(); ->save();
if (abs(floatval($paymentable_invoice->balance) - floatval($paymentable_invoice->amount)) < 0.005) { if (abs(floatval($paymentable_invoice->balance) - floatval($paymentable_invoice->amount)) < 0.005) {
@ -172,6 +174,7 @@ class DeletePayment
$reduced_paid_to_date = $this->payment->amount < 0 ? $this->payment->amount * -1 : min(0, ($this->payment->amount - $this->payment->refunded - $this->_paid_to_date_deleted) * -1); $reduced_paid_to_date = $this->payment->amount < 0 ? $this->payment->amount * -1 : min(0, ($this->payment->amount - $this->payment->refunded - $this->_paid_to_date_deleted) * -1);
nlog("reduced paid to date: {$reduced_paid_to_date}");
if($reduced_paid_to_date != 0) { if($reduced_paid_to_date != 0) {
$this->payment $this->payment
->client ->client

View File

@ -11,15 +11,17 @@
namespace Tests\Feature; namespace Tests\Feature;
use App\DataMapper\InvoiceItem;
use App\Models\Client; use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Credit; use App\Models\Credit;
use App\Utils\Traits\MakesHash; use App\Models\ClientContact;
use Illuminate\Database\Eloquent\Model; use App\DataMapper\InvoiceItem;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Session;
use Tests\MockAccountData; use Tests\MockAccountData;
use App\Utils\Traits\MakesHash;
use App\Repositories\InvoiceRepository;
use App\Repositories\CreditRepository;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Session;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase; use Tests\TestCase;
class CreditTest extends TestCase class CreditTest extends TestCase
@ -43,6 +45,125 @@ class CreditTest extends TestCase
$this->makeTestData(); $this->makeTestData();
} }
public function testPartialAmountWithPartialCreditAndPaymentDeletedBalance()
{
$c = Client::factory()->create([
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'balance' => 0,
'paid_to_date' => 0,
]);
$ii = new InvoiceItem();
$ii->cost = 100;
$ii->quantity = 1;
$ii->product_key = 'xx';
$ii->notes = 'yy';
$i = \App\Models\Invoice::factory()->create([
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'client_id' => $c->id,
'tax_name1' => '',
'tax_name2' => '',
'tax_name3' => '',
'tax_rate1' => 0,
'tax_rate2' => 0,
'tax_rate3' => 0,
'discount' => 0,
'line_items' => [
$ii
],
'status_id' => 1,
]);
$repo = new InvoiceRepository();
$repo->save([], $i);
$i = $i->calc()->getInvoice();
$i = $i->service()->markSent()->save();
$this->assertEquals(100, $i->balance);
$this->assertEquals(100, $i->amount);
$cr = \App\Models\Credit::factory()->create([
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'client_id' => $c->id,
'tax_name1' => '',
'tax_name2' => '',
'tax_name3' => '',
'tax_rate1' => 0,
'tax_rate2' => 0,
'tax_rate3' => 0,
'discount' => 0,
'line_items' => [
$ii
],
'status_id' => 1,
]);
$repo = new CreditRepository();
$repo->save([], $cr);
$cr = $cr->calc()->getInvoice();
$cr = $cr->service()->markSent()->save();
$this->assertEquals(100, $cr->balance);
$this->assertEquals(100, $cr->amount);
$data = [
'date' => '2020/12/12',
'client_id' => $c->hashed_id,
'amount' => 10,
'invoices' => [
[
'invoice_id' => $i->hashed_id,
'amount' => 10
],
],
'credits' => [
[
'credit_id' => $cr->hashed_id,
'amount' => 10
]
],
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/payments', $data);
$response->assertStatus(200);
$arr = $response->json();
$this->assertEquals(10, $arr['data']['amount']);
$this->assertEquals(20, $c->fresh()->paid_to_date);
$this->assertEquals(90, $i->fresh()->balance);
$this->assertEquals(90, $cr->fresh()->balance);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->deleteJson('/api/v1/payments/'.$arr['data']['id']);
$response->assertStatus(200);
$this->assertEquals(100, $i->fresh()->balance);
$this->assertEquals(100, $cr->fresh()->balance);
$this->assertEquals(100, $c->fresh()->balance);
$this->assertEquals(0, $c->fresh()->paid_to_date);
}
public function testCreditReversalScenarioInvoicePartiallyPaid() public function testCreditReversalScenarioInvoicePartiallyPaid()
{ {
@ -117,6 +238,8 @@ class CreditTest extends TestCase
$this->assertEquals(0, $c->balance); $this->assertEquals(0, $c->balance);
$this->assertEquals(6, $i->status_id); $this->assertEquals(6, $i->status_id);
} }