diff --git a/README.md b/README.md index f0776a9ae2..6e333aa72a 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ npm i npm run production ``` -Please Note: Your APP_KEY in the .env file is used to encrypt data, if you loose this you will not be able to run the application. +Please Note: Your APP_KEY in the .env file is used to encrypt data, if you lose this you will not be able to run the application. Run if you want to load sample data, remember to configure .env ``` diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 3444d6983e..5c3473f15e 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -291,7 +291,7 @@ class BaseController extends Controller * Thresholds for displaying large account on first load */ if (request()->has('first_load') && request()->input('first_load') == 'true') { - if (auth()->user()->getCompany()->invoices->count() > 1000) { + if (auth()->user()->getCompany()->invoices->count() > 1000 || auth()->user()->getCompany()->products->count() > 1000) { $data = $mini_load; } else { $data = $first_load; diff --git a/app/Http/Controllers/CompanyController.php b/app/Http/Controllers/CompanyController.php index 798f780e22..f34980d6f4 100644 --- a/app/Http/Controllers/CompanyController.php +++ b/app/Http/Controllers/CompanyController.php @@ -11,6 +11,7 @@ namespace App\Http\Controllers; +use App\DataMapper\CompanySettings; use App\DataMapper\DefaultSettings; use App\Http\Requests\Company\CreateCompanyRequest; use App\Http\Requests\Company\DestroyCompanyRequest; @@ -218,6 +219,7 @@ class CompanyController extends BaseController 'is_locked' => 0, 'permissions' => '', 'settings' => null, + 'notifications' => CompanySettings::notificationDefaults(), //'settings' => DefaultSettings::userSettings(), ]); diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 8649ccd1d7..e0e95cbc19 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -662,6 +662,13 @@ class InvoiceController extends BaseController case 'download': return response()->download(TempFile::path($invoice->pdf_file_path()), basename($invoice->pdf_file_path())); break; + case 'restore': + $this->invoice_repo->restore($invoice); + + if (!$bulk) { + return $this->listResponse($invoice); + } + break; case 'archive': $this->invoice_repo->archive($invoice); diff --git a/app/Http/Requests/Payment/UpdatePaymentRequest.php b/app/Http/Requests/Payment/UpdatePaymentRequest.php index 7ea172cf2c..8553998427 100644 --- a/app/Http/Requests/Payment/UpdatePaymentRequest.php +++ b/app/Http/Requests/Payment/UpdatePaymentRequest.php @@ -38,6 +38,7 @@ class UpdatePaymentRequest extends Request { return [ 'invoices' => ['required','array','min:1',new PaymentAppliedValidAmount,new ValidCreditsPresentRule], + 'invoices.*.invoice_id' => 'distinct', 'documents' => 'mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx', ]; } @@ -77,4 +78,11 @@ class UpdatePaymentRequest extends Request } $this->replace($input); } + + public function messages() + { + return [ + 'distinct' => 'Attemping duplicate payment on the same invoice Invoice', + ]; + } } diff --git a/app/Listeners/Invoice/UpdateInvoiceActivity.php b/app/Listeners/Invoice/UpdateInvoiceActivity.php index 9c7a0cb040..5f7bbd8cad 100644 --- a/app/Listeners/Invoice/UpdateInvoiceActivity.php +++ b/app/Listeners/Invoice/UpdateInvoiceActivity.php @@ -50,7 +50,8 @@ class UpdateInvoiceActivity implements ShouldQueue $fields->user_id = $event->invoice->user_id; $fields->company_id = $event->invoice->company_id; $fields->activity_type_id = Activity::UPDATE_INVOICE; - + $fields->invoice_id = $event->invoice->id; + $this->activity_repo->save($fields, $event->invoice); } } diff --git a/app/Models/Client.php b/app/Models/Client.php index 80846ac55c..636c2ebe64 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -251,12 +251,12 @@ class Client extends BaseModel implements HasLocalePreference * @param float $amount Adjustment amount * @return Client */ - public function processUnappliedPayment($amount) :Client - { - return $this->service()->updatePaidToDate($amount) - ->adjustCreditBalance($amount) - ->save(); - } + // public function processUnappliedPayment($amount) :Client + // { + // return $this->service()->updatePaidToDate($amount) + // ->adjustCreditBalance($amount) + // ->save(); + // } /** * diff --git a/app/Models/Presenters/ClientPresenter.php b/app/Models/Presenters/ClientPresenter.php index e368662995..b13d007437 100644 --- a/app/Models/Presenters/ClientPresenter.php +++ b/app/Models/Presenters/ClientPresenter.php @@ -25,15 +25,20 @@ class ClientPresenter extends EntityPresenter */ public function name() { + if($this->entity->name) + return $this->entity->name; + $contact = $this->entity->primary_contact->first(); $contact_name = 'No Contact Set'; - if ($contact) { + if ($contact && (strlen($contact->first_name) >=1 || strlen($contact->last_name) >=1)) { $contact_name = $contact->first_name. ' '. $contact->last_name; } + elseif($contact && (strlen($contact->email))) + $contact_name = $contact->email; - return $this->entity->name ?: $contact_name; + return $contact_name; } public function primary_contact_name() diff --git a/app/Repositories/BaseRepository.php b/app/Repositories/BaseRepository.php index 6a1084bec6..277b15a6b9 100644 --- a/app/Repositories/BaseRepository.php +++ b/app/Repositories/BaseRepository.php @@ -263,7 +263,7 @@ class BaseRepository //make sure we are creating an invite for a contact who belongs to the client only! $contact = ClientContact::find($invitation['client_contact_id']); - if ($model->client_id == $contact->client_id); + if ($contact && $model->client_id == $contact->client_id); { $new_invitation = $invitation_factory_class::create($model->company_id, $model->user_id); $new_invitation->{$lcfirst_resource_id} = $model->id; diff --git a/app/Repositories/PaymentRepository.php b/app/Repositories/PaymentRepository.php index 76f893de17..3f6e13c434 100644 --- a/app/Repositories/PaymentRepository.php +++ b/app/Repositories/PaymentRepository.php @@ -46,7 +46,6 @@ class PaymentRepository extends BaseRepository /** * Saves and updates a payment. //todo refactor to handle refunds and payments. * - * * @param array $data the request object * @param Payment $payment The Payment object * @return Payment|null Payment $payment @@ -57,61 +56,85 @@ class PaymentRepository extends BaseRepository return $this->applyPayment($data, $payment); } - return $this->refundPayment($data, $payment); + return $payment; } /** * Handles a positive payment request - * @param array $data The data object - * @param Payment $payment The $payment entity + * @param array $data The data object + * @param Payment $payment The $payment entity * @return Payment The updated/created payment object */ private function applyPayment(array $data, Payment $payment): ?Payment { + //check currencies here and fill the exchange rate data if necessary if (!$payment->id) { $this->processExchangeRates($data, $payment); + + /*We only update the paid to date ONCE per payment*/ + if (array_key_exists('invoices', $data) && is_array($data['invoices']) && count($data['invoices']) > 0) { + + if($data['amount'] == '') + $data['amount'] = array_sum(array_column($data['invoices'], 'amount')); + + $client = Client::find($data['client_id']); + $client->service()->updatePaidToDate($data['amount'])->save(); + + } } + /*Fill the payment*/ $payment->fill($data); - $payment->status_id = Payment::STATUS_COMPLETED; - $payment->save(); + + /*Ensure payment number generated*/ if (!$payment->number || strlen($payment->number) == 0) { $payment->number = $payment->client->getNextPaymentNumber($payment->client); } - $payment->client->service()->updatePaidToDate($payment->amount)->save(); - $invoice_totals = 0; $credit_totals = 0; - if (array_key_exists('invoices', $data) && is_array($data['invoices'])) { + /*Iterate through invoices and apply payments*/ + if (array_key_exists('invoices', $data) && is_array($data['invoices']) && count($data['invoices']) > 0) { $invoice_totals = array_sum(array_column($data['invoices'], 'amount')); $invoices = Invoice::whereIn('id', array_column($data['invoices'], 'invoice_id'))->get(); $payment->invoices()->saveMany($invoices); + info("iterating through payment invoices"); + foreach ($data['invoices'] as $paid_invoice) { - $invoice = Invoice::whereId($paid_invoice['invoice_id'])->first(); + + $invoice = Invoice::whereId($paid_invoice['invoice_id'])->with('client')->first(); + + info("current client balance = {$invoice->client->balance}"); if ($invoice) { - $invoice->service()->applyPayment($payment, $paid_invoice['amount'])->save(); + + info("apply payment amount {$paid_invoice['amount']}"); + + $invoice = $invoice->service()->markSent()->applyPayment($payment, $paid_invoice['amount'])->save(); + + info("after processing invoice the client balance is now {$invoice->client->balance}"); + } + + } } else { - //payment is made, but not to any invoice, therefore we are applying the payment to the clients credit - $payment->client->processUnappliedPayment($payment->amount); + //payment is made, but not to any invoice, therefore we are applying the payment to the clients paid_to_date only + $payment->client->service()->updatePaidToDate($payment->amount)->save(); } if (array_key_exists('credits', $data) && is_array($data['credits'])) { $credit_totals = array_sum(array_column($data['credits'], 'amount')); $credits = Credit::whereIn('id', $this->transformKeys(array_column($data['credits'], 'credit_id')))->get(); - $payment->credits()->saveMany($credits); foreach ($data['credits'] as $paid_credit) { @@ -136,57 +159,9 @@ class PaymentRepository extends BaseRepository } $payment->save(); - return $payment->fresh(); } - /** - * @deprecated Refundable trait replaces this. - */ - private function refundPayment(array $data, Payment $payment): string - { - // //temp variable to sum the total refund/credit amount - // $invoice_total_adjustment = 0; - - // if (array_key_exists('invoices', $data) && is_array($data['invoices'])) { - - // foreach ($data['invoices'] as $adjusted_invoice) { - - // $invoice = Invoice::whereId($adjusted_invoice['invoice_id'])->first(); - - // $invoice_total_adjustment += $adjusted_invoice['amount']; - - // if (array_key_exists('credits', $adjusted_invoice)) { - - // //process and insert credit notes - // foreach ($adjusted_invoice['credits'] as $credit) { - - // $credit = $this->credit_repo->save($credit, CreditFactory::create(auth()->user()->id, auth()->user()->id), $invoice); - - // } - - // } else { - // //todo - generate Credit Note for $amount on $invoice - the assumption here is that it is a FULL refund - // } - - // } - - // if (array_key_exists('amount', $data) && $data['amount'] != $invoice_total_adjustment) - // return 'Amount must equal the sum of invoice adjustments'; - // } - - - // //adjust applied amount - // $payment->applied += $invoice_total_adjustment; - - // //adjust clients paid to date - // $client = $payment->client; - // $client->paid_to_date += $invoice_total_adjustment; - - // $payment->save(); - // $client->save(); - } - /** * If the client is paying in a currency other than diff --git a/app/Services/Invoice/ApplyPayment.php b/app/Services/Invoice/ApplyPayment.php index dda5d1fcce..d8f5ead921 100644 --- a/app/Services/Invoice/ApplyPayment.php +++ b/app/Services/Invoice/ApplyPayment.php @@ -37,7 +37,14 @@ class ApplyPayment extends AbstractService ->ledger() ->updatePaymentBalance($this->payment_amount*-1); - $this->payment->client->service()->updateBalance($this->payment_amount*-1)->save(); + info("apply paymenet method - current client balance = {$this->payment->client->balance}"); + + info("reducing client balance by payment amount {$this->payment_amount}"); + + $this->invoice->client->service()->updateBalance($this->payment_amount*-1)->save(); +// $this->invoice->client->service()->updateBalance($this->payment_amount*-1)->updatePaidToDate($this->payment_amount)->save(); + + info("post client balance = {$this->invoice->client->balance}"); /* Update Pivot Record amount */ $this->payment->invoices->each(function ($inv) { @@ -47,6 +54,10 @@ class ApplyPayment extends AbstractService } }); + $this->invoice->fresh('client'); + + info("1 end of apply payment method the client balnace = {$this->invoice->client->balance}"); + if ($this->invoice->hasPartial()) { //is partial and amount is exactly the partial amount if ($this->invoice->partial == $this->payment_amount) { @@ -61,9 +72,11 @@ class ApplyPayment extends AbstractService } elseif ($this->payment_amount < $this->invoice->balance) { //partial invoice payment made $this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($this->payment_amount*-1); } + info("2 end of apply payment method the client balnace = {$this->invoice->client->balance}"); $this->invoice->service()->applyNumber()->save(); + info("3 end of apply payment method the client balnace = {$this->invoice->client->balance}"); return $this->invoice; } } diff --git a/app/Services/Invoice/MarkSent.php b/app/Services/Invoice/MarkSent.php index f31175598b..314777d357 100644 --- a/app/Services/Invoice/MarkSent.php +++ b/app/Services/Invoice/MarkSent.php @@ -48,10 +48,14 @@ class MarkSent extends AbstractService ->setDueDate() ->save(); + info("marking invoice sent currently client balance = {$this->client->balance}"); + $this->client->service()->updateBalance($this->invoice->balance)->save(); + info("after marking invoice sent currently client balance = {$this->client->balance}"); + $this->invoice->ledger()->updateInvoiceBalance($this->invoice->balance); - return $this->invoice; + return $this->invoice->fresh(); } } diff --git a/app/Utils/Traits/Notifications/UserNotifies.php b/app/Utils/Traits/Notifications/UserNotifies.php index a7d4addf2c..d5326910b2 100644 --- a/app/Utils/Traits/Notifications/UserNotifies.php +++ b/app/Utils/Traits/Notifications/UserNotifies.php @@ -50,6 +50,9 @@ trait UserNotifies $notifiable_methods = []; $notifications = $company_user->notifications; + if(!$notifications) + return []; + if ($entity->user_id == $company_user->_user_id || $entity->assigned_user_id == $company_user->user_id) { array_push($required_permissions, "all_user_notifications"); } diff --git a/database/factories/AccountFactory.php b/database/factories/AccountFactory.php index 67868546c1..dc068b1ae9 100644 --- a/database/factories/AccountFactory.php +++ b/database/factories/AccountFactory.php @@ -7,5 +7,6 @@ $factory->define(App\Models\Account::class, function (Faker $faker) { return [ 'default_company_id' => 1, 'key' => Str::random(32), + 'report_errors' => 1, ]; }); diff --git a/public/images/invoiceninja-black-logo-vertical.png b/public/images/invoiceninja-black-logo-vertical.png deleted file mode 100644 index 9ea0baffa9..0000000000 Binary files a/public/images/invoiceninja-black-logo-vertical.png and /dev/null differ diff --git a/public/images/invoiceninja-black-logo.png b/public/images/invoiceninja-black-logo.png deleted file mode 100644 index b5458b7102..0000000000 Binary files a/public/images/invoiceninja-black-logo.png and /dev/null differ diff --git a/public/images/logo.png b/public/images/logo.png index e88127bbbc..5141cd4d1e 100644 Binary files a/public/images/logo.png and b/public/images/logo.png differ diff --git a/tests/Integration/CompanyLedgerTest.php b/tests/Integration/CompanyLedgerTest.php index cac91786f1..905c2b77f2 100644 --- a/tests/Integration/CompanyLedgerTest.php +++ b/tests/Integration/CompanyLedgerTest.php @@ -171,6 +171,7 @@ class CompanyLedgerTest extends TestCase $invoice = Invoice::find($this->decodePrimaryKey($acc['data']['id'])); + //client->balance should = 10 $invoice->service()->markSent()->save(); $this->assertEquals($invoice->client->balance, 10); @@ -193,6 +194,7 @@ class CompanyLedgerTest extends TestCase $invoice = Invoice::find($this->decodePrimaryKey($acc['data']['id'])); $invoice->service()->markSent()->save(); + //client balance should = 20 $this->assertEquals($invoice->client->balance, 20); $invoice_ledger = $invoice->company_ledger->sortByDesc('id')->first(); @@ -211,7 +213,6 @@ class CompanyLedgerTest extends TestCase ], ], 'date' => '2020/12/11', - ]; $response = $this->withHeaders([ @@ -224,7 +225,8 @@ class CompanyLedgerTest extends TestCase $payment = Payment::find($this->decodePrimaryKey($acc['data']['id'])); $payment_ledger = $payment->company_ledger->sortByDesc('id')->first(); - $invoice->fresh(); + +info($payment->client->balance); $this->assertEquals($payment->client->balance, $payment_ledger->balance); $this->assertEquals($payment->client->paid_to_date, 10);