mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
v5.3.0
This commit is contained in:
commit
6e951a1739
2
.env.ci
2
.env.ci
@ -5,7 +5,7 @@ APP_DEBUG=true
|
||||
APP_URL=http://ninja.test
|
||||
MULTI_DB_ENABLED=false
|
||||
# database
|
||||
DB_CONNECTION=db-ninja-01
|
||||
DB_CONNECTION=mysql
|
||||
DB_DATABASE1=ninja
|
||||
DB_USERNAME1=root
|
||||
DB_PASSWORD1=ninja
|
||||
|
18
.env.example
18
.env.example
@ -5,20 +5,14 @@ APP_DEBUG=false
|
||||
|
||||
APP_URL=http://localhost
|
||||
|
||||
DB_CONNECTION=db-ninja-01
|
||||
DB_CONNECTION=mysql
|
||||
MULTI_DB_ENABLED=false
|
||||
|
||||
DB_HOST1=localhost
|
||||
DB_DATABASE1=ninja
|
||||
DB_USERNAME1=ninja
|
||||
DB_PASSWORD1=ninja
|
||||
DB_PORT1=3306
|
||||
|
||||
DB_HOST2=localhost
|
||||
DB_DATABASE2=ninja2
|
||||
DB_USERNAME2=ninja
|
||||
DB_PASSWORD2=ninja
|
||||
DB_PORT2=3306
|
||||
DB_HOST=localhost
|
||||
DB_DATABASE=ninja
|
||||
DB_USERNAME=ninja
|
||||
DB_PASSWORD=ninja
|
||||
DB_PORT=3306
|
||||
|
||||
DEMO_MODE=false
|
||||
|
||||
|
6
.github/workflows/phpunit.yml
vendored
6
.github/workflows/phpunit.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
operating-system: ['ubuntu-18.04', 'ubuntu-20.04']
|
||||
php-versions: ['7.3','7.4','8.0']
|
||||
php-versions: ['7.4','8.0']
|
||||
phpunit-versions: ['latest']
|
||||
|
||||
env:
|
||||
@ -49,6 +49,10 @@ jobs:
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
|
||||
steps:
|
||||
- name: Add hosts to /etc/hosts
|
||||
run: |
|
||||
sudo echo "127.0.0.1 ninja.test" | sudo tee -a /etc/hosts
|
||||
|
||||
- name: Start mysql service
|
||||
run: |
|
||||
sudo /etc/init.d/mysql start
|
||||
|
37
.github/workflows/release.yml
vendored
37
.github/workflows/release.yml
vendored
@ -49,37 +49,16 @@ jobs:
|
||||
sudo rm -rf bootstrap/cache/*
|
||||
sudo rm -rf node_modules
|
||||
sudo rm -rf .git
|
||||
# - name: Prune Git History
|
||||
# run: |
|
||||
# sudo git gc
|
||||
# sudo git gc --aggressive
|
||||
# sudo git prune
|
||||
- name: Build project # This would actually build your project, using zip for an example artifact
|
||||
|
||||
- name: Build project
|
||||
run: |
|
||||
zip -r ./invoiceninja.zip .* -x "../*"
|
||||
|
||||
- name: Get tag name
|
||||
id: get_tag_name
|
||||
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}-release
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ steps.get_tag_name.outputs.VERSION }}
|
||||
release_name: Release ${{ steps.get_tag_name.outputs.VERSION }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Upload Release Asset
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.RELEASE_TOKEN}}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||
asset_path: ./invoiceninja.zip
|
||||
asset_name: invoiceninja.zip
|
||||
asset_content_type: application/zip
|
||||
files: |
|
||||
invoiceninja.zip
|
64
CHANGELOG.md
64
CHANGELOG.md
@ -1,64 +0,0 @@
|
||||
# Release notes
|
||||
|
||||
## [Unreleased (daily channel)](https://github.com/invoiceninja/invoiceninja/tree/v5-develop)
|
||||
|
||||
## [v5.2.0-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.2.0-release)
|
||||
## Added:
|
||||
- Timezone Offset: Schedule emails based on timezone and time offsets.
|
||||
- Force client country to system country if none is set.
|
||||
- GMail Oauth via web
|
||||
|
||||
## Fixed:
|
||||
- Add Cache-control: no-cache to prevent overaggressive caching of assets
|
||||
- Improved labelling in the settings (client portal)
|
||||
- Client portal: Multiple accounts access improvements (#5703)
|
||||
- Client portal: "Credits" updates (#5734)
|
||||
- Client portal: Make sidebar white color, in order to make logo displaying more simple. (#5753)
|
||||
- Inject small delay into emails to allow all resources to be produced (ie PDFs) prior to sending
|
||||
- Fixes for endless reminders not firing
|
||||
|
||||
## [v5.1.56-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.1.56-release)
|
||||
## Fixed:
|
||||
- Fix User created/updated/deleted Actvity display format
|
||||
- Fix for Stripe autobill / token regression
|
||||
|
||||
## Added:
|
||||
- Invoice / Quote / Credit created notifications
|
||||
- Logout route - deletes all auth tokens
|
||||
|
||||
## [v5.1.54-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.1.54-release)
|
||||
## Fixed:
|
||||
- Fixes for e-mails, encoding & parsing invalid HTML
|
||||
|
||||
## [v5.1.50-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.1.50-release)
|
||||
## Fixed:
|
||||
- Refactor of e-mail templates
|
||||
- Client portal: Invoices & recurring invoices are now sorted by date (by default)
|
||||
|
||||
## Added:
|
||||
- Public notes of entities will now show in #footer section of designs (previously totals table).
|
||||
|
||||
## [v5.1.47-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.1.47-release)
|
||||
|
||||
### Added:
|
||||
- Subscriptions are now going to show the frequency in the table (#5412)
|
||||
- Subscriptions: During upgrade webhook request message will be shown for easier debugging (#5411)
|
||||
- PDF: Custom fields now will be shared across invoices, quotes & credits (#5410)
|
||||
- Client portal: Invoices are now sorted in the descending order (#5408)
|
||||
- Payments: ACH notification after the initial bank account connecting process (#5405)
|
||||
|
||||
### Fixed:
|
||||
- Fixes for counters where patterns without {$counter} could causes endless recursion.
|
||||
- Fixes for surcharge tax displayed amount on PDF.
|
||||
- Fixes for custom designs not rendering the custom template
|
||||
- Fixes for missing bulk actions on Subscriptions
|
||||
- Fixes CSS padding on the show page for recurring invoices (#5412)
|
||||
- Fixes for rendering invalid HTML & parsing invalid XML (#5395)
|
||||
|
||||
### Removed:
|
||||
- Removed one-time payments table (#5412)
|
||||
|
||||
## v5.1.43
|
||||
|
||||
### Fixed:
|
||||
- Whitelabel regression.
|
38
README.md
38
README.md
@ -4,15 +4,35 @@
|
||||
|
||||

|
||||

|
||||
[](https://www.codacy.com/gh/turbo124/invoiceninja/dashboard?utm_source=github.com&utm_medium=referral&utm_content=turbo124/invoiceninja&utm_campaign=Badge_Grade)
|
||||
|
||||
[](https://www.codacy.com/app/turbo124/invoiceninja?utm_source=github.com&utm_medium=referral&utm_content=invoiceninja/invoiceninja&utm_campaign=Badge_Grade)
|
||||
# Invoice Ninja 5
|
||||
|
||||
# Invoice Ninja version 5!
|
||||
## [Hosted](https://www.invoiceninja.com) | [Self-Hosted](https://www.invoiceninja.org)
|
||||
|
||||
### We're on Slack, join us at [slack.invoiceninja.com](http://slack.invoiceninja.com), [forum.invoiceninja.com](https://forum.invoiceninja.com) or if you like [StackOverflow](https://stackoverflow.com/tags/invoice-ninja/)
|
||||
|
||||
Just make sure to add the `invoice-ninja` tag to your question.
|
||||
|
||||
## Introduction
|
||||
|
||||
Version 5 of Invoice Ninja is here! We've taken the best parts of version 4 and bolted on all of the most requested features to produce a invoicing application like no other.
|
||||
|
||||
The new interface has a lot more functionality so it isn't a carbon copy of v4, but once you get used to the new layout and functionality we are sure you will love it!
|
||||
|
||||
## Referral Program
|
||||
* Earn 50% of Pro & Enterprise Plans up to 4 years - [Learn more](https://www.invoiceninja.com/referral-program/)
|
||||
|
||||
## Recommended Providers
|
||||
* [Stripe](https://stripe.com/)
|
||||
* [Postmark](https://postmarkapp.com/)
|
||||
|
||||
## Development
|
||||
* [API Documentation](https://app.swaggerhub.com/apis/invoiceninja/invoiceninja)
|
||||
* [APP Documentation](https://invoiceninja.github.io/)
|
||||
|
||||
## Quick Start
|
||||
|
||||
Currently the client portal and API are of alpha quality, to get started:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/invoiceninja/invoiceninja.git
|
||||
git checkout v5-stable
|
||||
@ -69,6 +89,8 @@ To improve chances of PRs being merged please include tests to ensure your code
|
||||
|
||||
API documentation is hosted using Swagger and can be found [HERE](https://app.swaggerhub.com/apis/invoiceninja/invoiceninja)
|
||||
|
||||
Installation, Configuration and Troubleshooting documentation can be found [HERE] (https://invoiceninja.github.io)
|
||||
|
||||
## Credits
|
||||
* [Hillel Coren](https://hillelcoren.com/)
|
||||
* [David Bomba](https://github.com/turbo124)
|
||||
@ -83,12 +105,10 @@ API documentation is hosted using Swagger and can be found [HERE](https://app.sw
|
||||
* [Clemens Mol](https://github.com/clemensmol)
|
||||
* [Benjamin Beganović](https://github.com/beganovich)
|
||||
|
||||
## Current work in progress
|
||||
## Security
|
||||
|
||||
Invoice Ninja is currently being written in a combination of Laravel for the API and Client Portal and Flutter for the front end management console. This will allow an immersive and consistent experience across any device: mobile, tablet or desktop.
|
||||
|
||||
To manage our workflow we will be creating separate branches for the client (Flutter) and server (Laravel API / Client Portal) and merge these into a release branch for deployments.
|
||||
If you find a security issue with this application please send an email to contact@invoiceninja.com Please follow responsible disclosure procedures if you detect an issue. For further information on responsible disclosure please read [here](https://cheatsheetseries.owasp.org/cheatsheets/Vulnerability_Disclosure_Cheat_Sheet.html)
|
||||
|
||||
## License
|
||||
Invoice Ninja is released under the Attribution Assurance License.
|
||||
Invoice Ninja is released under the Elastic License.
|
||||
See [LICENSE](LICENSE) for details.
|
||||
|
@ -1 +1 @@
|
||||
5.2.5
|
||||
5.3.0
|
@ -16,19 +16,21 @@ use App\Factory\ClientContactFactory;
|
||||
use App\Models\Account;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyLedger;
|
||||
use App\Models\Contact;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Paymentable;
|
||||
use App\Utils\Ninja;
|
||||
use DB;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Str;
|
||||
use Mail;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/*
|
||||
|
||||
@ -67,7 +69,7 @@ class CheckData extends Command
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'ninja:check-data {--database=} {--fix=} {--client_id=}';
|
||||
protected $signature = 'ninja:check-data {--database=} {--fix=} {--client_id=} {--paid_to_date=} {--client_balance=}';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
@ -78,8 +80,14 @@ class CheckData extends Command
|
||||
|
||||
protected $isValid = true;
|
||||
|
||||
protected $wrong_paid_to_dates = 0;
|
||||
|
||||
protected $wrong_balances = 0;
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$time_start = microtime(true);
|
||||
|
||||
$database_connection = $this->option('database') ? $this->option('database') : 'Connected to Default DB';
|
||||
$fix_status = $this->option('fix') ? "Fixing Issues" : "Just checking issues ";
|
||||
|
||||
@ -92,6 +100,7 @@ class CheckData extends Command
|
||||
$this->checkInvoiceBalances();
|
||||
$this->checkInvoicePayments();
|
||||
$this->checkPaidToDates();
|
||||
// $this->checkPaidToCompanyDates();
|
||||
$this->checkClientBalances();
|
||||
$this->checkContacts();
|
||||
$this->checkCompanyData();
|
||||
@ -103,6 +112,8 @@ class CheckData extends Command
|
||||
}
|
||||
|
||||
$this->logMessage('Done: '.strtoupper($this->isValid ? Account::RESULT_SUCCESS : Account::RESULT_FAILURE));
|
||||
$this->logMessage('Total execution time in seconds: ' . (microtime(true) - $time_start));
|
||||
|
||||
$errorEmail = config('ninja.error_email');
|
||||
|
||||
if ($errorEmail) {
|
||||
@ -223,38 +234,38 @@ class CheckData extends Command
|
||||
}
|
||||
}
|
||||
|
||||
// check for more than one primary contact
|
||||
$clients = DB::table('clients')
|
||||
->leftJoin('client_contacts', function ($join) {
|
||||
$join->on('client_contacts.client_id', '=', 'clients.id')
|
||||
->where('client_contacts.is_primary', '=', true)
|
||||
->whereNull('client_contacts.deleted_at');
|
||||
})
|
||||
->groupBy('clients.id')
|
||||
->havingRaw('count(client_contacts.id) != 1');
|
||||
// // check for more than one primary contact
|
||||
// $clients = DB::table('clients')
|
||||
// ->leftJoin('client_contacts', function ($join) {
|
||||
// $join->on('client_contacts.client_id', '=', 'clients.id')
|
||||
// ->where('client_contacts.is_primary', '=', true)
|
||||
// ->whereNull('client_contacts.deleted_at');
|
||||
// })
|
||||
// ->groupBy('clients.id')
|
||||
// ->havingRaw('count(client_contacts.id) != 1');
|
||||
|
||||
if ($this->option('client_id')) {
|
||||
$clients->where('clients.id', '=', $this->option('client_id'));
|
||||
}
|
||||
// if ($this->option('client_id')) {
|
||||
// $clients->where('clients.id', '=', $this->option('client_id'));
|
||||
// }
|
||||
|
||||
$clients = $clients->get(['clients.id', 'clients.user_id', 'clients.company_id']);
|
||||
$this->logMessage($clients->count().' clients without a single primary contact');
|
||||
// $clients = $clients->get(['clients.id', 'clients.user_id', 'clients.company_id']);
|
||||
// // $this->logMessage($clients->count().' clients without a single primary contact');
|
||||
|
||||
if ($this->option('fix') == 'true') {
|
||||
foreach ($clients as $client) {
|
||||
$this->logMessage("Fixing missing primary contacts #{$client->id}");
|
||||
// // if ($this->option('fix') == 'true') {
|
||||
// // foreach ($clients as $client) {
|
||||
// // $this->logMessage("Fixing missing primary contacts #{$client->id}");
|
||||
|
||||
$new_contact = ClientContactFactory::create($client->company_id, $client->user_id);
|
||||
$new_contact->client_id = $client->id;
|
||||
$new_contact->contact_key = Str::random(40);
|
||||
$new_contact->is_primary = true;
|
||||
$new_contact->save();
|
||||
}
|
||||
}
|
||||
// // $new_contact = ClientContactFactory::create($client->company_id, $client->user_id);
|
||||
// // $new_contact->client_id = $client->id;
|
||||
// // $new_contact->contact_key = Str::random(40);
|
||||
// // $new_contact->is_primary = true;
|
||||
// // $new_contact->save();
|
||||
// // }
|
||||
// // }
|
||||
|
||||
if ($clients->count() > 0) {
|
||||
$this->isValid = false;
|
||||
}
|
||||
// if ($clients->count() > 0) {
|
||||
// $this->isValid = false;
|
||||
// }
|
||||
}
|
||||
|
||||
private function checkFailedJobs()
|
||||
@ -303,63 +314,135 @@ class CheckData extends Command
|
||||
}
|
||||
}
|
||||
|
||||
// private function checkPaidToCompanyDates()
|
||||
// {
|
||||
// Company::cursor()->each(function ($company){
|
||||
|
||||
// $payments = Payment::where('is_deleted', 0)
|
||||
// ->where('company_id', $company->id)
|
||||
// ->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])
|
||||
// ->pluck('id');
|
||||
|
||||
// $unapplied = Payment::where('is_deleted', 0)
|
||||
// ->where('company_id', $company->id)
|
||||
// ->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
|
||||
// ->sum(\DB::Raw('amount - applied'));
|
||||
|
||||
// $paymentables = Paymentable::whereIn('payment_id', $payments)->sum(\DB::Raw('amount - refunded'));
|
||||
|
||||
// $client_paid_to_date = Client::where('company_id', $company->id)->where('is_deleted', 0)->withTrashed()->sum('paid_to_date');
|
||||
|
||||
// $total_payments = $paymentables + $unapplied;
|
||||
|
||||
// if (round($total_payments, 2) != round($client_paid_to_date, 2)) {
|
||||
// $this->wrong_paid_to_dates++;
|
||||
|
||||
// $this->logMessage($company->present()->name.' id = # '.$company->id." - Paid to date does not match Client Paid To Date = {$client_paid_to_date} - Invoice Payments = {$total_payments}");
|
||||
// }
|
||||
|
||||
// });
|
||||
|
||||
// }
|
||||
|
||||
private function checkPaidToDates()
|
||||
{
|
||||
$wrong_paid_to_dates = 0;
|
||||
$this->wrong_paid_to_dates = 0;
|
||||
$credit_total_applied = 0;
|
||||
|
||||
Client::withTrashed()->where('is_deleted', 0)->cursor()->each(function ($client) use ($wrong_paid_to_dates, $credit_total_applied) {
|
||||
|
||||
$clients = DB::table('clients')
|
||||
->leftJoin('payments', function($join) {
|
||||
$join->on('payments.client_id', '=', 'clients.id')
|
||||
->where('payments.is_deleted', 0)
|
||||
->whereIn('payments.status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED]);
|
||||
})
|
||||
->where('clients.is_deleted',0)
|
||||
->where('clients.updated_at', '>', now()->subDays(2))
|
||||
->groupBy('clients.id')
|
||||
->havingRaw('clients.paid_to_date != sum(coalesce(payments.amount - payments.refunded, 0))')
|
||||
->get(['clients.id', 'clients.paid_to_date', DB::raw('sum(coalesce(payments.amount - payments.refunded, 0)) as amount')]);
|
||||
|
||||
/* Due to accounting differences we need to perform a second loop here to ensure there actually is an issue */
|
||||
$clients->each(function ($client_record) use ($credit_total_applied) {
|
||||
|
||||
$client = Client::withTrashed()->find($client_record->id);
|
||||
|
||||
$total_invoice_payments = 0;
|
||||
|
||||
foreach ($client->invoices()->where('is_deleted', false)->where('status_id', '>', 1)->get() as $invoice) {
|
||||
|
||||
$total_amount = $invoice->payments()->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->get()->sum('pivot.amount');
|
||||
$total_refund = $invoice->payments()->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->get()->sum('pivot.refunded');
|
||||
$total_invoice_payments += $invoice->payments()
|
||||
->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
|
||||
->selectRaw('sum(paymentables.amount - paymentables.refunded) as p')
|
||||
->pluck('p')
|
||||
->first();
|
||||
|
||||
$total_invoice_payments += ($total_amount - $total_refund);
|
||||
}
|
||||
|
||||
//commented IN 27/06/2021 - sums ALL client payments AND the unapplied amounts to match the client paid to date
|
||||
$p = Payment::where('client_id', $client->id)
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
|
||||
->sum(DB::Raw('amount - applied'));
|
||||
|
||||
$total_invoice_payments += $p;
|
||||
|
||||
// 10/02/21
|
||||
foreach ($client->payments as $payment) {
|
||||
$credit_total_applied += $payment->paymentables()->where('paymentable_type', App\Models\Credit::class)->get()->sum(DB::raw('amount'));
|
||||
|
||||
$credit_total_applied += $payment->paymentables()
|
||||
->where('paymentable_type', App\Models\Credit::class)
|
||||
->selectRaw('sum(paymentables.amount - paymentables.refunded) as p')
|
||||
->pluck('p')
|
||||
->first();
|
||||
}
|
||||
|
||||
if ($credit_total_applied < 0) {
|
||||
$total_invoice_payments += $credit_total_applied;
|
||||
}
|
||||
|
||||
|
||||
if (round($total_invoice_payments, 2) != round($client->paid_to_date, 2)) {
|
||||
$wrong_paid_to_dates++;
|
||||
$this->wrong_paid_to_dates++;
|
||||
|
||||
$this->logMessage($client->present()->name.' id = # '.$client->id." - Paid to date does not match Client Paid To Date = {$client->paid_to_date} - Invoice Payments = {$total_invoice_payments}");
|
||||
|
||||
$this->isValid = false;
|
||||
|
||||
if($this->option('paid_to_date')){
|
||||
$this->logMessage("# {$client->id} " . $client->present()->name.' - '.$client->number." Fixing {$client->paid_to_date} to {$total_invoice_payments}");
|
||||
$client->paid_to_date = $total_invoice_payments;
|
||||
$client->save();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$this->logMessage("{$wrong_paid_to_dates} clients with incorrect paid to dates");
|
||||
$this->logMessage("{$this->wrong_paid_to_dates} clients with incorrect paid to dates");
|
||||
}
|
||||
|
||||
private function checkInvoicePayments()
|
||||
{
|
||||
$wrong_balances = 0;
|
||||
$wrong_paid_to_dates = 0;
|
||||
$this->wrong_balances = 0;
|
||||
|
||||
Client::cursor()->where('is_deleted', 0)->each(function ($client) use ($wrong_balances) {
|
||||
Client::cursor()->where('is_deleted', 0)->where('clients.updated_at', '>', now()->subDays(2))->each(function ($client) {
|
||||
|
||||
$client->invoices->where('is_deleted', false)->whereIn('status_id', '!=', Invoice::STATUS_DRAFT)->each(function ($invoice) use ($client) {
|
||||
|
||||
$total_paid = $invoice->payments()
|
||||
->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
|
||||
->selectRaw('sum(paymentables.amount - paymentables.refunded) as p')
|
||||
->pluck('p')
|
||||
->first();
|
||||
|
||||
// $total_paid = $total_amount - $total_refund;
|
||||
|
||||
$client->invoices->where('is_deleted', false)->whereIn('status_id', '!=', Invoice::STATUS_DRAFT)->each(function ($invoice) use ($wrong_balances, $client) {
|
||||
$total_amount = $invoice->payments()->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->get()->sum('pivot.amount');
|
||||
$total_refund = $invoice->payments()->get()->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->sum('pivot.refunded');
|
||||
$total_credit = $invoice->credits()->get()->sum('amount');
|
||||
|
||||
$total_paid = $total_amount - $total_refund;
|
||||
$calculated_paid_amount = $invoice->amount - $invoice->balance - $total_credit;
|
||||
|
||||
if ((string)$total_paid != (string)($invoice->amount - $invoice->balance - $total_credit)) {
|
||||
$wrong_balances++;
|
||||
$this->wrong_balances++;
|
||||
|
||||
$this->logMessage($client->present()->name.' - '.$client->id." - Total Amount = {$total_amount} != Calculated Total = {$calculated_paid_amount} - Total Refund = {$total_refund} Total credit = {$total_credit}");
|
||||
$this->logMessage($client->present()->name.' - '.$client->id." - Total Paid = {$total_paid} != Calculated Total = {$calculated_paid_amount}");
|
||||
|
||||
$this->isValid = false;
|
||||
}
|
||||
@ -367,15 +450,47 @@ class CheckData extends Command
|
||||
|
||||
});
|
||||
|
||||
$this->logMessage("{$wrong_balances} clients with incorrect invoice balances");
|
||||
$this->logMessage("{$this->wrong_balances} clients with incorrect invoice balances");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// $clients = DB::table('clients')
|
||||
// ->leftJoin('invoices', function($join){
|
||||
// $join->on('invoices.client_id', '=', 'clients.id')
|
||||
// ->where('invoices.is_deleted',0)
|
||||
// ->where('invoices.status_id', '>', 1);
|
||||
// })
|
||||
// ->leftJoin('credits', function($join){
|
||||
// $join->on('credits.client_id', '=', 'clients.id')
|
||||
// ->where('credits.is_deleted',0)
|
||||
// ->where('credits.status_id', '>', 1);
|
||||
// })
|
||||
// ->leftJoin('payments', function($join) {
|
||||
// $join->on('payments.client_id', '=', 'clients.id')
|
||||
// ->where('payments.is_deleted', 0)
|
||||
// ->whereIn('payments.status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED]);
|
||||
// })
|
||||
// ->where('clients.is_deleted',0)
|
||||
// //->where('clients.updated_at', '>', now()->subDays(2))
|
||||
// ->groupBy('clients.id')
|
||||
// ->havingRaw('sum(coalesce(invoices.amount - invoices.balance - credits.amount)) != sum(coalesce(payments.amount - payments.refunded, 0))')
|
||||
// ->get(['clients.id', DB::raw('sum(coalesce(invoices.amount - invoices.balance - credits.amount)) as invoice_amount'), DB::raw('sum(coalesce(payments.amount - payments.refunded, 0)) as payment_amount')]);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private function checkClientBalances()
|
||||
{
|
||||
$wrong_balances = 0;
|
||||
$wrong_paid_to_dates = 0;
|
||||
$this->wrong_balances = 0;
|
||||
$this->wrong_paid_to_dates = 0;
|
||||
|
||||
foreach (Client::cursor()->where('is_deleted', 0) as $client) {
|
||||
foreach (Client::cursor()->where('is_deleted', 0)->where('clients.updated_at', '>', now()->subDays(2)) as $client) {
|
||||
//$invoice_balance = $client->invoices->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance');
|
||||
$invoice_balance = Invoice::where('client_id', $client->id)->where('is_deleted', false)->where('status_id', '>', 1)->withTrashed()->sum('balance');
|
||||
$credit_balance = Credit::where('client_id', $client->id)->where('is_deleted', false)->withTrashed()->sum('balance');
|
||||
@ -387,14 +502,15 @@ class CheckData extends Command
|
||||
$ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first();
|
||||
|
||||
if ($ledger && (string) $invoice_balance != (string) $client->balance) {
|
||||
$wrong_paid_to_dates++;
|
||||
$this->wrong_paid_to_dates++;
|
||||
$this->logMessage($client->present()->name.' - '.$client->id." - calculated client balances do not match Invoice Balances = {$invoice_balance} - Client Balance = ".rtrim($client->balance, '0'). " Ledger balance = {$ledger->balance}");
|
||||
|
||||
$this->isValid = false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$this->logMessage("{$wrong_paid_to_dates} clients with incorrect client balances");
|
||||
$this->logMessage("{$this->wrong_paid_to_dates} clients with incorrect client balances");
|
||||
}
|
||||
|
||||
//fix for client balances =
|
||||
@ -406,27 +522,38 @@ class CheckData extends Command
|
||||
|
||||
private function checkInvoiceBalances()
|
||||
{
|
||||
$wrong_balances = 0;
|
||||
$wrong_paid_to_dates = 0;
|
||||
$this->wrong_balances = 0;
|
||||
$this->wrong_paid_to_dates = 0;
|
||||
|
||||
foreach (Client::where('is_deleted', 0)->cursor() as $client) {
|
||||
$invoice_balance = $client->invoices()->where('is_deleted', false)->where('status_id', '>', 1)->get()->sum('balance');
|
||||
$credit_balance = $client->credits()->where('is_deleted', false)->get()->sum('balance');
|
||||
|
||||
// if($client->balance != $invoice_balance)
|
||||
// $invoice_balance -= $credit_balance;//doesn't make sense to remove the credit amount
|
||||
foreach (Client::where('is_deleted', 0)->where('clients.updated_at', '>', now()->subDays(2))->cursor() as $client) {
|
||||
$invoice_balance = $client->invoices()->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance');
|
||||
$credit_balance = $client->credits()->where('is_deleted', false)->sum('balance');
|
||||
|
||||
$ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first();
|
||||
|
||||
if ($ledger && number_format($invoice_balance, 4) != number_format($client->balance, 4)) {
|
||||
$wrong_balances++;
|
||||
$this->wrong_balances++;
|
||||
$this->logMessage("# {$client->id} " . $client->present()->name.' - '.$client->number." - Balance Failure - Invoice Balances = {$invoice_balance} Client Balance = {$client->balance} Ledger Balance = {$ledger->balance}");
|
||||
|
||||
$this->isValid = false;
|
||||
|
||||
|
||||
if($this->option('client_balance')){
|
||||
|
||||
$this->logMessage("# {$client->id} " . $client->present()->name.' - '.$client->number." Fixing {$client->balance} to {$invoice_balance}");
|
||||
$client->balance = $invoice_balance;
|
||||
$client->save();
|
||||
|
||||
$ledger->adjustment = $invoice_balance;
|
||||
$ledger->balance = $invoice_balance;
|
||||
$ledger->notes = 'Ledger Adjustment';
|
||||
$ledger->save();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$this->logMessage("{$wrong_balances} clients with incorrect balances");
|
||||
$this->logMessage("{$this->wrong_balances} clients with incorrect balances");
|
||||
}
|
||||
|
||||
private function checkLogoFiles()
|
||||
@ -482,6 +609,7 @@ class CheckData extends Command
|
||||
'client',
|
||||
'client_contact',
|
||||
'payment',
|
||||
'recurring_invoice',
|
||||
],
|
||||
'invoices' => [
|
||||
'client',
|
||||
|
@ -91,6 +91,8 @@ class CreateAccount extends Command
|
||||
$account = Account::factory()->create();
|
||||
$company = Company::factory()->create([
|
||||
'account_id' => $account->id,
|
||||
'portal_domain' => config('ninja.app_url'),
|
||||
'portal_mode' => 'domain',
|
||||
]);
|
||||
|
||||
$account->default_company_id = $company->id;
|
||||
|
@ -14,9 +14,11 @@ namespace App\Console\Commands;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\DataMapper\FeesAndLimits;
|
||||
use App\Events\Invoice\InvoiceWasCreated;
|
||||
use App\Events\RecurringInvoice\RecurringInvoiceWasCreated;
|
||||
use App\Factory\GroupSettingFactory;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Factory\InvoiceItemFactory;
|
||||
use App\Factory\RecurringInvoiceFactory;
|
||||
use App\Factory\SubscriptionFactory;
|
||||
use App\Helpers\Invoice\InvoiceSum;
|
||||
use App\Jobs\Company\CreateCompanyTaskStatuses;
|
||||
@ -48,6 +50,7 @@ use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Str;
|
||||
use stdClass;
|
||||
|
||||
class CreateSingleAccount extends Command
|
||||
{
|
||||
@ -235,6 +238,9 @@ class CreateSingleAccount extends Command
|
||||
|
||||
$this->info('creating credit for client #' . $client->id);
|
||||
$this->createCredit($client);
|
||||
|
||||
$this->info('creating recurring invoice for client # ' . $client->id);
|
||||
$this->createRecurringInvoice($client);
|
||||
}
|
||||
|
||||
$this->createGateways($company, $user);
|
||||
@ -435,6 +441,10 @@ class CreateSingleAccount extends Command
|
||||
|
||||
$invoice = $invoice_calc->getInvoice();
|
||||
|
||||
if ($this->gateway === 'braintree') {
|
||||
$invoice->amount = 100; // Braintree sandbox only allows payments under 2,000 to complete successfully.
|
||||
}
|
||||
|
||||
$invoice->save();
|
||||
$invoice->service()->createInvitations()->markSent();
|
||||
|
||||
@ -619,7 +629,7 @@ class CreateSingleAccount extends Command
|
||||
|
||||
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
||||
|
||||
$fees_and_limits = new \stdClass;
|
||||
$fees_and_limits = new stdClass;
|
||||
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||
|
||||
$cg->fees_and_limits = $fees_and_limits;
|
||||
@ -642,7 +652,7 @@ class CreateSingleAccount extends Command
|
||||
|
||||
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
||||
|
||||
$fees_and_limits = new \stdClass;
|
||||
$fees_and_limits = new stdClass;
|
||||
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||
|
||||
$cg->fees_and_limits = $fees_and_limits;
|
||||
@ -663,7 +673,7 @@ class CreateSingleAccount extends Command
|
||||
|
||||
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
||||
|
||||
$fees_and_limits = new \stdClass;
|
||||
$fees_and_limits = new stdClass;
|
||||
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||
|
||||
$cg->fees_and_limits = $fees_and_limits;
|
||||
@ -684,11 +694,141 @@ class CreateSingleAccount extends Command
|
||||
|
||||
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
||||
|
||||
$fees_and_limits = new \stdClass;
|
||||
$fees_and_limits = new stdClass;
|
||||
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||
|
||||
$cg->fees_and_limits = $fees_and_limits;
|
||||
$cg->save();
|
||||
}
|
||||
|
||||
if (config('ninja.testvars.wepay') && ($this->gateway == 'all' || $this->gateway == 'wepay')) {
|
||||
$cg = new CompanyGateway;
|
||||
$cg->company_id = $company->id;
|
||||
$cg->user_id = $user->id;
|
||||
$cg->gateway_key = '8fdeed552015b3c7b44ed6c8ebd9e992';
|
||||
$cg->require_cvv = true;
|
||||
$cg->require_billing_address = true;
|
||||
$cg->require_shipping_address = true;
|
||||
$cg->update_details = true;
|
||||
$cg->config = encrypt(config('ninja.testvars.wepay'));
|
||||
$cg->save();
|
||||
|
||||
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
||||
|
||||
$fees_and_limits = new stdClass;
|
||||
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||
|
||||
$cg->fees_and_limits = $fees_and_limits;
|
||||
$cg->save();
|
||||
}
|
||||
|
||||
if (config('ninja.testvars.braintree') && ($this->gateway == 'all' || $this->gateway == 'braintree')) {
|
||||
$cg = new CompanyGateway;
|
||||
$cg->company_id = $company->id;
|
||||
$cg->user_id = $user->id;
|
||||
$cg->gateway_key = 'f7ec488676d310683fb51802d076d713';
|
||||
$cg->require_cvv = true;
|
||||
$cg->require_billing_address = true;
|
||||
$cg->require_shipping_address = true;
|
||||
$cg->update_details = true;
|
||||
$cg->config = encrypt(config('ninja.testvars.braintree'));
|
||||
$cg->save();
|
||||
|
||||
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
||||
|
||||
$fees_and_limits = new stdClass;
|
||||
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||
|
||||
$cg->fees_and_limits = $fees_and_limits;
|
||||
$cg->save();
|
||||
}
|
||||
|
||||
|
||||
if (config('ninja.testvars.paytrace.decrypted') && ($this->gateway == 'all' || $this->gateway == 'paytrace')) {
|
||||
$cg = new CompanyGateway;
|
||||
$cg->company_id = $company->id;
|
||||
$cg->user_id = $user->id;
|
||||
$cg->gateway_key = 'bbd736b3254b0aabed6ad7fda1298c88';
|
||||
$cg->require_cvv = true;
|
||||
$cg->require_billing_address = true;
|
||||
$cg->require_shipping_address = true;
|
||||
$cg->update_details = true;
|
||||
$cg->config = encrypt(config('ninja.testvars.paytrace.decrypted'));
|
||||
|
||||
$cg->save();
|
||||
|
||||
|
||||
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
||||
|
||||
$fees_and_limits = new stdClass;
|
||||
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||
|
||||
$cg->fees_and_limits = $fees_and_limits;
|
||||
$cg->save();
|
||||
}
|
||||
|
||||
if (config('ninja.testvars.mollie') && ($this->gateway == 'all' || $this->gateway == 'mollie')) {
|
||||
$cg = new CompanyGateway;
|
||||
$cg->company_id = $company->id;
|
||||
$cg->user_id = $user->id;
|
||||
$cg->gateway_key = '1bd651fb213ca0c9d66ae3c336dc77e8';
|
||||
$cg->require_cvv = true;
|
||||
$cg->require_billing_address = true;
|
||||
$cg->require_shipping_address = true;
|
||||
$cg->update_details = true;
|
||||
$cg->config = encrypt(config('ninja.testvars.mollie'));
|
||||
$cg->save();
|
||||
|
||||
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
||||
|
||||
$fees_and_limits = new stdClass;
|
||||
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||
|
||||
$cg->fees_and_limits = $fees_and_limits;
|
||||
$cg->save();
|
||||
}
|
||||
}
|
||||
|
||||
private function createRecurringInvoice($client)
|
||||
{
|
||||
$faker = Factory::create();
|
||||
|
||||
$invoice = RecurringInvoiceFactory::create($client->company->id, $client->user->id); //stub the company and user_id
|
||||
$invoice->client_id = $client->id;
|
||||
$dateable = Carbon::now()->subDays(rand(0, 90));
|
||||
$invoice->date = $dateable;
|
||||
|
||||
$invoice->line_items = $this->buildLineItems(rand(1, 10));
|
||||
$invoice->uses_inclusive_taxes = false;
|
||||
|
||||
if (rand(0, 1)) {
|
||||
$invoice->tax_name1 = 'GST';
|
||||
$invoice->tax_rate1 = 10.00;
|
||||
}
|
||||
|
||||
if (rand(0, 1)) {
|
||||
$invoice->tax_name2 = 'VAT';
|
||||
$invoice->tax_rate2 = 17.50;
|
||||
}
|
||||
|
||||
if (rand(0, 1)) {
|
||||
$invoice->tax_name3 = 'CA Sales Tax';
|
||||
$invoice->tax_rate3 = 5;
|
||||
}
|
||||
|
||||
$invoice->custom_value1 = $faker->date;
|
||||
$invoice->custom_value2 = rand(0, 1) ? 'yes' : 'no';
|
||||
|
||||
$invoice->status_id = RecurringInvoice::STATUS_ACTIVE;
|
||||
$invoice->save();
|
||||
|
||||
$invoice_calc = new InvoiceSum($invoice);
|
||||
$invoice_calc->build();
|
||||
|
||||
$invoice = $invoice_calc->getInvoice();
|
||||
|
||||
$invoice->save();
|
||||
|
||||
event(new RecurringInvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars()));
|
||||
}
|
||||
}
|
||||
|
95
app/Console/Commands/SubdomainFill.php
Normal file
95
app/Console/Commands/SubdomainFill.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class SubdomainFill extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'ninja:subdomain';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Pad subdomains';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
$c1 = Company::on('db-ninja-01')->whereNull('subdomain')->orWhere('subdomain', '')->get();
|
||||
$c2 = Company::on('db-ninja-02')->whereNull('subdomain')->orWhere('subdomain', '')->get();
|
||||
|
||||
|
||||
$c1->each(function ($company){
|
||||
|
||||
$company->subdomain = MultiDB::randomSubdomainGenerator();
|
||||
$company->save();
|
||||
|
||||
});
|
||||
|
||||
$c2->each(function ($company){
|
||||
|
||||
$company->subdomain = MultiDB::randomSubdomainGenerator();
|
||||
$company->save();
|
||||
|
||||
});
|
||||
|
||||
|
||||
// $db1 = Company::on('db-ninja-01')->get();
|
||||
|
||||
// $db1->each(function ($company){
|
||||
|
||||
// $db2 = Company::on('db-ninja-02a')->find($company->id);
|
||||
|
||||
// if($db2)
|
||||
// {
|
||||
// $db2->subdomain = $company->subdomain;
|
||||
// $db2->save();
|
||||
// }
|
||||
|
||||
// });
|
||||
|
||||
|
||||
// $db1 = null;
|
||||
// $db2 = null;
|
||||
|
||||
// $db2 = Company::on('db-ninja-02')->get();
|
||||
|
||||
// $db2->each(function ($company){
|
||||
|
||||
// $db1 = Company::on('db-ninja-01a')->find($company->id);
|
||||
|
||||
// if($db1)
|
||||
// {
|
||||
// $db1->subdomain = $company->subdomain;
|
||||
// $db1->save();
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
}
|
@ -64,12 +64,12 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
$schedule->job(new AutoBillCron)->dailyAt('00:30')->withoutOverlapping();
|
||||
|
||||
$schedule->job(new SchedulerCheck)->everyFiveMinutes();
|
||||
$schedule->job(new SchedulerCheck)->daily()->withoutOverlapping();
|
||||
|
||||
/* Run hosted specific jobs */
|
||||
if (Ninja::isHosted()) {
|
||||
|
||||
$schedule->job(new AdjustEmailQuota)->daily()->withoutOverlapping();
|
||||
$schedule->job(new AdjustEmailQuota)->dailyAt('23:00')->withoutOverlapping();
|
||||
$schedule->job(new SendFailedEmails)->daily()->withoutOverlapping();
|
||||
$schedule->command('ninja:check-data --database=db-ninja-02')->daily()->withoutOverlapping();
|
||||
|
||||
|
51
app/DataMapper/Analytics/LivePreview.php
Normal file
51
app/DataMapper/Analytics/LivePreview.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?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 App\DataMapper\Analytics;
|
||||
|
||||
use Turbo124\Beacon\ExampleMetric\GenericCounter;
|
||||
|
||||
class LivePreview extends GenericCounter
|
||||
{
|
||||
/**
|
||||
* The type of Sample.
|
||||
*
|
||||
* Monotonically incrementing counter
|
||||
*
|
||||
* - counter
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $type = 'counter';
|
||||
|
||||
/**
|
||||
* The name of the counter.
|
||||
* @var string
|
||||
*/
|
||||
public $name = 'live_preview.created';
|
||||
|
||||
/**
|
||||
* The datetime of the counter measurement.
|
||||
*
|
||||
* date("Y-m-d H:i:s")
|
||||
*
|
||||
* @var DateTime
|
||||
*/
|
||||
public $datetime;
|
||||
|
||||
/**
|
||||
* The increment amount... should always be
|
||||
* set to 0.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $metric = 0;
|
||||
}
|
@ -28,6 +28,7 @@ class CompanySettings extends BaseSettings
|
||||
public $lock_invoices = 'off'; //off,when_sent,when_paid //@implemented
|
||||
|
||||
public $enable_client_portal_tasks = false; //@ben to implement
|
||||
public $show_all_tasks_client_portal = 'invoiced'; // all, uninvoiced, invoiced
|
||||
public $enable_client_portal_password = false; //@implemented
|
||||
public $enable_client_portal = true; //@implemented
|
||||
public $enable_client_portal_dashboard = false; // @TODO There currently is no dashboard so this is pending
|
||||
@ -242,7 +243,7 @@ class CompanySettings extends BaseSettings
|
||||
public $font_size = 7; //@implemented
|
||||
public $primary_font = 'Roboto';
|
||||
public $secondary_font = 'Roboto';
|
||||
public $primary_color = '#142cb5';
|
||||
public $primary_color = '#298AAB';
|
||||
public $secondary_color = '#7081e0';
|
||||
|
||||
public $hide_paid_to_date = false; //@TODO where?
|
||||
@ -268,6 +269,7 @@ class CompanySettings extends BaseSettings
|
||||
public $hide_empty_columns_on_pdf = false;
|
||||
|
||||
public static $casts = [
|
||||
'show_all_tasks_client_portal' => 'string',
|
||||
'entity_send_time' => 'int',
|
||||
'shared_invoice_credit_counter' => 'bool',
|
||||
'reply_to_name' => 'string',
|
||||
@ -396,7 +398,6 @@ class CompanySettings extends BaseSettings
|
||||
'email_template_reminder2' => 'string',
|
||||
'email_template_reminder3' => 'string',
|
||||
'email_template_reminder_endless' => 'string',
|
||||
'enable_client_portal_password' => 'bool',
|
||||
'inclusive_taxes' => 'bool',
|
||||
'invoice_number_pattern' => 'string',
|
||||
'invoice_number_counter' => 'integer',
|
||||
@ -662,6 +663,7 @@ class CompanySettings extends BaseSettings
|
||||
'$task.line_total',
|
||||
],
|
||||
'total_columns' => [
|
||||
'$net_subtotal',
|
||||
'$subtotal',
|
||||
'$discount',
|
||||
'$custom_surcharge1',
|
||||
|
@ -19,6 +19,8 @@ class InvoiceItem
|
||||
|
||||
public $product_key = '';
|
||||
|
||||
public $product_cost = 0;
|
||||
|
||||
public $notes = '';
|
||||
|
||||
public $discount = 0;
|
||||
@ -57,6 +59,7 @@ class InvoiceItem
|
||||
'type_id' => 'string',
|
||||
'quantity' => 'float',
|
||||
'cost' => 'float',
|
||||
'product_cost' => 'float',
|
||||
'product_key' => 'string',
|
||||
'notes' => 'string',
|
||||
'discount' => 'float',
|
||||
|
@ -27,4 +27,7 @@ class PaymentMethodMeta
|
||||
|
||||
/** @var int */
|
||||
public $type;
|
||||
|
||||
/** @var string */
|
||||
public $state;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ namespace App\Exceptions;
|
||||
use App\Exceptions\FilePermissionsFailure;
|
||||
use App\Exceptions\InternalPDFFailure;
|
||||
use App\Exceptions\PhantomPDFFailure;
|
||||
use App\Exceptions\StripeConnectFailure;
|
||||
use App\Utils\Ninja;
|
||||
use Exception;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
@ -77,21 +78,27 @@ class Handler extends ExceptionHandler
|
||||
return;
|
||||
}
|
||||
|
||||
if(Ninja::isHosted()){
|
||||
if(Ninja::isHosted() && !($exception instanceof ValidationException)){
|
||||
|
||||
app('sentry')->configureScope(function (Scope $scope): void {
|
||||
|
||||
if(auth()->guard('contact') && auth()->guard('contact')->user())
|
||||
$name = 'hosted@invoiceninja.com';
|
||||
|
||||
if(auth()->guard('contact') && auth()->guard('contact')->user()){
|
||||
$name = "Contact = ".auth()->guard('contact')->user()->email;
|
||||
$key = auth()->guard('contact')->user()->company->account->key;
|
||||
elseif (auth()->guard('user') && auth()->guard('user')->user())
|
||||
}
|
||||
elseif (auth()->guard('user') && auth()->guard('user')->user()){
|
||||
$name = "Admin = ".auth()->guard('user')->user()->email;
|
||||
$key = auth()->user()->account->key;
|
||||
}
|
||||
else
|
||||
$key = 'Anonymous';
|
||||
|
||||
$scope->setUser([
|
||||
'id' => 'Hosted_User',
|
||||
'id' => $key,
|
||||
'email' => 'hosted@invoiceninja.com',
|
||||
'name' => $key,
|
||||
'name' => $name,
|
||||
]);
|
||||
});
|
||||
|
||||
@ -120,7 +127,6 @@ class Handler extends ExceptionHandler
|
||||
}
|
||||
}
|
||||
|
||||
// if(config('ninja.expanded_logging'))
|
||||
parent::report($exception);
|
||||
|
||||
}
|
||||
@ -182,7 +188,7 @@ class Handler extends ExceptionHandler
|
||||
} elseif ($exception instanceof NotFoundHttpException && $request->expectsJson()) {
|
||||
return response()->json(['message'=>'Route does not exist'], 404);
|
||||
} elseif ($exception instanceof MethodNotAllowedHttpException && $request->expectsJson()) {
|
||||
return response()->json(['message'=>'Method not support for this route'], 404);
|
||||
return response()->json(['message'=>'Method not supported for this route'], 404);
|
||||
} elseif ($exception instanceof ValidationException && $request->expectsJson()) {
|
||||
nlog($exception->validator->getMessageBag());
|
||||
return response()->json(['message' => 'The given data was invalid.', 'errors' => $exception->validator->getMessageBag()], 422);
|
||||
@ -191,7 +197,9 @@ class Handler extends ExceptionHandler
|
||||
} elseif ($exception instanceof GenericPaymentDriverFailure && $request->expectsJson()) {
|
||||
return response()->json(['message' => $exception->getMessage()], 400);
|
||||
} elseif ($exception instanceof GenericPaymentDriverFailure) {
|
||||
$data['message'] = $exception->getMessage();
|
||||
return response()->json(['message' => $exception->getMessage()], 400);
|
||||
} elseif ($exception instanceof StripeConnectFailure) {
|
||||
return response()->json(['message' => $exception->getMessage()], 400);
|
||||
}
|
||||
|
||||
return parent::render($request, $exception);
|
||||
|
10
app/Exceptions/StripeConnectFailure.php
Normal file
10
app/Exceptions/StripeConnectFailure.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class StripeConnectFailure extends Exception
|
||||
{
|
||||
// ..
|
||||
}
|
@ -28,6 +28,10 @@ class CloneQuoteToInvoiceFactory
|
||||
unset($quote_array['invoice_id']);
|
||||
unset($quote_array['id']);
|
||||
unset($quote_array['invitations']);
|
||||
unset($quote_array['terms']);
|
||||
unset($quote_array['public_notes']);
|
||||
unset($quote_array['footer']);
|
||||
unset($quote_array['design_id']);
|
||||
|
||||
foreach ($quote_array as $key => $value) {
|
||||
$invoice->{$key} = $value;
|
||||
|
@ -22,7 +22,7 @@ class ExpenseCategoryFactory
|
||||
$expense->company_id = $company_id;
|
||||
$expense->name = '';
|
||||
$expense->is_deleted = false;
|
||||
$expense->color = '#fff';
|
||||
$expense->color = '';
|
||||
|
||||
return $expense;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ class RecurringInvoiceToInvoiceFactory
|
||||
$invoice->public_notes = $recurring_invoice->public_notes;
|
||||
$invoice->private_notes = $recurring_invoice->private_notes;
|
||||
//$invoice->date = now()->format($client->date_format());
|
||||
$invoice->due_date = $recurring_invoice->calculateDueDate($recurring_invoice->next_send_date);
|
||||
//$invoice->due_date = $recurring_invoice->calculateDueDate(now());
|
||||
$invoice->is_deleted = $recurring_invoice->is_deleted;
|
||||
$invoice->line_items = $recurring_invoice->line_items;
|
||||
$invoice->tax_name1 = $recurring_invoice->tax_name1;
|
||||
@ -38,6 +38,8 @@ class RecurringInvoiceToInvoiceFactory
|
||||
$invoice->tax_rate2 = $recurring_invoice->tax_rate2;
|
||||
$invoice->tax_name3 = $recurring_invoice->tax_name3;
|
||||
$invoice->tax_rate3 = $recurring_invoice->tax_rate3;
|
||||
$invoice->total_taxes = $recurring_invoice->total_taxes;
|
||||
$invoice->subscription_id = $recurring_invoice->subscription_id;
|
||||
$invoice->custom_value1 = $recurring_invoice->custom_value1;
|
||||
$invoice->custom_value2 = $recurring_invoice->custom_value2;
|
||||
$invoice->custom_value3 = $recurring_invoice->custom_value3;
|
||||
|
@ -21,7 +21,7 @@ class TaskStatusFactory
|
||||
$task_status->user_id = $user_id;
|
||||
$task_status->company_id = $company_id;
|
||||
$task_status->name = '';
|
||||
$task_status->color = '#fff';
|
||||
$task_status->color = '';
|
||||
$task_status->status_order = 9999;
|
||||
|
||||
return $task_status;
|
||||
|
@ -76,6 +76,11 @@ class ClientFilters extends QueryFilters
|
||||
return $this->builder->where('id_number', $id_number);
|
||||
}
|
||||
|
||||
public function number(string $number):Builder
|
||||
{
|
||||
return $this->builder->where('number', $number);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter based on search text.
|
||||
*
|
||||
@ -92,9 +97,11 @@ class ClientFilters extends QueryFilters
|
||||
return $this->builder->where(function ($query) use ($filter) {
|
||||
$query->where('clients.name', 'like', '%'.$filter.'%')
|
||||
->orWhere('clients.id_number', 'like', '%'.$filter.'%')
|
||||
->orWhere('client_contacts.first_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('client_contacts.last_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('client_contacts.email', 'like', '%'.$filter.'%')
|
||||
->orWhereHas('contacts', function ($query) use($filter){
|
||||
$query->where('first_name', 'like', '%'.$filter.'%');
|
||||
$query->orWhere('last_name', 'like', '%'.$filter.'%');
|
||||
$query->orWhere('email', 'like', '%'.$filter.'%');
|
||||
})
|
||||
->orWhere('clients.custom_value1', 'like', '%'.$filter.'%')
|
||||
->orWhere('clients.custom_value2', 'like', '%'.$filter.'%')
|
||||
->orWhere('clients.custom_value3', 'like', '%'.$filter.'%')
|
||||
|
@ -38,9 +38,11 @@ class ExpenseFilters extends QueryFilters
|
||||
return $this->builder->where(function ($query) use ($filter) {
|
||||
$query->where('expenses.name', 'like', '%'.$filter.'%')
|
||||
->orWhere('expenses.id_number', 'like', '%'.$filter.'%')
|
||||
->orWhere('expense_contacts.first_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('expense_contacts.last_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('expense_contacts.email', 'like', '%'.$filter.'%')
|
||||
->orWhereHas('contacts', function ($query) use($filter){
|
||||
$query->where('expense_contacts.first_name', 'like', '%'.$filter.'%');
|
||||
$query->orWhere('expense_contacts.last_name', 'like', '%'.$filter.'%');
|
||||
$query->orWhere('expense_contacts.email', 'like', '%'.$filter.'%');
|
||||
})
|
||||
->orWhere('expenses.custom_value1', 'like', '%'.$filter.'%')
|
||||
->orWhere('expenses.custom_value2', 'like', '%'.$filter.'%')
|
||||
->orWhere('expenses.custom_value3', 'like', '%'.$filter.'%')
|
||||
|
@ -13,6 +13,7 @@ namespace App\Filters;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\Models\User;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
@ -21,6 +22,7 @@ use Illuminate\Support\Carbon;
|
||||
*/
|
||||
class InvoiceFilters extends QueryFilters
|
||||
{
|
||||
use MakesHash;
|
||||
/**
|
||||
* Filter based on client status.
|
||||
*
|
||||
@ -65,6 +67,18 @@ class InvoiceFilters extends QueryFilters
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
public function client_id(string $client_id = '') :Builder
|
||||
{
|
||||
if (strlen($client_id) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
$this->builder->where('client_id', $this->decodePrimaryKey($client_id));
|
||||
|
||||
return $this->builder;
|
||||
|
||||
}
|
||||
|
||||
public function number(string $number) :Builder
|
||||
{
|
||||
return $this->builder->where('number', $number);
|
||||
@ -96,6 +110,7 @@ class InvoiceFilters extends QueryFilters
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filters the list based on the status
|
||||
* archived, active, deleted - legacy from V1.
|
||||
|
@ -38,9 +38,11 @@ class VendorFilters extends QueryFilters
|
||||
return $this->builder->where(function ($query) use ($filter) {
|
||||
$query->where('vendors.name', 'like', '%'.$filter.'%')
|
||||
->orWhere('vendors.id_number', 'like', '%'.$filter.'%')
|
||||
->orWhere('vendor_contacts.first_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('vendor_contacts.last_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('vendor_contacts.email', 'like', '%'.$filter.'%')
|
||||
->orWhereHas('contacts', function ($query) use($filter){
|
||||
$query->where('vendor_contacts.first_name', 'like', '%'.$filter.'%');
|
||||
$query->orWhere('vendor_contacts.last_name', 'like', '%'.$filter.'%');
|
||||
$query->orWhere('vendor_contacts.email', 'like', '%'.$filter.'%');
|
||||
})
|
||||
->orWhere('vendors.custom_value1', 'like', '%'.$filter.'%')
|
||||
->orWhere('vendors.custom_value2', 'like', '%'.$filter.'%')
|
||||
->orWhere('vendors.custom_value3', 'like', '%'.$filter.'%')
|
||||
|
@ -1,5 +1,4 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
@ -10,6 +9,8 @@
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
use App\Utils\Ninja;
|
||||
|
||||
/**
|
||||
* Simple helper function that will log into "invoiceninja.log" file
|
||||
* only when extended logging is enabled.
|
||||
@ -32,6 +33,15 @@ function nlog($output, $context = []): void
|
||||
$trace = debug_backtrace();
|
||||
//nlog( debug_backtrace()[1]['function']);
|
||||
// \Illuminate\Support\Facades\Log::channel('invoiceninja')->info(print_r($trace[1]['class'],1), []);
|
||||
if(Ninja::isHosted()) {
|
||||
try{
|
||||
info($output);
|
||||
}
|
||||
catch(\Exception $e){
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
\Illuminate\Support\Facades\Log::channel('invoiceninja')->info($output, $context);
|
||||
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ class InvoiceItemSum
|
||||
private function sumLineItem()
|
||||
{ //todo need to support quantities less than the precision amount
|
||||
// $this->setLineTotal($this->formatValue($this->item->cost, $this->currency->precision) * $this->formatValue($this->item->quantity, $this->currency->precision));
|
||||
|
||||
$this->setLineTotal($this->item->cost * $this->item->quantity);
|
||||
|
||||
return $this;
|
||||
@ -102,7 +103,15 @@ class InvoiceItemSum
|
||||
if ($this->invoice->is_amount_discount) {
|
||||
$this->setLineTotal($this->getLineTotal() - $this->formatValue($this->item->discount, $this->currency->precision));
|
||||
} else {
|
||||
$this->setLineTotal($this->getLineTotal() - $this->formatValue(round($this->item->line_total * ($this->item->discount / 100), 2), $this->currency->precision));
|
||||
|
||||
/*Test 16-08-2021*/
|
||||
$discount = ($this->item->line_total * ($this->item->discount / 100));
|
||||
$this->setLineTotal($this->formatValue(($this->getLineTotal() - $discount), $this->currency->precision));
|
||||
/*Test 16-08-2021*/
|
||||
|
||||
//replaces the following
|
||||
|
||||
// $this->setLineTotal($this->getLineTotal() - $this->formatValue(round($this->item->line_total * ($this->item->discount / 100), 2), $this->currency->precision));
|
||||
}
|
||||
|
||||
$this->item->is_amount_discount = $this->invoice->is_amount_discount;
|
||||
@ -114,9 +123,6 @@ class InvoiceItemSum
|
||||
{
|
||||
$item_tax = 0;
|
||||
|
||||
// nlog(print_r($this->item,1));
|
||||
// nlog(print_r($this->invoice,1));
|
||||
|
||||
$amount = $this->item->line_total - ($this->item->line_total * ($this->invoice->discount / 100));
|
||||
$item_tax_rate1_total = $this->calcAmountLineTax($this->item->tax_rate1, $amount);
|
||||
|
||||
|
@ -46,8 +46,6 @@ class GmailTransport extends Transport
|
||||
$this->gmail = null;
|
||||
$this->gmail = new Mail;
|
||||
|
||||
nlog($message->getBcc());
|
||||
|
||||
/*We should nest the token in the message and then discard it as needed*/
|
||||
$token = $message->getHeaders()->get('GmailToken')->getValue();
|
||||
|
||||
@ -62,13 +60,13 @@ class GmailTransport extends Transport
|
||||
$this->gmail->message($message->getBody());
|
||||
|
||||
$this->gmail->cc($message->getCc());
|
||||
$this->gmail->bcc($message->getBcc());
|
||||
|
||||
if(is_array($message->getBcc()))
|
||||
$this->gmail->bcc(array_keys($message->getBcc()));
|
||||
|
||||
foreach ($message->getChildren() as $child)
|
||||
{
|
||||
|
||||
nlog("trying to attach");
|
||||
|
||||
if($child->getContentType() != 'text/plain')
|
||||
{
|
||||
|
||||
|
@ -163,6 +163,6 @@ class ActivityController extends BaseController
|
||||
|
||||
return response()->streamDownload(function () use ($pdf) {
|
||||
echo $pdf;
|
||||
}, $filename);
|
||||
}, $filename, ['Content-Type' => 'application/pdf']);
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ClientPortal\Contact\ContactPasswordResetRequest;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Account;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
@ -73,10 +74,8 @@ class ContactForgotPasswordController extends Controller
|
||||
return Password::broker('contacts');
|
||||
}
|
||||
|
||||
public function sendResetLinkEmail(Request $request)
|
||||
public function sendResetLinkEmail(ContactPasswordResetRequest $request)
|
||||
{
|
||||
//MultiDB::userFindAndSetDb($request->input('email'));
|
||||
|
||||
$user = MultiDB::hasContact($request->input('email'));
|
||||
|
||||
$this->validateEmail($request);
|
||||
|
@ -35,9 +35,16 @@ class ContactLoginController extends Controller
|
||||
|
||||
public function showLoginForm(Request $request)
|
||||
{
|
||||
if ($request->subdomain) {
|
||||
$company = Company::where('subdomain', $request->subdomain)->first();
|
||||
} elseif (Ninja::isSelfHost()) {
|
||||
//if we are on the root domain invoicing.co do not show any company logos
|
||||
if(Ninja::isHosted() && count(explode('.', request()->getHost())) == 2){
|
||||
$company = null;
|
||||
}elseif (strpos($request->getHost(), 'invoicing.co') !== false) {
|
||||
$subdomain = explode('.', $request->getHost())[0];
|
||||
$company = Company::where('subdomain', $subdomain)->first();
|
||||
} elseif(Ninja::isHosted() && $company = Company::where('portal_domain', $request->getSchemeAndHttpHost())->first()){
|
||||
|
||||
}
|
||||
elseif (Ninja::isSelfHost()) {
|
||||
$company = Account::first()->default_company;
|
||||
} else {
|
||||
$company = null;
|
||||
|
@ -1,4 +1,13 @@
|
||||
<?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 App\Http\Controllers\Auth;
|
||||
|
||||
|
@ -71,6 +71,31 @@ class ContactResetPasswordController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
public function reset(Request $request)
|
||||
{
|
||||
$request->validate($this->rules(), $this->validationErrorMessages());
|
||||
|
||||
// Here we will attempt to reset the user's password. If it is successful we
|
||||
// will update the password on an actual user model and persist it to the
|
||||
// database. Otherwise we will parse the error and return the response.
|
||||
$response = $this->broker()->reset(
|
||||
$this->credentials($request), function ($user, $password) {
|
||||
$this->resetPassword($user, $password);
|
||||
}
|
||||
);
|
||||
|
||||
// Added this because it collides the session between
|
||||
// client & main portal giving unlimited redirects.
|
||||
auth()->logout();
|
||||
|
||||
// If the password was successfully reset, we will redirect the user back to
|
||||
// the application's home authenticated view. If there is an error we can
|
||||
// redirect them back to where they came from with their error message.
|
||||
return $response == Password::PASSWORD_RESET
|
||||
? $this->sendResetResponse($request, $response)
|
||||
: $this->sendResetFailedResponse($request, $response);
|
||||
}
|
||||
|
||||
protected function guard()
|
||||
{
|
||||
return Auth::guard('contact');
|
||||
|
@ -16,6 +16,7 @@ use App\DataMapper\Analytics\LoginSuccess;
|
||||
use App\Events\User\UserLoggedIn;
|
||||
use App\Http\Controllers\BaseController;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Login\LoginRequest;
|
||||
use App\Jobs\Account\CreateAccount;
|
||||
use App\Jobs\Company\CreateCompanyToken;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
@ -156,7 +157,7 @@ class LoginController extends BaseController
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function apiLogin(Request $request)
|
||||
public function apiLogin(LoginRequest $request)
|
||||
{
|
||||
$this->forced_includes = ['company_users'];
|
||||
|
||||
@ -179,8 +180,6 @@ class LoginController extends BaseController
|
||||
|
||||
$user = $this->guard()->user();
|
||||
|
||||
event(new UserLoggedIn($user, $user->account->default_company, Ninja::eventVars($user->id)));
|
||||
|
||||
//2FA
|
||||
if($user->google_2fa_secret && $request->has('one_time_password'))
|
||||
{
|
||||
@ -203,6 +202,14 @@ class LoginController extends BaseController
|
||||
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||
}
|
||||
|
||||
/* If for some reason we lose state on the default company ie. a company is deleted - always make sure we can default to a company*/
|
||||
if(!$user->account->default_company){
|
||||
$account = $user->account;
|
||||
$account->default_company_id = $user->companies->first()->id;
|
||||
$account->save();
|
||||
$user = $user->fresh();
|
||||
}
|
||||
|
||||
$user->setCompany($user->account->default_company);
|
||||
|
||||
$this->setLoginCache($user);
|
||||
@ -226,6 +233,8 @@ class LoginController extends BaseController
|
||||
if(Ninja::isHosted() && !$cu->first()->is_owner && !$user->account->isEnterpriseClient())
|
||||
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
|
||||
|
||||
event(new UserLoggedIn($user, $user->account->default_company, Ninja::eventVars($user->id)));
|
||||
|
||||
return $this->timeConstrainedResponse($cu);
|
||||
|
||||
|
||||
@ -300,7 +309,6 @@ class LoginController extends BaseController
|
||||
$cu = CompanyUser::query()
|
||||
->where('user_id', $company_token->user_id);
|
||||
|
||||
|
||||
$cu->first()->account->companies->each(function ($company) use($cu, $request){
|
||||
|
||||
if($company->tokens()->where('is_system', true)->count() == 0)
|
||||
@ -309,7 +317,6 @@ class LoginController extends BaseController
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if($request->has('current_company') && $request->input('current_company') == 'true')
|
||||
$cu->where("company_id", $company_token->company_id);
|
||||
|
||||
@ -361,6 +368,9 @@ class LoginController extends BaseController
|
||||
|
||||
if ($existing_user = MultiDB::hasUser($query)) {
|
||||
|
||||
if(!$existing_user->account)
|
||||
return response()->json(['message' => 'User exists, but not attached to any companies! Orphaned user!'], 400);
|
||||
|
||||
Auth::login($existing_user, true);
|
||||
$existing_user->setCompany($existing_user->account->default_company);
|
||||
|
||||
@ -387,6 +397,9 @@ class LoginController extends BaseController
|
||||
//If this is a result user/email combo - lets add their OAuth details details
|
||||
if($existing_login_user = MultiDB::hasUser(['email' => $google->harvestEmail($user)]))
|
||||
{
|
||||
if(!$existing_login_user->account)
|
||||
return response()->json(['message' => 'User exists, but not attached to any companies! Orphaned user!'], 400);
|
||||
|
||||
Auth::login($existing_login_user, true);
|
||||
$existing_login_user->setCompany($existing_login_user->account->default_company);
|
||||
|
||||
@ -422,6 +435,9 @@ class LoginController extends BaseController
|
||||
|
||||
if($existing_login_user = MultiDB::hasUser(['email' => $google->harvestEmail($user)]))
|
||||
{
|
||||
if(!$existing_login_user->account)
|
||||
return response()->json(['message' => 'User exists, but not attached to any companies! Orphaned user!'], 400);
|
||||
|
||||
Auth::login($existing_login_user, true);
|
||||
$existing_login_user->setCompany($existing_login_user->account->default_company);
|
||||
|
||||
@ -473,6 +489,8 @@ class LoginController extends BaseController
|
||||
auth()->user()->email_verified_at = now();
|
||||
auth()->user()->save();
|
||||
|
||||
auth()->user()->setCompany(auth()->user()->account->default_company);
|
||||
|
||||
$this->setLoginCache(auth()->user());
|
||||
|
||||
$cu = CompanyUser::whereUserId(auth()->user()->id);
|
||||
|
@ -112,6 +112,7 @@ class BaseController extends Controller
|
||||
'company.groups',
|
||||
'company.payment_terms',
|
||||
'company.designs.company',
|
||||
'company.expense_categories',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
@ -202,6 +203,10 @@ class BaseController extends Controller
|
||||
$transformer = new $this->entity_transformer($this->serializer);
|
||||
$updated_at = request()->has('updated_at') ? request()->input('updated_at') : 0;
|
||||
|
||||
if ($user->getCompany()->is_large && $updated_at == 0){
|
||||
$updated_at = time();
|
||||
}
|
||||
|
||||
$updated_at = date('Y-m-d H:i:s', $updated_at);
|
||||
|
||||
$query->with(
|
||||
@ -379,8 +384,6 @@ class BaseController extends Controller
|
||||
'company.designs'=> function ($query) use ($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at)->with('company');
|
||||
|
||||
if(!$user->isAdmin())
|
||||
$query->where('designs.user_id', $user->id);
|
||||
},
|
||||
'company.documents'=> function ($query) use ($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at);
|
||||
@ -388,22 +391,14 @@ class BaseController extends Controller
|
||||
'company.groups' => function ($query) use ($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at);
|
||||
|
||||
if(!$user->isAdmin())
|
||||
$query->where('group_settings.user_id', $user->id);
|
||||
},
|
||||
'company.payment_terms'=> function ($query) use ($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at);
|
||||
|
||||
if(!$user->isAdmin())
|
||||
$query->where('payment_terms.user_id', $user->id);
|
||||
|
||||
},
|
||||
'company.tax_rates' => function ($query) use ($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at);
|
||||
|
||||
if(!$user->isAdmin())
|
||||
$query->where('tax_rates.user_id', $user->id);
|
||||
|
||||
},
|
||||
'company.activities'=> function ($query) use($user) {
|
||||
|
||||
@ -519,8 +514,6 @@ class BaseController extends Controller
|
||||
'company.payment_terms'=> function ($query) use ($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at);
|
||||
|
||||
if(!$user->isAdmin())
|
||||
$query->where('payment_terms.user_id', $user->id);
|
||||
|
||||
},
|
||||
'company.products' => function ($query) use ($created_at, $user) {
|
||||
@ -561,9 +554,6 @@ class BaseController extends Controller
|
||||
'company.tax_rates' => function ($query) use ($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at);
|
||||
|
||||
if(!$user->isAdmin())
|
||||
$query->where('tax_rates.user_id', $user->id);
|
||||
|
||||
},
|
||||
'company.vendors'=> function ($query) use ($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at)->with('contacts', 'documents');
|
||||
@ -575,9 +565,6 @@ class BaseController extends Controller
|
||||
'company.expense_categories'=> function ($query) use ($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at);
|
||||
|
||||
if(!$user->isAdmin())
|
||||
$query->where('expense_categories.user_id', $user->id);
|
||||
|
||||
},
|
||||
'company.task_statuses'=> function ($query) use ($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at);
|
||||
@ -748,11 +735,14 @@ class BaseController extends Controller
|
||||
|
||||
$data = [];
|
||||
|
||||
if (Ninja::isSelfHost()) {
|
||||
$data['report_errors'] = $account->report_errors;
|
||||
} else {
|
||||
$data['report_errors'] = true;
|
||||
}
|
||||
//pass report errors bool to front end
|
||||
$data['report_errors'] = Ninja::isSelfHost() ? $account->report_errors : true;
|
||||
|
||||
//pass referral code to front end
|
||||
$data['rc'] = request()->has('rc') ? request()->input('rc') : '';
|
||||
$data['build'] = request()->has('build') ? request()->input('build') : '';
|
||||
|
||||
$data['path'] = $this->setBuild();
|
||||
|
||||
$this->buildCache();
|
||||
|
||||
@ -762,6 +752,29 @@ class BaseController extends Controller
|
||||
return redirect('/setup');
|
||||
}
|
||||
|
||||
private function setBuild()
|
||||
{
|
||||
$build = '';
|
||||
|
||||
if(request()->has('build')) {
|
||||
$build = request()->input('build');
|
||||
}
|
||||
|
||||
switch ($build) {
|
||||
case 'wasm':
|
||||
return 'main.wasm.dart.js';
|
||||
case 'foss':
|
||||
return 'main.foss.dart.js';
|
||||
case 'last':
|
||||
return 'main.last.dart.js';
|
||||
case 'next':
|
||||
return 'main.next.dart.js';
|
||||
default:
|
||||
return 'main.dart.js';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function checkFeature($feature)
|
||||
{
|
||||
|
||||
|
@ -510,7 +510,7 @@ class ClientController extends BaseController
|
||||
$action = request()->input('action');
|
||||
|
||||
$ids = request()->input('ids');
|
||||
$clients = Client::withTrashed()->find($this->transformKeys($ids));
|
||||
$clients = Client::withTrashed()->whereIn('id', $this->transformKeys($ids))->cursor();
|
||||
|
||||
$clients->each(function ($client, $key) use ($action) {
|
||||
if (auth()->user()->can('edit', $client)) {
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace App\Http\Controllers\ClientPortal;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\RecurringInvoice;
|
||||
use Auth;
|
||||
|
||||
class ContactHashLoginController extends Controller
|
||||
@ -24,6 +25,17 @@ class ContactHashLoginController extends Controller
|
||||
*/
|
||||
public function login(string $contact_key)
|
||||
{
|
||||
if(request()->has('subscription') && $request->subscription == 'true') {
|
||||
|
||||
$recurring_invoice = RecurringInvoice::where('client_id', auth()->guard('contact')->client->id)
|
||||
->whereNotNull('subscription_id')
|
||||
->whereNull('deleted_at')
|
||||
->first();
|
||||
|
||||
return redirect()->route('client.recurring_invoice.show', $recurring_invoice->hashed_id);
|
||||
|
||||
}
|
||||
|
||||
return redirect('/client/invoices');
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\View\View;
|
||||
use ZipStream\Option\Archive;
|
||||
use ZipStream\ZipStream;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class InvoiceController extends Controller
|
||||
{
|
||||
@ -89,6 +90,7 @@ class InvoiceController extends Controller
|
||||
{
|
||||
$invoices = Invoice::whereIn('id', $ids)
|
||||
->whereClientId(auth()->user()->client->id)
|
||||
->withTrashed()
|
||||
->get();
|
||||
|
||||
//filter invoices which are payable
|
||||
@ -167,9 +169,12 @@ class InvoiceController extends Controller
|
||||
if ($invoices->count() == 1) {
|
||||
$invoice = $invoices->first();
|
||||
$invitation = $invoice->invitations->first();
|
||||
$file = $invoice->pdf_file_path($invitation);
|
||||
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);;
|
||||
|
||||
//$file = $invoice->pdf_file_path($invitation);
|
||||
$file = $invoice->service()->getInvoicePdf(auth()->user());
|
||||
// return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);;
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
}
|
||||
|
||||
// enable output of HTTP headers
|
||||
@ -180,7 +185,10 @@ class InvoiceController extends Controller
|
||||
$zip = new ZipStream(date('Y-m-d').'_'.str_replace(' ', '_', trans('texts.invoices')).'.zip', $options);
|
||||
|
||||
foreach ($invoices as $invoice) {
|
||||
$zip->addFileFromPath(basename($invoice->pdf_file_path()), TempFile::path($invoice->pdf_file_path()));
|
||||
|
||||
#add it to the zip
|
||||
$zip->addFile(basename($invoice->pdf_file_path()), file_get_contents($invoice->pdf_file_path(null, 'url', true)));
|
||||
|
||||
}
|
||||
|
||||
// finish the zip stream
|
||||
|
@ -95,7 +95,7 @@ class PaymentController extends Controller
|
||||
*/
|
||||
|
||||
$payable_invoices = collect($request->payable_invoices);
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys($payable_invoices->pluck('invoice_id')->toArray()))->get();
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys($payable_invoices->pluck('invoice_id')->toArray()))->withTrashed()->get();
|
||||
|
||||
$invoices->each(function($invoice){
|
||||
$invoice->service()->removeUnpaidGatewayFees()->save();
|
||||
@ -243,6 +243,10 @@ class PaymentController extends Controller
|
||||
->get();
|
||||
}
|
||||
|
||||
if(!$is_credit_payment){
|
||||
$credit_totals = 0;
|
||||
}
|
||||
|
||||
$hash_data = ['invoices' => $payable_invoices->toArray(), 'credits' => $credit_totals, 'amount_with_fee' => max(0, (($invoice_totals + $fee_totals) - $credit_totals))];
|
||||
|
||||
if ($request->query('hash')) {
|
||||
@ -257,11 +261,19 @@ class PaymentController extends Controller
|
||||
|
||||
$payment_hash->save();
|
||||
|
||||
if($is_credit_payment){
|
||||
$amount_with_fee = max(0, (($invoice_totals + $fee_totals) - $credit_totals));
|
||||
}
|
||||
else{
|
||||
$credit_totals = 0;
|
||||
$amount_with_fee = max(0, $invoice_totals + $fee_totals);
|
||||
}
|
||||
|
||||
$totals = [
|
||||
'credit_totals' => $credit_totals,
|
||||
'invoice_totals' => $invoice_totals,
|
||||
'fee_total' => $fee_totals,
|
||||
'amount_with_fee' => max(0, (($invoice_totals + $fee_totals) - $credit_totals)),
|
||||
'amount_with_fee' => $amount_with_fee,
|
||||
];
|
||||
|
||||
$data = [
|
||||
@ -273,7 +285,7 @@ class PaymentController extends Controller
|
||||
'amount_with_fee' => $invoice_totals + $fee_totals,
|
||||
];
|
||||
|
||||
if ($is_credit_payment) {
|
||||
if ($is_credit_payment || $totals <= 0) {
|
||||
return $this->processCreditPayment($request, $data);
|
||||
}
|
||||
|
||||
@ -320,6 +332,7 @@ class PaymentController extends Controller
|
||||
*/
|
||||
public function credit_response(Request $request)
|
||||
{
|
||||
|
||||
$payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$request->input('payment_hash')])->first();
|
||||
|
||||
/* Hydrate the $payment */
|
||||
|
@ -14,7 +14,7 @@ namespace App\Http\Controllers\ClientPortal;
|
||||
|
||||
use App\Events\Payment\Methods\MethodDeleted;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ClientPortal\CreatePaymentMethodRequest;
|
||||
use App\Http\Requests\ClientPortal\PaymentMethod\CreatePaymentMethodRequest;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
@ -51,6 +51,7 @@ class PaymentMethodController extends Controller
|
||||
$gateway = $this->getClientGateway();
|
||||
|
||||
$data['gateway'] = $gateway;
|
||||
$data['client'] = auth()->user()->client;
|
||||
|
||||
return $gateway
|
||||
->driver(auth()->user()->client)
|
||||
@ -91,9 +92,9 @@ class PaymentMethodController extends Controller
|
||||
|
||||
public function verify(ClientGatewayToken $payment_method)
|
||||
{
|
||||
$gateway = $this->getClientGateway();
|
||||
// $gateway = $this->getClientGateway();
|
||||
|
||||
return $gateway
|
||||
return $payment_method->gateway
|
||||
->driver(auth()->user()->client)
|
||||
->setPaymentMethod(request()->query('method'))
|
||||
->verificationView($payment_method);
|
||||
@ -101,9 +102,9 @@ class PaymentMethodController extends Controller
|
||||
|
||||
public function processVerification(Request $request, ClientGatewaytoken $payment_method)
|
||||
{
|
||||
$gateway = $this->getClientGateway();
|
||||
// $gateway = $this->getClientGateway();
|
||||
|
||||
return $gateway
|
||||
return $payment_method->gateway
|
||||
->driver(auth()->user()->client)
|
||||
->setPaymentMethod(request()->query('method'))
|
||||
->processVerification($request, $payment_method);
|
||||
@ -117,16 +118,22 @@ class PaymentMethodController extends Controller
|
||||
*/
|
||||
public function destroy(ClientGatewayToken $payment_method)
|
||||
{
|
||||
$gateway = $this->getClientGateway();
|
||||
|
||||
$gateway
|
||||
if($payment_method->gateway()->exists()){
|
||||
|
||||
$payment_method->gateway
|
||||
->driver(auth()->user()->client)
|
||||
->setPaymentMethod(request()->query('method'))
|
||||
->detach($payment_method);
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
event(new MethodDeleted($payment_method, auth('contact')->user()->company, Ninja::eventVars(auth('contact')->user()->id)));
|
||||
|
||||
$payment_method->delete();
|
||||
|
||||
} catch (Exception $e) {
|
||||
|
||||
nlog($e->getMessage());
|
||||
|
@ -7,7 +7,7 @@
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\ClientPortal;
|
||||
@ -24,8 +24,10 @@ use App\Utils\TempFile;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\View\View;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use ZipStream\Option\Archive;
|
||||
use ZipStream\ZipStream;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class QuoteController extends Controller
|
||||
{
|
||||
@ -46,7 +48,7 @@ class QuoteController extends Controller
|
||||
*
|
||||
* @param ShowQuoteRequest $request
|
||||
* @param Quote $quote
|
||||
* @return Factory|View|\Symfony\Component\HttpFoundation\BinaryFileResponse
|
||||
* @return Factory|View|BinaryFileResponse
|
||||
*/
|
||||
public function show(ShowQuoteRequest $request, Quote $quote)
|
||||
{
|
||||
@ -83,13 +85,18 @@ class QuoteController extends Controller
|
||||
->get();
|
||||
|
||||
if (! $quotes || $quotes->count() == 0) {
|
||||
return;
|
||||
return redirect()
|
||||
->route('client.quotes.index')
|
||||
->with('message', ctrans('texts.no_quotes_available_for_download'));
|
||||
}
|
||||
|
||||
if ($quotes->count() == 1) {
|
||||
|
||||
$file = $quotes->first()->pdf_file_path();
|
||||
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
$file = $quotes->first()->service()->getQuotePdf();
|
||||
// return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
}
|
||||
|
||||
// enable output of HTTP headers
|
||||
@ -100,7 +107,9 @@ class QuoteController extends Controller
|
||||
$zip = new ZipStream(date('Y-m-d').'_'.str_replace(' ', '_', trans('texts.invoices')).'.zip', $options);
|
||||
|
||||
foreach ($quotes as $quote) {
|
||||
$zip->addFileFromPath(basename($quote->pdf_file_path()), TempFile::path($quote->pdf_file_path()));
|
||||
$zip->addFile(basename($quote->pdf_file_path()), file_get_contents($quote->pdf_file_path(null, 'url', true)));
|
||||
|
||||
// $zip->addFileFromPath(basename($quote->pdf_file_path()), TempFile::path($quote->pdf_file_path()));
|
||||
}
|
||||
|
||||
// finish the zip stream
|
||||
@ -110,11 +119,15 @@ class QuoteController extends Controller
|
||||
protected function approve(array $ids, $process = false)
|
||||
{
|
||||
$quotes = Quote::whereIn('id', $ids)
|
||||
->whereClientId(auth()->user()->client->id)
|
||||
->where('client_id', auth('contact')->user()->client->id)
|
||||
->where('company_id', auth('contact')->user()->client->company_id)
|
||||
->where('status_id', Quote::STATUS_SENT)
|
||||
->get();
|
||||
|
||||
if (!$quotes || $quotes->count() == 0) {
|
||||
return redirect()->route('client.quotes.index');
|
||||
return redirect()
|
||||
->route('client.quotes.index')
|
||||
->with('message', ctrans('texts.quotes_with_status_sent_can_be_approved'));
|
||||
}
|
||||
|
||||
if ($process) {
|
||||
|
@ -44,7 +44,6 @@ class SubscriptionPlanSwitchController extends Controller
|
||||
if(is_null($amount))
|
||||
render('subscriptions.denied');
|
||||
|
||||
|
||||
return render('subscriptions.switch', [
|
||||
'subscription' => $recurring_invoice->subscription,
|
||||
'recurring_invoice' => $recurring_invoice,
|
||||
|
@ -25,7 +25,10 @@ use App\Jobs\Company\CreateCompany;
|
||||
use App\Jobs\Company\CreateCompanyPaymentTerms;
|
||||
use App\Jobs\Company\CreateCompanyTaskStatuses;
|
||||
use App\Jobs\Company\CreateCompanyToken;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Jobs\Ninja\RefundCancelledAccount;
|
||||
use App\Mail\Company\CompanyDeleted;
|
||||
use App\Models\Account;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyUser;
|
||||
@ -474,10 +477,16 @@ class CompanyController extends BaseController
|
||||
*/
|
||||
public function destroy(DestroyCompanyRequest $request, Company $company)
|
||||
{
|
||||
|
||||
if(Ninja::isHosted() && config('ninja.ninja_default_company_id') == $company->id)
|
||||
return response()->json(['message' => 'Cannot purge this company'], 400);
|
||||
|
||||
$company_count = $company->account->companies->count();
|
||||
$account = $company->account;
|
||||
$account_key = $account->key;
|
||||
|
||||
if ($company_count == 1) {
|
||||
|
||||
$company->company_users->each(function ($company_user) {
|
||||
$company_user->user->forceDelete();
|
||||
$company_user->forceDelete();
|
||||
@ -485,9 +494,13 @@ class CompanyController extends BaseController
|
||||
|
||||
$account->delete();
|
||||
|
||||
if(Ninja::isHosted())
|
||||
\Modules\Admin\Jobs\Account\NinjaDeletedAccount::dispatch($account_key);
|
||||
|
||||
LightLogs::create(new AccountDeleted())
|
||||
->increment()
|
||||
->batch();
|
||||
|
||||
} else {
|
||||
$company_id = $company->id;
|
||||
|
||||
@ -495,6 +508,15 @@ class CompanyController extends BaseController
|
||||
$company_user->forceDelete();
|
||||
});
|
||||
|
||||
$other_company = $company->account->companies->where('id', '!=', $company->id)->first();
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new CompanyDeleted($company->present()->name, auth()->user(), $company->account, $company->settings);
|
||||
$nmo->company = $other_company;
|
||||
$nmo->settings = $other_company->settings;
|
||||
$nmo->to_user = auth()->user();
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
|
||||
$company->delete();
|
||||
|
||||
//If we are deleting the default companies, we'll need to make a new company the default.
|
||||
@ -571,4 +593,9 @@ class CompanyController extends BaseController
|
||||
return $this->itemResponse($company->fresh());
|
||||
|
||||
}
|
||||
|
||||
// public function default(DefaultCompanyRequest $request, Company $company)
|
||||
// {
|
||||
|
||||
// }
|
||||
}
|
||||
|
@ -433,9 +433,14 @@ class CompanyGatewayController extends BaseController
|
||||
*/
|
||||
public function destroy(DestroyCompanyGatewayRequest $request, CompanyGateway $company_gateway)
|
||||
{
|
||||
|
||||
$company_gateway->driver(new Client)
|
||||
->disconnect();
|
||||
|
||||
$company_gateway->delete();
|
||||
|
||||
return $this->itemResponse($company_gateway->fresh());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -123,8 +123,8 @@ class CompanyUserController extends BaseController
|
||||
if (auth()->user()->isAdmin()) {
|
||||
$company_user->fill($request->input('company_user'));
|
||||
} else {
|
||||
$company_user->fill($request->input('company_user')['settings']);
|
||||
$company_user->fill($request->input('company_user')['notifications']);
|
||||
$company_user->settings = $request->input('company_user')['settings'];
|
||||
$company_user->notifications = $request->input('company_user')['notifications'];
|
||||
}
|
||||
|
||||
$company_user->save();
|
||||
|
@ -104,8 +104,13 @@ class ConnectedAccountController extends BaseController
|
||||
$refresh_token = '';
|
||||
$token = '';
|
||||
|
||||
$email = $google->harvestEmail($user);
|
||||
|
||||
if(auth()->user()->email != $email && MultiDB::checkUserEmailExists($email))
|
||||
return response()->json(['message' => ctrans('texts.email_already_register')], 400);
|
||||
|
||||
$connected_account = [
|
||||
'email' => $google->harvestEmail($user),
|
||||
'email' => $email,
|
||||
'oauth_user_id' => $google->harvestSubField($user),
|
||||
'oauth_provider_id' => 'google',
|
||||
'email_verified_at' =>now()
|
||||
|
@ -37,6 +37,7 @@ use App\Utils\TempFile;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\SavesDocuments;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* Class CreditController.
|
||||
@ -536,8 +537,14 @@ class CreditController extends BaseController
|
||||
}
|
||||
break;
|
||||
case 'download':
|
||||
$file = $credit->pdf_file_path();
|
||||
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
// $file = $credit->pdf_file_path();
|
||||
$file = $credit->service()->getCreditPdf($credit->invitations->first());
|
||||
|
||||
// return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
break;
|
||||
case 'archive':
|
||||
$this->credit_repository->archive($credit);
|
||||
@ -585,9 +592,12 @@ class CreditController extends BaseController
|
||||
// $contact = $invitation->contact;
|
||||
$credit = $invitation->credit;
|
||||
|
||||
$file_path = $credit->service()->getCreditPdf($invitation);
|
||||
$file = $credit->service()->getCreditPdf($invitation);
|
||||
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
|
||||
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -131,8 +131,8 @@ class EmailController extends BaseController
|
||||
|
||||
$entity_obj->service()->markSent()->save();
|
||||
|
||||
EmailEntity::dispatch($invitation->fresh(), $invitation->company, $template, $data)
|
||||
->delay(now()->addSeconds(30));
|
||||
EmailEntity::dispatch($invitation->fresh(), $invitation->company, $template, $data);
|
||||
// ->delay(now()->addSeconds(45));
|
||||
|
||||
}
|
||||
|
||||
|
27
app/Http/Controllers/Gateways/Mollie3dsController.php
Normal file
27
app/Http/Controllers/Gateways/Mollie3dsController.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?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 App\Http\Controllers\Gateways;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Gateways\Mollie\Mollie3dsRequest;
|
||||
use App\Models\PaymentHash;
|
||||
|
||||
class Mollie3dsController extends Controller
|
||||
{
|
||||
public function index(Mollie3dsRequest $request)
|
||||
{
|
||||
return $request->getCompanyGateway()
|
||||
->driver($request->getClient())
|
||||
->process3dsConfirmation($request);
|
||||
}
|
||||
}
|
52
app/Http/Controllers/HostedMigrationController.php
Normal file
52
app/Http/Controllers/HostedMigrationController.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?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 App\Http\Controllers;
|
||||
|
||||
use App\Jobs\Account\CreateAccount;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\CompanyToken;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class HostedMigrationController extends Controller
|
||||
{
|
||||
|
||||
public function getAccount(Request $request)
|
||||
{
|
||||
|
||||
if($request->header('X-API-HOSTED-SECRET') != config('ninja.ninja_hosted_secret'))
|
||||
return;
|
||||
|
||||
if($user = MultiDB::hasUser(['email' => $request->input('email')]))
|
||||
{
|
||||
|
||||
if($user->account->owner() && $user->account->companies()->count() >= 1)
|
||||
{
|
||||
return response()->json(['token' => $user->account->companies->first()->tokens->first()->token] ,200);
|
||||
}
|
||||
|
||||
return response()->json(['error' => 'This user is not able to perform a migration. Please contact us at contact@invoiceninja.com to discuss.'], 401);
|
||||
|
||||
}
|
||||
|
||||
$account = CreateAccount::dispatchNow($request->all(), $request->getClientIp());
|
||||
|
||||
$company = $account->companies->first();
|
||||
|
||||
$company_token = CompanyToken::where('user_id', auth()->user()->id)
|
||||
->where('company_id', $company->id)
|
||||
->first();
|
||||
|
||||
return response()->json(['token' => $company_token->token], 200);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -11,14 +11,17 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Exceptions\NonExistingMigrationFile;
|
||||
use App\Http\Requests\Import\ImportJsonRequest;
|
||||
use App\Jobs\Company\CompanyExport;
|
||||
use App\Jobs\Company\CompanyImport;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
use ZipArchive;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ImportJsonController extends BaseController
|
||||
{
|
||||
@ -60,40 +63,19 @@ class ImportJsonController extends BaseController
|
||||
public function import(ImportJsonRequest $request)
|
||||
{
|
||||
|
||||
$import_file = $request->file('files');
|
||||
$file_location = $request->file('files')
|
||||
->storeAs(
|
||||
'migrations',
|
||||
$request->file('files')->getClientOriginalName()
|
||||
);
|
||||
|
||||
$contents = $this->unzipFile($import_file->getPathname());
|
||||
|
||||
$hash = Str::random(32);
|
||||
|
||||
nlog($hash);
|
||||
|
||||
Cache::put( $hash, base64_encode( $contents ), 3600 );
|
||||
|
||||
CompanyImport::dispatch(auth()->user()->getCompany(), auth()->user(), $hash, $request->except('files'))->delay(now()->addMinutes(1));
|
||||
if(Ninja::isHosted())
|
||||
CompanyImport::dispatch(auth()->user()->getCompany(), auth()->user(), $file_location, $request->except('files'))->onQueue('migration');
|
||||
else
|
||||
CompanyImport::dispatch(auth()->user()->getCompany(), auth()->user(), $file_location, $request->except('files'));
|
||||
|
||||
return response()->json(['message' => 'Processing'], 200);
|
||||
|
||||
}
|
||||
|
||||
private function unzipFile($file_contents)
|
||||
{
|
||||
$zip = new ZipArchive();
|
||||
$archive = $zip->open($file_contents);
|
||||
|
||||
$filename = pathinfo($file_contents, PATHINFO_FILENAME);
|
||||
$zip->extractTo(public_path("storage/backups/{$filename}"));
|
||||
$zip->close();
|
||||
$file_location = public_path("storage/backups/$filename/backup.json");
|
||||
|
||||
if (! file_exists($file_location))
|
||||
throw new NonExistingMigrationFile('Backup file does not exist, or is corrupted.');
|
||||
|
||||
$data = file_get_contents($file_location);
|
||||
|
||||
unlink($file_contents);
|
||||
unlink($file_location);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
@ -672,8 +672,12 @@ class InvoiceController extends BaseController
|
||||
break;
|
||||
case 'download':
|
||||
|
||||
$file = $invoice->pdf_file_path();
|
||||
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
$file = $invoice->service()->getInvoicePdf();
|
||||
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
|
||||
|
||||
break;
|
||||
case 'restore':
|
||||
@ -722,10 +726,11 @@ class InvoiceController extends BaseController
|
||||
}
|
||||
|
||||
//touch reminder1,2,3_sent + last_sent here if the email is a reminder.
|
||||
$invoice->service()->touchReminder($this->reminder_template)->deletePdf()->save();
|
||||
//$invoice->service()->touchReminder($this->reminder_template)->deletePdf()->save();
|
||||
$invoice->service()->touchReminder($this->reminder_template)->markSent()->save();
|
||||
|
||||
$invoice->invitations->load('contact.client.country', 'invoice.client.country', 'invoice.company')->each(function ($invitation) use ($invoice) {
|
||||
EmailEntity::dispatch($invitation, $invoice->company, $this->reminder_template);
|
||||
EmailEntity::dispatch($invitation, $invoice->company, $this->reminder_template)->delay(now()->addSeconds(30));
|
||||
});
|
||||
|
||||
if ($invoice->invitations->count() >= 1) {
|
||||
@ -790,13 +795,18 @@ class InvoiceController extends BaseController
|
||||
public function downloadPdf($invitation_key)
|
||||
{
|
||||
$invitation = $this->invoice_repo->getInvitationByKey($invitation_key);
|
||||
|
||||
if(!$invitation)
|
||||
return response()->json(["message" => "no record found"], 400);
|
||||
|
||||
$contact = $invitation->contact;
|
||||
$invoice = $invitation->invoice;
|
||||
|
||||
$file = $invoice->service()->getInvoicePdf($contact);
|
||||
|
||||
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -848,7 +858,9 @@ class InvoiceController extends BaseController
|
||||
|
||||
$file = $invoice->service()->getInvoiceDeliveryNote($invoice, $invoice->invitations->first()->contact);
|
||||
|
||||
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ use App\Utils\CurlUtils;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use stdClass;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class LicenseController extends BaseController
|
||||
{
|
||||
@ -152,7 +153,7 @@ class LicenseController extends BaseController
|
||||
{
|
||||
$account = auth()->user()->company()->account;
|
||||
|
||||
if($account->plan == 'white_label' && $account->plan_expires->lt(now())){
|
||||
if($account->plan == 'white_label' && Carbon::parse($account->plan_expires)->lt(now())){
|
||||
$account->plan = null;
|
||||
$account->plan_expires = null;
|
||||
$account->save();
|
||||
|
@ -82,6 +82,9 @@ class MigrationController extends BaseController
|
||||
*/
|
||||
public function purgeCompany(Company $company)
|
||||
{
|
||||
if(Ninja::isHosted() && config('ninja.ninja_default_company_id') == $company->id)
|
||||
return response()->json(['message' => 'Cannot purge this company'], 400);
|
||||
|
||||
$account = $company->account;
|
||||
$company_id = $company->id;
|
||||
|
||||
@ -102,6 +105,9 @@ class MigrationController extends BaseController
|
||||
|
||||
private function purgeCompanyWithForceFlag(Company $company)
|
||||
{
|
||||
if(Ninja::isHosted() && config('ninja.ninja_default_company_id') == $company->id)
|
||||
return response()->json(['message' => 'Cannot purge this company'], 400);
|
||||
|
||||
$account = $company->account;
|
||||
$company_id = $company->id;
|
||||
|
||||
@ -387,7 +393,6 @@ class MigrationController extends BaseController
|
||||
else
|
||||
StartMigration::dispatch($migration_file, $user, $fresh_company);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -208,9 +208,6 @@ class PaymentController extends BaseController
|
||||
{
|
||||
$payment = $this->payment_repo->save($request->all(), PaymentFactory::create(auth()->user()->company()->id, auth()->user()->id));
|
||||
|
||||
if($request->has('email_receipt') && $request->input('email_receipt') == 'true' && !$payment->client->getSetting('client_manual_payment_notification'))
|
||||
$payment->service()->sendEmail();
|
||||
|
||||
return $this->itemResponse($payment);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,36 @@
|
||||
<?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 App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\Payments\PaymentNotificationWebhookRequest;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Client;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Auth;
|
||||
|
||||
class PaymentNotificationWebhookController extends Controller
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public function __invoke(PaymentNotificationWebhookRequest $request, string $company_key, string $company_gateway_id, string $client_hash)
|
||||
{
|
||||
|
||||
$company_gateway = CompanyGateway::find($this->decodePrimaryKey($company_gateway_id));
|
||||
$client = Client::find($this->decodePrimaryKey($client_hash));
|
||||
|
||||
return $company_gateway
|
||||
->driver($client)
|
||||
->processWebhookRequest($request);
|
||||
}
|
||||
}
|
@ -13,29 +13,14 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\Payments\PaymentWebhookRequest;
|
||||
use App\Libraries\MultiDB;
|
||||
use Auth;
|
||||
|
||||
class PaymentWebhookController extends Controller
|
||||
{
|
||||
public function __invoke(PaymentWebhookRequest $request, string $company_key, string $company_gateway_id)
|
||||
public function __invoke(PaymentWebhookRequest $request)
|
||||
{
|
||||
|
||||
// MultiDB::findAndSetDbByCompanyKey($company_key);
|
||||
|
||||
$payment = $request->getPayment();
|
||||
|
||||
if(!$payment)
|
||||
return response()->json(['message' => 'Payment record not found.'], 400);
|
||||
|
||||
$client = is_null($payment) ? $request->getClient() : $payment->client;
|
||||
|
||||
if(!$client)
|
||||
return response()->json(['message' => 'Client record not found.'], 400);
|
||||
|
||||
|
||||
return $request->getCompanyGateway()
|
||||
->driver($client)
|
||||
->processWebhookRequest($request, $payment);
|
||||
return $request
|
||||
->getCompanyGateway()
|
||||
->driver()
|
||||
->processWebhookRequest($request);
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\DataMapper\Analytics\EmailBounce;
|
||||
use App\DataMapper\Analytics\EmailSpam;
|
||||
use App\DataMapper\Analytics\Mail\EmailSpam;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\CreditInvitation;
|
||||
|
@ -11,11 +11,28 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\DataMapper\Analytics\LivePreview;
|
||||
use App\Factory\CreditFactory;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Factory\QuoteFactory;
|
||||
use App\Factory\RecurringInvoiceFactory;
|
||||
use App\Http\Requests\Invoice\StoreInvoiceRequest;
|
||||
use App\Http\Requests\Preview\PreviewInvoiceRequest;
|
||||
use App\Jobs\Util\PreviewPdf;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Repositories\CreditRepository;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
use App\Repositories\QuoteRepository;
|
||||
use App\Repositories\RecurringInvoiceRepository;
|
||||
use App\Services\PdfMaker\Design as PdfDesignModel;
|
||||
use App\Services\PdfMaker\Design as PdfMakerDesign;
|
||||
use App\Services\PdfMaker\Design;
|
||||
use App\Services\PdfMaker\PdfMaker;
|
||||
use App\Utils\HostedPDF\NinjaPdf;
|
||||
@ -28,6 +45,7 @@ use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Turbo124\Beacon\Facades\LightLogs;
|
||||
|
||||
class PreviewController extends BaseController
|
||||
{
|
||||
@ -118,6 +136,7 @@ class PreviewController extends BaseController
|
||||
'products' => request()->design['design']['product'],
|
||||
]),
|
||||
'variables' => $html->generateLabelsAndValues(),
|
||||
'process_markdown' => $entity_obj->client->company->markdown_enabled,
|
||||
];
|
||||
|
||||
$design = new Design(request()->design['name']);
|
||||
@ -149,13 +168,150 @@ class PreviewController extends BaseController
|
||||
return $this->blankEntity();
|
||||
}
|
||||
|
||||
public function live(PreviewInvoiceRequest $request)
|
||||
{
|
||||
$company = auth()->user()->company();
|
||||
|
||||
MultiDB::setDb($company->db);
|
||||
|
||||
if($request->input('entity') == 'invoice'){
|
||||
$repo = new InvoiceRepository();
|
||||
$entity_obj = InvoiceFactory::create($company->id, auth()->user()->id);
|
||||
$class = Invoice::class;
|
||||
}
|
||||
elseif($request->input('entity') == 'quote'){
|
||||
$repo = new QuoteRepository();
|
||||
$entity_obj = QuoteFactory::create($company->id, auth()->user()->id);
|
||||
$class = Quote::class;
|
||||
|
||||
}
|
||||
elseif($request->input('entity') == 'credit'){
|
||||
$repo = new CreditRepository();
|
||||
$entity_obj = CreditFactory::create($company->id, auth()->user()->id);
|
||||
$class = Credit::class;
|
||||
}
|
||||
elseif($request->input('entity') == 'recurring_invoice'){
|
||||
$repo = new RecurringInvoiceRepository();
|
||||
$entity_obj = RecurringInvoiceFactory::create($company->id, auth()->user()->id);
|
||||
$class = RecurringInvoice::class;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
|
||||
DB::connection(config('database.default'))->beginTransaction();
|
||||
|
||||
if($request->has('entity_id')){
|
||||
|
||||
$entity_obj = $class::on(config('database.default'))
|
||||
->where('id', $this->decodePrimaryKey($request->input('entity_id')))
|
||||
->where('company_id', $company->id)
|
||||
->withTrashed()
|
||||
->first();
|
||||
|
||||
}
|
||||
|
||||
$entity_obj = $repo->save($request->all(), $entity_obj);
|
||||
|
||||
$entity_obj->load('client');
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
App::setLocale($entity_obj->client->contacts()->first()->preferredLocale());
|
||||
$t->replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings()));
|
||||
|
||||
$html = new HtmlEngine($entity_obj->invitations()->first());
|
||||
|
||||
$design = \App\Models\Design::find($entity_obj->design_id);
|
||||
|
||||
/* Catch all in case migration doesn't pass back a valid design */
|
||||
if(!$design)
|
||||
$design = \App\Models\Design::find(2);
|
||||
|
||||
if ($design->is_custom) {
|
||||
$options = [
|
||||
'custom_partials' => json_decode(json_encode($design->design), true)
|
||||
];
|
||||
$template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options);
|
||||
} else {
|
||||
$template = new PdfMakerDesign(strtolower($design->name));
|
||||
}
|
||||
|
||||
$variables = $html->generateLabelsAndValues();
|
||||
|
||||
$state = [
|
||||
'template' => $template->elements([
|
||||
'client' => $entity_obj->client,
|
||||
'entity' => $entity_obj,
|
||||
'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables,
|
||||
'$product' => $design->design->product,
|
||||
'variables' => $variables,
|
||||
]),
|
||||
'variables' => $variables,
|
||||
'options' => [
|
||||
'all_pages_header' => $entity_obj->client->getSetting('all_pages_header'),
|
||||
'all_pages_footer' => $entity_obj->client->getSetting('all_pages_footer'),
|
||||
],
|
||||
'process_markdown' => $entity_obj->client->company->markdown_enabled,
|
||||
];
|
||||
|
||||
|
||||
$maker = new PdfMaker($state);
|
||||
|
||||
$maker
|
||||
->design($template)
|
||||
->build();
|
||||
|
||||
DB::connection(config('database.default'))->rollBack();
|
||||
|
||||
if (request()->query('html') == 'true') {
|
||||
return $maker->getCompiledHTML;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch(\Exception $e){
|
||||
|
||||
DB::connection(config('database.default'))->rollBack();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//if phantom js...... inject here..
|
||||
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
|
||||
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
|
||||
}
|
||||
|
||||
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
|
||||
return (new NinjaPdf())->build($maker->getCompiledHTML(true));
|
||||
}
|
||||
|
||||
$file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), $company);
|
||||
|
||||
|
||||
if(Ninja::isHosted())
|
||||
{
|
||||
LightLogs::create(new LivePreview())
|
||||
->increment()
|
||||
->batch();
|
||||
}
|
||||
|
||||
|
||||
$response = Response::make($file_path, 200);
|
||||
$response->header('Content-Type', 'application/pdf');
|
||||
|
||||
return $response;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function blankEntity()
|
||||
{
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
$t->replace(Ninja::transformTranslations(auth()->user()->company()->settings));
|
||||
|
||||
DB::beginTransaction();
|
||||
DB::connection(auth()->user()->company()->db)->beginTransaction();
|
||||
|
||||
$client = Client::factory()->create([
|
||||
'user_id' => auth()->user()->id,
|
||||
@ -208,6 +364,7 @@ class PreviewController extends BaseController
|
||||
'products' => request()->design['design']['product'],
|
||||
]),
|
||||
'variables' => $html->generateLabelsAndValues(),
|
||||
'process_markdown' => $invoice->client->company->markdown_enabled,
|
||||
];
|
||||
|
||||
$maker = new PdfMaker($state);
|
||||
@ -230,7 +387,7 @@ class PreviewController extends BaseController
|
||||
|
||||
$file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), auth()->user()->company());
|
||||
|
||||
DB::rollBack();
|
||||
DB::connection(auth()->user()->company()->db)->rollBack();
|
||||
|
||||
$response = Response::make($file_path, 200);
|
||||
$response->header('Content-Type', 'application/pdf');
|
||||
|
@ -470,7 +470,7 @@ class ProductController extends BaseController
|
||||
|
||||
$ids = request()->input('ids');
|
||||
|
||||
$products = Product::withTrashed()->find($this->transformKeys($ids));
|
||||
$products = Product::withTrashed()->whereIn('id', $this->transformKeys($ids))->cursor();
|
||||
|
||||
$products->each(function ($product, $key) use ($action) {
|
||||
if (auth()->user()->can('edit', $product)) {
|
||||
|
@ -39,6 +39,7 @@ use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\SavesDocuments;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* Class QuoteController.
|
||||
@ -676,8 +677,14 @@ class QuoteController extends BaseController
|
||||
break;
|
||||
case 'download':
|
||||
|
||||
$file = $quote->pdf_file_path();
|
||||
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
//$file = $quote->pdf_file_path();
|
||||
$file = $quote->service()->getQuotePdf();
|
||||
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
|
||||
//return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
|
||||
break;
|
||||
case 'restore':
|
||||
@ -728,9 +735,13 @@ class QuoteController extends BaseController
|
||||
$contact = $invitation->contact;
|
||||
$quote = $invitation->quote;
|
||||
|
||||
$file_path = $quote->service()->getQuotePdf($contact);
|
||||
$file = $quote->service()->getQuotePdf($contact);
|
||||
|
||||
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
|
||||
// return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,6 +33,7 @@ use App\Utils\Traits\SavesDocuments;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* Class RecurringInvoiceController.
|
||||
@ -500,9 +501,12 @@ class RecurringInvoiceController extends BaseController
|
||||
$contact = $invitation->contact;
|
||||
$recurring_invoice = $invitation->recurring_invoice;
|
||||
|
||||
$file_path = $recurring_invoice->service()->getInvoicePdf($contact);
|
||||
$file = $recurring_invoice->service()->getInvoicePdf($contact);
|
||||
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
|
||||
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,6 +16,7 @@ use App\Http\Requests\Setup\CheckDatabaseRequest;
|
||||
use App\Http\Requests\Setup\CheckMailRequest;
|
||||
use App\Http\Requests\Setup\StoreSetupRequest;
|
||||
use App\Jobs\Account\CreateAccount;
|
||||
use App\Jobs\Util\SchedulerCheck;
|
||||
use App\Jobs\Util\VersionCheck;
|
||||
use App\Models\Account;
|
||||
use App\Utils\CurlUtils;
|
||||
@ -69,8 +70,6 @@ class SetupController extends Controller
|
||||
}
|
||||
|
||||
if ($check['system_health'] === false) {
|
||||
nlog($check);
|
||||
|
||||
return response('Oops, something went wrong. Check your logs.'); /* We should never reach this block, but just in case. */
|
||||
}
|
||||
|
||||
@ -109,11 +108,11 @@ class SetupController extends Controller
|
||||
'REQUIRE_HTTPS' => $request->input('https') ? 'true' : 'false',
|
||||
'APP_DEBUG' => 'false',
|
||||
|
||||
'DB_HOST1' => $request->input('db_host'),
|
||||
'DB_PORT1' => $request->input('db_port'),
|
||||
'DB_DATABASE1' => $request->input('db_database'),
|
||||
'DB_USERNAME1' => $request->input('db_username'),
|
||||
'DB_PASSWORD1' => $request->input('db_password'),
|
||||
'DB_HOST' => $request->input('db_host'),
|
||||
'DB_PORT' => $request->input('db_port'),
|
||||
'DB_DATABASE' => $request->input('db_database'),
|
||||
'DB_USERNAME' => $request->input('db_username'),
|
||||
'DB_PASSWORD' => $request->input('db_password'),
|
||||
|
||||
'MAIL_MAILER' => $mail_driver,
|
||||
'MAIL_PORT' => $request->input('mail_port'),
|
||||
@ -125,6 +124,7 @@ class SetupController extends Controller
|
||||
'MAIL_PASSWORD' => $request->input('mail_password'),
|
||||
|
||||
'NINJA_ENVIRONMENT' => 'selfhost',
|
||||
'DB_CONNECTION' => 'mysql',
|
||||
];
|
||||
|
||||
if (config('ninja.db.multi_db_enabled')) {
|
||||
@ -133,11 +133,11 @@ class SetupController extends Controller
|
||||
|
||||
if (config('ninja.preconfigured_install')) {
|
||||
// Database connection was already configured. Don't let the user override it.
|
||||
unset($env_values['DB_HOST1']);
|
||||
unset($env_values['DB_PORT1']);
|
||||
unset($env_values['DB_DATABASE1']);
|
||||
unset($env_values['DB_USERNAME1']);
|
||||
unset($env_values['DB_PASSWORD1']);
|
||||
unset($env_values['DB_HOST']);
|
||||
unset($env_values['DB_PORT']);
|
||||
unset($env_values['DB_DATABASE']);
|
||||
unset($env_values['DB_USERNAME']);
|
||||
unset($env_values['DB_PASSWORD']);
|
||||
}
|
||||
|
||||
try {
|
||||
@ -240,6 +240,11 @@ class SetupController extends Controller
|
||||
$pdf->setChromiumPath(config('ninja.snappdf_chromium_path'));
|
||||
}
|
||||
|
||||
if (config('ninja.snappdf_chromium_arguments')) {
|
||||
$pdf->clearChromiumArguments();
|
||||
$pdf->addChromiumArguments(config('ninja.snappdf_chromium_arguments'));
|
||||
}
|
||||
|
||||
$pdf = $pdf
|
||||
->setHtml('GENERATING PDFs WORKS! Thank you for using Invoice Ninja!')
|
||||
->generate();
|
||||
@ -275,10 +280,7 @@ class SetupController extends Controller
|
||||
|
||||
public function update()
|
||||
{
|
||||
// if(Ninja::isHosted())
|
||||
// return redirect('/');
|
||||
|
||||
// if( Ninja::isNinja() || !request()->has('secret') || (request()->input('secret') != config('ninja.update_secret')) )
|
||||
if(!request()->has('secret') || (request()->input('secret') != config('ninja.update_secret')) )
|
||||
return redirect('/');
|
||||
|
||||
@ -307,6 +309,8 @@ class SetupController extends Controller
|
||||
|
||||
$this->buildCache(true);
|
||||
|
||||
SchedulerCheck::dispatchNow();
|
||||
|
||||
return redirect('/');
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
@ -52,7 +51,7 @@ class StripeConnectController extends BaseController
|
||||
|
||||
$config = $company_gateway->getConfig();
|
||||
|
||||
if(property_exists($config, 'account_id'))
|
||||
if(property_exists($config, 'account_id') && strlen($config->account_id) > 1)
|
||||
return view('auth.connect.existing');
|
||||
|
||||
}
|
||||
@ -61,12 +60,6 @@ class StripeConnectController extends BaseController
|
||||
$redirect_uri = 'https://invoicing.co/stripe/completed';
|
||||
$endpoint = "https://connect.stripe.com/oauth/authorize?response_type=code&client_id={$stripe_client_id}&redirect_uri={$redirect_uri}&scope=read_write&state={$token}";
|
||||
|
||||
// if($email = $request->getContact()->email)
|
||||
// $endpoint .= "&stripe_user[email]={$email}";
|
||||
|
||||
// $company_name = str_replace(" ", "_", $company->present()->name());
|
||||
// $endpoint .= "&stripe_user[business_name]={$company_name}";
|
||||
|
||||
return redirect($endpoint);
|
||||
}
|
||||
|
||||
@ -88,17 +81,26 @@ class StripeConnectController extends BaseController
|
||||
|
||||
}
|
||||
|
||||
// nlog($response);
|
||||
MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']);
|
||||
|
||||
$company = Company::where('company_key', $request->getTokenContent()['company_key'])->first();
|
||||
|
||||
$company_gateway = CompanyGateway::query()
|
||||
->where('gateway_key', 'd14dd26a47cecc30fdd65700bfb67b34')
|
||||
->where('company_id', $company->id)
|
||||
->first();
|
||||
|
||||
if(!$company_gateway)
|
||||
{
|
||||
$company_gateway = CompanyGatewayFactory::create($company->id, $company->owner()->id);
|
||||
$fees_and_limits = new \stdClass;
|
||||
$fees_and_limits->{GatewayType::CREDIT_CARD} = new FeesAndLimits;
|
||||
$company_gateway->gateway_key = 'd14dd26a47cecc30fdd65700bfb67b34';
|
||||
$company_gateway->fees_and_limits = $fees_and_limits;
|
||||
$company_gateway->setConfig([]);
|
||||
$company_gateway->token_billing = 'always';
|
||||
// $company_gateway->save();
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'account_id' => $response->stripe_user_id,
|
||||
@ -111,18 +113,6 @@ class StripeConnectController extends BaseController
|
||||
"access_token" => $response->access_token
|
||||
];
|
||||
|
||||
/* Link account if existing account exists */
|
||||
// if($account_id = $this->checkAccountAlreadyLinkToEmail($company_gateway, $request->getContact()->email)) {
|
||||
|
||||
// $payload['account_id'] = $account_id;
|
||||
// $payload['stripe_user_id'] = $account_id;
|
||||
// $company_gateway->setConfig($payload);
|
||||
// $company_gateway->save();
|
||||
|
||||
// return view('auth.connect.existing');
|
||||
|
||||
// }
|
||||
|
||||
$company_gateway->setConfig($payload);
|
||||
$company_gateway->save();
|
||||
|
||||
|
@ -14,10 +14,14 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Jobs\Util\ImportStripeCustomers;
|
||||
use App\Jobs\Util\StripeUpdatePaymentMethods;
|
||||
use App\Models\Client;
|
||||
use App\Models\CompanyGateway;
|
||||
|
||||
class StripeController extends BaseController
|
||||
{
|
||||
|
||||
private $stripe_keys = ['d14dd26a47cecc30fdd65700bfb67b34', 'd14dd26a37cecc30fdd65700bfb55b23'];
|
||||
|
||||
public function update()
|
||||
{
|
||||
if(auth()->user()->isAdmin())
|
||||
@ -29,13 +33,15 @@ class StripeController extends BaseController
|
||||
|
||||
}
|
||||
|
||||
|
||||
return response()->json(['message' => 'Unauthorized'], 403);
|
||||
}
|
||||
|
||||
public function import()
|
||||
{
|
||||
|
||||
// return response()->json(['message' => 'Processing'], 200);
|
||||
|
||||
|
||||
if(auth()->user()->isAdmin())
|
||||
{
|
||||
|
||||
@ -45,8 +51,25 @@ class StripeController extends BaseController
|
||||
|
||||
}
|
||||
|
||||
|
||||
return response()->json(['message' => 'Unauthorized'], 403);
|
||||
}
|
||||
|
||||
public function verify()
|
||||
{
|
||||
|
||||
if(auth()->user()->isAdmin())
|
||||
{
|
||||
|
||||
$company_gateway = CompanyGateway::where('company_id', auth()->user()->company()->id)
|
||||
->where('is_deleted',0)
|
||||
->whereIn('gateway_key', $this->stripe_keys)
|
||||
->first();
|
||||
|
||||
return $company_gateway->driver(new Client)->verifyConnect();
|
||||
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Unauthorized'], 403);
|
||||
|
||||
}
|
||||
}
|
@ -26,6 +26,9 @@ class SubdomainController extends BaseController
|
||||
'docs',
|
||||
'client_domain',
|
||||
'custom_domain',
|
||||
'preview',
|
||||
'invoiceninja',
|
||||
'cname',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
|
@ -354,6 +354,8 @@ class SubscriptionController extends BaseController
|
||||
|
||||
event(new SubscriptionWasUpdated($subscription, $subscription->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
nlog($subscription->id);
|
||||
|
||||
return $this->itemResponse($subscription);
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ class SendingController extends Controller
|
||||
}
|
||||
|
||||
Mail::to(config('ninja.contact.ninja_official_contact'))
|
||||
->send(new SupportMessageSent($request->message, $send_logs));
|
||||
->send(new SupportMessageSent($request->input('message'), $send_logs));
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
|
@ -11,11 +11,17 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
use App\Models\User;
|
||||
use App\Transformers\UserTransformer;
|
||||
use Crypt;
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
|
||||
class TwoFactorController extends BaseController
|
||||
{
|
||||
protected $entity_type = User::class;
|
||||
|
||||
protected $entity_transformer = UserTransformer::class;
|
||||
|
||||
public function setupTwoFactor()
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
54
app/Http/Controllers/WePayController.php
Normal file
54
app/Http/Controllers/WePayController.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?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 App\Http\Controllers;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\User;
|
||||
use App\PaymentDrivers\WePayPaymentDriver;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class WePayController extends BaseController
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
/**
|
||||
* Initialize WePay Signup.
|
||||
*/
|
||||
public function signup(string $token)
|
||||
{
|
||||
|
||||
$hash = Cache::get($token);
|
||||
|
||||
MultiDB::findAndSetDbByCompanyKey($hash['company_key']);
|
||||
|
||||
$user = User::findOrFail($hash['user_id']);
|
||||
|
||||
$company = Company::where('company_key', $hash['company_key'])->firstOrFail();
|
||||
|
||||
$data['user_id'] = $user->id;
|
||||
$data['company'] = $company;
|
||||
|
||||
$wepay_driver = new WePayPaymentDriver(new CompanyGateway, null, null);
|
||||
|
||||
return $wepay_driver->setup($data);
|
||||
|
||||
}
|
||||
|
||||
public function finished()
|
||||
{
|
||||
return render('gateways.wepay.signup.finished');
|
||||
}
|
||||
}
|
@ -30,6 +30,7 @@ use App\Http\Middleware\QueryLogging;
|
||||
use App\Http\Middleware\RedirectIfAuthenticated;
|
||||
use App\Http\Middleware\SetDb;
|
||||
use App\Http\Middleware\SetDbByCompanyKey;
|
||||
use App\Http\Middleware\SetDocumentDb;
|
||||
use App\Http\Middleware\SetDomainNameDb;
|
||||
use App\Http\Middleware\SetEmailDb;
|
||||
use App\Http\Middleware\SetInviteDb;
|
||||
@ -158,6 +159,7 @@ class Kernel extends HttpKernel
|
||||
'contact_key_login' => ContactKeyLogin::class,
|
||||
'check_client_existence' => CheckClientExistence::class,
|
||||
'user_verified' => UserVerified::class,
|
||||
'document_db' => SetDocumentDb::class,
|
||||
];
|
||||
|
||||
|
||||
|
@ -237,7 +237,7 @@ class BillingPortalPurchase extends Component
|
||||
$client_repo = new ClientRepository(new ClientContactRepository());
|
||||
|
||||
$data = [
|
||||
'name' => 'Client Name',
|
||||
'name' => '',
|
||||
'contacts' => [
|
||||
['email' => $this->email],
|
||||
],
|
||||
|
@ -34,9 +34,15 @@ class CreditsTable extends Component
|
||||
|
||||
public function render()
|
||||
{
|
||||
|
||||
$query = Credit::query()
|
||||
->where('client_id', auth('contact')->user()->client->id)
|
||||
->where('company_id', $this->company->id)
|
||||
->where('status_id', '<>', Credit::STATUS_DRAFT)
|
||||
->where(function ($query){
|
||||
$query->whereDate('due_date', '<=', now())
|
||||
->orWhereNull('due_date');
|
||||
})
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||
->paginate($this->per_page);
|
||||
|
||||
|
@ -44,6 +44,7 @@ class InvoicesTable extends Component
|
||||
|
||||
$query = Invoice::query()
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', false);
|
||||
|
||||
if (in_array('paid', $this->status)) {
|
||||
|
56
app/Http/Livewire/PaymentMethods/UpdateDefaultMethod.php
Normal file
56
app/Http/Livewire/PaymentMethods/UpdateDefaultMethod.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?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 App\Http\Livewire\PaymentMethods;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use Livewire\Component;
|
||||
|
||||
class UpdateDefaultMethod extends Component
|
||||
{
|
||||
/** @var \App\Models\Company */
|
||||
public $company;
|
||||
|
||||
/** @var \App\Models\ClientGatewayToken */
|
||||
public $token;
|
||||
|
||||
/** @var \App\Models\Client */
|
||||
public $client;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->company = $this->client->company;
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$this->is_disabled = $this->token->is_default;
|
||||
}
|
||||
|
||||
public function makeDefault(): void
|
||||
{
|
||||
if ($this->token->is_default) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->client->gateway_tokens()->update(['is_default' => 0]);
|
||||
|
||||
$this->token->is_default = 1;
|
||||
$this->token->save();
|
||||
|
||||
$this->emit('UpdateDefaultMethod::method-updated');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return render('components.livewire.update-default-payment-method');
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ class PaymentMethodsTable extends Component
|
||||
{
|
||||
$query = ClientGatewayToken::query()
|
||||
->with('gateway_type')
|
||||
->where('company_id', $this->company->id)
|
||||
->where('client_id', $this->client->id)
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||
->paginate($this->per_page);
|
||||
|
@ -41,6 +41,7 @@ class PaymentsTable extends Component
|
||||
{
|
||||
$query = Payment::query()
|
||||
->with('type', 'client')
|
||||
->where('company_id', $this->company->id)
|
||||
->where('client_id', auth('contact')->user()->client->id)
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||
->paginate($this->per_page);
|
||||
|
@ -32,6 +32,7 @@ class General extends Component
|
||||
'first_name' => ['sometimes'],
|
||||
'last_name' => ['sometimes'],
|
||||
'email' => ['required', 'email'],
|
||||
'phone' => ['sometimes'],
|
||||
];
|
||||
|
||||
public function mount()
|
||||
|
@ -45,6 +45,7 @@ class QuotesTable extends Component
|
||||
}
|
||||
|
||||
$query = $query
|
||||
->where('company_id', $this->company->id)
|
||||
->where('client_id', auth('contact')->user()->client->id)
|
||||
->where('status_id', '<>', Quote::STATUS_DRAFT)
|
||||
->paginate($this->per_page);
|
||||
|
34
app/Http/Livewire/RecurringInvoices/UpdateAutoBilling.php
Normal file
34
app/Http/Livewire/RecurringInvoices/UpdateAutoBilling.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?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 App\Http\Livewire\RecurringInvoices;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class UpdateAutoBilling extends Component
|
||||
{
|
||||
/** @var \App\Models\RecurringInvoice */
|
||||
public $invoice;
|
||||
|
||||
public function updateAutoBilling(): void
|
||||
{
|
||||
if ($this->invoice->auto_bill === 'optin' || $this->invoice->auto_bill === 'optout') {
|
||||
$this->invoice->auto_bill_enabled = !$this->invoice->auto_bill_enabled;
|
||||
$this->invoice->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return render('components.livewire.recurring-invoices-switch-autobilling');
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\Traits\WithSorting;
|
||||
use Livewire\Component;
|
||||
@ -23,8 +24,12 @@ class RecurringInvoicesTable extends Component
|
||||
|
||||
public $per_page = 10;
|
||||
|
||||
public $company;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$this->sort_asc = false;
|
||||
|
||||
$this->sort_field = 'date';
|
||||
@ -36,6 +41,7 @@ class RecurringInvoicesTable extends Component
|
||||
|
||||
$query = $query
|
||||
->where('client_id', auth('contact')->user()->client->id)
|
||||
->where('company_id', $this->company->id)
|
||||
->whereIn('status_id', [RecurringInvoice::STATUS_PENDING, RecurringInvoice::STATUS_ACTIVE, RecurringInvoice::STATUS_PAUSED,RecurringInvoice::STATUS_COMPLETED])
|
||||
->orderBy('status_id', 'asc')
|
||||
->with('client')
|
||||
|
@ -158,6 +158,37 @@ class RequiredClientInfo extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function showCopyBillingCheckbox(): bool
|
||||
{
|
||||
$fields = [];
|
||||
|
||||
collect($this->fields)->map(function ($field) use (&$fields) {
|
||||
if (! array_key_exists('filled', $field)) {
|
||||
$fields[] = $field['name'];
|
||||
}
|
||||
});
|
||||
|
||||
foreach ($fields as $field) {
|
||||
if (Str::startsWith($field, 'client_shipping')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleCopyBilling(): void
|
||||
{
|
||||
$this->emit('update-shipping-data', [
|
||||
'client_shipping_address_line_1' => $this->contact->client->address1,
|
||||
'client_shipping_address_line_2' => $this->contact->client->address2,
|
||||
'client_shipping_city' => $this->contact->client->city,
|
||||
'client_shipping_state' => $this->contact->client->state,
|
||||
'client_shipping_postal_code' => $this->contact->client->postal_code,
|
||||
'client_shipping_country_id' => $this->contact->client->country_id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
count($this->fields) > 0
|
||||
|
@ -90,6 +90,16 @@ class SubscriptionPlanSwitch extends Component
|
||||
|
||||
$this->state['show_loading_bar'] = true;
|
||||
|
||||
$payment_required = $this->target->service()->changePlanPaymentCheck([
|
||||
'recurring_invoice' => $this->recurring_invoice,
|
||||
'subscription' => $this->subscription,
|
||||
'target' => $this->target,
|
||||
'hash' => $this->hash,
|
||||
]);
|
||||
|
||||
if($payment_required)
|
||||
{
|
||||
|
||||
$this->state['invoice'] = $this->target->service()->createChangePlanInvoice([
|
||||
'recurring_invoice' => $this->recurring_invoice,
|
||||
'subscription' => $this->subscription,
|
||||
@ -109,6 +119,12 @@ class SubscriptionPlanSwitch extends Component
|
||||
|
||||
$this->state['payment_initialised'] = true;
|
||||
|
||||
}
|
||||
else
|
||||
$this->handlePaymentNotRequired();
|
||||
|
||||
|
||||
|
||||
$this->emit('beforePaymentEventsCompleted');
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@ class SubscriptionRecurringInvoicesTable extends Component
|
||||
{
|
||||
$query = RecurringInvoice::query()
|
||||
->where('client_id', auth('contact')->user()->client->id)
|
||||
->where('company_id', $this->company->id)
|
||||
->whereNotNull('subscription_id')
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||
->paginate($this->per_page);
|
||||
|
@ -35,8 +35,18 @@ class TasksTable extends Component
|
||||
public function render()
|
||||
{
|
||||
$query = Task::query()
|
||||
->where('client_id', auth('contact')->user()->client->id)
|
||||
->whereNotNull('invoice_id')
|
||||
->where('company_id', $this->company->id)
|
||||
->where('client_id', auth('contact')->user()->client->id);
|
||||
|
||||
if ($this->company->getSetting('show_all_tasks_client_portal') === 'invoiced') {
|
||||
$query = $query->whereNotNull('invoice_id');
|
||||
}
|
||||
|
||||
if ($this->company->getSetting('show_all_tasks_client_portal') === 'uninvoiced') {
|
||||
$query = $query->whereNull('invoice_id');
|
||||
}
|
||||
|
||||
$query = $query
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||
->paginate($this->per_page);
|
||||
|
||||
|
201
app/Http/Livewire/WepaySignup.php
Normal file
201
app/Http/Livewire/WepaySignup.php
Normal file
@ -0,0 +1,201 @@
|
||||
<?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 App\Http\Livewire;
|
||||
|
||||
use App\DataMapper\FeesAndLimits;
|
||||
use App\Factory\CompanyGatewayFactory;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\User;
|
||||
use App\PaymentDrivers\WePayPaymentDriver;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Livewire\Component;
|
||||
use WePay;
|
||||
|
||||
class WepaySignup extends Component
|
||||
{
|
||||
public $user;
|
||||
public $user_id;
|
||||
public $company_key;
|
||||
public $first_name;
|
||||
public $last_name;
|
||||
public $email;
|
||||
public $company_name;
|
||||
public $country;
|
||||
public $ach;
|
||||
public $wepay_payment_tos_agree;
|
||||
public $debit_cards;
|
||||
|
||||
public $terms;
|
||||
public $privacy_policy;
|
||||
|
||||
public $saved;
|
||||
public $company;
|
||||
|
||||
protected $rules = [
|
||||
'first_name' => ['required'],
|
||||
'last_name' => ['required'],
|
||||
'email' => ['required', 'email'],
|
||||
'company_name' => ['required'],
|
||||
'country' => ['required'],
|
||||
'ach' => ['sometimes'],
|
||||
'wepay_payment_tos_agree' => ['accepted'],
|
||||
'debit_cards' => ['sometimes'],
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$user = User::find($this->user_id);
|
||||
$this->company = Company::where('company_key', $this->company->company_key)->first();
|
||||
|
||||
$this->fill([
|
||||
'wepay_payment_tos_agree' => '',
|
||||
'ach' => '',
|
||||
'country' => 'US',
|
||||
'user' => $user,
|
||||
'first_name' => $user->first_name,
|
||||
'last_name' => $user->last_name,
|
||||
'email' => $user->email,
|
||||
'company_name' => $this->company->present()->name(),
|
||||
'saved' => ctrans('texts.confirm'),
|
||||
'terms' => '<a href="https://go.wepay.com/terms-of-service" target="_blank">'.ctrans('texts.terms_of_service').'</a>',
|
||||
'privacy_policy' => '<a href="https://go.wepay.com/privacy-policy" target="_blank">'.ctrans('texts.privacy_policy').'</a>',
|
||||
]);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return render('gateways.wepay.signup.wepay-signup');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$data = $this->validate($this->rules);
|
||||
|
||||
//need to create or get a new WePay CompanyGateway
|
||||
$cg = CompanyGateway::where('gateway_key', '8fdeed552015b3c7b44ed6c8ebd9e992')
|
||||
->where('company_id', $this->company->id)
|
||||
->firstOrNew();
|
||||
|
||||
if(!$cg->id) {
|
||||
|
||||
$fees_and_limits = new \stdClass;
|
||||
$fees_and_limits->{GatewayType::CREDIT_CARD} = new FeesAndLimits;
|
||||
$fees_and_limits->{GatewayType::BANK_TRANSFER} = new FeesAndLimits;
|
||||
|
||||
$cg = CompanyGatewayFactory::create($this->company->id, $this->user->id);
|
||||
$cg->gateway_key = '8fdeed552015b3c7b44ed6c8ebd9e992';
|
||||
$cg->require_cvv = false;
|
||||
$cg->require_billing_address = false;
|
||||
$cg->require_shipping_address = false;
|
||||
$cg->update_details = false;
|
||||
$cg->config = encrypt(config('ninja.testvars.checkout'));
|
||||
$cg->fees_and_limits = $fees_and_limits;
|
||||
$cg->token_billing = 'always';
|
||||
$cg->save();
|
||||
|
||||
}
|
||||
|
||||
$this->saved = ctrans('texts.processing');
|
||||
|
||||
$wepay_driver = new WePayPaymentDriver($cg, null, null);
|
||||
|
||||
$wepay = $wepay_driver->init()->wepay;
|
||||
|
||||
$user_details = [
|
||||
'client_id' => config('ninja.wepay.client_id'),
|
||||
'client_secret' => config('ninja.wepay.client_secret'),
|
||||
'email' => $data['email'],
|
||||
'first_name' => $data['first_name'],
|
||||
'last_name' => $data['last_name'],
|
||||
'original_ip' => request()->ip(),
|
||||
'original_device' => request()->server('HTTP_USER_AGENT'),
|
||||
'tos_acceptance_time' => time(),
|
||||
'redirect_uri' => route('wepay.finished'),
|
||||
'scope' => 'manage_accounts,collect_payments,view_user,preapprove_payments,send_money',
|
||||
];
|
||||
|
||||
$wepay_user = $wepay->request('user/register/', $user_details);
|
||||
|
||||
$access_token = $wepay_user->access_token;
|
||||
|
||||
$access_token_expires = $wepay_user->expires_in ? (time() + $wepay_user->expires_in) : null;
|
||||
|
||||
$wepay = new WePay($access_token);
|
||||
|
||||
$account_details = [
|
||||
'name' => $data['company_name'],
|
||||
'description' => ctrans('texts.wepay_account_description'),
|
||||
'theme_object' => json_decode('{"name":"Invoice Ninja","primary_color":"0b4d78","secondary_color":"0b4d78","background_color":"f8f8f8","button_color":"33b753"}'),
|
||||
'callback_uri' => route('payment_webhook', ['company_key' => $this->company->company_key, 'company_gateway_id' => $cg->hashed_id]),
|
||||
'rbits' => $this->company->rBits(),
|
||||
'country' => $data['country'],
|
||||
];
|
||||
|
||||
if ($data['country'] == 'CA') {
|
||||
$account_details['currencies'] = ['CAD'];
|
||||
$account_details['country_options'] = ['debit_opt_in' => boolval($data['debit_cards'])];
|
||||
} elseif ($data['country'] == 'GB') {
|
||||
$account_details['currencies'] = ['GBP'];
|
||||
}
|
||||
|
||||
$wepay_account = $wepay->request('account/create/', $account_details);
|
||||
|
||||
try {
|
||||
$wepay->request('user/send_confirmation/', []);
|
||||
$confirmation_required = true;
|
||||
} catch (\WePayException $ex) {
|
||||
if ($ex->getMessage() == 'This access_token is already approved.') {
|
||||
$confirmation_required = false;
|
||||
} else {
|
||||
request()->session()->flash('message', $ex->getMessage());
|
||||
}
|
||||
|
||||
nlog("failed in try catch ");
|
||||
nlog($ex->getMessage());
|
||||
}
|
||||
|
||||
$config = [
|
||||
'userId' => $wepay_user->user_id,
|
||||
'accessToken' => $access_token,
|
||||
'tokenType' => $wepay_user->token_type,
|
||||
'tokenExpires' => $access_token_expires,
|
||||
'accountId' => $wepay_account->account_id,
|
||||
'state' => $wepay_account->state,
|
||||
'testMode' => config('ninja.wepay.environment') == 'staging',
|
||||
'country' => $data['country'],
|
||||
];
|
||||
|
||||
$cg->setConfig($config);
|
||||
$cg->save();
|
||||
|
||||
if ($confirmation_required) {
|
||||
request()->session()->flash('message', trans('texts.created_wepay_confirmation_required'));
|
||||
} else {
|
||||
$update_uri = $wepay->request('/account/get_update_uri', [
|
||||
'account_id' => $wepay_account->account_id,
|
||||
'redirect_uri' => config('ninja.app_url'),
|
||||
]);
|
||||
|
||||
return redirect($update_uri->uri);
|
||||
}
|
||||
|
||||
|
||||
return redirect()->to('/wepay/finished');
|
||||
}
|
||||
|
||||
}
|
@ -29,6 +29,7 @@ class CheckClientExistence
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$multiple_contacts = ClientContact::query()
|
||||
->with('company','client')
|
||||
->where('email', auth('contact')->user()->email)
|
||||
->whereNotNull('email')
|
||||
->where('email', '<>', '')
|
||||
|
@ -71,11 +71,16 @@ class ContactKeyLogin
|
||||
}
|
||||
} elseif ($request->segment(2) && $request->segment(2) == 'key_login' && $request->segment(3)) {
|
||||
if ($client_contact = ClientContact::where('contact_key', $request->segment(3))->first()) {
|
||||
|
||||
if(empty($client_contact->email))
|
||||
if(empty($client_contact->email)) {
|
||||
$client_contact->email = Str::random(6) . "@example.com"; $client_contact->save();
|
||||
}
|
||||
|
||||
auth()->guard('contact')->login($client_contact, true);
|
||||
|
||||
if ($request->query('next')) {
|
||||
return redirect($request->query('next'));
|
||||
}
|
||||
|
||||
return redirect()->to('client/dashboard');
|
||||
}
|
||||
} elseif ($request->has('client_hash') && config('ninja.db.multi_db_enabled')) {
|
||||
@ -106,7 +111,6 @@ class ContactKeyLogin
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,8 @@ class ContactRegister
|
||||
|
||||
if($company)
|
||||
{
|
||||
abort_unless($company->client_can_register, 404);
|
||||
if(! $company->client_can_register)
|
||||
abort(400, 'Registration disabled');
|
||||
|
||||
$request->merge(['key' => $company->company_key]);
|
||||
|
||||
@ -49,7 +50,9 @@ class ContactRegister
|
||||
|
||||
if($company = Company::where($query)->first())
|
||||
{
|
||||
abort_unless($company->client_can_register, 404);
|
||||
|
||||
if(! $company->client_can_register)
|
||||
abort(400, 'Registration disabled');
|
||||
|
||||
$request->merge(['key' => $company->company_key]);
|
||||
|
||||
@ -62,7 +65,10 @@ class ContactRegister
|
||||
if ($request->route()->parameter('company_key') && Ninja::isSelfHost()) {
|
||||
$company = Company::where('company_key', $request->company_key)->firstOrFail();
|
||||
|
||||
abort_unless($company->client_can_register, 404);
|
||||
if(! (bool)$company->client_can_register);
|
||||
abort(400, 'Registration disabled');
|
||||
|
||||
$request->merge(['key' => $company->company_key]);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@ -72,7 +78,8 @@ class ContactRegister
|
||||
if (!$request->route()->parameter('company_key') && Ninja::isSelfHost()) {
|
||||
$company = Account::first()->default_company;
|
||||
|
||||
abort_unless($company->client_can_register, 404);
|
||||
if(! $company->client_can_register)
|
||||
abort(400, 'Registration disabled');
|
||||
|
||||
$request->merge(['key' => $company->company_key]);
|
||||
|
||||
|
@ -44,6 +44,14 @@ class PasswordProtection
|
||||
else
|
||||
$timeout = $timeout/1000;
|
||||
|
||||
//test if password if base64 encoded
|
||||
$x_api_password = $request->header('X-API-PASSWORD');
|
||||
|
||||
if($request->header('X-API-PASSWORD-BASE64'))
|
||||
{
|
||||
$x_api_password = base64_decode($request->header('X-API-PASSWORD-BASE64'));
|
||||
}
|
||||
|
||||
if (Cache::get(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in')) {
|
||||
|
||||
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
|
||||
@ -58,9 +66,6 @@ class PasswordProtection
|
||||
$google = new Google();
|
||||
$user = $google->getTokenResponse(request()->header('X-API-OAUTH-PASSWORD'));
|
||||
|
||||
nlog("user");
|
||||
nlog($user);
|
||||
|
||||
if (is_array($user)) {
|
||||
|
||||
$query = [
|
||||
@ -68,10 +73,8 @@ class PasswordProtection
|
||||
'oauth_provider_id'=> 'google'
|
||||
];
|
||||
|
||||
nlog($query);
|
||||
|
||||
//If OAuth and user also has a password set - check both
|
||||
if ($existing_user = MultiDB::hasUser($query) && auth()->user()->company()->oauth_password_required && auth()->user()->has_password && Hash::check(auth()->user()->password, $request->header('X-API-PASSWORD'))) {
|
||||
if ($existing_user = MultiDB::hasUser($query) && auth()->user()->company()->oauth_password_required && auth()->user()->has_password && Hash::check(auth()->user()->password, $x_api_password)) {
|
||||
|
||||
nlog("existing user with password");
|
||||
|
||||
@ -91,7 +94,7 @@ class PasswordProtection
|
||||
return response()->json($error, 412);
|
||||
|
||||
|
||||
}elseif ($request->header('X-API-PASSWORD') && Hash::check($request->header('X-API-PASSWORD'), auth()->user()->password)) {
|
||||
}elseif ($x_api_password && Hash::check($x_api_password, auth()->user()->password)) {
|
||||
|
||||
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
|
||||
|
||||
|
@ -52,11 +52,18 @@ class QueryLogging
|
||||
$timeEnd = microtime(true);
|
||||
$time = $timeEnd - $timeStart;
|
||||
|
||||
//nlog($request->method().' - '.urldecode($request->url()).": $count queries - ".$time);
|
||||
// if($count > 50)
|
||||
// if($count > 150)
|
||||
// nlog($queries);
|
||||
|
||||
LightLogs::create(new DbQuery($request->method(), urldecode($request->url()), $count, $time, request()->ip()))
|
||||
$ip = '';
|
||||
|
||||
if(request()->header('Cf-Connecting-Ip'))
|
||||
$ip = request()->header('Cf-Connecting-Ip');
|
||||
else{
|
||||
$ip = request()->ip();
|
||||
}
|
||||
|
||||
LightLogs::create(new DbQuery($request->method(), urldecode($request->url()), $count, $time, $ip))
|
||||
->batch();
|
||||
}
|
||||
|
||||
|
44
app/Http/Middleware/SetDocumentDb.php
Normal file
44
app/Http/Middleware/SetDocumentDb.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?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 App\Http\Middleware;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use stdClass;
|
||||
|
||||
class SetDocumentDb
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$error = [
|
||||
'message' => 'Document not set or not found',
|
||||
'errors' => new stdClass,
|
||||
];
|
||||
|
||||
if (config('ninja.db.multi_db_enabled')) {
|
||||
|
||||
if (! MultiDB::documentFindAndSetDb($request->segment(2)))
|
||||
return response()->json($error, 400);
|
||||
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
@ -30,17 +30,25 @@ class UrlSetDb
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
|
||||
if (config('ninja.db.multi_db_enabled')) {
|
||||
$hashids = new Hashids('', 10); //decoded output is _always_ an array.
|
||||
$hashids = new Hashids(config('ninja.hash_salt'), 10);
|
||||
|
||||
//parse URL hash and set DB
|
||||
$segments = explode('-', $request->route('confirmation_code'));
|
||||
|
||||
if(!is_array($segments))
|
||||
return response()->json(['message' => 'Invalid confirmation code'], 403);
|
||||
|
||||
$hashed_db = $hashids->decode($segments[0]);
|
||||
|
||||
if(!is_array($hashed_db))
|
||||
return response()->json(['message' => 'Invalid confirmation code'], 403);
|
||||
|
||||
MultiDB::setDB(MultiDB::DB_PREFIX.str_pad($hashed_db[0], 2, '0', STR_PAD_LEFT));
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,8 @@ class CreateAccountRequest extends Request
|
||||
}
|
||||
|
||||
protected function prepareForValidation()
|
||||
{nlog($this->all());
|
||||
{
|
||||
|
||||
$input = $this->all();
|
||||
|
||||
$input['user_agent'] = request()->server('HTTP_USER_AGENT');
|
||||
|
@ -54,6 +54,7 @@ class StoreClientRequest extends Request
|
||||
/* Ensure we have a client name, and that all emails are unique*/
|
||||
//$rules['name'] = 'required|min:1';
|
||||
$rules['settings'] = new ValidClientGroupSettingsRule();
|
||||
$rules['contacts'] = 'array';
|
||||
$rules['contacts.*.email'] = 'bail|nullable|distinct|sometimes|email';
|
||||
$rules['contacts.*.password'] = [
|
||||
'nullable',
|
||||
@ -144,7 +145,10 @@ class StoreClientRequest extends Request
|
||||
return $item->iso_3166_2 == $country_code || $item->iso_3166_3 == $country_code;
|
||||
})->first();
|
||||
|
||||
if($country)
|
||||
return (string) $country->id;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private function getCurrencyCode($code)
|
||||
|
@ -62,6 +62,7 @@ class UpdateClientRequest extends Request
|
||||
$rules['number'] = Rule::unique('clients')->where('company_id', auth()->user()->company()->id)->ignore($this->client->id);
|
||||
|
||||
$rules['settings'] = new ValidClientGroupSettingsRule();
|
||||
$rules['contacts'] = 'array';
|
||||
$rules['contacts.*.email'] = 'bail|nullable|distinct|sometimes|email';
|
||||
$rules['contacts.*.password'] = [
|
||||
'nullable',
|
||||
|
@ -37,8 +37,9 @@ class StoreClientGatewayTokenRequest extends Request
|
||||
|
||||
public function rules()
|
||||
{
|
||||
//ensure client is present
|
||||
$rules = [
|
||||
'client_id' => 'required',
|
||||
'client_id' => 'required|exists:clients,id,company_id,'.auth()->user()->company()->id,
|
||||
'company_gateway_id' => 'required',
|
||||
'gateway_type_id' => 'required|integer',
|
||||
'meta' => 'required',
|
||||
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\ClientPortal\Contact;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ContactPasswordResetRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'email' => 'required',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
@ -7,7 +7,7 @@
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\ClientPortal\Credits;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user