Merge pull request #9852 from turbo124/preview

Preview
This commit is contained in:
David Bomba 2024-08-03 14:17:59 +10:00 committed by GitHub
commit 2dd64afebd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
646 changed files with 395088 additions and 382238 deletions

View File

@ -23,4 +23,5 @@ API_SECRET=superdoopersecrethere
PHANTOMJS_PDF_GENERATION=false
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
SESSION_DRIVER=redis
PDF_GENERATOR=snappdf

View File

@ -63,3 +63,5 @@ APPLE_REDIRECT_URI=
NORDIGEN_SECRET_ID=
NORDIGEN_SECRET_KEY=
OPENEXCHANGE_APP_ID=

View File

@ -38,17 +38,22 @@ jobs:
sudo php artisan cache:clear
sudo find ./vendor/bin/ -type f -exec chmod +x {} \;
sudo find ./ -type d -exec chmod 755 {} \;
- name: Set current date to variable
id: set_date
run: echo "current_date=$(date '+%Y-%m-%d')" >> $GITHUB_ENV
- name: Prepare React FrontEnd
run: |
git clone https://${{secrets.commit_secret}}@github.com/invoiceninja/ui.git
cd ui
git checkout develop
cp .env.example .env
cp ../vite.config.ts.react ./vite.config.js
sed -i '/"version"/c\ "version": " Latest Build - ${{ env.current_date }}",' package.json
npm i
npm run build
cp -r dist/* ../public/
cp dist/index.html ../resources/views/react/index.blade.php
mv ../public/index.html ../resources/views/react/index.blade.php
- name: Prepare JS/CSS assets
run: |
@ -66,7 +71,7 @@ jobs:
- name: Build project
run: |
shopt -s dotglob
tar --exclude='public/storage' --exclude='./htaccess' --exclude='invoiceninja.zip' -zcvf /home/runner/work/invoiceninja/react-invoiceninja.tar *
tar --exclude='public/storage' --exclude='./htaccess' --exclude='invoiceninja.zip' -zcvf /home/runner/work/invoiceninja/invoiceninja.tar *
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
@ -74,4 +79,4 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: |
/home/runner/work/invoiceninja/react-invoiceninja.tar
/home/runner/work/invoiceninja/invoiceninja.tar

View File

@ -1,84 +0,0 @@
on:
release:
types: [released]
name: Upload Release Asset
jobs:
build:
name: Upload Release Asset
runs-on: ubuntu-latest
steps:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.2
extensions: mysql, mysqlnd, sqlite3, bcmath, gd, curl, zip, openssl, mbstring, xml
- name: Checkout code
uses: actions/checkout@v1
with:
ref: v5-develop
- name: Copy .env file
run: |
cp .env.example .env
- name: Install composer dependencies
run: |
composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }}
composer install --no-dev
- name: Prepare Laravel Application
run: |
cp .env.example .env
php artisan key:generate --force
php artisan optimize
php artisan storage:link --force
sudo php artisan cache:clear
sudo find ./vendor/bin/ -type f -exec chmod +x {} \;
sudo find ./ -type d -exec chmod 755 {} \;
- name: Prepare React FrontEnd
run: |
git clone https://${{secrets.commit_secret}}@github.com/invoiceninja/ui.git
cd ui
git checkout develop
npm i
npm run build
mkdir -p ../public/react/${{ github.event.release.tag_name }}/
cp -r dist/react/* ../public/react/${{ github.event.release.tag_name }}/
cp -r dist/react/* ../public/react/
cp dist/index.html ../resources/views/react/index.blade.php
mkdir -p ../public/tinymce_6.4.2/tinymce/js/
cp -r node_modules/tinymce ../public/tinymce_6.4.2/tinymce/js/
cd ..
rm -rf ui
php artisan ninja:react
- name: Prepare JS/CSS assets
run: |
npm i
npm run production
- name: Cleanup Builds
run: |
sudo rm -rf bootstrap/cache/*
sudo rm -rf node_modules
sudo rm -rf .git
sudo rm .env
- name: Build project
run: |
shopt -s dotglob
tar --exclude='public/storage' --exclude='./htaccess' --exclude='invoiceninja.zip' -zcvf /home/runner/work/invoiceninja/invoiceninja.tar *
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: |
/home/runner/work/invoiceninja/invoiceninja.tar

View File

@ -1,5 +1,5 @@
<p align="center">
<img src="https://raw.githubusercontent.com/hillelcoren/invoice-ninja/master/public/images/round_logo.png" alt="Sublime's custom image"/>
<a href ="https://www.youtube.com/watch?v=CxGxXiotv0I" target="_blank" title="Invoice Ninja Overview Video"><img src="https://raw.githubusercontent.com/hillelcoren/invoice-ninja/master/public/images/round_logo.png" alt="Sublime's custom image"/></a>
</p>
![v5-develop phpunit](https://github.com/invoiceninja/invoiceninja/workflows/phpunit/badge.svg?branch=v5-develop)
@ -8,25 +8,30 @@
# Invoice Ninja 5
## [Hosted](https://www.invoiceninja.com) | [Self-Hosted](https://www.invoiceninja.org)
Invoice Ninja Version 5 is here! We've taken the best parts of version 4 and added the most requested features to create an invoicing application like no other. Check the [Invoice Ninja YouTube Channel](https://www.youtube.com/@appinvoiceninja) to get up to speed, or try the [Demo](https://react.invoicing.co/demo) now.
Join us on [Slack](http://slack.invoiceninja.com), [Discord](https://discord.gg/ZwEdtfCwXA), [Support Forum](https://forum.invoiceninja.com)
**Choose your setup**
## Introduction
- [Hosted](https://www.invoiceninja.com): Our hosted version is a Software as a Service (SaaS) solution. You're up and running in under 5 minutes, with no need to worry about hosting or server infrastructure.
- [Self-Hosted](https://www.invoiceninja.org): For those who prefer to manage their own hosting and server infrastructure. This version gives you full control and flexibility.
Version 5 of Invoice Ninja is here!
We took the best parts of version 4 and add the most requested features
to produce a invoicing application like no other.
All Pro and Enterprise features from the hosted app are included in the open-source code. We offer a $30 per year white-label license to remove the Invoice Ninja branding from client-facing parts of the app.
All Pro and Enterprise features from the hosted app are included in the open code.
We offer a $30 per year white-label license to remove the Invoice Ninja branding from client facing parts of the app.
#### Get social with us
* [Videos](https://www.youtube.com/@appinvoiceninja)
* [API Documentation](https://api-docs.invoicing.co/)
* [APP Documentation](https://invoiceninja.github.io/)
* [Support Forum](https://forum.invoiceninja.com)
* [Slack](http://slack.invoiceninja.com)
* [Discord](https://discord.gg/ZwEdtfCwXA)
* [Instagram](https://www.instagram.com/appinvoiceninja)
## Setup
#### Documentation
* [Invoice Ninja - API](https://api-docs.invoicing.co/)
* [Invoice Ninja - Developer Guide](https://invoiceninja.github.io/en/developer-guide/)
* [Invoice Ninja - User Guide](https://invoiceninja.github.io/en/user-guide/)
* [Invoice Ninja - Self-Hosted Installation Guide](https://invoiceninja.github.io/en/self-host-installation/)
## Installation Options and Clients
### Mobile Apps
* [iPhone](https://apps.apple.com/app/id1503970375?platform=iphone)
@ -39,22 +44,26 @@ We offer a $30 per year white-label license to remove the Invoice Ninja branding
* [Linux - Snap](https://snapcraft.io/invoiceninja)
* [Linux - Flatpak](https://flathub.org/apps/com.invoiceninja.InvoiceNinja)
### Installation Options
### Self-Hosted Server Installation
**Note:** The self-hosted options do support the desktop and mobile apps.
* [Server or VM](https://invoiceninja.github.io/en/self-host-installation/)
* [Docker File](https://hub.docker.com/r/invoiceninja/invoiceninja/)
* [Cloudron](https://cloudron.io/store/com.invoiceninja.cloudronapp.html)
* [Cloudron](https://www.cloudron.io/store/com.invoiceninja.cloudronapp2.html)
* [Softaculous](https://www.softaculous.com/apps/ecommerce/Invoice_Ninja)
### Recommended Providers
* [Stripe](https://stripe.com/)
* [Postmark](https://postmarkapp.com/)
## Quick Hosting Setup
## [Advanced] Quick Hosting Setup
In addition to the official [Invoice Ninja - Self-Hosted Installation Guide](https://invoiceninja.github.io/en/self-host-installation/) we have a few commands for you.
```sh
git clone --single-branch --branch v5-stable https://github.com/invoiceninja/invoiceninja.git
cp .env.example .env
composer i -o --no-dev
php artisan key:generate
```
Please Note:
@ -85,6 +94,7 @@ pass: password
```
## Developers Guide
In addition to the official [Invoice Ninja - Developer Guide](https://invoiceninja.github.io/en/developer-guide/) we've got your back with some insights.
### App Design

View File

@ -1 +1 @@
5.9.6
5.10.17

View File

@ -176,7 +176,7 @@ class BackupUpdate extends Command
try {
$doc_bin = $document->getFile();
} catch(\Exception $e) {
nlog($e->getMessage());
nlog("Exception:: BackupUpdate::" . $e->getMessage());
}
if ($doc_bin) {
@ -184,8 +184,6 @@ class BackupUpdate extends Command
$document->disk = $this->option('disk');
$document->saveQuietly();
nlog("Documents - Moving {$document->url} to {$this->option('disk')}");
}
});
@ -199,8 +197,6 @@ class BackupUpdate extends Command
if ($backup_bin) {
Storage::disk($this->option('disk'))->put($backup->filename, $backup_bin);
nlog("Backups - Moving {$backup->filename} to {$this->option('disk')}");
}
});
}

View File

@ -12,37 +12,38 @@
namespace App\Console\Commands;
use App;
use App\Models\User;
use App\Utils\Ninja;
use App\Models\Quote;
use App\Models\Client;
use App\Models\Credit;
use App\Models\Vendor;
use App\Models\Account;
use App\Models\Company;
use App\Models\Contact;
use App\Models\Expense;
use App\Models\Invoice;
use App\Models\Payment;
use App\Libraries\MultiDB;
use App\Models\CompanyUser;
use Illuminate\Support\Str;
use App\Models\CompanyToken;
use App\Models\ClientContact;
use App\Models\CompanyLedger;
use App\Models\PurchaseOrder;
use App\Models\VendorContact;
use App\Models\BankTransaction;
use App\Models\QuoteInvitation;
use Illuminate\Console\Command;
use App\Models\CreditInvitation;
use App\Models\RecurringInvoice;
use App\Models\InvoiceInvitation;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use App\Factory\ClientContactFactory;
use App\Factory\VendorContactFactory;
use App\Jobs\Company\CreateCompanyToken;
use App\Libraries\MultiDB;
use App\Models\Account;
use App\Models\BankTransaction;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Company;
use App\Models\CompanyLedger;
use App\Models\CompanyToken;
use App\Models\CompanyUser;
use App\Models\Contact;
use App\Models\Credit;
use App\Models\CreditInvitation;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
use App\Models\Payment;
use App\Models\PurchaseOrder;
use App\Models\Quote;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoice;
use App\Models\RecurringInvoiceInvitation;
use App\Models\User;
use App\Models\Vendor;
use App\Models\VendorContact;
use App\Utils\Ninja;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use Symfony\Component\Console\Input\InputOption;
/*
@ -130,6 +131,7 @@ class CheckData extends Command
$this->checkContactEmailAndSendEmailStatus();
$this->checkPaymentCurrency();
$this->checkSubdomainsSet();
$this->checkExpenseCurrency();
if (Ninja::isHosted()) {
$this->checkAccountStatuses();
@ -1158,7 +1160,21 @@ class CheckData extends Command
});
}
}
public function checkExpenseCurrency()
{
Expense::with('company')
->withTrashed()
->whereNull('exchange_rate')
->orWhere('exchange_rate', 0)
->cursor()
->each(function ($expense){
$expense->exchange_rate = 1;
$expense->saveQuietly();
$this->logMessage("Fixing - exchange rate for expense :: {$expense->id}");
});
}
}

View File

@ -56,8 +56,6 @@ class CreateAccount extends Command
{
$this->info(date('r').' Create Single Account...');
$this->warmCache();
$this->createAccount();
}
@ -121,28 +119,6 @@ class CreateAccount extends Command
(new CreateCompanyTaskStatuses($company, $user))->handle();
(new VersionCheck())->handle();
$this->warmCache();
}
private function warmCache()
{
/* Warm up the cache !*/
$cached_tables = config('ninja.cached_tables');
foreach ($cached_tables as $name => $class) {
if ($name == 'payment_terms') {
$orderBy = 'num_days';
} elseif ($name == 'fonts') {
$orderBy = 'sort_order';
} elseif (in_array($name, ['currencies', 'industries', 'languages', 'countries', 'banks'])) {
$orderBy = 'name';
} else {
$orderBy = 'id';
}
$tableData = $class::orderBy($orderBy)->get();
if ($tableData->count()) {
Cache::forever($name, $tableData);
}
}
}
}

View File

@ -97,10 +97,6 @@ class CreateSingleAccount extends Command
$this->count = 5;
$this->gateway = $this->argument('gateway');
$this->info('Warming up cache');
$this->warmCache();
$this->createSmallAccount();
@ -389,6 +385,9 @@ class CreateSingleAccount extends Command
});
$this->countryClients($company, $user);
$this->info("finished");
}
@ -774,32 +773,6 @@ class CreateSingleAccount extends Command
return $line_items;
}
private function warmCache()
{
/* Warm up the cache !*/
$cached_tables = config('ninja.cached_tables');
foreach ($cached_tables as $name => $class) {
// check that the table exists in case the migration is pending
if (! Schema::hasTable((new $class())->getTable())) {
continue;
}
if ($name == 'payment_terms') {
$orderBy = 'num_days';
} elseif ($name == 'fonts') {
$orderBy = 'sort_order';
} elseif (in_array($name, ['currencies', 'industries', 'languages', 'countries', 'banks'])) {
$orderBy = 'name';
} else {
$orderBy = 'id';
}
$tableData = $class::orderBy($orderBy)->get();
if ($tableData->count()) {
Cache::forever($name, $tableData);
}
}
}
private function createGateways($company, $user)
{
if (config('ninja.testvars.stripe') && ($this->gateway == 'all' || $this->gateway == 'stripe')) {
@ -1139,4 +1112,44 @@ class CreateSingleAccount extends Command
event(new RecurringInvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars()));
}
private function countryClients($company, $user)
{
Client::unguard();
Client::create([
'company_id' => $company->id,
'user_id' => $user->id,
'name' => 'Swiss Company AG',
'website' => 'https://www.testcompany.ch',
'private_notes' => 'These are some private notes about the test client.',
'balance' => 0,
'paid_to_date' => 0,
'vat_number' => '654321987',
'id_number' => 'CH9300762011623852957', // Sample Swiss IBAN
'custom_value1' => '2024-07-22 10:00:00',
'custom_value2' => 'blue',
'custom_value3' => 'sampleword',
'custom_value4' => 'test@example.com',
'address1' => '123',
'address2' => 'Test Street 45',
'city' => 'Zurich',
'state' => 'Zurich',
'postal_code' => '8001',
'country_id' => '756', // Switzerland
'shipping_address1' => '123',
'shipping_address2' => 'Test Street 45',
'shipping_city' => 'Zurich',
'shipping_state' => 'Zurich',
'shipping_postal_code' => '8001',
'shipping_country_id' => '756', // Switzerland
'settings' => ClientSettings::Defaults(),
'client_hash' => \Illuminate\Support\Str::random(32),
'routing_id' => '',
]);
}
}

View File

@ -86,8 +86,6 @@ class CreateTestData extends Command
$this->info('Warming up cache');
$this->warmCache();
$this->createSmallAccount();
$this->createMediumAccount();
$this->createLargeAccount();
@ -673,31 +671,4 @@ class CreateTestData extends Command
return $line_items;
}
private function warmCache()
{
/* Warm up the cache !*/
$cached_tables = config('ninja.cached_tables');
foreach ($cached_tables as $name => $class) {
if (! Cache::has($name)) {
// check that the table exists in case the migration is pending
if (! Schema::hasTable((new $class())->getTable())) {
continue;
}
if ($name == 'payment_terms') {
$orderBy = 'num_days';
} elseif ($name == 'fonts') {
$orderBy = 'sort_order';
} elseif (in_array($name, ['currencies', 'industries', 'languages', 'countries', 'banks'])) {
$orderBy = 'name';
} else {
$orderBy = 'id';
}
$tableData = $class::orderBy($orderBy)->get();
if ($tableData->count()) {
Cache::forever($name, $tableData);
}
}
}
}
}

View File

@ -84,15 +84,13 @@ class DemoMode extends Command
$this->invoice_repo = new InvoiceRepository();
$cached_tables = config('ninja.cached_tables');
$this->info('Migrating');
Artisan::call('migrate:fresh --force');
$this->info('Seeding');
Artisan::call('db:seed --force');
$this->buildCache(true);
Artisan::call('db:seed --force');
Artisan::call('cache:clear');
$this->info('Seeding Random Data');
$this->createSmallAccount();
@ -623,31 +621,4 @@ class DemoMode extends Command
return $line_items;
}
private function warmCache()
{
/* Warm up the cache !*/
$cached_tables = config('ninja.cached_tables');
foreach ($cached_tables as $name => $class) {
if (! Cache::has($name)) {
// check that the table exists in case the migration is pending
if (! Schema::hasTable((new $class())->getTable())) {
continue;
}
if ($name == 'payment_terms') {
$orderBy = 'num_days';
} elseif ($name == 'fonts') {
$orderBy = 'sort_order';
} elseif (in_array($name, ['currencies', 'industries', 'languages', 'countries', 'banks'])) {
$orderBy = 'name';
} else {
$orderBy = 'id';
}
$tableData = $class::orderBy($orderBy)->get();
if ($tableData->count()) {
Cache::forever($name, $tableData);
}
}
}
}
}

View File

@ -62,7 +62,6 @@ class HostedMigrations extends Command
*/
public function handle()
{
$this->buildCache();
if (! MultiDB::userFindAndSetDb($this->option('email'))) {
$this->info('Could not find a user with that email address');

View File

@ -75,8 +75,6 @@ class ImportMigrations extends Command
{
$this->faker = Factory::create();
$this->buildCache();
$path = $this->option('path') ?? public_path('storage/migrations/import');
$directory = new DirectoryIterator($path);

View File

@ -54,13 +54,6 @@ class PostUpdate extends Command
info('finished migrating');
$output = [];
// exec('vendor/bin/composer install --no-dev -o', $output);
info(print_r($output, 1));
info('finished running composer install ');
try {
// Artisan::call('optimize');
Artisan::call('config:clear');
@ -86,8 +79,7 @@ class PostUpdate extends Command
info('queue restarted');
$this->buildCache(true);
Artisan::call('cache:clear');
VersionCheck::dispatch();
info('Sent for version check');

View File

@ -62,10 +62,10 @@ class SendRemindersCron extends Command
public function handle()
{
Invoice::where('next_send_date', '<=', now()->toDateTimeString())
->whereNull('deleted_at')
->where('is_deleted', 0)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('balance', '>', 0)
->whereNull('invoices.deleted_at')
->where('invoices.is_deleted', 0)
->whereIn('invoices.status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('invoices.balance', '>', 0)
->whereHas('client', function ($query) {
$query->where('is_deleted', 0)
->where('deleted_at', null);
@ -73,6 +73,7 @@ class SendRemindersCron extends Command
->whereHas('company', function ($query) {
$query->where('is_disabled', 0);
})
->with('invitations')->cursor()->each(function ($invoice) {
if ($invoice->isPayable()) {
$reminder_template = $invoice->calculateTemplate('invoice');

View File

@ -15,7 +15,6 @@ use App\Jobs\Cron\AutoBillCron;
use App\Jobs\Cron\RecurringExpensesCron;
use App\Jobs\Cron\RecurringInvoicesCron;
use App\Jobs\Cron\SubscriptionCron;
use App\Jobs\Cron\UpdateCalculatedFields;
use App\Jobs\Invoice\InvoiceCheckLateWebhook;
use App\Jobs\Ninja\AdjustEmailQuota;
use App\Jobs\Ninja\BankTransactionSync;
@ -27,11 +26,13 @@ use App\Jobs\Ninja\TaskScheduler;
use App\Jobs\Quote\QuoteCheckExpired;
use App\Jobs\Subscription\CleanStaleInvoiceOrder;
use App\Jobs\Util\DiskCleanup;
use App\Jobs\Util\QuoteReminderJob;
use App\Jobs\Util\ReminderJob;
use App\Jobs\Util\SchedulerCheck;
use App\Jobs\Util\UpdateExchangeRates;
use App\Jobs\Util\VersionCheck;
use App\Models\Account;
use App\PaymentDrivers\Rotessa\Jobs\TransactionReport;
use App\Utils\Ninja;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -55,18 +56,21 @@ class Kernel extends ConsoleKernel
/* Send reminders */
$schedule->job(new ReminderJob())->hourly()->withoutOverlapping()->name('reminder-job')->onOneServer();
/* Send quote reminders */
$schedule->job(new QuoteReminderJob())->hourly()->withoutOverlapping()->name('quote-reminder-job')->onOneServer();
/* Sends recurring invoices*/
$schedule->job(new RecurringInvoicesCron())->hourly()->withoutOverlapping()->name('recurring-invoice-job')->onOneServer();
/* Checks for scheduled tasks */
$schedule->job(new TaskScheduler())->hourlyAt(10)->withoutOverlapping()->name('task-scheduler-job')->onOneServer();
/* Checks Rotessa Transactions */
$schedule->job(new TransactionReport())->dailyAt('01:48')->withoutOverlapping()->name('rotessa-transaction-report')->onOneServer();
/* Stale Invoice Cleanup*/
$schedule->job(new CleanStaleInvoiceOrder())->hourlyAt(30)->withoutOverlapping()->name('stale-invoice-job')->onOneServer();
/* Stale Invoice Cleanup*/
$schedule->job(new UpdateCalculatedFields())->hourlyAt(40)->withoutOverlapping()->name('update-calculated-fields-job')->onOneServer();
/* Checks for large companies and marked them as is_large */
$schedule->job(new CompanySizeCheck())->dailyAt('23:20')->withoutOverlapping()->name('company-size-job')->onOneServer();

View File

@ -54,8 +54,8 @@ class AccountPlatform extends GenericMixedMetric
public function __construct($string_metric5, $string_metric6, $string_metric7)
{
$this->string_metric5 = $string_metric5;
$this->string_metric6 = $string_metric6;
$this->string_metric5 = mb_convert_encoding($string_metric5, 'UTF-8');
$this->string_metric6 = mb_convert_encoding($string_metric6, 'UTF-8');
$this->string_metric7 = $string_metric7;
}
}

View File

@ -73,7 +73,7 @@ class DbQuery extends GenericMixedMetric
$this->string_metric6 = $string_metric6;
$this->double_metric2 = $double_metric2;
$this->string_metric7 = $string_metric7;
$this->string_metric8 = $string_metric8;
$this->string_metric9 = $string_metric9;
$this->string_metric8 = mb_convert_encoding($string_metric8, "UTF-8");
$this->string_metric9 = mb_convert_encoding($string_metric9, "UTF-8");
}
}

View File

@ -507,7 +507,25 @@ class CompanySettings extends BaseSettings
public int $task_round_to_nearest = 1;
/** quote reminders */
public $email_quote_template_reminder1 = '';
public $email_quote_subject_reminder1 = '';
public $enable_quote_reminder1 = false;
public $quote_num_days_reminder1 = 0;
public $quote_schedule_reminder1 = ''; //before_valid_until_date,after_valid_until_date,after_quote_date
public $quote_late_fee_amount1 = 0;
public $quote_late_fee_percent1 = 0;
public static $casts = [
'enable_quote_reminder1' => 'bool',
'quote_num_days_reminder1' => 'int',
'quote_schedule_reminder1' => 'string',
'quote_late_fee_amount1' => 'float',
'quote_late_fee_percent1' => 'float',
'email_quote_template_reminder1' => 'string',
'email_quote_subject_reminder1' => 'string',
'task_round_up' => 'bool',
'task_round_to_nearest' => 'int',
'e_quote_type' => 'string',
@ -962,6 +980,7 @@ class CompanySettings extends BaseSettings
'$invoice.due_date',
'$invoice.total',
'$invoice.balance_due',
'$invoice.project',
],
'quote_details' => [
'$quote.number',
@ -969,6 +988,7 @@ class CompanySettings extends BaseSettings
'$quote.date',
'$quote.valid_until',
'$quote.total',
'$quote.project',
],
'credit_details' => [
'$credit.number',

View File

@ -115,12 +115,32 @@ class EmailTemplateDefaults
case 'email_vendor_notification_body':
return self::emailVendorNotificationBody();
case 'email_quote_template_reminder1':
return self::emailQuoteReminder1Body();
case 'email_quote_subject_reminder1':
return self::emailQuoteReminder1Subject();
default:
return self::emailInvoiceTemplate();
}
}
public static function emailQuoteReminder1Subject()
{
return ctrans('texts.quote_reminder_subject', ['quote' => '$number', 'company' => '$company.name']);
}
public static function emailQuoteReminder1Body()
{
$invoice_message = '<p>$client<br><br>'.self::transformText('quote_reminder_message').'</p><div class="center">$view_button</div>';
return $invoice_message;
}
public static function emailVendorNotificationSubject()
{
return self::transformText('vendor_notification_subject');

View File

@ -220,6 +220,7 @@ class BaseRule implements RuleInterface
try {
$this->invoice->saveQuietly();
} catch(\Exception $e) {
nlog("Exception:: BaseRule::" . $e->getMessage());
}
}
@ -261,7 +262,7 @@ class BaseRule implements RuleInterface
return $this->client->state;
}
return USStates::getState(strlen($this->client->postal_code) > 1 ? $this->client->postal_code : $this->client->shipping_postal_code);
return USStates::getState(strlen($this->client->postal_code ?? '') > 1 ? $this->client->postal_code : $this->client->shipping_postal_code);
} catch (\Exception $e) {
return 'CA';

View File

@ -207,6 +207,14 @@ class Rule extends BaseRule implements RuleInterface
*/
public function override($item): self
{
$this->tax_rate1 = $item->tax_rate1;
$this->tax_name1 = $item->tax_name1;
$this->tax_rate2 = $item->tax_rate2;
$this->tax_name2 = $item->tax_name2;
$this->tax_rate3 = $item->tax_rate3;
$this->tax_name3 = $item->tax_name3;
return $this;
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\PL;
use App\DataMapper\Tax\DE\Rule as DERule;
class Rule extends DERule
{
/** @var string $seller_region */
public string $seller_region = 'EU';
/** @var bool $consumer_tax_exempt */
public bool $consumer_tax_exempt = false;
/** @var bool $business_tax_exempt */
public bool $business_tax_exempt = false;
/** @var bool $eu_business_tax_exempt */
public bool $eu_business_tax_exempt = true;
/** @var bool $foreign_business_tax_exempt */
public bool $foreign_business_tax_exempt = false;
/** @var bool $foreign_consumer_tax_exempt */
public bool $foreign_consumer_tax_exempt = false;
/** @var float $tax_rate */
public float $tax_rate = 0;
/** @var float $reduced_tax_rate */
public float $reduced_tax_rate = 0;
public string $tax_name1 = 'VAT';
}

View File

@ -24,6 +24,7 @@ class TaxData
public function __construct(public Response $origin)
{
// @phpstan-ignore-next-line
foreach($origin as $key => $value) {
$this->{$key} = $value;
}

View File

@ -17,7 +17,7 @@ class TaxModel
public string $seller_subregion = 'CA';
/** @var string $version */
public string $version = 'alpha';
public string $version = 'beta';
/** @var object $regions */
public object $regions;
@ -28,15 +28,38 @@ class TaxModel
* @param TaxModel $model
* @return void
*/
public function __construct(public ?TaxModel $model = null)
public function __construct(public mixed $model = null)
{
if(!$this->model) {
if(!$model) {
$this->regions = $this->init();
} else {
$this->regions = $model;
//@phpstan-ignore-next-line
foreach($model as $key => $value) {
$this->{$key} = $value;
}
}
$this->migrate();
}
public function migrate(): self
{
if($this->version == 'alpha')
{
$this->regions->EU->subregions->PL = new \stdClass();
$this->regions->EU->subregions->PL->tax_rate = 23;
$this->regions->EU->subregions->PL->tax_name = 'VAT';
$this->regions->EU->subregions->PL->reduced_tax_rate = 8;
$this->regions->EU->subregions->PL->apply_tax = false;
$this->version = 'beta';
}
return $this;
}
/**
@ -474,6 +497,12 @@ class TaxModel
$this->regions->EU->subregions->NL->reduced_tax_rate = 9;
$this->regions->EU->subregions->NL->apply_tax = false;
$this->regions->EU->subregions->PL = new \stdClass();
$this->regions->EU->subregions->PL->tax_rate = 23;
$this->regions->EU->subregions->PL->tax_name = 'VAT';
$this->regions->EU->subregions->PL->reduced_tax_rate = 8;
$this->regions->EU->subregions->PL->apply_tax = false;
$this->regions->EU->subregions->PT = new \stdClass();
$this->regions->EU->subregions->PT->tax_rate = 23;
$this->regions->EU->subregions->PT->tax_name = 'IVA';

View File

@ -197,6 +197,10 @@ region:
vat: 21
reduced_vat: 9
apply_tax: false
PL:
vat: 23
reduced_vat: 8
apply_tax: false
PT:
vat: 23
reduced_vat: 6

View File

@ -0,0 +1,55 @@
<?php
namespace App\DataProviders;
final class CAProvinces {
/**
* The provinces and territories of Canada
*
* @var array
*/
protected static $provinces = [
'AB' => 'Alberta',
'BC' => 'British Columbia',
'MB' => 'Manitoba',
'NB' => 'New Brunswick',
'NL' => 'Newfoundland And Labrador',
'NS' => 'Nova Scotia',
'ON' => 'Ontario',
'PE' => 'Prince Edward Island',
'QC' => 'Quebec',
'SK' => 'Saskatchewan',
'NT' => 'Northwest Territories',
'NU' => 'Nunavut',
'YT' => 'Yukon'
];
/**
* Get the name of the province or territory for a given abbreviation.
*
* @param string $abbreviation
* @return string
*/
public static function getName($abbreviation) {
return self::$provinces[$abbreviation];
}
/**
* Get all provinces and territories.
*
* @return array
*/
public static function get() {
return self::$provinces;
}
/**
* Get the abbreviation for a given province or territory name.
*
* @param string $name
* @return string
*/
public static function getAbbreviation($name) {
return array_search(ucwords($name), self::$provinces);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\DataProviders;
use Omnipay\Rotessa\Object\Frequency;
final class Frequencies
{
public static function get() : array {
return Frequency::getTypes();
}
public static function getFromType() {
}
public static function getOnePayment() {
return Frequency::ONCE;
}
}

View File

@ -34006,7 +34006,7 @@ class USStates
'WA', 'WA', 'WA', 'WA', 'WA', 'WA', 'WA', 'AK', 'AK', 'AK', 'AK', 'AK'
];
$prefix = substr($zip, 0, 3);
$prefix = substr(($zip ?? ''), 0, 3);
$index = intval($prefix);
/* converts prefix to integer */
return $zip_by_state[$index] == "--" ? false : $zip_by_state[$index];

View File

@ -13,15 +13,21 @@ namespace App\Events\Client;
use App\Models\Client;
use App\Models\Company;
use League\Fractal\Manager;
use League\Fractal\Resource\Item;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use App\Transformers\ArraySerializer;
use Illuminate\Queue\SerializesModels;
use App\Transformers\ClientTransformer;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
/**
* Class ClientWasArchived.
*/
class ClientWasArchived
class ClientWasArchived implements ShouldBroadcast
{
use Dispatchable;
use InteractsWithSockets;
@ -50,13 +56,34 @@ class ClientWasArchived
$this->event_vars = $event_vars;
}
// /**
// * Get the channels the event should broadcast on.
// *
// * @return Channel|array
// */
public function broadcastWith()
{
$manager = new Manager();
$manager->setSerializer(new ArraySerializer());
$class = sprintf('App\\Transformers\\%sTransformer', class_basename($this->client));
$transformer = new $class();
$resource = new Item($this->client, $transformer, $this->client->getEntityType());
$data = $manager->createData($resource)->toArray();
return $data;
}
/**
* Get the channels the event should broadcast on.
*
* @return Channel|array
*/
public function broadcastOn()
{
return [];
return [
new PrivateChannel("company-{$this->company->company_key}"),
];
}
}

View File

@ -0,0 +1,28 @@
<?php
/**
* Quote Ninja (https://Quoteninja.com).
*
* @link https://github.com/Quoteninja/Quoteninja source repository
*
* @copyright Copyright (c) 2024. Quote Ninja LLC (https://Quoteninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Events\Quote;
use App\Models\Company;
use App\Models\QuoteInvitation;
use Illuminate\Queue\SerializesModels;
/**
* Class QuoteReminderWasEmailed.
*/
class QuoteReminderWasEmailed
{
use SerializesModels;
public function __construct(public QuoteInvitation $invitation, public Company $company, public array $event_vars, public string $template)
{
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class DuplicatePaymentException extends Exception
{
/**
* Report the exception.
*
* @return void
*/
public function report()
{
//
}
/**
* Render the exception into an HTTP response.
*
* @param Request $request
* @return JsonResponse
*/
public function render($request)
{
return response()->json([
'message' => 'Duplicate request',
], 400);
}
}

View File

@ -18,6 +18,7 @@ use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException;
use Illuminate\Database\Eloquent\RelationNotFoundException;
use Illuminate\Encryption\MissingAppKeyException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Exceptions\ThrottleRequestsException;
use Illuminate\Http\Request;
@ -94,25 +95,16 @@ class Handler extends ExceptionHandler
*/
public function report(Throwable $exception)
{
if (! Schema::hasTable('accounts')) {
info('account table not found');
return;
}
if (Ninja::isHosted()) {
// if($exception instanceof ThrottleRequestsException && class_exists(\Modules\Admin\Events\ThrottledExceptionRaised::class)) {
// $uri = urldecode(request()->getRequestUri());
// event(new \Modules\Admin\Events\ThrottledExceptionRaised(auth()->user()?->account?->key, $uri, request()->ip()));
// }
Integration::configureScope(function (Scope $scope): void {
$name = 'hosted@invoiceninja.com';
if (auth()->guard('contact') && auth()->guard('contact')->user()) {
if (auth()->guard('contact') && auth()->guard('contact')->user()) { // @phpstan-ignore-line
$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()) { // @phpstan-ignore-line
$name = 'Admin = '.auth()->guard('user')->user()->email;
$key = auth()->user()->account->key;
} else {
@ -131,13 +123,14 @@ class Handler extends ExceptionHandler
}
} elseif (app()->bound('sentry')) {
Integration::configureScope(function (Scope $scope): void {
if (auth()->guard('contact') && auth()->guard('contact')->user() && auth()->guard('contact')->user()->company->account->report_errors) {
if (auth()->guard('contact') && auth()->guard('contact')->user() && auth()->guard('contact')->user()->company->account->report_errors) {// @phpstan-ignore-line
$scope->setUser([
'id' => auth()->guard('contact')->user()->company->account->key,
'email' => 'anonymous@example.com',
'name' => 'Anonymous User',
]);
} elseif (auth()->guard('user') && auth()->guard('user')->user() && auth()->user()->companyIsSet() && auth()->user()->company()->account->report_errors) {
} elseif (auth()->guard('user') && auth()->guard('user')->user() && auth()->user()->companyIsSet() && auth()->user()->company()->account->report_errors) {// @phpstan-ignore-line
$scope->setUser([
'id' => auth()->user()->account->key,
'email' => 'anonymous@example.com',
@ -152,6 +145,10 @@ class Handler extends ExceptionHandler
}
parent::report($exception);
if (Ninja::isSelfHost() && $exception instanceof MissingAppKeyException) {
info('To setup the app run: cp .env.example .env');
}
}
private function validException($exception)

View File

@ -25,8 +25,6 @@ use League\Csv\Writer;
class ActivityExport extends BaseExport
{
private $entity_transformer;
public string $date_key = 'created_at';
private string $date_format = 'YYYY-MM-DD';
@ -43,7 +41,7 @@ class ActivityExport extends BaseExport
{
$this->company = $company;
$this->input = $input;
$this->entity_transformer = new ActivityTransformer();
}
public function returnJson()
@ -59,6 +57,7 @@ class ActivityExport extends BaseExport
$report = $query->cursor()
->map(function ($resource) {
/** @var \App\Models\Activity $resource */
$row = $this->buildActivityRow($resource);
return $this->processMetaData($row, $resource);
})->toArray();
@ -111,7 +110,7 @@ class ActivityExport extends BaseExport
$query = Activity::query()
->where('company_id', $this->company->id);
$query = $this->addDateRange($query);
$query = $this->addDateRange($query, 'activities');
return $query;
}
@ -130,6 +129,9 @@ class ActivityExport extends BaseExport
$query->cursor()
->each(function ($entity) {
/** @var \App\Models\Activity $entity */
$this->buildRow($entity);
});
@ -143,10 +145,10 @@ class ActivityExport extends BaseExport
}
private function decorateAdvancedFields(Task $task, array $entity): array
{
return $entity;
}
// private function decorateAdvancedFields(Task $task, array $entity): array
// {
// return $entity;
// }
public function processMetaData(array $row, $resource): array

View File

@ -172,6 +172,7 @@ class BaseExport
'tax_rate3' => 'invoice.tax_rate3',
'recurring_invoice' => 'invoice.recurring_id',
'auto_bill' => 'invoice.auto_bill_enabled',
'project' => 'invoice.project',
];
protected array $recurring_invoice_report_keys = [
@ -449,6 +450,7 @@ class BaseExport
'status' => 'task.status_id',
'project' => 'task.project_id',
'billable' => 'task.billable',
'item_notes' => 'task.item_notes',
];
protected array $forced_client_fields = [
@ -971,9 +973,10 @@ class BaseExport
protected function addPaymentStatusFilters(Builder $query, string $status): Builder
{
/** @var array $status_parameters */
$status_parameters = explode(',', $status);
if(in_array('all', $status_parameters) || count($status_parameters) == 0) {
if((count($status_parameters) == 0) || in_array('all', $status_parameters)) {
return $query;
}
@ -1028,6 +1031,7 @@ class BaseExport
protected function addRecurringInvoiceStatusFilter(Builder $query, string $status): Builder
{
/** @var array $status_parameters */
$status_parameters = explode(',', $status);
if (in_array('all', $status_parameters) || count($status_parameters) == 0) {
@ -1036,6 +1040,10 @@ class BaseExport
$recurring_filters = [];
if($this->company->getSetting('report_include_drafts')){
$recurring_filters[] = RecurringInvoice::STATUS_DRAFT;
}
if (in_array('active', $status_parameters)) {
$recurring_filters[] = RecurringInvoice::STATUS_ACTIVE;
}
@ -1132,6 +1140,7 @@ class BaseExport
protected function addPurchaseOrderStatusFilter(Builder $query, string $status): Builder
{
/** @var array $status_parameters */
$status_parameters = explode(',', $status);
if (in_array('all', $status_parameters) || count($status_parameters) == 0) {
@ -1179,7 +1188,8 @@ class BaseExport
*/
protected function addInvoiceStatusFilter(Builder $query, string $status): Builder
{
/** @var array $status_parameters */
$status_parameters = explode(',', $status);
if(in_array('all', $status_parameters) || count($status_parameters) == 0) {
@ -1239,15 +1249,16 @@ class BaseExport
* Add Date Range
*
* @param Builder $query
* @param ?string $table_name
* @return Builder
*/
protected function addDateRange(Builder $query): Builder
protected function addDateRange(Builder $query, ?string $table_name = null): Builder
{
$query = $this->applyProductFilters($query);
$date_range = $this->input['date_range'];
if (array_key_exists('date_key', $this->input) && strlen($this->input['date_key']) > 1) {
if (array_key_exists('date_key', $this->input) && strlen($this->input['date_key'] ?? '') > 1 && ($table_name && $this->columnExists($table_name, $this->input['date_key']))) {
$this->date_key = $this->input['date_key'];
}
@ -1258,7 +1269,7 @@ class BaseExport
$custom_start_date = now()->startOfYear();
$custom_end_date = now();
}
switch ($date_range) {
case 'all':
$this->start_date = 'All available data';
@ -1604,5 +1615,18 @@ class BaseExport
ZipDocuments::dispatch($documents, $this->company, $user);
}
}
/**
* Tests that the column exists
* on the table prior to adding it to
* the query builder
*
* @param string $table
* @param string $column
* @return bool
*/
public function columnExists($table, $column): bool
{
return \Illuminate\Support\Facades\Schema::hasColumn($table, $column);
}
}

View File

@ -102,6 +102,8 @@ class ClientExport extends BaseExport
$report = $query->cursor()
->map(function ($client) {
/** @var \App\Models\Client $client */
$row = $this->buildRow($client);
return $this->processMetaData($row, $client);
})->toArray();
@ -131,7 +133,7 @@ class ClientExport extends BaseExport
$query->where('is_deleted', 0);
}
$query = $this->addDateRange($query);
$query = $this->addDateRange($query,' clients');
if($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query);
@ -154,6 +156,8 @@ class ClientExport extends BaseExport
$query->cursor()
->each(function ($client) {
/** @var \App\Models\Client $client */
$this->csv->insertOne($this->buildRow($client));
});
@ -243,16 +247,16 @@ class ClientExport extends BaseExport
return $entity;
}
private function calculateStatus($client)
{
if ($client->is_deleted) {
return ctrans('texts.deleted');
}
// private function calculateStatus($client)
// {
// if ($client->is_deleted) {
// return ctrans('texts.deleted');
// }
if ($client->deleted_at) {
return ctrans('texts.archived');
}
// if ($client->deleted_at) {
// return ctrans('texts.archived');
// }
return ctrans('texts.active');
}
// return ctrans('texts.active');
// }
}

View File

@ -63,7 +63,7 @@ class ContactExport extends BaseExport
$q->where('is_deleted', false);
});
$query = $this->addDateRange($query);
$query = $this->addDateRange($query, 'client_contacts');
return $query;
@ -82,6 +82,7 @@ class ContactExport extends BaseExport
$this->csv->insertOne($this->buildHeader());
$query->cursor()->each(function ($contact) {
/** @var \App\Models\ClientContact $contact */
$this->csv->insertOne($this->buildRow($contact));
});
@ -101,6 +102,7 @@ class ContactExport extends BaseExport
$report = $query->cursor()
->map(function ($contact) {
/** @var \App\Models\ClientContact $contact */
$row = $this->buildRow($contact);
return $this->processMetaData($row, $contact);
})->toArray();
@ -155,7 +157,7 @@ class ContactExport extends BaseExport
}
if (in_array('client.user_id', $this->input['report_keys'])) {
$entity['client.user_id'] = $client->user ? $client->user->present()->name() : '';
$entity['client.user_id'] = $client->user ? $client->user->present()->name() : '';// @phpstan-ignore-line
}
if (in_array('client.assigned_user_id', $this->input['report_keys'])) {

View File

@ -52,6 +52,8 @@ class CreditExport extends BaseExport
$report = $query->cursor()
->map(function ($credit) {
/** @var \App\Models\Credit $credit */
$row = $this->buildRow($credit);
return $this->processMetaData($row, $credit);
})->toArray();
@ -108,7 +110,7 @@ class CreditExport extends BaseExport
->where('company_id', $this->company->id)
->where('is_deleted', $this->input['include_deleted'] ?? false);
$query = $this->addDateRange($query);
$query = $this->addDateRange($query, 'credits');
$clients = &$this->input['client_id'];
@ -139,6 +141,7 @@ class CreditExport extends BaseExport
$query->cursor()
->each(function ($credit) {
/** @var \App\Models\Credit $credit */
$this->csv->insertOne($this->buildRow($credit));
});
@ -241,7 +244,7 @@ class CreditExport extends BaseExport
}
if (in_array('credit.user_id', $this->input['report_keys'])) {
$entity['credit.user_id'] = $credit->user ? $credit->user->present()->name() : '';
$entity['credit.user_id'] = $credit->user ? $credit->user->present()->name() : ''; //@phpstan-ignore-line
}
return $entity;

View File

@ -54,6 +54,8 @@ class DocumentExport extends BaseExport
$report = $query->cursor()
->map(function ($document) {
/** @var \App\Models\Document $document */
$row = $this->buildRow($document);
return $this->processMetaData($row, $document);
})->toArray();
@ -76,7 +78,7 @@ class DocumentExport extends BaseExport
$query = Document::query()->where('company_id', $this->company->id);
$query = $this->addDateRange($query);
$query = $this->addDateRange($query, 'documents');
if($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query);
@ -99,6 +101,7 @@ class DocumentExport extends BaseExport
$query->cursor()
->each(function ($entity) {
/** @var mixed $entity */
$this->csv->insertOne($this->buildRow($entity));
});

View File

@ -52,6 +52,8 @@ class ExpenseExport extends BaseExport
$report = $query->cursor()
->map(function ($resource) {
/** @var \App\Models\Expense $resource */
$row = $this->buildRow($resource);
return $this->processMetaData($row, $resource);
})->toArray();
@ -85,11 +87,11 @@ class ExpenseExport extends BaseExport
->where('company_id', $this->company->id);
if(!$this->input['include_deleted'] ?? false) {
if(!$this->input['include_deleted'] ?? false) { // @phpstan-ignore-line
$query->where('is_deleted', 0);
}
$query = $this->addDateRange($query);
$query = $this->addDateRange($query, 'expenses');
if($this->input['status'] ?? false) {
$query = $this->addExpenseStatusFilter($query, $this->input['status']);
@ -132,6 +134,8 @@ class ExpenseExport extends BaseExport
$query->cursor()
->each(function ($expense) {
/** @var \App\Models\Expense $expense */
$this->csv->insertOne($this->buildRow($expense));
});
@ -259,10 +263,17 @@ class ExpenseExport extends BaseExport
{
$precision = $expense->currency->precision ?? 2;
$entity['expense.net_amount'] = round($expense->amount, $precision);
if($expense->calculate_tax_by_amount) {
$total_tax_amount = round($expense->tax_amount1 + $expense->tax_amount2 + $expense->tax_amount3, $precision);
if($expense->uses_inclusive_taxes) {
$entity['expense.net_amount'] = round($expense->amount, $precision) - $total_tax_amount;
}
else {
$entity['expense.net_amount'] = round($expense->amount, $precision);
}
} else {
if($expense->uses_inclusive_taxes) {

View File

@ -63,11 +63,11 @@ class InvoiceExport extends BaseExport
->where('company_id', $this->company->id);
if(!$this->input['include_deleted'] ?? false) {
if(!$this->input['include_deleted'] ?? false) {// @phpstan-ignore-line
$query->where('is_deleted', 0);
}
$query = $this->addDateRange($query);
$query = $this->addDateRange($query, 'invoices');
$clients = &$this->input['client_id'];
@ -99,6 +99,8 @@ class InvoiceExport extends BaseExport
$report = $query->cursor()
->map(function ($resource) {
/** @var \App\Models\Invoice $resource */
$row = $this->buildRow($resource);
return $this->processMetaData($row, $resource);
})->toArray();
@ -119,6 +121,8 @@ class InvoiceExport extends BaseExport
$query->cursor()
->each(function ($invoice) {
/** @var \App\Models\Invoice $invoice */
$this->csv->insertOne($this->buildRow($invoice));
});
@ -149,9 +153,9 @@ class InvoiceExport extends BaseExport
private function decorateAdvancedFields(Invoice $invoice, array $entity): array
{
// if (in_array('invoice.status', $this->input['report_keys'])) {
// $entity['invoice.status'] = $invoice->stringStatus($invoice->status_id);
// }
if (in_array('invoice.project', $this->input['report_keys'])) {
$entity['invoice.project'] = $invoice->project ? $invoice->project->name : '';
}
if (in_array('invoice.recurring_id', $this->input['report_keys'])) {
$entity['invoice.recurring_id'] = $invoice->recurring_invoice->number ?? '';
@ -166,7 +170,8 @@ class InvoiceExport extends BaseExport
}
if (in_array('invoice.user_id', $this->input['report_keys'])) {
$entity['invoice.user_id'] = $invoice->user ? $invoice->user->present()->name() : '';
$entity['invoice.user_id'] = $invoice->user ? $invoice->user->present()->name() : ''; // @phpstan-ignore-line
}
return $entity;

View File

@ -75,11 +75,11 @@ class InvoiceItemExport extends BaseExport
})
->where('company_id', $this->company->id);
if(!$this->input['include_deleted'] ?? false) {
if(!$this->input['include_deleted'] ?? false) {// @phpstan-ignore-line
$query->where('is_deleted', 0);
}
$query = $this->addDateRange($query);
$query = $this->addDateRange($query, 'invoices');
$clients = &$this->input['client_id'];
@ -113,6 +113,8 @@ class InvoiceItemExport extends BaseExport
$query->cursor()
->each(function ($resource) {
/** @var \App\Models\Invoice $resource */
$this->iterateItems($resource);
foreach($this->storage_array as $row) {
@ -141,6 +143,8 @@ class InvoiceItemExport extends BaseExport
$query->cursor()
->each(function ($invoice) {
/** @var \App\Models\Invoice $invoice */
$this->iterateItems($invoice);
});
@ -258,9 +262,13 @@ class InvoiceItemExport extends BaseExport
}
if (in_array('invoice.user_id', $this->input['report_keys'])) {
$entity['invoice.user_id'] = $invoice->user ? $invoice->user->present()->name() : '';
$entity['invoice.user_id'] = $invoice->user ? $invoice->user->present()->name() : '';// @phpstan-ignore-line
}
if (in_array('invoice.project', $this->input['report_keys'])) {
$entity['invoice.project'] = $invoice->project ? $invoice->project->name : '';// @phpstan-ignore-line
}
return $entity;
}

View File

@ -62,7 +62,7 @@ class PaymentExport extends BaseExport
->where('company_id', $this->company->id)
->where('is_deleted', 0);
$query = $this->addDateRange($query);
$query = $this->addDateRange($query, 'payments');
$clients = &$this->input['client_id'];
@ -92,6 +92,8 @@ class PaymentExport extends BaseExport
$report = $query->cursor()
->map(function ($resource) {
/** @var \App\Models\Payment $resource */
$row = $this->buildRow($resource);
return $this->processMetaData($row, $resource);
})->toArray();
@ -112,6 +114,8 @@ class PaymentExport extends BaseExport
$query->cursor()
->each(function ($entity) {
/** @var \App\Models\Payment $entity */
$this->csv->insertOne($this->buildRow($entity));
});

View File

@ -51,6 +51,8 @@ class ProductExport extends BaseExport
$report = $query->cursor()
->map(function ($resource) {
/** @var \App\Models\Product $resource */
$row = $this->buildRow($resource);
return $this->processMetaData($row, $resource);
})->toArray();
@ -75,12 +77,11 @@ class ProductExport extends BaseExport
->withTrashed()
->where('company_id', $this->company->id);
if(!$this->input['include_deleted'] ?? false) {
if(!$this->input['include_deleted'] ?? false) { //@phpstan-ignore-line
$query->where('is_deleted', 0);
}
$query = $this->addDateRange($query);
$query = $this->addDateRange($query, 'products');
if($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query);
@ -104,7 +105,9 @@ class ProductExport extends BaseExport
$query->cursor()
->each(function ($entity) {
$this->csv->insertOne($this->buildRow($entity));
/** @var \App\Models\Product $entity */
$this->csv->insertOne($this->buildRow($entity));
});
return $this->csv->toString();
@ -133,16 +136,16 @@ class ProductExport extends BaseExport
// return $this->decorateAdvancedFields($product, $entity);
}
private function decorateAdvancedFields(Product $product, array $entity): array
{
if (in_array('vendor_id', $this->input['report_keys'])) {
$entity['vendor'] = $product->vendor()->exists() ? $product->vendor->name : '';
}
// private function decorateAdvancedFields(Product $product, array $entity): array
// {
// if (in_array('vendor_id', $this->input['report_keys'])) {
// $entity['vendor'] = $product->vendor()->exists() ? $product->vendor->name : '';
// }
// if (array_key_exists('project_id', $this->input['report_keys'])) {
// $entity['project'] = $product->project()->exists() ? $product->project->name : '';
// }
// // if (array_key_exists('project_id', $this->input['report_keys'])) {
// // $entity['project'] = $product->project()->exists() ? $product->project->name : '';
// // }
return $entity;
}
// return $entity;
// }
}

View File

@ -66,11 +66,11 @@ class ProductSalesExport extends BaseExport
'custom_value4' => 'custom_value4',
];
private array $decorate_keys = [
'client',
'currency',
'date',
];
// private array $decorate_keys = [
// 'client',
// 'currency',
// 'date',
// ];
public function __construct(Company $company, array $input)
{
@ -129,7 +129,7 @@ class ProductSalesExport extends BaseExport
->where('is_deleted', 0)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID]);
$query = $this->addDateRange($query);
$query = $this->addDateRange($query, 'invoices');
$query = $this->filterByClients($query);
@ -330,8 +330,8 @@ class ProductSalesExport extends BaseExport
* @param string $product_key
* @return ?\Illuminate\Database\Eloquent\Model
*/
private function getProduct(string $product_key)
{
return $this->products->firstWhere('product_key', $product_key);
}
// private function getProduct(string $product_key)
// {
// return $this->products->firstWhere('product_key', $product_key);
// }
}

View File

@ -63,11 +63,11 @@ class PurchaseOrderExport extends BaseExport
})
->where('company_id', $this->company->id);
if(!$this->input['include_deleted'] ?? false) {
if(!$this->input['include_deleted'] ?? false) { // @phpstan-ignore-line
$query->where('is_deleted', 0);
}
$query = $this->addDateRange($query);
$query = $this->addDateRange($query, 'purchase_orders');
$clients = &$this->input['client_id'];
@ -98,6 +98,8 @@ class PurchaseOrderExport extends BaseExport
$report = $query->cursor()
->map(function ($resource) {
/** @var \App\Models\PurchaseOrder $resource */
$row = $this->buildRow($resource);
return $this->processMetaData($row, $resource);
})->toArray();
@ -119,7 +121,9 @@ class PurchaseOrderExport extends BaseExport
$query->cursor()
->each(function ($purchase_order) {
$this->csv->insertOne($this->buildRow($purchase_order));
/** @var \App\Models\PurchaseOrder $purchase_order */
$this->csv->insertOne($this->buildRow($purchase_order));
});
return $this->csv->toString();
@ -167,7 +171,8 @@ class PurchaseOrderExport extends BaseExport
}
if (in_array('purchase_order.user_id', $this->input['report_keys'])) {
$entity['purchase_order.user_id'] = $purchase_order->user ? $purchase_order->user->present()->name() : '';
$entity['purchase_order.user_id'] = $purchase_order->user ? $purchase_order->user->present()->name() : ''; // @phpstan-ignore-line
}
if (in_array('purchase_order.assigned_user_id', $this->input['report_keys'])) {

View File

@ -71,7 +71,7 @@ class PurchaseOrderItemExport extends BaseExport
$query->where('is_deleted', 0);
}
$query = $this->addDateRange($query);
$query = $this->addDateRange($query, 'purchase_orders');
$clients = &$this->input['client_id'];
@ -101,13 +101,15 @@ class PurchaseOrderItemExport extends BaseExport
$query->cursor()
->each(function ($resource) {
$this->iterateItems($resource);
/** @var \App\Models\PurchaseOrder $resource */
$this->iterateItems($resource);
foreach($this->storage_array as $row) {
$this->storage_item_array[] = $this->processItemMetaData($row, $resource);
}
foreach($this->storage_array as $row) {
$this->storage_item_array[] = $this->processItemMetaData($row, $resource);
}
$this->storage_array = [];
$this->storage_array = [];
});
@ -127,7 +129,9 @@ class PurchaseOrderItemExport extends BaseExport
$query->cursor()
->each(function ($purchase_order) {
$this->iterateItems($purchase_order);
/** @var \App\Models\PurchaseOrder $purchase_order */
$this->iterateItems($purchase_order);
});
$this->csv->insertAll($this->storage_array);

View File

@ -31,11 +31,11 @@ class QuoteExport extends BaseExport
private Decorator $decorator;
private array $decorate_keys = [
'client',
'currency',
'invoice',
];
// private array $decorate_keys = [
// 'client',
// 'currency',
// 'invoice',
// ];
public function __construct(Company $company, array $input)
{
@ -73,7 +73,7 @@ class QuoteExport extends BaseExport
$query->where('is_deleted', 0);
}
$query = $this->addDateRange($query);
$query = $this->addDateRange($query, 'quotes');
$clients = &$this->input['client_id'];
@ -103,6 +103,8 @@ class QuoteExport extends BaseExport
$report = $query->cursor()
->map(function ($resource) {
/** @var \App\Models\Quote $resource */
$row = $this->buildRow($resource);
return $this->processMetaData($row, $resource);
})->toArray();
@ -125,6 +127,8 @@ class QuoteExport extends BaseExport
$query->cursor()
->each(function ($quote) {
/** @var \App\Models\Quote $quote */
$this->csv->insertOne($this->buildRow($quote));
});

View File

@ -74,7 +74,7 @@ class QuoteItemExport extends BaseExport
$query->where('is_deleted', 0);
}
$query = $this->addDateRange($query);
$query = $this->addDateRange($query, 'quotes');
$clients = &$this->input['client_id'];
@ -104,6 +104,8 @@ class QuoteItemExport extends BaseExport
$query->cursor()
->each(function ($resource) {
/** @var \App\Models\Quote $resource */
$this->iterateItems($resource);
foreach($this->storage_array as $row) {
@ -134,6 +136,8 @@ class QuoteItemExport extends BaseExport
$query->cursor()
->each(function ($quote) {
/** @var \App\Models\Quote $quote */
$this->iterateItems($quote);
});

View File

@ -65,7 +65,7 @@ class RecurringInvoiceExport extends BaseExport
$query->where('is_deleted', 0);
}
$query = $this->addDateRange($query);
$query = $this->addDateRange($query, 'recurring_invoices');
$clients = &$this->input['client_id'];
@ -93,6 +93,8 @@ class RecurringInvoiceExport extends BaseExport
$query->cursor()
->each(function ($invoice) {
/** @var \App\Models\RecurringInvoice $invoice */
$this->csv->insertOne($this->buildRow($invoice));
});
@ -112,6 +114,8 @@ class RecurringInvoiceExport extends BaseExport
$report = $query->cursor()
->map(function ($resource) {
/** @var \App\Models\RecurringInvoice $resource */
$row = $this->buildRow($resource);
return $this->processMetaData($row, $resource);
})->toArray();

View File

@ -29,9 +29,9 @@ class TaskExport extends BaseExport
{
private $entity_transformer;
public string $date_key = 'created_at';
public string $date_key = 'calculated_start_date';
private string $date_format = 'YYYY-MM-DD';
private string $date_format = 'Y-m-d';
public Writer $csv;
@ -74,7 +74,7 @@ class TaskExport extends BaseExport
$query->where('is_deleted', 0);
}
$query = $this->addDateRange($query);
$query = $this->addDateRange($query, 'tasks');
$clients = &$this->input['client_id'];
@ -106,7 +106,9 @@ class TaskExport extends BaseExport
$query->cursor()
->each(function ($entity) {
$this->buildRow($entity);
/** @var \App\Models\Task $entity*/
$this->buildRow($entity);
});
$this->csv->insertAll($this->storage_array);
@ -128,6 +130,7 @@ class TaskExport extends BaseExport
$query->cursor()
->each(function ($resource) {
/** @var \App\Models\Task $resource*/
$this->buildRow($resource);
foreach($this->storage_array as $row) {
@ -153,7 +156,7 @@ class TaskExport extends BaseExport
$entity[$key] = $transformed_entity[$parts[1]];
} elseif (array_key_exists($key, $transformed_entity)) {
$entity[$key] = $transformed_entity[$key];
} elseif (in_array($key, ['task.start_date', 'task.end_date', 'task.duration'])) {
} elseif (in_array($key, ['task.start_date', 'task.end_date', 'task.duration', 'task.billable', 'task.item_notes'])) {
$entity[$key] = '';
} else {
$entity[$key] = $this->decorator->transform($key, $task);
@ -161,7 +164,7 @@ class TaskExport extends BaseExport
}
if (is_null($task->time_log) || (is_array(json_decode($task->time_log, 1)) && count(json_decode($task->time_log, 1)) == 0)) {
if (is_null($task->time_log) || (is_array(json_decode($task->time_log, true)) && count(json_decode($task->time_log, true)) == 0)) {
$this->storage_array[] = $entity;
} else {
$this->iterateLogs($task, $entity);
@ -172,31 +175,25 @@ class TaskExport extends BaseExport
private function iterateLogs(Task $task, array $entity)
{
$timezone = Timezone::find($task->company->settings->timezone_id);
$timezone_name = 'US/Eastern';
$timezone_name = 'America/New_York';
if ($timezone) {
$timezone_name = $timezone->name;
}
$logs = json_decode($task->time_log, 1);
$logs = json_decode($task->time_log, true);
$date_format_default = 'Y-m-d';
$date_format = DateFormat::find($task->company->settings->date_format_id);
if ($date_format) {
$date_format_default = $date_format->format;
}
$date_format_default = $this->date_format;
foreach ($logs as $key => $item) {
if (in_array('task.start_date', $this->input['report_keys']) || in_array('start_date', $this->input['report_keys'])) {
$carbon_object = Carbon::createFromTimeStamp($item[0])->setTimezone($timezone_name);
$carbon_object = Carbon::createFromTimeStamp((int)$item[0])->setTimezone($timezone_name);
$entity['task.start_date'] = $carbon_object->format($date_format_default);
$entity['task.start_time'] = $carbon_object->format('H:i:s');
}
if ((in_array('task.end_date', $this->input['report_keys']) || in_array('end_date', $this->input['report_keys'])) && $item[1] > 0) {
$carbon_object = Carbon::createFromTimeStamp($item[1])->setTimezone($timezone_name);
$carbon_object = Carbon::createFromTimeStamp((int)$item[1])->setTimezone($timezone_name);
$entity['task.end_date'] = $carbon_object->format($date_format_default);
$entity['task.end_time'] = $carbon_object->format('H:i:s');
}
@ -212,6 +209,14 @@ class TaskExport extends BaseExport
$entity['task.duration_words'] = $seconds > 86400 ? CarbonInterval::seconds($seconds)->locale($this->company->locale())->cascade()->forHumans() : now()->startOfDay()->addSeconds($seconds)->format('H:i:s');
}
if (in_array('task.billable', $this->input['report_keys']) || in_array('billable', $this->input['report_keys'])) {
$entity['task.billable'] = isset($item[3]) && $item[3] == 'true' ? ctrans('texts.yes') : ctrans('texts.no');
}
if (in_array('task.item_notes', $this->input['report_keys']) || in_array('item_notes', $this->input['report_keys'])) {
$entity['task.item_notes'] = isset($item[2]) ? (string)$item[2] : '';
}
$entity = $this->decorateAdvancedFields($task, $entity);
$this->storage_array[] = $entity;
@ -222,6 +227,8 @@ class TaskExport extends BaseExport
$entity['task.end_time'] = '';
$entity['task.duration'] = '';
$entity['task.duration_words'] = '';
$entity['task.billable'] = '';
$entity['task.item_notes'] = '';
}
@ -236,7 +243,7 @@ class TaskExport extends BaseExport
*/
protected function addTaskStatusFilter(Builder $query, string $status): Builder
{
/** @var array $status_parameters */
$status_parameters = explode(',', $status);
if (in_array('all', $status_parameters) || count($status_parameters) == 0) {

View File

@ -68,7 +68,7 @@ class VendorExport extends BaseExport
$query->where('is_deleted', 0);
}
$query = $this->addDateRange($query);
$query = $this->addDateRange($query, 'vendors');
if($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query);
@ -90,6 +90,8 @@ class VendorExport extends BaseExport
$report = $query->cursor()
->map(function ($resource) {
/** @var \App\Models\Vendor $resource */
$row = $this->buildRow($resource);
return $this->processMetaData($row, $resource);
})->toArray();
@ -107,7 +109,9 @@ class VendorExport extends BaseExport
$query->cursor()
->each(function ($vendor) {
$this->csv->insertOne($this->buildRow($vendor));
/** @var \App\Models\Vendor $vendor */
$this->csv->insertOne($this->buildRow($vendor));
});
return $this->csv->toString();
@ -171,16 +175,16 @@ class VendorExport extends BaseExport
return $entity;
}
private function calculateStatus($vendor)
{
if ($vendor->is_deleted) {
return ctrans('texts.deleted');
}
// private function calculateStatus($vendor)
// {
// if ($vendor->is_deleted) {
// return ctrans('texts.deleted');
// }
if ($vendor->deleted_at) {
return ctrans('texts.archived');
}
// if ($vendor->deleted_at) {
// return ctrans('texts.archived');
// }
return ctrans('texts.active');
}
// return ctrans('texts.active');
// }
}

View File

@ -92,6 +92,7 @@ class InvoiceDecorator extends Decorator implements DecoratorInterface
{
return $invoice->recurring_invoice ? $invoice->recurring_invoice->number : '';
}
public function auto_bill_enabled(Invoice $invoice)
{
return $invoice->auto_bill_enabled ? ctrans('texts.yes') : ctrans('texts.no');

View File

@ -18,6 +18,7 @@ use Carbon\Carbon;
class TaskDecorator extends Decorator implements DecoratorInterface
{
//@todo - we do not handle iterating through the timelog here.
public function transform(string $key, mixed $entity): mixed
{
$task = false;
@ -42,13 +43,13 @@ class TaskDecorator extends Decorator implements DecoratorInterface
{
$timezone = Timezone::find($task->company->settings->timezone_id);
$timezone_name = 'US/Eastern';
$timezone_name = 'America/New_York';
if ($timezone) {
$timezone_name = $timezone->name;
}
$logs = json_decode($task->time_log, 1);
$logs = json_decode($task->time_log, true);
$date_format_default = 'Y-m-d';
@ -60,7 +61,7 @@ class TaskDecorator extends Decorator implements DecoratorInterface
if(is_array($logs)) {
$item = $logs[0];
return Carbon::createFromTimeStamp($item[0])->setTimezone($timezone_name)->format($date_format_default);
return Carbon::createFromTimeStamp((int)$item[0])->setTimezone($timezone_name)->format($date_format_default);
}
return '';
@ -71,13 +72,13 @@ class TaskDecorator extends Decorator implements DecoratorInterface
{
$timezone = Timezone::find($task->company->settings->timezone_id);
$timezone_name = 'US/Eastern';
$timezone_name = 'America/New_York';
if ($timezone) {
$timezone_name = $timezone->name;
}
$logs = json_decode($task->time_log, 1);
$logs = json_decode($task->time_log, true);
$date_format_default = 'Y-m-d';
@ -89,12 +90,32 @@ class TaskDecorator extends Decorator implements DecoratorInterface
if(is_array($logs)) {
$item = $logs[1];
return Carbon::createFromTimeStamp($item[1])->setTimezone($timezone_name)->format($date_format_default);
return Carbon::createFromTimeStamp((int)$item[1])->setTimezone($timezone_name)->format($date_format_default);
}
return '';
}
/**
* billable
*
* @todo
*/
public function billable(Task $task)
{
return '';
}
/**
* items_notes
* @todo
*/
public function items_notes(Task $task)
{
return '';
}
public function duration(Task $task)
{
return $task->calcDuration();

View File

@ -82,11 +82,15 @@ class RecurringExpenseToExpenseFactory
} else {
$locale = $recurring_expense->company->locale();
$date_formats = Cache::get('date_formats');
//@deprecated
// $date_formats = Cache::get('date_formats');
$date_format = $date_formats->filter(function ($item) use ($recurring_expense) {
/** @var \Illuminate\Support\Collection<\App\Models\DateFormat> */
$date_formats = app('date_formats');
$date_format = $date_formats->first(function ($item) use ($recurring_expense) {
return $item->id == $recurring_expense->company->settings->date_format_id;
})->first()->format;
})->format;
}
Carbon::setLocale($locale);
@ -144,7 +148,7 @@ class RecurringExpenseToExpenseFactory
continue;
}
if (Str::contains($match, '|')) {
// if (Str::contains($match, '|')) {
$parts = explode('|', $match); // [ '[MONTH', 'MONTH+2]' ]
$left = substr($parts[0], 1); // 'MONTH'
@ -171,7 +175,7 @@ class RecurringExpenseToExpenseFactory
$_value = explode($_operation, $right); // [MONTHYEAR, 4]
$_right = Carbon::createFromDate(now()->year, now()->month)->addMonths($_value[1])->translatedFormat('F Y');
$_right = Carbon::createFromDate(now()->year, now()->month)->addMonths($_value[1])->translatedFormat('F Y'); //@phpstan-ignore-line
}
$replacement = sprintf('%s to %s', $_left, $_right);
@ -182,7 +186,7 @@ class RecurringExpenseToExpenseFactory
$value,
1
);
}
// }
}
// Second case with more common calculations.

View File

@ -155,11 +155,13 @@ class BankTransactionFilters extends QueryFilters
$dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
if ($sort_col[0] == 'deposit') {
return $this->builder->where('base_type', 'CREDIT')->orderBy('amount', $dir);
return $this->builder->orderByRaw("(CASE WHEN base_type = 'CREDIT' THEN amount END) $dir")->orderBy('amount', $dir);
// return $this->builder->where('base_type', 'CREDIT')->orderBy('amount', $dir);
}
if ($sort_col[0] == 'withdrawal') {
return $this->builder->where('base_type', 'DEBIT')->orderBy('amount', $dir);
return $this->builder->orderByRaw("(CASE WHEN base_type = 'DEBIT' THEN amount END) $dir")->orderBy('amount', $dir);
// return $this->builder->where('base_type', 'DEBIT')->orderBy('amount', $dir);
}
if ($sort_col[0] == 'status') {

View File

@ -41,7 +41,7 @@ class ClientFilters extends QueryFilters
*/
public function balance(string $balance = ''): Builder
{
if (strlen($balance) == 0) {
if (strlen($balance) == 0 || count(explode(":", $balance)) < 2) {
return $this->builder;
}

View File

@ -97,7 +97,8 @@ class CreditFilters extends QueryFilters
$q->where('first_name', 'like', '%'.$filter.'%')
->orWhere('last_name', 'like', '%'.$filter.'%')
->orWhere('email', 'like', '%'.$filter.'%');
});
})
->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
});
}

View File

@ -79,7 +79,7 @@ class ExpenseFilters extends QueryFilters
$this->builder->where(function ($query) use ($status_parameters) {
if (in_array('logged', $status_parameters)) {
$query->orWhere(function ($query) {
$query->where('amount', '>', 0)
$query->where('amount', '>=', 0)
->whereNull('invoice_id')
->whereNull('payment_date')
->where('should_be_invoiced', false);

View File

@ -124,7 +124,8 @@ class InvoiceFilters extends QueryFilters
$q->where('first_name', 'like', '%'.$filter.'%')
->orWhere('last_name', 'like', '%'.$filter.'%')
->orWhere('email', 'like', '%'.$filter.'%');
});
})
->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
});
}
@ -271,6 +272,7 @@ class InvoiceFilters extends QueryFilters
if (count($parts) != 2) {
return $this->builder;
}
try {
$start_date = Carbon::parse($parts[0]);
@ -281,7 +283,6 @@ class InvoiceFilters extends QueryFilters
return $this->builder;
}
return $this->builder;
}
/**
@ -307,7 +308,6 @@ class InvoiceFilters extends QueryFilters
return $this->builder;
}
return $this->builder;
}
@ -321,7 +321,7 @@ class InvoiceFilters extends QueryFilters
{
$sort_col = explode('|', $sort);
if (!is_array($sort_col) || count($sort_col) != 2) {
if (!is_array($sort_col) || count($sort_col) != 2 || in_array($sort_col[0], ['documents'])) {
return $this->builder;
}
@ -338,10 +338,10 @@ class InvoiceFilters extends QueryFilters
// return $this->builder->orderByRaw('CAST(number AS UNSIGNED), number ' . $dir);
// return $this->builder->orderByRaw("number REGEXP '^[A-Za-z]+$',CAST(number as SIGNED INTEGER),CAST(REPLACE(number,'-','')AS SIGNED INTEGER) ,number");
// return $this->builder->orderByRaw('ABS(number) ' . $dir);
return $this->builder->orderByRaw("REGEXP_REPLACE(number,'[^0-9]+','')+0 " . $dir);
return $this->builder->orderByRaw("REGEXP_REPLACE(invoices.number,'[^0-9]+','')+0 " . $dir);
}
return $this->builder->orderBy($sort_col[0], $dir);
return $this->builder->orderBy("{$this->builder->getQuery()->from}.".$sort_col[0], $dir);
}
/**

View File

@ -204,7 +204,6 @@ class PaymentFilters extends QueryFilters
return $this->builder;
}
return $this->builder;
}
/**

View File

@ -45,7 +45,8 @@ class QuoteFilters extends QueryFilters
$q->where('first_name', 'like', '%'.$filter.'%')
->orWhere('last_name', 'like', '%'.$filter.'%')
->orWhere('email', 'like', '%'.$filter.'%');
});
})
->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
});
}

View File

@ -48,7 +48,8 @@ class RecurringInvoiceFilters extends QueryFilters
$q->where('first_name', 'like', '%'.$filter.'%')
->orWhere('last_name', 'like', '%'.$filter.'%')
->orWhere('email', 'like', '%'.$filter.'%');
});
})
->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
});
}
@ -133,6 +134,10 @@ class RecurringInvoiceFilters extends QueryFilters
return $this->builder->orderByRaw("REGEXP_REPLACE(number,'[^0-9]+','')+0 " . $dir);
}
if($sort_col[0] == 'status_id'){
return $this->builder->orderBy('status_id', $dir)->orderBy('last_sent_date', $dir);
}
if($sort_col[0] == 'next_send_datetime') {
$sort_col[0] = 'next_send_date';
}
@ -162,9 +167,10 @@ class RecurringInvoiceFilters extends QueryFilters
return $this->builder;
}
/** @var array $key_parameters */
$key_parameters = explode(',', $value);
if (count($key_parameters)) {
if (count($key_parameters) > 0) {
return $this->builder->where(function ($query) use ($key_parameters) {
foreach ($key_parameters as $key) {
$query->orWhereJsonContains('line_items', ['product_key' => $key]);
@ -183,6 +189,7 @@ class RecurringInvoiceFilters extends QueryFilters
*/
public function next_send_between(string $range = ''): Builder
{
/** @var array $parts */
$parts = explode('|', $range);
if (!isset($parts[0]) || !isset($parts[1])) {

View File

@ -175,6 +175,7 @@ class TaskFilters extends QueryFilters
return $this->builder;
}
/** @var array $status_parameters */
$status_parameters = explode(',', $value);
if(count($status_parameters) >= 1) {

View File

@ -46,7 +46,7 @@ class WebhookFilters extends QueryFilters
{
$sort_col = explode('|', $sort);
if (!is_array($sort_col) || count($sort_col) != 2) {
if (!is_array($sort_col) || count($sort_col) != 2 || !in_array($sort_col[0], \Illuminate\Support\Facades\Schema::getColumnListing('webhooks'))) {
return $this->builder;
}

View File

@ -156,28 +156,22 @@ class TransactionTransformer implements BankRevenueInterface
private function convertCurrency(string $code)
{
$currencies = Cache::get('currencies');
$currencies = app('currencies');
if (!$currencies) {
$this->buildCache(true);
}
$currency = $currencies->filter(function ($item) use ($code) {
$currency = $currencies->first(function ($item) use ($code) {
/** @var \App\Models\Currency $item */
return $item->code == $code;
})->first();
if ($currency) {
return $currency->id;
}
return 1;
});
/** @var \App\Models\Currency $currency */
return $currency ? $currency->id : 1; //@phpstan-ignore-line
}
private function formatDate(string $input)
{
$timezone = Timezone::find($this->company->settings->timezone_id);
$timezone_name = 'US/Eastern';
$timezone_name = 'America/New_York';
if ($timezone) {
$timezone_name = $timezone->name;
@ -192,7 +186,7 @@ class TransactionTransformer implements BankRevenueInterface
}
try {
return Carbon::createFromFormat("d-m-Y", $input)->setTimezone($timezone_name)->format($date_format_default) ?? $input;
return Carbon::createFromFormat("d-m-Y", $input)->setTimezone($timezone_name)->format($date_format_default);
} catch (\Exception $e) {
return $input;
}

View File

@ -1,111 +0,0 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Helpers\Bank\Yodlee\DTO;
use Illuminate\Support\Collection;
use Spatie\LaravelData\Attributes\MapInputName;
use Spatie\LaravelData\Data;
/**
* @deprecated
* [
"account": [
[
"CONTAINER": "bank",
"providerAccountId": 1005,
"accountName": "Business Acct",
"accountStatus": "ACTIVE",
"accountNumber": "1011",
"aggregationSource": "USER",
"isAsset": true,
"balance": [
"currency": "AUD",
"amount": 304.98,
],
"id": 10139315,
"includeInNetWorth": true,
"providerId": "3857",
"providerName": "Bank",
"isManual": false,
"availableBalance": {#2966
"currency": "AUD",
"amount": 304.98,
],
"currentBalance": [
"currency": "AUD",
"amount": 3044.98,
],
"accountType": "CHECKING",
"displayedName": "after David",
"createdDate": "2023-01-10T08:29:07Z",
"classification": "SMALL_BUSINESS",
"lastUpdated": "2023-08-01T23:50:13Z",
"nickname": "Business ",
"bankTransferCode": [
[
"id": "062",
"type": "BSB",
],
],
"dataset": [
[
"name": "BASIC_AGG_DATA",
"additionalStatus": "AVAILABLE_DATA_RETRIEVED",
"updateEligibility": "ALLOW_UPDATE",
"lastUpdated": "2023-08-01T23:49:53Z",
"lastUpdateAttempt": "2023-08-01T23:49:53Z",
"nextUpdateScheduled": "2023-08-03T14:45:14Z",
],
],
],
],
];
*/
class AccountSummary extends Data
{
public ?int $id;
#[MapInputName('CONTAINER')]
public ?string $account_type = '';
#[MapInputName('accountName')]
public ?string $account_name = '';
#[MapInputName('accountStatus')]
public ?string $account_status = '';
#[MapInputName('accountNumber')]
public ?string $account_number = '';
#[MapInputName('providerAccountId')]
public int $provider_account_id;
#[MapInputName('providerId')]
public ?string $provider_id = '';
#[MapInputName('providerName')]
public ?string $provider_name = '';
public ?string $nickname = '';
public ?float $current_balance = 0;
public ?string $account_currency = '';
public static function prepareForPipeline(Collection $properties): Collection
{
$properties->put('current_balance', $properties['currentBalance']['amount'] ?? 0);
$properties->put('account_currency', $properties['currentBalance']['currency'] ?? 0);
return $properties;
}
}

View File

@ -171,20 +171,16 @@ class IncomeTransformer implements BankRevenueInterface
private function convertCurrency(string $code)
{
$currencies = Cache::get('currencies');
if (! $currencies) {
$this->buildCache(true);
}
$currencies = app('currencies');
$currency = $currencies->filter(function ($item) use ($code) {
$currency = $currencies->first(function ($item) use ($code) {
/** @var \App\Models\Currency $item */
return $item->code == $code;
})->first();
});
if ($currency) {
return $currency->id;
}
/** @var \App\Models\Currency $currency */
return $currency ? $currency->id : 1; //@phpstan-ignore-line
return 1;
}
}

View File

@ -26,15 +26,15 @@ use BaconQrCode\Writer;
*/
class EpcQrGenerator
{
private array $sepa = [
'serviceTag' => 'BCD',
'version' => 2,
'characterSet' => 1,
'identification' => 'SCT',
'bic' => '',
'purpose' => '',
// private array $sepa = [
// 'serviceTag' => 'BCD',
// 'version' => 2,
// 'characterSet' => 1,
// 'identification' => 'SCT',
// 'bic' => '',
// 'purpose' => '',
];
// ];
public function __construct(protected Company $company, protected Invoice|RecurringInvoice $invoice, protected float $amount)
{
@ -61,13 +61,7 @@ class EpcQrGenerator
} catch(\Throwable $e) {
nlog("EPC QR failure => ".$e->getMessage());
return '';
} catch(\Exception $e) {
nlog("EPC QR failure => ".$e->getMessage());
return '';
} catch(InvalidArgumentException $e) {
nlog("EPC QR failure => ".$e->getMessage());
return '';
}
}
}

View File

@ -27,7 +27,7 @@ function nlog($output, $context = []): void
}
if (gettype($output) == 'object') {
$output = print_r($output, 1);
$output = print_r($output, true);
}
// $trace = debug_backtrace();
@ -53,7 +53,7 @@ function nrlog($output, $context = []): void
}
if (gettype($output) == 'object') {
$output = print_r($output, 1);
$output = print_r($output, true);
}
// $trace = debug_backtrace();

View File

@ -11,16 +11,18 @@
namespace App\Helpers\Invoice;
use App\DataMapper\BaseSettings;
use App\DataMapper\InvoiceItem;
use App\DataMapper\Tax\RuleInterface;
use App\Models\Quote;
use App\Utils\Number;
use App\Models\Client;
use App\Models\Credit;
use App\Models\Vendor;
use App\Models\Invoice;
use App\Models\PurchaseOrder;
use App\Models\Quote;
use App\Models\RecurringInvoice;
use App\Models\RecurringQuote;
use App\DataMapper\InvoiceItem;
use App\DataMapper\BaseSettings;
use App\Models\RecurringInvoice;
use App\DataMapper\Tax\RuleInterface;
use App\Utils\Traits\NumberFormatter;
class InvoiceItemSum
@ -29,6 +31,7 @@ class InvoiceItemSum
use Discounter;
use Taxer;
//@phpstan-ignore-next-line
private array $eu_tax_jurisdictions = [
'AT', // Austria
'BE', // Belgium
@ -119,7 +122,7 @@ class InvoiceItemSum
private $tax_collection;
private ?Client $client;
private Client | Vendor $client;
private bool $calc_tax = false;
@ -130,10 +133,10 @@ class InvoiceItemSum
$this->tax_collection = collect([]);
$this->invoice = $invoice;
$this->client = $invoice->client ?? $invoice->vendor;
if ($this->invoice->client) {
$this->currency = $this->invoice->client->currency();
$this->client = $this->invoice->client;
$this->shouldCalculateTax();
} else {
$this->currency = $this->invoice->vendor->currency();
@ -170,7 +173,7 @@ class InvoiceItemSum
private function shouldCalculateTax(): self
{
if (!$this->invoice->company?->calculate_taxes || $this->invoice->company->account->isFreeHostedClient()) {
if (!$this->invoice->company?->calculate_taxes || $this->invoice->company->account->isFreeHostedClient()) { //@phpstan-ignore-line
$this->calc_tax = false;
return $this;
}
@ -312,7 +315,7 @@ class InvoiceItemSum
$key = str_replace(' ', '', $tax_name.$tax_rate);
$group_tax = ['key' => $key, 'total' => $tax_total, 'tax_name' => $tax_name.' '.floatval($tax_rate).'%'];
$group_tax = ['key' => $key, 'total' => $tax_total, 'tax_name' => $tax_name.' '.Number::formatValueNoTrailingZeroes(floatval($tax_rate), $this->client).'%'];
$this->tax_collection->push(collect($group_tax));
}
@ -331,7 +334,7 @@ class InvoiceItemSum
public function setLineTotal($total)
{
$this->item->line_total = $total;
$this->item->line_total = (float) $total;
return $this;
}

View File

@ -11,14 +11,16 @@
namespace App\Helpers\Invoice;
use App\DataMapper\Tax\RuleInterface;
use App\Models\Quote;
use App\Utils\Number;
use App\Models\Client;
use App\Models\Credit;
use App\Models\Vendor;
use App\Models\Invoice;
use App\Models\PurchaseOrder;
use App\Models\Quote;
use App\Models\RecurringInvoice;
use App\Models\RecurringQuote;
use App\Models\RecurringInvoice;
use App\DataMapper\Tax\RuleInterface;
use App\Utils\Traits\NumberFormatter;
class InvoiceItemSumInclusive
@ -27,7 +29,7 @@ class InvoiceItemSumInclusive
use Discounter;
use Taxer;
//@phpstan-ignore-next-line
private array $eu_tax_jurisdictions = [
'AT', // Austria
'BE', // Belgium
@ -98,6 +100,7 @@ class InvoiceItemSumInclusive
private $total_taxes;
/** @phpstan-ignore-next-line */
private $item;
private $line_items;
@ -108,7 +111,7 @@ class InvoiceItemSumInclusive
private bool $calc_tax = false;
private ?Client $client;
private Client | Vendor $client;
private RuleInterface $rule;
@ -117,10 +120,10 @@ class InvoiceItemSumInclusive
$this->tax_collection = collect([]);
$this->invoice = $invoice;
$this->client = $invoice->client ?? $invoice->vendor;
if ($this->invoice->client) {
$this->currency = $this->invoice->client->currency();
$this->client = $this->invoice->client;
$this->shouldCalculateTax();
} else {
$this->currency = $this->invoice->vendor->currency();
@ -264,7 +267,7 @@ class InvoiceItemSumInclusive
$key = str_replace(' ', '', $tax_name.$tax_rate);
$group_tax = ['key' => $key, 'total' => $tax_total, 'tax_name' => $tax_name.' '.$tax_rate.'%'];
$group_tax = ['key' => $key, 'total' => $tax_total, 'tax_name' => $tax_name.' '.Number::formatValueNoTrailingZeroes(floatval($tax_rate), $this->client).'%'];
$this->tax_collection->push(collect($group_tax));
}
@ -399,7 +402,7 @@ class InvoiceItemSumInclusive
private function shouldCalculateTax(): self
{
if (!$this->invoice->company?->calculate_taxes || $this->invoice->company->account->isFreeHostedClient()) {
if (!$this->invoice->company?->calculate_taxes || $this->invoice->company->account->isFreeHostedClient()) {//@phpstan-ignore-line
$this->calc_tax = false;
return $this;
}
@ -410,7 +413,7 @@ class InvoiceItemSumInclusive
$this->rule = new $class();
if($this->rule->regionWithNoTaxCoverage($this->client->country->iso_3166_2)) {
if($this->rule->regionWithNoTaxCoverage($this->client->country->iso_3166_2 ?? false)) {
return $this;
}

View File

@ -11,12 +11,14 @@
namespace App\Helpers\Invoice;
use App\Models\Client;
use App\Models\Credit;
use App\Models\Invoice;
use App\Models\PurchaseOrder;
use App\Models\Quote;
use App\Models\RecurringInvoice;
use App\Models\RecurringQuote;
use App\Models\Vendor;
use App\Utils\Number;
use App\Utils\Traits\NumberFormatter;
use Illuminate\Support\Collection;
@ -50,6 +52,8 @@ class InvoiceSum
private $precision;
private Client | Vendor $client;
public InvoiceItemSum $invoice_items;
private $rappen_rounding = false;
@ -60,18 +64,15 @@ class InvoiceSum
*/
public function __construct($invoice)
{
$this->invoice = $invoice;
$this->client = $invoice->client ?? $invoice->vendor;
if ($this->invoice->client) {
$this->precision = $this->invoice->client->currency()->precision;
$this->rappen_rounding = $this->invoice->client->getSetting('enable_rappen_rounding');
} else {
$this->precision = $this->invoice->vendor->currency()->precision;
$this->rappen_rounding = $this->invoice->vendor->getSetting('enable_rappen_rounding');
}
$this->precision = $this->client->currency()->precision;
$this->rappen_rounding = $this->client->getSetting('enable_rappen_rounding');
$this->tax_map = new Collection();
}
public function build()
@ -131,7 +132,7 @@ class InvoiceSum
$tax += $this->getSurchargeTaxTotalForKey($this->invoice->tax_name1, $this->invoice->tax_rate1);
$this->total_taxes += $tax;
$this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.floatval($this->invoice->tax_rate1).'%', 'total' => $tax];
$this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate1), $this->client).'%', 'total' => $tax];
}
if (is_string($this->invoice->tax_name2) && strlen($this->invoice->tax_name2) >= 2) {
@ -139,7 +140,7 @@ class InvoiceSum
$tax += $this->getSurchargeTaxTotalForKey($this->invoice->tax_name2, $this->invoice->tax_rate2);
$this->total_taxes += $tax;
$this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.floatval($this->invoice->tax_rate2).'%', 'total' => $tax];
$this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate2), $this->client).'%', 'total' => $tax];
}
if (is_string($this->invoice->tax_name3) && strlen($this->invoice->tax_name3) >= 2) {
@ -147,7 +148,7 @@ class InvoiceSum
$tax += $this->getSurchargeTaxTotalForKey($this->invoice->tax_name3, $this->invoice->tax_rate3);
$this->total_taxes += $tax;
$this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.floatval($this->invoice->tax_rate3).'%', 'total' => $tax];
$this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate3), $this->client).'%', 'total' => $tax];
}
return $this;

View File

@ -12,7 +12,10 @@
namespace App\Helpers\Invoice;
use App\Models\Quote;
use App\Utils\Number;
use App\Models\Client;
use App\Models\Credit;
use App\Models\Vendor;
use App\Models\Invoice;
use App\Models\PurchaseOrder;
use App\Models\RecurringQuote;
@ -49,6 +52,8 @@ class InvoiceSumInclusive
private $rappen_rounding = false;
private Client | Vendor $client;
public InvoiceItemSumInclusive $invoice_items;
/**
* Constructs the object with Invoice and Settings object.
@ -58,14 +63,10 @@ class InvoiceSumInclusive
public function __construct($invoice)
{
$this->invoice = $invoice;
if ($this->invoice->client) {
$this->precision = $this->invoice->client->currency()->precision;
$this->rappen_rounding = $this->invoice->client->getSetting('enable_rappen_rounding');
} else {
$this->precision = $this->invoice->vendor->currency()->precision;
$this->rappen_rounding = $this->invoice->vendor->getSetting('enable_rappen_rounding');
}
$this->client = $invoice->client ?? $invoice->vendor;
$this->precision = $this->client->currency()->precision;
$this->rappen_rounding = $this->client->getSetting('enable_rappen_rounding');
$this->tax_map = new Collection();
}
@ -107,16 +108,16 @@ class InvoiceSumInclusive
private function calculateCustomValues()
{
$this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge1, $this->invoice->custom_surcharge_tax1);
// $this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge1, $this->invoice->custom_surcharge_tax1);
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge1);
$this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge2, $this->invoice->custom_surcharge_tax2);
// $this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge2, $this->invoice->custom_surcharge_tax2);
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge2);
$this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge3, $this->invoice->custom_surcharge_tax3);
// $this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge3, $this->invoice->custom_surcharge_tax3);
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge3);
$this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge4, $this->invoice->custom_surcharge_tax4);
// $this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge4, $this->invoice->custom_surcharge_tax4);
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge4);
$this->total += $this->total_custom_values;
@ -137,39 +138,39 @@ class InvoiceSumInclusive
}
//Handles cases where the surcharge is not taxed
// if(is_numeric($this->invoice->custom_surcharge1) && $this->invoice->custom_surcharge1 > 0 && !$this->invoice->custom_surcharge_tax1) {
// $amount += $this->invoice->custom_surcharge1;
// }
if(is_numeric($this->invoice->custom_surcharge1) && $this->invoice->custom_surcharge1 > 0 && $this->invoice->custom_surcharge_tax1) {
$amount += $this->invoice->custom_surcharge1;
}
// if(is_numeric($this->invoice->custom_surcharge2) && $this->invoice->custom_surcharge2 > 0 && !$this->invoice->custom_surcharge_tax2) {
// $amount += $this->invoice->custom_surcharge2;
// }
if(is_numeric($this->invoice->custom_surcharge2) && $this->invoice->custom_surcharge2 > 0 && $this->invoice->custom_surcharge_tax2) {
$amount += $this->invoice->custom_surcharge2;
}
// if(is_numeric($this->invoice->custom_surcharge3) && $this->invoice->custom_surcharge3 > 0 && !$this->invoice->custom_surcharge_tax3) {
// $amount += $this->invoice->custom_surcharge3;
// }
if(is_numeric($this->invoice->custom_surcharge3) && $this->invoice->custom_surcharge3 > 0 && $this->invoice->custom_surcharge_tax3) {
$amount += $this->invoice->custom_surcharge3;
}
// if(is_numeric($this->invoice->custom_surcharge4) && $this->invoice->custom_surcharge4 > 0 && !$this->invoice->custom_surcharge_tax4) {
// $amount += $this->invoice->custom_surcharge4;
// }
if(is_numeric($this->invoice->custom_surcharge4) && $this->invoice->custom_surcharge4 > 0 && $this->invoice->custom_surcharge_tax4) {
$amount += $this->invoice->custom_surcharge4;
}
if (is_string($this->invoice->tax_name1) && strlen($this->invoice->tax_name1) > 1) {
$tax = $this->calcInclusiveLineTax($this->invoice->tax_rate1, $amount);
$this->total_taxes += $tax;
$this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.floatval($this->invoice->tax_rate1).'%', 'total' => $tax];
$this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate1), $this->client).'%', 'total' => $tax];
}
if (is_string($this->invoice->tax_name2) && strlen($this->invoice->tax_name2) > 1) {
$tax = $this->calcInclusiveLineTax($this->invoice->tax_rate2, $amount);
$this->total_taxes += $tax;
$this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.floatval($this->invoice->tax_rate2).'%', 'total' => $tax];
$this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate2), $this->client).'%', 'total' => $tax];
}
if (is_string($this->invoice->tax_name3) && strlen($this->invoice->tax_name3) > 1) {
$tax = $this->calcInclusiveLineTax($this->invoice->tax_rate3, $amount);
$this->total_taxes += $tax;
$this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.floatval($this->invoice->tax_rate3).'%', 'total' => $tax];
$this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate3), $this->client).'%', 'total' => $tax];
}
return $this;

View File

@ -30,7 +30,7 @@ class ProRata
*/
public function refund(float $amount, Carbon $from_date, Carbon $to_date, int $frequency): float
{
$days = $from_date->copy()->diffInDays($to_date);
$days = intval(abs($from_date->copy()->diffInDays($to_date)));
$days_in_frequency = $this->getDaysInFrequency($frequency);
return round((($days / $days_in_frequency) * $amount), 2);
@ -48,7 +48,7 @@ class ProRata
*/
public function charge(float $amount, Carbon $from_date, Carbon $to_date, int $frequency): float
{
$days = $from_date->copy()->diffInDays($to_date);
$days = intval(abs($from_date->copy()->diffInDays($to_date)));
$days_in_frequency = $this->getDaysInFrequency($frequency);
return round((($days / $days_in_frequency) * $amount), 2);
@ -58,21 +58,21 @@ class ProRata
* Prepares the line items of an invoice
* to be pro rata refunded.
*
* @param Invoice $invoice
* @param ?Invoice $invoice
* @param bool $is_credit
* @return array
* @throws Exception
*/
public function refundItems(Invoice $invoice, $is_credit = false): array
public function refundItems(?Invoice $invoice, $is_credit = false): array
{
if (! $invoice) {
return [];
}
/** @var \App\Models\RecurringInvoice $recurring_invoice **/
$recurring_invoice = RecurringInvoice::find($invoice->recurring_id)->first();
$recurring_invoice = RecurringInvoice::find($invoice->recurring_id);
if (! $recurring_invoice) {
if (! $recurring_invoice) { // @phpstan-ignore-line
throw new \Exception("Invoice isn't attached to a recurring invoice");
}
@ -107,23 +107,23 @@ class ProRata
case RecurringInvoice::FREQUENCY_TWO_WEEKS:
return 14;
case RecurringInvoice::FREQUENCY_FOUR_WEEKS:
return now()->diffInDays(now()->addWeeks(4));
return intval(abs(now()->diffInDays(now()->addWeeks(4))));
case RecurringInvoice::FREQUENCY_MONTHLY:
return now()->diffInDays(now()->addMonthNoOverflow());
return intval(abs(now()->diffInDays(now()->addMonthNoOverflow())));
case RecurringInvoice::FREQUENCY_TWO_MONTHS:
return now()->diffInDays(now()->addMonthsNoOverflow(2));
return intval(abs(now()->diffInDays(now()->addMonthsNoOverflow(2))));
case RecurringInvoice::FREQUENCY_THREE_MONTHS:
return now()->diffInDays(now()->addMonthsNoOverflow(3));
return intval(abs(now()->diffInDays(now()->addMonthsNoOverflow(3))));
case RecurringInvoice::FREQUENCY_FOUR_MONTHS:
return now()->diffInDays(now()->addMonthsNoOverflow(4));
return intval(abs(now()->diffInDays(now()->addMonthsNoOverflow(4))));
case RecurringInvoice::FREQUENCY_SIX_MONTHS:
return now()->diffInDays(now()->addMonthsNoOverflow(6));
return intval(abs(now()->diffInDays(now()->addMonthsNoOverflow(6))));
case RecurringInvoice::FREQUENCY_ANNUALLY:
return now()->diffInDays(now()->addYear());
return intval(abs(now()->diffInDays(now()->addYear())));
case RecurringInvoice::FREQUENCY_TWO_YEARS:
return now()->diffInDays(now()->addYears(2));
return intval(abs(now()->diffInDays(now()->addYears(2))));
case RecurringInvoice::FREQUENCY_THREE_YEARS:
return now()->diffInDays(now()->addYears(3));
return intval(abs(now()->diffInDays(now()->addYears(3))));
default:
return 0;
}

View File

@ -31,7 +31,7 @@ class GmailTransport extends AbstractTransport
protected function doSend(SentMessage $message): void
{
nlog("In Do Send");
$message = MessageConverter::toEmail($message->getOriginalMessage());
$message = MessageConverter::toEmail($message->getOriginalMessage()); //@phpstan-ignore-line
/** @phpstan-ignore-next-line **/
$token = $message->getHeaders()->get('gmailtoken')->getValue(); // @phpstan-ignore-line

View File

@ -25,7 +25,8 @@ class Office365MailTransport extends AbstractTransport
protected function doSend(SentMessage $message): void
{
$symfony_message = MessageConverter::toEmail($message->getOriginalMessage());
$symfony_message = MessageConverter::toEmail($message->getOriginalMessage()); //@phpstan-ignore-line
$graph = new Graph();

View File

@ -115,13 +115,8 @@ class AccountController extends BaseController
public function update(UpdateAccountRequest $request, Account $account)
{
$fi = new \FilesystemIterator(public_path('react'), \FilesystemIterator::SKIP_DOTS);
if (iterator_count($fi) < 30) {
return response()->json(['message' => 'React App Not Installed, Please install the React app before attempting to switch.'], 400);
}
$account->fill($request->all());
$account->set_react_as_default_ap = $request->input('set_react_as_default_ap');
$account->save();
$this->entity_type = Account::class;

View File

@ -11,19 +11,31 @@
namespace App\Http\Controllers;
use App\Http\Requests\Activity\DownloadHistoricalEntityRequest;
use App\Http\Requests\Activity\ShowActivityRequest;
use App\Models\Activity;
use App\Transformers\ActivityTransformer;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\Ninja;
use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\Pdf\PageNumbering;
use App\Utils\Traits\Pdf\PdfMaker;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use stdClass;
use App\Utils\Ninja;
use App\Models\Client;
use App\Models\Invoice;
use App\Models\Activity;
use Illuminate\Http\Request;
use App\Utils\Traits\MakesHash;
use App\Utils\PhantomJS\Phantom;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\Traits\Pdf\PdfMaker;
use App\Utils\Traits\Pdf\PageNumbering;
use Illuminate\Support\Facades\Storage;
use App\Transformers\ActivityTransformer;
use App\Http\Requests\Activity\StoreNoteRequest;
use App\Http\Requests\Activity\ShowActivityRequest;
use App\Http\Requests\Activity\DownloadHistoricalEntityRequest;
use App\Models\Credit;
use App\Models\Expense;
use App\Models\Payment;
use App\Models\PurchaseOrder;
use App\Models\Quote;
use App\Models\RecurringExpense;
use App\Models\RecurringInvoice;
use App\Models\Task;
use App\Models\Vendor;
class ActivityController extends BaseController
{
@ -177,4 +189,89 @@ class ActivityController extends BaseController
echo $pdf;
}, $filename, ['Content-Type' => 'application/pdf']);
}
public function note(StoreNoteRequest $request)
{
/** @var \App\Models\User $user */
$user = auth()->user();
$entity = $request->getEntity();
$activity = new Activity();
$activity->account_id = $user->account_id;
$activity->company_id = $user->company()->id;
$activity->notes = $request->notes;
$activity->user_id = $user->id;
$activity->ip = $request->ip();
$activity->activity_type_id = Activity::USER_NOTE;
switch (get_class($entity)) {
case Invoice::class:
$activity->invoice_id = $entity->id;
$activity->client_id = $entity->client_id;
$activity->project_id = $entity->project_id;
$activity->vendor_id = $entity->vendor_id;
break;
case Credit::class:
$activity->credit_id = $entity->id;
$activity->client_id = $entity->client_id;
$activity->project_id = $entity->project_id;
$activity->vendor_id = $entity->vendor_id;
$activity->invoice_id = $entity->invoice_id;
break;
case Client::class:
$activity->client_id = $entity->id;
break;
case Quote::class:
$activity->quote_id = $entity->id;
$activity->client_id = $entity->client_id;
$activity->project_id = $entity->project_id;
$activity->vendor_id = $entity->vendor_id;
break;
case RecurringInvoice::class:
$activity->recurring_invoice_id = $entity->id;
$activity->client_id = $entity->client_id;
break;
case Expense::class:
$activity->expense_id = $entity->id;
$activity->client_id = $entity->client_id;
$activity->project_id = $entity->project_id;
$activity->vendor_id = $entity->vendor_id;
break;
case RecurringExpense::class:
$activity->recurring_expense_id = $entity->id;
$activity->expense_id = $entity->id;
$activity->client_id = $entity->client_id;
$activity->project_id = $entity->project_id;
$activity->vendor_id = $entity->vendor_id;
break;
case Vendor::class:
$activity->vendor_id = $entity->id;
break;
case PurchaseOrder::class:
$activity->purchase_order_id = $entity->id;
$activity->expense_id = $entity->id;
$activity->client_id = $entity->client_id;
$activity->project_id = $entity->project_id;
$activity->vendor_id = $entity->vendor_id;
case Task::class:
$activity->task_id = $entity->id;
$activity->expense_id = $entity->id;
$activity->client_id = $entity->client_id;
$activity->project_id = $entity->project_id;
$activity->vendor_id = $entity->vendor_id;
case Payment::class:
$activity->payment_id = $entity->id;
$activity->expense_id = $entity->id;
$activity->client_id = $entity->client_id;
$activity->project_id = $entity->project_id;
default:
# code...
break;
}
$activity->save();
return $this->itemResponse($activity);
}
}

View File

@ -118,7 +118,7 @@ class ContactForgotPasswordController extends Controller
})->first();
}
$response = false;
// $response = false;
if ($contact) {
/* Update all instances of the client */
@ -131,16 +131,16 @@ class ContactForgotPasswordController extends Controller
}
if ($request->ajax()) {
if ($response == Password::RESET_THROTTLED) {
if ($response == Password::RESET_THROTTLED) { // @phpstan-ignore-line
return response()->json(['message' => ctrans('passwords.throttled'), 'status' => false], 429);
}
return $response == Password::RESET_LINK_SENT
return $response == Password::RESET_LINK_SENT // @phpstan-ignore-line
? response()->json(['message' => 'Reset link sent to your email.', 'status' => true], 201)
: response()->json(['message' => 'Email not found', 'status' => false], 401);
}
return $response == Password::RESET_LINK_SENT
return $response == Password::RESET_LINK_SENT // @phpstan-ignore-line
? $this->sendResetLinkResponse($request, $response)
: $this->sendResetLinkFailedResponse($request, $response);
}

View File

@ -41,6 +41,9 @@ class ContactLoginController extends Controller
$company = false;
$account = false;
if($request->query('intended'))
$request->session()->put('url.intended', $request->query('intended'));
if ($request->session()->has('company_key')) {
MultiDB::findAndSetDbByCompanyKey($request->session()->get('company_key'));
$company = Company::where('company_key', $request->session()->get('company_key'))->first();
@ -52,7 +55,7 @@ class ContactLoginController extends Controller
$company = Company::where('company_key', $company_key)->first();
}
/** @var \App\Models\Company $company **/
/** @var ?\App\Models\Company $company **/
if ($company) {
$account = $company->account;
} elseif (! $company && strpos($request->getHost(), config('ninja.app_domain')) !== false) {
@ -81,6 +84,7 @@ class ContactLoginController extends Controller
public function login(Request $request)
{
Auth::shouldUse('contact');
if (Ninja::isHosted() && $request->has('company_key')) {
@ -125,6 +129,9 @@ class ContactLoginController extends Controller
protected function sendLoginResponse(Request $request)
{
$intended = $request->session()->has('url.intended') ? $request->session()->get('url.intended') : false;
$request->session()->regenerate();
$this->clearLoginAttempts($request);
@ -134,6 +141,9 @@ class ContactLoginController extends Controller
}
$this->setRedirectPath();
if($intended)
$this->redirectTo = $intended;
return $request->wantsJson()
? new JsonResponse([], 204)
@ -146,8 +156,8 @@ class ContactLoginController extends Controller
event(new ContactLoggedIn($client, $client->company, Ninja::eventVars()));
if (session()->get('url.intended')) {
return redirect(session()->get('url.intended'));
if ($request->session()->has('url.intended')) {
return redirect($request->session()->get('url.intended'));
}
$this->setRedirectPath();
@ -165,19 +175,20 @@ class ContactLoginController extends Controller
private function setRedirectPath()
{
if (auth()->guard('contact')->user()->client->getSetting('enable_client_portal_dashboard') === true) {
$this->redirectTo = '/client/dashboard';
} elseif (auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_INVOICES) {
} elseif ((bool)(auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_INVOICES)) {
$this->redirectTo = '/client/invoices';
} elseif (auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_RECURRING_INVOICES) {
} elseif ((bool)(auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_RECURRING_INVOICES)) {
$this->redirectTo = '/client/recurring_invoices';
} elseif (auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_QUOTES) {
} elseif ((bool)(auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_QUOTES)) {
$this->redirectTo = '/client/quotes';
} elseif (auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_CREDITS) {
} elseif ((bool)(auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_CREDITS)) {
$this->redirectTo = '/client/credits';
} elseif (auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_TASKS) {
} elseif ((bool)(auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_TASKS)) {
$this->redirectTo = '/client/tasks';
} elseif (auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_EXPENSES) {
} elseif ((bool)(auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_EXPENSES)) {
$this->redirectTo = '/client/expenses';
}
}

View File

@ -376,6 +376,7 @@ class LoginController extends BaseController
/** @var \App\Models\User $user */
$user = auth()->user();
/** @var Builder $cu */
$cu = CompanyUser::query()->where('user_id', $user->id);
if ($cu->count() == 0) {
@ -398,18 +399,15 @@ class LoginController extends BaseController
$truth->setCompany($set_company);
//21-03-2024
$cu->each(function ($cu) {
if(CompanyToken::where('company_id', $cu->company_id)->where('user_id', $cu->user_id)->where('is_system', true)->doesntExist()) {
/** @var \App\Models\CompanyUser $cu */
if(CompanyToken::query()->where('company_id', $cu->company_id)->where('user_id', $cu->user_id)->where('is_system', true)->doesntExist()) {
(new CreateCompanyToken($cu->company, $cu->user, request()->server('HTTP_USER_AGENT')))->handle();
}
});
// $user->account->companies->each(function ($company) use ($user) {
// if ($company->tokens()->where('user_id',$user->id)->where('is_system', true)->count() == 0) {
// (new CreateCompanyToken($company, $user, request()->server('HTTP_USER_AGENT')))->handle();
// }
// });
$truth->setCompanyToken(CompanyToken::where('user_id', $user->id)->where('company_id', $set_company->id)->where('is_system', true)->first());
return CompanyUser::query()->where('user_id', $user->id);

View File

@ -137,8 +137,8 @@ class ResetPasswordController extends Controller
return redirect('/#/login');
}
return redirect($this->redirectPath())
->with('status', trans($response));
// return redirect($this->redirectPath())
// ->with('status', trans($response));
}
}

View File

@ -11,7 +11,6 @@
namespace App\Http\Controllers\Bank;
use App\Helpers\Bank\Yodlee\DTO\AccountSummary;
use App\Helpers\Bank\Yodlee\Yodlee;
use App\Http\Controllers\BaseController;
use App\Http\Requests\Yodlee\YodleeAdminRequest;
@ -98,7 +97,7 @@ class YodleeController extends BaseController
}
$company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->each(function ($bank_integration) use ($company) { // TODO: filter to yodlee only
ProcessBankTransactionsYodlee::dispatch($company->account->id, $bank_integration);
ProcessBankTransactionsYodlee::dispatch($company->account->bank_integration_account_id, $bank_integration);
});
}
@ -301,8 +300,6 @@ class YodleeController extends BaseController
$summary = $yodlee->getAccountSummary($account_number);
//@todo remove laravel-data
// $transformed_summary = AccountSummary::from($summary[0]);
$transformed_summary = $this->transformSummary($summary[0]);
return response()->json($transformed_summary, 200);

View File

@ -197,6 +197,7 @@ class BankIntegrationController extends BaseController
/** @var \App\Models\User $user */
$user = auth()->user();
/** @var \App\Models\Account $user_account */
$user_account = $user->account;
$this->refreshAccountsYodlee($user);
@ -210,12 +211,14 @@ class BankIntegrationController extends BaseController
// Processing transactions for each bank account
if (Ninja::isHosted() && $user->account->bank_integration_account_id) {
$user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->each(function ($bank_integration) use ($user_account) {
ProcessBankTransactionsYodlee::dispatch($user_account->id, $bank_integration);
/** @var \App\Models\BankIntegration $bank_integration */
ProcessBankTransactionsYodlee::dispatch($user_account->bank_integration_account_id, $bank_integration);
});
}
if (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key') && (Ninja::isSelfHost() || (Ninja::isHosted() && $user_account->isEnterprisePaidClient()))) {
$user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function ($bank_integration) {
$user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function ($bank_integration) {
/** @var \App\Models\BankIntegration $bank_integration */
ProcessBankTransactionsNordigen::dispatch($bank_integration);
});
}
@ -270,7 +273,7 @@ class BankIntegrationController extends BaseController
$nordigen = new Nordigen();
BankIntegration::where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->whereNotNull('nordigen_account_id')->each(function (BankIntegration $bank_integration) use ($nordigen) {
BankIntegration::where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('account_id', $user->account_id)->whereNotNull('nordigen_account_id')->each(function (BankIntegration $bank_integration) use ($nordigen) {
$is_account_active = $nordigen->isAccountActive($bank_integration->nordigen_account_id);
$account = $nordigen->getAccount($bank_integration->nordigen_account_id);
@ -345,7 +348,7 @@ class BankIntegrationController extends BaseController
if (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise') {
$account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) {
(new ProcessBankTransactionsYodlee($account->id, $bank_integration))->handle();
(new ProcessBankTransactionsYodlee($account->bank_integration_account_id, $bank_integration))->handle();
});
}

View File

@ -35,6 +35,7 @@ use League\Fractal\Resource\Item;
use App\Models\BankTransactionRule;
use Illuminate\Support\Facades\Auth;
use App\Transformers\ArraySerializer;
use Illuminate\Support\Facades\Schema as DbSchema;
use App\Transformers\EntityTransformer;
use League\Fractal\Resource\Collection;
use Illuminate\Database\Eloquent\Builder;
@ -653,7 +654,7 @@ class BaseController extends Controller
/**
* Passes back the miniloaded data response
*
* @param Builder $query
* @param mixed $query
*
*/
protected function timeConstrainedResponse($query)
@ -894,11 +895,7 @@ class BaseController extends Controller
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
}
// else {
// $resource = new Collection($query, $transformer, $this->entity_type);
// }
return $this->response($this->manager->createData($resource)->toArray());
return $this->response($this->manager->createData($resource)->toArray()); //@phpstan-ignore-line
}
/**
@ -937,7 +934,9 @@ class BaseController extends Controller
} elseif (in_array($this->entity_type, [Design::class, GroupSetting::class, PaymentTerm::class, TaskStatus::class])) {
// nlog($this->entity_type);
} else {
$query->where('user_id', '=', $user->id)->orWhere('assigned_user_id', $user->id);
$query->where(function ($q) use ($user){ //grouping these together improves query performance significantly)
$q->where('user_id', '=', $user->id)->orWhere('assigned_user_id', $user->id);
});
}
}
@ -1035,7 +1034,7 @@ class BaseController extends Controller
$resource = new Item($item, $transformer, $this->entity_type);
/** @var \App\Models\User $user */
/** @var ?\App\Models\User $user */
$user = auth()->user();
if ($user && request()->include_static) {
@ -1104,7 +1103,7 @@ class BaseController extends Controller
public function flutterRoute()
{
if ((bool) $this->checkAppSetup() !== false && $account = Account::first()) {
if ((bool) $this->checkAppSetup() !== false && DbSchema::hasTable('accounts') && $account = Account::first()) {
/** @var \App\Models\Account $account */
@ -1158,8 +1157,6 @@ class BaseController extends Controller
$data['path'] = $this->setBuild();
$this->buildCache();
if (Ninja::isSelfHost() && $account->set_react_as_default_ap) {
return response()->view('react.index', $data)->header('X-Frame-Options', 'SAMEORIGIN', false);
} else {

View File

@ -19,7 +19,6 @@ use Illuminate\Http\Request;
*/
class BrevoController extends BaseController
{
private $invitation;
public function __construct()
{

View File

@ -11,8 +11,9 @@
namespace App\Http\Controllers;
use App\Http\Requests\Chart\ShowChartRequest;
use App\Services\Chart\ChartService;
use App\Http\Requests\Chart\ShowChartRequest;
use App\Http\Requests\Chart\ShowCalculatedFieldRequest;
class ChartController extends BaseController
{
@ -65,5 +66,15 @@ class ChartController extends BaseController
return response()->json($cs->chart_summary($request->input('start_date'), $request->input('end_date')), 200);
}
public function calculatedFields(ShowCalculatedFieldRequest $request)
{
/** @var \App\Models\User auth()->user() */
$user = auth()->user();
$cs = new ChartService($user->company(), $user, $user->isAdmin());
$result = $cs->getCalculatedField($request->all());
return response()->json($result, 200);
}
}

View File

@ -141,7 +141,7 @@ class ClientController extends BaseController
return $request->disallowUpdate();
}
/** @var \App\Models\User $user */
/** @var ?\App\Models\User $user */
$user = auth()->user();
$client = $this->client_repo->save($request->all(), $client);
@ -426,10 +426,10 @@ class ClientController extends BaseController
try {
/** @var \Postmark\Models\DynamicResponseModel $response */
/** @var ?\Postmark\Models\DynamicResponseModel $response */
$response = $postmark->activateBounce((int)$bounce_id);
if($response && $response?->Message == 'OK' && !$response->Bounce->Inactive && $response->Bounce->Email) {
if($response && $response?->Message == 'OK' && !$response->Bounce->Inactive && $response->Bounce->Email) { // @phpstan-ignore-line
$email = $response->Bounce->Email;
//remove email from quarantine. //@TODO

View File

@ -57,8 +57,9 @@ class ContactHashLoginController extends Controller
return render('generic.error', [
'title' => session()->get('title'),
'notification' => session()->get('notification'),
'account' => auth()->guard('contact')?->user()?->user?->account,
'company' => auth()->guard('contact')?->user()?->user?->company
'account' => auth()->guard('contact')?->user()?->user?->account,// @phpstan-ignore-line
'company' => auth()->guard('contact')?->user()?->user?->company // @phpstan-ignore-line
]);
}
@ -66,15 +67,15 @@ class ContactHashLoginController extends Controller
{
if (auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_INVOICES) {
return '/client/invoices';
} elseif (auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_RECURRING_INVOICES) {
} elseif ((bool)(auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_RECURRING_INVOICES)) {
return '/client/recurring_invoices';
} elseif (auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_QUOTES) {
} elseif ((bool)(auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_QUOTES)) {
return '/client/quotes';
} elseif (auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_CREDITS) {
} elseif ((bool)(auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_CREDITS)) {
return '/client/credits';
} elseif (auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_TASKS) {
} elseif ((bool)(auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_TASKS)) {
return '/client/tasks';
} elseif (auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_EXPENSES) {
} elseif ((bool)(auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_EXPENSES)) {
return '/client/expenses';
}
}

View File

@ -114,15 +114,17 @@ class InvitationController extends Controller
'invitation_key' => $invitation_key
]);
}
if(!auth()->guard('contact')->check()){
$this->middleware('auth:contact');
return redirect()->route('client.login', ['intended' => route('client.'.$entity.'.show', [$entity => $this->encodePrimaryKey($invitation->{$key}), 'silent' => $is_silent])]);
}
$this->middleware('auth:contact');
return redirect()->route('client.login');
} else {
request()->session()->invalidate();
auth()->guard('contact')->loginUsingId($client_contact->id, true);
}
if (auth()->guard('contact')->user() && ! request()->has('silent') && ! $invitation->viewed_date) {
$invitation->markViewed();
@ -144,6 +146,7 @@ class InvitationController extends Controller
}
private function fireEntityViewedEvent($invitation, $entity_string)
{
switch ($entity_string) {
@ -297,7 +300,9 @@ class InvitationController extends Controller
'signature' => false,
'contact_first_name' => $invitation->contact->first_name ?? '',
'contact_last_name' => $invitation->contact->last_name ?? '',
'contact_email' => $invitation->contact->email ?? ''
'contact_email' => $invitation->contact->email ?? '',
'client_city' => $invitation->client->city ?? '',
'client_postal_code' => $invitation->client->postal_code ?? '',
];
$request->replace($data);

View File

@ -97,12 +97,12 @@ class InvoiceController extends Controller
$invitation = false;
match($data['entity_type'] ?? false) {
match($data['entity_type'] ?? 'invoice') {
'invoice' => $invitation = InvoiceInvitation::withTrashed()->find($data['invitation_id']),
'quote' => $invitation = QuoteInvitation::withTrashed()->find($data['invitation_id']),
'credit' => $invitation = CreditInvitation::withTrashed()->find($data['invitation_id']),
'recurring_invoice' => $invitation = RecurringInvoiceInvitation::withTrashed()->find($data['invitation_id']),
false => $invitation = false,
default => $invitation = false,
};
if (! $invitation) {

View File

@ -77,6 +77,7 @@ class PaymentController extends Controller
'EUR' => $data = $bt->formatDataforEur($payment_intent),
'JPY' => $data = $bt->formatDataforJp($payment_intent),
'GBP' => $data = $bt->formatDataforUk($payment_intent),
default => $data = $bt->formatDataforUk($payment_intent),
};
$gateway = $stripe;
@ -107,11 +108,11 @@ class PaymentController extends Controller
*/
public function process(Request $request)
{
$request->validate([
'contact_first_name' => ['required'],
'contact_last_name' => ['required'],
'contact_email' => ['required', 'email'],
]);
// $request->validate([
// 'contact_first_name' => ['required'],
// 'contact_last_name' => ['required'],
// 'contact_email' => ['required', 'email'],
// ]);
return (new InstantPayment($request))->run();
}
@ -120,8 +121,16 @@ class PaymentController extends Controller
{
/** @var \App\Models\CompanyGateway $gateway **/
$gateway = CompanyGateway::findOrFail($request->input('company_gateway_id'));
$payment_hash = PaymentHash::where('hash', $request->payment_hash)->firstOrFail();
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
$payment_hash = PaymentHash::with('fee_invoice')->where('hash', $request->payment_hash)->firstOrFail();
// if($payment_hash)
$invoice = $payment_hash->fee_invoice;
// else
// $invoice = Invoice::with('client')->where('id',$payment_hash->fee_invoice_id)->orderBy('id','desc')->first();
// $invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
$client = $invoice ? $invoice->client : auth()->guard('contact')->user()->client;
// 09-07-2022 catch duplicate responses for invoices that already paid here.
@ -200,7 +209,7 @@ class PaymentController extends Controller
if (property_exists($payment_hash->data, 'billing_context')) {
$billing_subscription = \App\Models\Subscription::find($this->decodePrimaryKey($payment_hash->data->billing_context->subscription_id));
/** @var \App\Models\Subscription $billing_subscription */
return (new SubscriptionService($billing_subscription))->completePurchase($payment_hash);
}

View File

@ -90,15 +90,20 @@ class SubscriptionPurchaseController extends Controller
* Set locale for incoming request.
*
* @param string $locale
* @return string
*/
private function setLocale(string $locale): void
private function setLocale(string $locale): string
{
$record = Cache::get('languages')->filter(function ($item) use ($locale) {
return $item->locale == $locale;
})->first();
/** @var \Illuminate\Support\Collection<\App\Models\Language> */
$languages = app('languages');
if ($record) {
App::setLocale($record->locale);
}
$record = $languages->first(function ($item) use ($locale) {
/** @var \App\Models\Language $item */
return $item->locale == $locale;
});
return $record ? $record->locale : 'en';
}
}

View File

@ -693,7 +693,7 @@ class CompanyController extends BaseController
public function updateOriginTaxData(DefaultCompanyRequest $request, Company $company)
{
if($company->settings->country_id == "840" && !$company?->account->isFreeHostedClient()) {
if($company->settings->country_id == "840" && !$company->account->isFreeHostedClient()) {
try {
(new CompanyTaxRate($company))->handle();
} catch(\Exception $e) {

View File

@ -567,9 +567,9 @@ class CompanyGatewayController extends BaseController
{
//Throttle here
if (Cache::has("throttle_polling:import_customers:{$company_gateway->company->company_key}:{$company_gateway->hashed_id}")) {
return response()->json(['message' => 'Please wait whilst your previous attempts complete.'], 200);
}
// if (Cache::has("throttle_polling:import_customers:{$company_gateway->company->company_key}:{$company_gateway->hashed_id}")) {
// return response()->json(['message' => 'Please wait whilst your previous attempts complete.'], 200);
// }
dispatch(function () use ($company_gateway) {
MultiDB::setDb($company_gateway->company->db);

View File

@ -119,8 +119,6 @@ class CompanyUserController extends BaseController
if (! $company_user) {
throw new ModelNotFoundException(ctrans('texts.company_user_not_found'));
return;
}
if ($auth_user->isAdmin()) {
@ -152,7 +150,6 @@ class CompanyUserController extends BaseController
if (! $company_user) {
throw new ModelNotFoundException(ctrans('texts.company_user_not_found'));
return;
}
$this->entity_type = User::class;

View File

@ -458,7 +458,7 @@ class CreditController extends BaseController
/**
* Perform bulk actions on the list view.
*
* @return \Symfony\Component\HttpFoundation\StreamedResponse | \Illuminate\Http\JsonResponse
* @return \Symfony\Component\HttpFoundation\StreamedResponse | \Illuminate\Http\JsonResponse | \Illuminate\Http\Response
*
* @OA\Post(
* path="/api/v1/credits/bulk",
@ -594,13 +594,11 @@ class CreditController extends BaseController
$credit->service()->markPaid()->save();
return $this->itemResponse($credit);
break;
case 'clone_to_credit':
$credit = CloneCreditFactory::create($credit, auth()->user()->id);
return $this->itemResponse($credit);
break;
case 'history':
// code...
break;
@ -617,7 +615,7 @@ class CreditController extends BaseController
return response()->streamDownload(function () use ($file) {
echo $file;
}, $credit->numberFormatter() . '.pdf', ['Content-Type' => 'application/pdf']);
break;
case 'archive':
$this->credit_repository->archive($credit);
@ -655,7 +653,6 @@ class CreditController extends BaseController
default:
return response()->json(['message' => ctrans('texts.action_unavailable', ['action' => $action])], 400);
break;
}
}
@ -700,7 +697,7 @@ class CreditController extends BaseController
* ),
* )
* @param $invitation_key
* @return \Symfony\Component\HttpFoundation\StreamedResponse
* @return \Symfony\Component\HttpFoundation\StreamedResponse | \Illuminate\Http\JsonResponse | \Illuminate\Http\Response
*/
public function downloadPdf($invitation_key)
{
@ -768,7 +765,7 @@ class CreditController extends BaseController
* ),
* )
* @param $invitation_key
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
* @return \Symfony\Component\HttpFoundation\StreamedResponse | \Illuminate\Http\JsonResponse | \Illuminate\Http\Response
*/
public function downloadECredit($invitation_key)
{

View File

@ -183,7 +183,7 @@ class DocumentController extends BaseController
}
if ($action == 'download') {
ZipDocuments::dispatch($documents->pluck('id'), $user->company(), auth()->user());
ZipDocuments::dispatch($documents->pluck('id'), $user->company(), auth()->user()); //@phpstan-ignore-line
return response()->json(['message' => ctrans('texts.sent_message')], 200);
}

Some files were not shown because too many files have changed in this diff Show More