diff --git a/app/Repositories/BaseRepository.php b/app/Repositories/BaseRepository.php index 18e3a00c1d..4f7c68b4f0 100644 --- a/app/Repositories/BaseRepository.php +++ b/app/Repositories/BaseRepository.php @@ -203,10 +203,8 @@ class BaseRepository $resource = explode('\\', $class->name)[2]; /** This will extract 'Invoice' from App\Models\Invoice */ $lcfirst_resource_id = lcfirst($resource).'_id'; - if ($class->name == Invoice::class || $class->name == Quote::class) { - $state['starting_amount'] = $model->amount; - } - + $state['starting_amount'] = $model->amount; + if (! $model->id) { $company_defaults = $client->setCompanyDefaults($data, lcfirst($resource)); $model->uses_inclusive_taxes = $client->getSetting('inclusive_taxes'); @@ -320,7 +318,10 @@ class BaseRepository } if ($class->name == Credit::class) { + $model = $model->calc()->getCredit(); + + $model->ledger()->updateCreditBalance(($state['finished_amount'] - $state['starting_amount'])); if (! $model->design_id) { $model->design_id = $this->decodePrimaryKey($client->getSetting('credit_design_id')); diff --git a/app/Services/Credit/MarkSent.php b/app/Services/Credit/MarkSent.php index 2e86b79c8b..fdd6e1d891 100644 --- a/app/Services/Credit/MarkSent.php +++ b/app/Services/Credit/MarkSent.php @@ -45,6 +45,7 @@ class MarkSent ->applyNumber() ->save(); + return $this->credit; } } diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php index 10230eeb5b..4d3b0781ae 100644 --- a/app/Services/Invoice/AutoBillInvoice.php +++ b/app/Services/Invoice/AutoBillInvoice.php @@ -21,6 +21,7 @@ use App\Models\PaymentHash; use App\Services\AbstractService; use App\Services\Client\ClientService; use App\Services\Payment\PaymentService; +use App\Utils\Ninja; use App\Utils\Traits\GeneratesCounter; use Illuminate\Support\Str; @@ -30,7 +31,7 @@ class AutoBillInvoice extends AbstractService private $client; - private $payment; + private $used_credit = []; public function __construct(Invoice $invoice) { @@ -52,15 +53,15 @@ class AutoBillInvoice extends AbstractService if ((int)$this->invoice->balance == 0) return $this->invoice->service()->markPaid()->save(); - $this->applyCreditPayment(); + $this->applyCreditPayment(); //if the credits cover the payments, we stop here, build the payment with credits and exit early /* Determine $amount */ if ($this->invoice->partial > 0) $amount = $this->invoice->partial; - elseif($this->invoice->balance >0) + elseif($this->invoice->balance > 0) $amount = $this->invoice->balance; else - return $this->invoice; + return $this->finalizePaymentUsingCredits(); $gateway_token = $this->getGateway($amount); @@ -86,6 +87,56 @@ class AutoBillInvoice extends AbstractService return $this->invoice; } + /** + * If the credits on file cover the invoice amount + * the we create a matching payment using credits only + * + * @return Invoice $invoice + */ + private function finalizePaymentUsingCredits() + { + info("finalizing"); + info(print_r($this->used_credit,1)); + $amount = array_sum(array_column($this->used_credit, 'amount')); + info("amount {$amount}"); + + $payment = PaymentFactory::create($this->invoice->company_id, $this->invoice->user_id); + $payment->amount = $amount; + $payment->client_id = $this->invoice->client_id; + $payment->currency_id = $this->invoice->client->getSetting('currency_id'); + $payment->date = now(); + $payment->status_id = Payment::STATUS_COMPLETED; + $payment->service()->applyNumber()->save(); + + $payment->invoices()->attach($this->invoice->id, ['amount' => $amount]); + + $this->invoice->service()->setStatus(Invoice::STATUS_PAID)->save(); + + foreach($this->used_credit as $credit) + { + $payment->credits()->attach($credit['credit_id'], ['amount' => $credit['amount']]); + } + + $payment->ledger() + ->updatePaymentBalance($amount * -1) + ->save(); + + $this->invoice->client->service() + ->updateBalance($amount * -1) + ->updatePaidToDate($amount) + ->adjustCreditBalance($amount * -1) + ->save(); + + $this->invoice->ledger() + ->updateInvoiceBalance($amount * -1, 'Invoice payment using Credit') + ->updateCreditBalance($amount * -1, 'Credits used to pay down Invoice ' . $this->invoice->number) + ->save(); + + event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars())); + + return $this->invoice->service()->setStatus(Invoice::STATUS_PAID)->save(); + } + /** * Applies credits to a payment prior to push * to the payment gateway @@ -112,48 +163,50 @@ class AutoBillInvoice extends AbstractService $is_partial_amount = true; } - $this->payment = PaymentFactory::create($this->client->company_id, $this->client->user_id); - $this->payment->save(); + $this->used_credit = []; - $available_credits->each(function($credit) use($is_partial_amount){ + foreach($available_credits as $key => $credit) { - //todo need to iterate until the partial or balance is completely consumed - //by the credit, any remaining balance is then dealt with by - //the gateway - //each time a credit is applied SAVE the invoice + if($is_partial_amount) { + + //more credit than needed + if($credit->balance >= $this->invoice->partial) { + $this->used_credit[$key]['credit_id'] = $credit->id; + $this->used_credit[$key]['amount'] = $this->invoice->partial; + $this->invoice->balance -= $this->invoice->partial; + $this->invoice->partial = 0; + break; + } + else { + $this->used_credit[$key]['credit_id'] = $credit->id; + $this->used_credit[$key]['amount'] = $credit->balance; + $this->invoice->partial -= $credit->balance; + $this->invoice->balance -= $credit->balance; + } + } + else { + + //more credit than needed + if($credit->balance >= $this->invoice->balance) { + $this->used_credit[$key]['credit_id'] = $credit->id; + $this->used_credit[$key]['amount'] = $this->invoice->balance; + $this->invoice->balance = 0; + break; + } + else { + $this->used_credit[$key]['credit_id'] = $credit->id; + $this->used_credit[$key]['amount'] = $credit->balance; + $this->invoice->balance -= $credit->balance; + } + + } + } - // if($credit->balance >= $amount){ - // //current credit covers the total amount - // } - //return false to exit each loop - }); return $this; } - private function buildPayment($credit, $is_partial_amount) - { - if($is_partial_amount) { - - if($this->invoice->partial >= $credit->balance) { - - $amount = $this->invoice->partial - $credit->balance; - $this->invoice->partial -= $amount; - - $this->payment->credits()->attach([ - $credit->id => ['amount' => $amount] - ]); - - $this->payment->invoice()->attach([ - $this->invoice->id => ['amount' => $amount] - ]); - - $this->applyPaymentToCredit($credit, $amount); - } - } - - } private function applyPaymentToCredit($credit, $amount) @@ -173,7 +226,6 @@ class AutoBillInvoice extends AbstractService $credit = $credit->calc()->getCredit(); - } /** diff --git a/app/Services/Ledger/LedgerService.php b/app/Services/Ledger/LedgerService.php index 4f34a6ba77..ace36ad43d 100644 --- a/app/Services/Ledger/LedgerService.php +++ b/app/Services/Ledger/LedgerService.php @@ -66,6 +66,8 @@ class LedgerService $company_ledger->save(); $this->entity->company_ledger()->save($company_ledger); + + return $this; } public function updateCreditBalance($adjustment, $notes = '') diff --git a/tests/MockAccountData.php b/tests/MockAccountData.php index 4d7209d7c3..31c88f16fd 100644 --- a/tests/MockAccountData.php +++ b/tests/MockAccountData.php @@ -227,12 +227,6 @@ trait MockAccountData $this->invoice = InvoiceFactory::create($this->company->id, $this->user->id); //stub the company and user_id $this->invoice->client_id = $this->client->id; - // $this->invoice = Invoice::factory()->create([ - // 'user_id' => $this->user->id, - // 'client_id' => $this->client->id, - // 'company_id' => $this->company->id, - // ]); - $this->invoice->line_items = $this->buildLineItems(); $this->invoice->uses_inclusive_taxes = false; @@ -316,7 +310,6 @@ trait MockAccountData $this->credit->uses_inclusive_taxes = false; $this->credit->save(); - $this->credit->service()->createInvitations()->markSent(); $this->credit_calc = new InvoiceSum($this->credit); @@ -325,6 +318,9 @@ trait MockAccountData $this->credit = $this->credit_calc->getCredit(); $this->credit->service()->markSent(); + $this->client->service()->adjustCreditBalance($this->credit->balance)->save(); + $this->credit->ledger()->updateCreditBalance($this->credit->balance)->save(); + $contacts = $this->invoice->client->contacts; $contacts->each(function ($contact) { diff --git a/tests/Unit/AutoBillInvoiceTest.php b/tests/Unit/AutoBillInvoiceTest.php new file mode 100644 index 0000000000..e31ddd2f15 --- /dev/null +++ b/tests/Unit/AutoBillInvoiceTest.php @@ -0,0 +1,57 @@ +makeTestData(); + } + + public function testAutoBillFunctionality() + { + + // info("client balance = {$this->client->balance}"); + // info("invoice balance = {$this->invoice->balance}"); + + + $this->assertEquals($this->client->balance, 10); + $this->assertEquals($this->client->paid_to_date, 0); + $this->assertEquals($this->client->credit_balance, 10); + + $this->invoice->service()->markSent()->autoBill()->save(); + + // info(print_r($this->invoice->payments()->first()->toArray(),1)); + $this->assertNotNull($this->invoice->payments()); + $this->assertEquals(10, $this->invoice->payments()->sum('payments.amount')); + + //info(print_r($this->invoice->payments()->get(),1)); + $this->assertEquals($this->client->balance, 0); + $this->assertEquals($this->client->paid_to_date, 10); + $this->assertEquals($this->client->credit_balance, 0); + } + +}