diff --git a/README.md b/README.md index f0776a9ae..6e333aa72 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 3444d6983..5c3473f15 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 798f780e2..f34980d6f 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 8649ccd1d..e0e95cbc1 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 7ea172cf2..855399842 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 9c7a0cb04..5f7bbd8ca 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 80846ac55..636c2ebe6 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 e36866299..b13d00743 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 6a1084bec..277b15a6b 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 76f893de1..3f6e13c43 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 dda5d1fcc..d8f5ead92 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 f31175598..314777d35 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 a7d4addf2..d5326910b 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 67868546c..dc068b1ae 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 9ea0baffa..000000000 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 b5458b710..000000000 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 e88127bbb..5141cd4d1 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 cac91786f..905c2b77f 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);