mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Merge remote-tracking branch 'upstream/v5-develop' into 1403-gocardless
This commit is contained in:
commit
c7ba9cb908
3
.env.ci
3
.env.ci
@ -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
|
@ -24,6 +24,7 @@ QUEUE_CONNECTION=sync
|
||||
SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
|
||||
MAIL_MAILER=smtp
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
@ -66,3 +67,5 @@ NORDIGEN_SECRET_KEY=
|
||||
|
||||
GOCARDLESS_CLIENT_ID=
|
||||
GOCARDLESS_CLIENT_SECRET=
|
||||
|
||||
OPENEXCHANGE_APP_ID=
|
||||
|
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -8,7 +8,7 @@ assignees: ''
|
||||
---
|
||||
|
||||
<!-- Before posting please check our "Troubleshooting" category in the docs:
|
||||
https://invoiceninja.github.io/docs/self-host-troubleshooting/ -->
|
||||
https://invoiceninja.github.io/en/self-host-troubleshooting/ -->
|
||||
|
||||
## Setup
|
||||
- Version: <!-- i.e. v4.5.25 / v5.0.30 -->
|
||||
|
3
.github/workflows/phpunit.yml
vendored
3
.github/workflows/phpunit.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
||||
phpunit-versions: ['latest']
|
||||
ci_node_total: [ 8 ]
|
||||
ci_node_index: [ 0, 1, 2, 3, 4, 5, 6, 7]
|
||||
laravel: [10.*]
|
||||
laravel: [11.*]
|
||||
dependency-version: [prefer-stable]
|
||||
|
||||
env:
|
||||
@ -103,7 +103,6 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.php }}-composer-
|
||||
|
||||
|
||||
- name: Install composer dependencies
|
||||
run: |
|
||||
composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }}
|
||||
|
15
.github/workflows/react_release.yml
vendored
15
.github/workflows/react_release.yml
vendored
@ -38,16 +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: |
|
||||
@ -60,11 +66,12 @@ jobs:
|
||||
sudo rm -rf node_modules
|
||||
sudo rm -rf .git
|
||||
sudo rm .env
|
||||
|
||||
sudo rm -rf ui
|
||||
|
||||
- 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/')
|
||||
@ -72,4 +79,4 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
files: |
|
||||
/home/runner/work/invoiceninja/react-invoiceninja.tar
|
||||
/home/runner/work/invoiceninja/invoiceninja.tar
|
86
.github/workflows/release.yml
vendored
86
.github/workflows/release.yml
vendored
@ -1,86 +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: |
|
||||
zip -r /home/runner/work/invoiceninja/invoiceninja.zip .* -x "../*"
|
||||
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
|
||||
/home/runner/work/invoiceninja/invoiceninja.zip
|
48
README.md
48
README.md
@ -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>
|
||||
|
||||

|
||||
@ -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,28 @@ 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)
|
||||
|
||||
* [Elestio](https://elest.io/open-source/invoiceninja)
|
||||
* [YunoHost](https://apps.yunohost.org/app/invoiceninja5)
|
||||
|
||||
### 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 +96,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
|
||||
|
||||
|
@ -1 +1 @@
|
||||
5.9.1
|
||||
5.10.29
|
50
app/Casts/QuickbooksSettingsCast.php
Normal file
50
app/Casts/QuickbooksSettingsCast.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Casts;
|
||||
|
||||
use App\DataMapper\QuickbooksSettings;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
|
||||
class QuickbooksSettingsCast implements CastsAttributes
|
||||
{
|
||||
public function get($model, string $key, $value, array $attributes)
|
||||
{
|
||||
$data = json_decode($value, true);
|
||||
|
||||
if(!is_array($data))
|
||||
return null;
|
||||
|
||||
$qb = new QuickbooksSettings();
|
||||
$qb->accessTokenKey = $data['accessTokenKey'];
|
||||
$qb->refresh_token = $data['refresh_token'];
|
||||
$qb->realmID = $data['realmID'];
|
||||
$qb->accessTokenExpiresAt = $data['accessTokenExpiresAt'];
|
||||
$qb->refreshTokenExpiresAt = $data['refreshTokenExpiresAt'];
|
||||
$qb->settings = $data['settings'] ?? [];
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
public function set($model, string $key, $value, array $attributes)
|
||||
{
|
||||
return [
|
||||
$key => json_encode([
|
||||
'accessTokenKey' => $value->accessTokenKey,
|
||||
'refresh_token' => $value->refresh_token,
|
||||
'realmID' => $value->realmID,
|
||||
'accessTokenExpiresAt' => $value->accessTokenExpiresAt,
|
||||
'refreshTokenExpiresAt' => $value->refreshTokenExpiresAt,
|
||||
'settings' => $value->settings,
|
||||
])
|
||||
];
|
||||
}
|
||||
}
|
@ -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')}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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();
|
||||
@ -172,18 +174,18 @@ class CheckData extends Command
|
||||
CompanyUser::query()->cursor()->each(function ($cu) {
|
||||
|
||||
if (CompanyToken::where('user_id', $cu->user_id)->where('company_id', $cu->company_id)->where('is_system', 1)->doesntExist()) {
|
||||
|
||||
|
||||
|
||||
if ($cu->company && $cu->user) {
|
||||
$this->logMessage("Creating missing company token for user # {$cu->user_id} for company id # {$cu->company_id}");
|
||||
(new CreateCompanyToken($cu->company, $cu->user, 'System'))->handle();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!$cu->user) {
|
||||
$this->logMessage("No user found for company user - removing company user");
|
||||
$cu->forceDelete();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -477,14 +479,13 @@ class CheckData extends Command
|
||||
}
|
||||
} else {
|
||||
$this->logMessage("No contact present, so cannot add invitation for {$entity_key} - {$entity->id}");
|
||||
|
||||
try{
|
||||
|
||||
try {
|
||||
$entity->service()->createInvitations()->save();
|
||||
}
|
||||
catch(\Exception $e){
|
||||
} catch(\Exception $e) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
@ -949,12 +950,12 @@ class CheckData extends Command
|
||||
|
||||
});
|
||||
|
||||
Company::whereDoesntHave('company_users', function ($query){
|
||||
$query->where('is_owner', 1);
|
||||
Company::whereDoesntHave('company_users', function ($query) {
|
||||
$query->where('is_owner', 1);
|
||||
})
|
||||
->cursor()
|
||||
->when(Ninja::isHosted())
|
||||
->each(function ($c){
|
||||
->each(function ($c) {
|
||||
|
||||
$this->logMessage("Orphan Account # {$c->account_id}");
|
||||
|
||||
@ -963,8 +964,8 @@ class CheckData extends Command
|
||||
CompanyUser::whereDoesntHave('tokens')
|
||||
->cursor()
|
||||
->when(Ninja::isHosted())
|
||||
->each(function ($cu){
|
||||
|
||||
->each(function ($cu) {
|
||||
|
||||
$this->logMessage("Missing tokens for Company User # {$cu->id}");
|
||||
|
||||
});
|
||||
@ -1159,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}");
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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')) {
|
||||
@ -1016,7 +989,7 @@ class CreateSingleAccount extends Command
|
||||
$cg->fees_and_limits = $fees_and_limits;
|
||||
$cg->save();
|
||||
}
|
||||
|
||||
|
||||
if (config('ninja.testvars.eway') && ($this->gateway == 'all' || $this->gateway == 'eway')) {
|
||||
$cg = new CompanyGateway();
|
||||
$cg->company_id = $company->id;
|
||||
@ -1038,12 +1011,12 @@ class CreateSingleAccount extends Command
|
||||
$cg->save();
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (config('ninja.testvars.gocardless') && ($this->gateway == 'all' || $this->gateway == 'gocardless')) {
|
||||
|
||||
$c_settings = ClientSettings::defaults();
|
||||
$c_settings->currency_id = '2';
|
||||
|
||||
|
||||
$client = Client::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $company->id,
|
||||
@ -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' => '',
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
|
@ -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);
|
||||
|
@ -75,7 +75,7 @@ class MobileLocalization extends Command
|
||||
|
||||
private function flutterResources()
|
||||
{
|
||||
$languages = cache('languages');
|
||||
$languages = app('languages');
|
||||
$resources = $this->getResources();
|
||||
|
||||
foreach ($languages as $language) {
|
||||
|
@ -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');
|
||||
|
@ -48,37 +48,37 @@ class ReactBuilder extends Command
|
||||
{
|
||||
if($this->option('type') == 'local') {
|
||||
|
||||
|
||||
|
||||
$includes = '';
|
||||
|
||||
$directoryIterator = false;
|
||||
$includes = '';
|
||||
|
||||
try {
|
||||
$directoryIterator = new \RecursiveDirectoryIterator(public_path('react/v'.config('ninja.app_version').'/'), \RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('React files not found');
|
||||
return;
|
||||
}
|
||||
$directoryIterator = false;
|
||||
|
||||
foreach (new \RecursiveIteratorIterator($directoryIterator) as $file) {
|
||||
if ($file->getExtension() == 'js') {
|
||||
if (str_contains($file->getFileName(), 'index-')) {
|
||||
$includes .= '<script type="module" crossorigin src="/react/v'.config('ninja.app_version').'/'.$file->getFileName().'"></script>'."\n";
|
||||
} else {
|
||||
$includes .= '<link rel="modulepreload" href="/react/v'.config('ninja.app_version').'/'.$file->getFileName().'">'."\n";
|
||||
try {
|
||||
$directoryIterator = new \RecursiveDirectoryIterator(public_path('react/v'.config('ninja.app_version').'/'), \RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('React files not found');
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (new \RecursiveIteratorIterator($directoryIterator) as $file) {
|
||||
if ($file->getExtension() == 'js') {
|
||||
if (str_contains($file->getFileName(), 'index-')) {
|
||||
$includes .= '<script type="module" crossorigin src="/react/v'.config('ninja.app_version').'/'.$file->getFileName().'"></script>'."\n";
|
||||
} else {
|
||||
$includes .= '<link rel="modulepreload" href="/react/v'.config('ninja.app_version').'/'.$file->getFileName().'">'."\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (str_contains($file->getFileName(), '.css')) {
|
||||
$includes .= '<link rel="stylesheet" href="/react/v'.config('ninja.app_version').'/'.$file->getFileName().'">'."\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (str_contains($file->getFileName(), '.css')) {
|
||||
$includes .= '<link rel="stylesheet" href="/react/v'.config('ninja.app_version').'/'.$file->getFileName().'">'."\n";
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents(resource_path('views/react/head.blade.php'), $includes);
|
||||
file_put_contents(resource_path('views/react/head.blade.php'), $includes);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
|
@ -76,6 +76,7 @@ class TranslationsExport extends Command
|
||||
'sv',
|
||||
'th',
|
||||
'tr_TR',
|
||||
'vi',
|
||||
'zh_TW',
|
||||
];
|
||||
|
||||
|
@ -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,17 +56,20 @@ 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();
|
||||
|
||||
/* Stale Invoice Cleanup*/
|
||||
$schedule->job(new CleanStaleInvoiceOrder())->hourlyAt(30)->withoutOverlapping()->name('stale-invoice-job')->onOneServer();
|
||||
/* Checks Rotessa Transactions */
|
||||
$schedule->job(new TransactionReport())->dailyAt('01:48')->withoutOverlapping()->name('rotessa-transaction-report')->onOneServer();
|
||||
|
||||
/* Stale Invoice Cleanup*/
|
||||
$schedule->job(new UpdateCalculatedFields())->hourlyAt(40)->withoutOverlapping()->name('update-calculated-fields-job')->onOneServer();
|
||||
$schedule->job(new CleanStaleInvoiceOrder())->hourlyAt(30)->withoutOverlapping()->name('stale-invoice-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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ class DbQuery extends GenericMixedMetric
|
||||
public $string_metric8 = 'client_version';
|
||||
|
||||
public $string_metric9 = 'platform';
|
||||
|
||||
|
||||
/**
|
||||
* The counter
|
||||
* set to 1.
|
||||
@ -66,12 +66,14 @@ class DbQuery extends GenericMixedMetric
|
||||
|
||||
public $double_metric2 = 1;
|
||||
|
||||
public function __construct($string_metric5, $string_metric6, $int_metric1, $double_metric2, $string_metric7)
|
||||
public function __construct($string_metric5, $string_metric6, $int_metric1, $double_metric2, $string_metric7, $string_metric8, $string_metric9)
|
||||
{
|
||||
$this->int_metric1 = $int_metric1;
|
||||
$this->string_metric5 = $string_metric5;
|
||||
$this->string_metric6 = $string_metric6;
|
||||
$this->double_metric2 = $double_metric2;
|
||||
$this->string_metric7 = $string_metric7;
|
||||
$this->string_metric8 = mb_convert_encoding($string_metric8, "UTF-8");
|
||||
$this->string_metric9 = mb_convert_encoding($string_metric9, "UTF-8");
|
||||
}
|
||||
}
|
||||
|
83
app/DataMapper/Analytics/LegalEntityCreated.php
Normal file
83
app/DataMapper/Analytics/LegalEntityCreated.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?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\Analytics;
|
||||
|
||||
use Turbo124\Beacon\ExampleMetric\GenericMixedMetric;
|
||||
|
||||
class LegalEntityCreated extends GenericMixedMetric
|
||||
{
|
||||
/**
|
||||
* The type of Sample.
|
||||
*
|
||||
* Monotonically incrementing counter
|
||||
*
|
||||
* - counter
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $type = 'mixed_metric';
|
||||
|
||||
/**
|
||||
* The name of the counter.
|
||||
* @var string
|
||||
*/
|
||||
public $name = 'einvoice.legal_entity.created';
|
||||
|
||||
/**
|
||||
* The datetime of the counter measurement.
|
||||
*
|
||||
* date("Y-m-d H:i:s")
|
||||
*
|
||||
*/
|
||||
public $datetime;
|
||||
|
||||
/**
|
||||
* The Class failure name
|
||||
* set to 0.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $string_metric5 = 'stub';
|
||||
|
||||
/**
|
||||
* The exception string
|
||||
* set to 0.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $string_metric6 = 'stub';
|
||||
|
||||
/**
|
||||
* The counter
|
||||
* set to 1.
|
||||
*
|
||||
*/
|
||||
public $int_metric1 = 1;
|
||||
|
||||
/**
|
||||
* Company Key
|
||||
* @var string
|
||||
*/
|
||||
public $string_metric7 = '';
|
||||
|
||||
/**
|
||||
* Subject
|
||||
* @var string
|
||||
*/
|
||||
public $string_metric8 = '';
|
||||
|
||||
public function __construct($string_metric7 = '', $string_metric8 = '')
|
||||
{
|
||||
$this->string_metric7 = $string_metric7;
|
||||
$this->string_metric8 = $string_metric8;
|
||||
}
|
||||
}
|
61
app/DataMapper/Analytics/LoginMeta.php
Normal file
61
app/DataMapper/Analytics/LoginMeta.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?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\Analytics;
|
||||
|
||||
use Turbo124\Beacon\ExampleMetric\GenericMixedMetric;
|
||||
|
||||
class LoginMeta extends GenericMixedMetric
|
||||
{
|
||||
/**
|
||||
* The type of Sample.
|
||||
*
|
||||
* Monotonically incrementing counter
|
||||
*
|
||||
* - counter
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $type = 'mixed_metric';
|
||||
|
||||
/**
|
||||
* The name of the counter.
|
||||
* @var string
|
||||
*/
|
||||
public $name = 'login.meta';
|
||||
|
||||
/**
|
||||
* The datetime of the counter measurement.
|
||||
*
|
||||
* date("Y-m-d H:i:s")
|
||||
*
|
||||
*/
|
||||
public $datetime;
|
||||
|
||||
/**
|
||||
* The Class failure name
|
||||
* set to 0.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $string_metric5 = 'email';
|
||||
public $string_metric6 = 'ip';
|
||||
public $string_metric7 = 'result';
|
||||
|
||||
public $int_metric1 = 1;
|
||||
|
||||
public function __construct($string_metric5, $string_metric6, $string_metric7)
|
||||
{
|
||||
$this->string_metric7 = $string_metric7;
|
||||
$this->string_metric6 = $string_metric6;
|
||||
$this->string_metric5 = $string_metric5;
|
||||
}
|
||||
}
|
@ -16,13 +16,6 @@ namespace App\DataMapper;
|
||||
*/
|
||||
class BaseSettings
|
||||
{
|
||||
// //@deprecated
|
||||
// public function __construct($obj)
|
||||
// {
|
||||
// // foreach ($obj as $key => $value) {
|
||||
// // $obj->{$key} = $value;
|
||||
// // }
|
||||
// }
|
||||
|
||||
public static function setCasts($obj, $casts)
|
||||
{
|
||||
|
@ -29,7 +29,7 @@ class CompanySettings extends BaseSettings
|
||||
|
||||
public $besr_id = ''; //@implemented
|
||||
|
||||
public $lock_invoices = 'off'; //off,when_sent,when_paid //@implemented
|
||||
public $lock_invoices = 'off'; //off,when_sent,when_paid,end_of_month //@implemented
|
||||
|
||||
public $enable_client_portal_tasks = false; //@ben to implement
|
||||
|
||||
@ -507,7 +507,29 @@ 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 string $payment_flow = 'default'; //smooth
|
||||
|
||||
public string $email_subject_payment_failed = '';
|
||||
public string $email_template_payment_failed = '';
|
||||
|
||||
public static $casts = [
|
||||
'payment_flow' => 'string',
|
||||
'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',
|
||||
@ -750,6 +772,8 @@ class CompanySettings extends BaseSettings
|
||||
'portal_custom_js' => 'string',
|
||||
'client_portal_enable_uploads' => 'bool',
|
||||
'purchase_order_number_counter' => 'integer',
|
||||
'email_template_payment_failed' => 'string',
|
||||
'email_subject_payment_failed' => 'string',
|
||||
];
|
||||
|
||||
public static $free_plan_casts = [
|
||||
@ -962,6 +986,7 @@ class CompanySettings extends BaseSettings
|
||||
'$invoice.due_date',
|
||||
'$invoice.total',
|
||||
'$invoice.balance_due',
|
||||
'$invoice.project',
|
||||
],
|
||||
'quote_details' => [
|
||||
'$quote.number',
|
||||
@ -969,6 +994,7 @@ class CompanySettings extends BaseSettings
|
||||
'$quote.date',
|
||||
'$quote.valid_until',
|
||||
'$quote.total',
|
||||
'$quote.project',
|
||||
],
|
||||
'credit_details' => [
|
||||
'$credit.number',
|
||||
|
@ -30,6 +30,7 @@ class EmailTemplateDefaults
|
||||
'email_template_custom2',
|
||||
'email_template_custom3',
|
||||
'email_template_purchase_order',
|
||||
'email_template_payment_failed'
|
||||
];
|
||||
|
||||
public static function getDefaultTemplate($template, $locale)
|
||||
@ -39,6 +40,8 @@ class EmailTemplateDefaults
|
||||
switch ($template) {
|
||||
/* Template */
|
||||
|
||||
case 'email_template_payment_failed':
|
||||
return self::emailPaymentFailedTemplate();
|
||||
case 'email_template_invoice':
|
||||
return self::emailInvoiceTemplate();
|
||||
case 'email_template_quote':
|
||||
@ -73,6 +76,9 @@ class EmailTemplateDefaults
|
||||
case 'email_subject_invoice':
|
||||
return self::emailInvoiceSubject();
|
||||
|
||||
case 'email_subject_payment_failed':
|
||||
return self::emailPaymentFailedSubject();
|
||||
|
||||
case 'email_subject_quote':
|
||||
return self::emailQuoteSubject();
|
||||
|
||||
@ -115,12 +121,40 @@ 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 emailPaymentFailedSubject()
|
||||
{
|
||||
return ctrans('texts.notification_invoice_payment_failed_subject', ['invoice' => '$number']);
|
||||
}
|
||||
|
||||
public static function emailPaymentFailedTemplate()
|
||||
{
|
||||
return '<p>$client<br><br>'.ctrans('texts.client_payment_failure_body', ['invoice' => '$number', 'amount' => '$amount']).'</p><div>$payment_error</div><br><div>$view_button</div>';
|
||||
}
|
||||
|
||||
public static function emailQuoteReminder1Subject()
|
||||
{
|
||||
return ctrans('texts.quote_reminder_subject', ['quote' => '$number', 'company' => '$company.name']);
|
||||
}
|
||||
|
||||
public static function emailQuoteReminder1Body()
|
||||
{
|
||||
|
||||
return '<p>$client<br><br>'.self::transformText('quote_reminder_message').'</p><div>$view_button</div>';
|
||||
|
||||
}
|
||||
|
||||
public static function emailVendorNotificationSubject()
|
||||
{
|
||||
return self::transformText('vendor_notification_subject');
|
||||
@ -143,14 +177,14 @@ class EmailTemplateDefaults
|
||||
|
||||
public static function emailInvoiceTemplate()
|
||||
{
|
||||
$invoice_message = '<p>$client<br><br>'.self::transformText('invoice_message').'</p><div class="center">$view_button</div>';
|
||||
$invoice_message = '<p>$client<br><br>'.self::transformText('invoice_message').'</p><div>$view_button</div>';
|
||||
|
||||
return $invoice_message;
|
||||
}
|
||||
|
||||
public static function emailInvoiceReminderTemplate()
|
||||
{
|
||||
$invoice_message = '<p>$client<br><br>'.self::transformText('reminder_message').'</p><div class="center">$view_button</div>';
|
||||
$invoice_message = '<p>$client<br><br>'.self::transformText('reminder_message').'</p><div>$view_button</div>';
|
||||
|
||||
return $invoice_message;
|
||||
}
|
||||
@ -162,7 +196,7 @@ class EmailTemplateDefaults
|
||||
|
||||
public static function emailQuoteTemplate()
|
||||
{
|
||||
$quote_message = '<p>$client<br><br>'.self::transformText('quote_message').'</p><div class="center">$view_button</div>';
|
||||
$quote_message = '<p>$client<br><br>'.self::transformText('quote_message').'</p><div>$view_button</div>';
|
||||
|
||||
return $quote_message;
|
||||
}
|
||||
@ -179,28 +213,28 @@ class EmailTemplateDefaults
|
||||
|
||||
public static function emailPurchaseOrderTemplate()
|
||||
{
|
||||
$purchase_order_message = '<p>$vendor<br><br>'.self::transformText('purchase_order_message').'</p><div class="center">$view_button</div>';
|
||||
$purchase_order_message = '<p>$vendor<br><br>'.self::transformText('purchase_order_message').'</p><div>$view_button</div>';
|
||||
|
||||
return $purchase_order_message;
|
||||
}
|
||||
|
||||
public static function emailPaymentTemplate()
|
||||
{
|
||||
$payment_message = '<p>$client<br><br>'.self::transformText('payment_message').'<br><br>$invoices</p><div class="center">$view_button</div>';
|
||||
$payment_message = '<p>$client<br><br>'.self::transformText('payment_message').'<br><br>$invoices</p><div>$view_button</div>';
|
||||
|
||||
return $payment_message;
|
||||
}
|
||||
|
||||
public static function emailCreditTemplate()
|
||||
{
|
||||
$credit_message = '<p>$client<br><br>'.self::transformText('credit_message').'</p><div class="center">$view_button</div>';
|
||||
$credit_message = '<p>$client<br><br>'.self::transformText('credit_message').'</p><div>$view_button</div>';
|
||||
|
||||
return $credit_message;
|
||||
}
|
||||
|
||||
public static function emailPaymentPartialTemplate()
|
||||
{
|
||||
$payment_message = '<p>$client<br><br>'.self::transformText('payment_message').'<br><br>$invoices</p><div class="center">$view_button</div>';
|
||||
$payment_message = '<p>$client<br><br>'.self::transformText('payment_message').'<br><br>$invoices</p><div>$view_button</div>';
|
||||
|
||||
return $payment_message;
|
||||
}
|
||||
|
@ -64,9 +64,9 @@ class InvoiceItem
|
||||
public $task_id = '';
|
||||
|
||||
public $expense_id = '';
|
||||
|
||||
|
||||
public $unit_code = 'C62';
|
||||
|
||||
|
||||
public static $casts = [
|
||||
'task_id' => 'string',
|
||||
'expense_id' => 'string',
|
||||
|
63
app/DataMapper/QuickbooksSettings.php
Normal file
63
app/DataMapper/QuickbooksSettings.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?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;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\Castable;
|
||||
use App\Casts\QuickbooksSettingsCast;
|
||||
|
||||
/**
|
||||
* QuickbooksSettings.
|
||||
*/
|
||||
class QuickbooksSettings implements Castable
|
||||
{
|
||||
public string $accessTokenKey;
|
||||
|
||||
public string $refresh_token;
|
||||
|
||||
public string $realmID;
|
||||
|
||||
public int $accessTokenExpiresAt;
|
||||
|
||||
public int $refreshTokenExpiresAt;
|
||||
|
||||
public string $baseURL;
|
||||
/**
|
||||
* entity client,invoice,quote,purchase_order,vendor,payment
|
||||
* sync true/false
|
||||
* update_record true/false
|
||||
* direction push/pull/birectional
|
||||
* */
|
||||
public array $settings = [
|
||||
'client' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
|
||||
'vendor' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
|
||||
'invoice' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
|
||||
'sales' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
|
||||
'quote' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
|
||||
'purchase_order' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
|
||||
'product' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
|
||||
'payment' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
|
||||
'vendor' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
|
||||
'expense' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Get the name of the caster class to use when casting from / to this cast target.
|
||||
*
|
||||
* @param array<string, mixed> $arguments
|
||||
*/
|
||||
public static function castUsing(array $arguments): string
|
||||
{
|
||||
return QuickbooksSettingsCast::class;
|
||||
}
|
||||
|
||||
}
|
@ -15,7 +15,6 @@ use App\DataMapper\InvoiceItem;
|
||||
|
||||
class PayPalBalanceAffecting
|
||||
{
|
||||
|
||||
private array $key_map = [
|
||||
'Date' => 'date',
|
||||
'Time' => 'time',
|
||||
@ -106,16 +105,35 @@ class PayPalBalanceAffecting
|
||||
public $creditTransactionalFee;
|
||||
public $originalInvoiceId;
|
||||
|
||||
public function __construct(private array $import_row){}
|
||||
public function __construct(private array $import_row)
|
||||
{
|
||||
}
|
||||
|
||||
public function run(): self
|
||||
{
|
||||
$this->cleanUp();
|
||||
|
||||
foreach($this->import_row as $key => $value) {
|
||||
|
||||
$prop = $this->key_map[$key] ?? false;
|
||||
|
||||
if($prop)
|
||||
if($prop) {
|
||||
|
||||
echo "Setting {$prop} to {$value}".PHP_EOL;
|
||||
$this->{$prop} = $value;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function cleanUp(): self
|
||||
{
|
||||
|
||||
foreach($this->key_map as $value) {
|
||||
echo "Setting {$value} to null".PHP_EOL;
|
||||
$this->{$value} = null;
|
||||
}
|
||||
|
||||
return $this;
|
||||
@ -137,7 +155,7 @@ class PayPalBalanceAffecting
|
||||
|
||||
public function getInvoice(): array
|
||||
{
|
||||
$item = new InvoiceItem;
|
||||
$item = new InvoiceItem();
|
||||
$item->cost = $this->gross ?? 0;
|
||||
$item->product_key = $this->itemId ?? '';
|
||||
$item->notes = $this->subject ?? $this->itemDetails;
|
||||
@ -145,10 +163,11 @@ class PayPalBalanceAffecting
|
||||
|
||||
return [
|
||||
'number' => trim($this->invoiceNumber ?? $this->transactionId),
|
||||
'date' => str_replace('/','-', $this->date ?? ''),
|
||||
'date' => str_replace('/', '-', $this->date ?? ''),
|
||||
'line_items' => [$item],
|
||||
'name' => $this->name ?? '',
|
||||
'email' => $this->fromEmailAddress ?? '',
|
||||
'transaction_reference' => $this->transactionId ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
@ -156,12 +175,10 @@ class PayPalBalanceAffecting
|
||||
{
|
||||
$name_parts = explode(" ", $this->name ?? '');
|
||||
|
||||
if(count($name_parts) == 2)
|
||||
{
|
||||
if(count($name_parts) == 2) {
|
||||
$contact['first_name'] = $name_parts[0];
|
||||
$contact['last_name'] = $name_parts[1];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$contact['first_name'] = $this->name ?? '';
|
||||
}
|
||||
|
||||
@ -170,7 +187,7 @@ class PayPalBalanceAffecting
|
||||
|
||||
return $contact;
|
||||
}
|
||||
|
||||
|
||||
private function returnAddress(): array
|
||||
{
|
||||
return [
|
||||
@ -185,13 +202,15 @@ class PayPalBalanceAffecting
|
||||
|
||||
private function returnShippingAddress(): array
|
||||
{
|
||||
if(strlen($this->shippingAddress ?? '') <3)
|
||||
if(strlen($this->shippingAddress ?? '') < 3) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$ship_parts = explode(",", $this->shippingAddress);
|
||||
|
||||
if(count($ship_parts) != 7)
|
||||
if(count($ship_parts) != 7) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'shipping_address1' => $ship_parts[2],
|
||||
|
@ -17,6 +17,7 @@ use App\Models\Invoice;
|
||||
use App\Models\Product;
|
||||
use App\DataProviders\USStates;
|
||||
use App\DataMapper\Tax\ZipTax\Response;
|
||||
use App\Models\RecurringInvoice;
|
||||
|
||||
class BaseRule implements RuleInterface
|
||||
{
|
||||
@ -47,6 +48,9 @@ class BaseRule implements RuleInterface
|
||||
'DK', // Denmark
|
||||
'EE', // Estonia
|
||||
'ES', // Spain
|
||||
'ES-CN', // Canary Islands
|
||||
'ES-CE', // Ceuta
|
||||
'ES-ML', // Melilla
|
||||
'FI', // Finland
|
||||
'FR', // France
|
||||
'GR', // Greece
|
||||
@ -77,6 +81,9 @@ class BaseRule implements RuleInterface
|
||||
'DK' => 'EU', // Denmark
|
||||
'EE' => 'EU', // Estonia
|
||||
'ES' => 'EU', // Spain
|
||||
'ES-CN' => 'EU', // Canary Islands
|
||||
'ES-CE' => 'EU', // Ceuta
|
||||
'ES-ML' => 'EU', // Melilla
|
||||
'FI' => 'EU', // Finland
|
||||
'FR' => 'EU', // France
|
||||
'GR' => 'EU', // Greece
|
||||
@ -132,7 +139,7 @@ class BaseRule implements RuleInterface
|
||||
|
||||
public function shouldCalcTax(): bool
|
||||
{
|
||||
return $this->should_calc_tax;
|
||||
return $this->should_calc_tax && $this->checkIfInvoiceLocked();
|
||||
}
|
||||
/**
|
||||
* Initializes the tax rule for the entity.
|
||||
@ -215,11 +222,12 @@ class BaseRule implements RuleInterface
|
||||
|
||||
$this->invoice->tax_data = $tax_data;
|
||||
|
||||
if(\DB::transactionLevel() == 0) {
|
||||
if(\DB::transactionLevel() == 0 && isset($this->invoice->id)) {
|
||||
|
||||
try {
|
||||
$this->invoice->saveQuietly();
|
||||
} catch(\Exception $e) {
|
||||
nlog("Exception:: BaseRule::" . $e->getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
@ -261,7 +269,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';
|
||||
@ -399,4 +407,40 @@ class BaseRule implements RuleInterface
|
||||
return ! in_array($iso_3166_2, array_merge($this->eu_country_codes, array_keys($this->region_codes)));
|
||||
}
|
||||
|
||||
private function checkIfInvoiceLocked(): bool
|
||||
{
|
||||
$lock_invoices = $this->client->getSetting('lock_invoices');
|
||||
|
||||
if($this->invoice instanceof RecurringInvoice) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch ($lock_invoices) {
|
||||
case 'off':
|
||||
return true;
|
||||
case 'when_sent':
|
||||
if ($this->invoice->status_id == Invoice::STATUS_SENT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
case 'when_paid':
|
||||
if ($this->invoice->status_id == Invoice::STATUS_PAID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
//if now is greater than the end of month the invoice was dated - do not modify
|
||||
case 'end_of_month':
|
||||
if(\Carbon\Carbon::parse($this->invoice->date)->setTimezone($this->invoice->company->timezone()->name)->endOfMonth()->lte(now())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -43,6 +43,8 @@ class Rule extends BaseRule implements RuleInterface
|
||||
public float $reduced_tax_rate = 0;
|
||||
|
||||
public string $tax_name1 = 'MwSt.';
|
||||
|
||||
private string $tax_name;
|
||||
/**
|
||||
* Initializes the rules and builds any required data.
|
||||
*
|
||||
@ -50,6 +52,7 @@ class Rule extends BaseRule implements RuleInterface
|
||||
*/
|
||||
public function init(): self
|
||||
{
|
||||
$this->tax_name = $this->tax_name1;
|
||||
$this->calculateRates();
|
||||
|
||||
return $this;
|
||||
@ -91,6 +94,7 @@ class Rule extends BaseRule implements RuleInterface
|
||||
*/
|
||||
public function reverseTax($item): self
|
||||
{
|
||||
$this->tax_name1 = $this->tax_name;
|
||||
$this->tax_rate1 = 0;
|
||||
|
||||
return $this;
|
||||
@ -103,6 +107,8 @@ class Rule extends BaseRule implements RuleInterface
|
||||
*/
|
||||
public function taxReduced($item): self
|
||||
{
|
||||
|
||||
$this->tax_name1 = $this->tax_name;
|
||||
$this->tax_rate1 = $this->reduced_tax_rate;
|
||||
|
||||
return $this;
|
||||
@ -115,6 +121,8 @@ class Rule extends BaseRule implements RuleInterface
|
||||
*/
|
||||
public function zeroRated($item): self
|
||||
{
|
||||
|
||||
$this->tax_name1 = $this->tax_name;
|
||||
$this->tax_rate1 = 0;
|
||||
|
||||
return $this;
|
||||
@ -142,6 +150,7 @@ class Rule extends BaseRule implements RuleInterface
|
||||
public function taxDigital($item): self
|
||||
{
|
||||
|
||||
$this->tax_name1 = $this->tax_name;
|
||||
$this->tax_rate1 = $this->tax_rate;
|
||||
|
||||
return $this;
|
||||
@ -155,6 +164,7 @@ class Rule extends BaseRule implements RuleInterface
|
||||
public function taxService($item): self
|
||||
{
|
||||
|
||||
$this->tax_name1 = $this->tax_name;
|
||||
$this->tax_rate1 = $this->tax_rate;
|
||||
|
||||
return $this;
|
||||
@ -168,6 +178,7 @@ class Rule extends BaseRule implements RuleInterface
|
||||
public function taxShipping($item): self
|
||||
{
|
||||
|
||||
$this->tax_name1 = $this->tax_name;
|
||||
$this->tax_rate1 = $this->tax_rate;
|
||||
|
||||
return $this;
|
||||
@ -181,6 +192,7 @@ class Rule extends BaseRule implements RuleInterface
|
||||
public function taxPhysical($item): self
|
||||
{
|
||||
|
||||
$this->tax_name1 = $this->tax_name;
|
||||
$this->tax_rate1 = $this->tax_rate;
|
||||
|
||||
return $this;
|
||||
@ -207,6 +219,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;
|
||||
}
|
||||
|
||||
@ -221,8 +241,7 @@ class Rule extends BaseRule implements RuleInterface
|
||||
// nlog("tax exempt");
|
||||
$this->tax_rate = 0;
|
||||
$this->reduced_tax_rate = 0;
|
||||
} elseif($this->client_subregion != $this->client->company->tax_data->seller_subregion && in_array($this->client_subregion, $this->eu_country_codes) && $this->client->vat_number && $this->eu_business_tax_exempt) {
|
||||
// elseif($this->client_subregion != $this->client->company->tax_data->seller_subregion && in_array($this->client_subregion, $this->eu_country_codes) && $this->client->has_valid_vat_number && $this->eu_business_tax_exempt)
|
||||
} elseif($this->client_subregion != $this->client->company->tax_data->seller_subregion && in_array($this->client_subregion, $this->eu_country_codes) && $this->client->vat_number && $this->client->has_valid_vat_number && $this->eu_business_tax_exempt) {
|
||||
// nlog("euro zone and tax exempt");
|
||||
$this->tax_rate = 0;
|
||||
$this->reduced_tax_rate = 0;
|
||||
@ -232,8 +251,8 @@ class Rule extends BaseRule implements RuleInterface
|
||||
$this->reduced_tax_rate = 0;
|
||||
} elseif(!in_array($this->client_subregion, $this->eu_country_codes)) {
|
||||
$this->defaultForeign();
|
||||
} elseif(in_array($this->client_subregion, $this->eu_country_codes) && !$this->client->vat_number) { //eu country / no valid vat
|
||||
if(($this->client->company->tax_data->seller_subregion != $this->client_subregion) && $this->client->company->tax_data->regions->EU->has_sales_above_threshold) {
|
||||
} elseif(in_array($this->client_subregion, $this->eu_country_codes) && ((strlen($this->client->vat_number ?? '') == 1) || !$this->client->has_valid_vat_number)) { //eu country / no valid vat
|
||||
if($this->client->company->tax_data->seller_subregion != $this->client_subregion) {
|
||||
// nlog("eu zone with sales above threshold");
|
||||
$this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->tax_rate ?? 0;
|
||||
$this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->reduced_tax_rate ?? 0;
|
||||
|
44
app/DataMapper/Tax/ES_CE/Rule.php
Normal file
44
app/DataMapper/Tax/ES_CE/Rule.php
Normal 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\ES_CE;
|
||||
|
||||
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 = 'IGIC';
|
||||
|
||||
}
|
44
app/DataMapper/Tax/ES_CN/Rule.php
Normal file
44
app/DataMapper/Tax/ES_CN/Rule.php
Normal 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\ES_CN;
|
||||
|
||||
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 = 'IGIC';
|
||||
|
||||
}
|
44
app/DataMapper/Tax/ES_ML/Rule.php
Normal file
44
app/DataMapper/Tax/ES_ML/Rule.php
Normal 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\ES_ML;
|
||||
|
||||
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 = 'IGIC';
|
||||
|
||||
}
|
44
app/DataMapper/Tax/PL/Rule.php
Normal file
44
app/DataMapper/Tax/PL/Rule.php
Normal 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';
|
||||
|
||||
}
|
@ -24,6 +24,7 @@ class TaxData
|
||||
|
||||
public function __construct(public Response $origin)
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
foreach($origin as $key => $value) {
|
||||
$this->{$key} = $value;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ class TaxModel
|
||||
public string $seller_subregion = 'CA';
|
||||
|
||||
/** @var string $version */
|
||||
public string $version = 'alpha';
|
||||
public string $version = 'gamma';
|
||||
|
||||
/** @var object $regions */
|
||||
public object $regions;
|
||||
@ -28,15 +28,63 @@ 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';
|
||||
}
|
||||
|
||||
if($this->version == 'beta') {
|
||||
|
||||
//CEUTA
|
||||
$this->regions->EU->subregions->{'ES-CE'} = new \stdClass();
|
||||
$this->regions->EU->subregions->{'ES-CE'}->tax_rate = 4;
|
||||
$this->regions->EU->subregions->{'ES-CE'}->tax_name = 'IGIC';
|
||||
$this->regions->EU->subregions->{'ES-CE'}->reduced_tax_rate = 4;
|
||||
$this->regions->EU->subregions->{'ES-CE'}->apply_tax = false;
|
||||
|
||||
//MELILLA ML 4
|
||||
$this->regions->EU->subregions->{'ES-ML'} = new \stdClass();
|
||||
$this->regions->EU->subregions->{'ES-ML'}->tax_rate = 4;
|
||||
$this->regions->EU->subregions->{'ES-ML'}->tax_name = 'IGIC';
|
||||
$this->regions->EU->subregions->{'ES-ML'}->reduced_tax_rate = 4;
|
||||
$this->regions->EU->subregions->{'ES-ML'}->apply_tax = false;
|
||||
|
||||
//CANARIAS CN 7/3
|
||||
$this->regions->EU->subregions->{'ES-CN'} = new \stdClass();
|
||||
$this->regions->EU->subregions->{'ES-CN'}->tax_rate = 7;
|
||||
$this->regions->EU->subregions->{'ES-CN'}->tax_name = 'IGIC';
|
||||
$this->regions->EU->subregions->{'ES-CN'}->reduced_tax_rate = 4;
|
||||
$this->regions->EU->subregions->{'ES-CN'}->apply_tax = false;
|
||||
|
||||
$this->version = 'gamma';
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -397,6 +445,25 @@ class TaxModel
|
||||
$this->regions->EU->subregions->ES->reduced_tax_rate = 10;
|
||||
$this->regions->EU->subregions->ES->apply_tax = false;
|
||||
|
||||
$this->regions->EU->subregions->{'ES-CE'} = new \stdClass();
|
||||
$this->regions->EU->subregions->{'ES-CE'}->tax_rate = 4;
|
||||
$this->regions->EU->subregions->{'ES-CE'}->tax_name = 'IGIC';
|
||||
$this->regions->EU->subregions->{'ES-CE'}->reduced_tax_rate = 4;
|
||||
$this->regions->EU->subregions->{'ES-CE'}->apply_tax = false;
|
||||
|
||||
$this->regions->EU->subregions->{'ES-ML'} = new \stdClass();
|
||||
$this->regions->EU->subregions->{'ES-ML'}->tax_rate = 4;
|
||||
$this->regions->EU->subregions->{'ES-ML'}->tax_name = 'IGIC';
|
||||
$this->regions->EU->subregions->{'ES-ML'}->reduced_tax_rate = 4;
|
||||
$this->regions->EU->subregions->{'ES-ML'}->apply_tax = false;
|
||||
|
||||
$this->regions->EU->subregions->{'ES-CN'} = new \stdClass();
|
||||
$this->regions->EU->subregions->{'ES-CN'}->tax_rate = 7;
|
||||
$this->regions->EU->subregions->{'ES-CN'}->tax_name = 'IGIC';
|
||||
$this->regions->EU->subregions->{'ES-CN'}->reduced_tax_rate = 3;
|
||||
$this->regions->EU->subregions->{'ES-CN'}->apply_tax = false;
|
||||
|
||||
|
||||
$this->regions->EU->subregions->FI = new \stdClass();
|
||||
$this->regions->EU->subregions->FI->tax_rate = 24;
|
||||
$this->regions->EU->subregions->FI->tax_name = 'ALV';
|
||||
@ -474,6 +541,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';
|
||||
|
@ -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
|
||||
|
68
app/DataProviders/CAProvinces.php
Normal file
68
app/DataProviders/CAProvinces.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?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\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);
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\DataProviders;
|
||||
|
||||
/**
|
||||
* Class FACT1.
|
||||
*/
|
||||
class FACT1
|
||||
{
|
||||
public function build()
|
||||
{
|
||||
$i = new \InvoiceNinja\EInvoice\Models\FACT1\Invoice();
|
||||
}
|
||||
}
|
@ -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];
|
||||
|
22
app/Enum/HttpVerb.php
Normal file
22
app/Enum/HttpVerb.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?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\Enum;
|
||||
|
||||
enum HttpVerb: string
|
||||
{
|
||||
case POST = 'post';
|
||||
case PUT = 'put';
|
||||
case GET = 'get';
|
||||
case PATCH = 'patch';
|
||||
case DELETE = 'delete';
|
||||
}
|
@ -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}"),
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
35
app/Events/Invoice/InvoiceAutoBillFailed.php
Normal file
35
app/Events/Invoice/InvoiceAutoBillFailed.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?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\Events\Invoice;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class InvoiceAutoBillFailed.
|
||||
*/
|
||||
class InvoiceAutoBillFailed
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param Invoice $invoice
|
||||
* @param Company $company
|
||||
* @param array $event_vars
|
||||
*/
|
||||
public function __construct(public Invoice $invoice, public Company $company, public array $event_vars, public ?string $notes)
|
||||
{
|
||||
}
|
||||
}
|
35
app/Events/Invoice/InvoiceAutoBillSuccess.php
Normal file
35
app/Events/Invoice/InvoiceAutoBillSuccess.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?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\Events\Invoice;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class InvoiceAutoBillSuccess.
|
||||
*/
|
||||
class InvoiceAutoBillSuccess
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param Invoice $invoice
|
||||
* @param Company $company
|
||||
* @param array $event_vars
|
||||
*/
|
||||
public function __construct(public Invoice $invoice, public Company $company, public array $event_vars)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
@ -14,14 +15,16 @@ namespace App\Events\Invoice;
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Utils\Traits\Invoice\Broadcasting\DefaultInvoiceBroadcast;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class InvoiceWasPaid.
|
||||
*/
|
||||
class InvoiceWasPaid
|
||||
class InvoiceWasPaid implements ShouldBroadcast
|
||||
{
|
||||
use SerializesModels;
|
||||
use SerializesModels, DefaultInvoiceBroadcast;
|
||||
|
||||
/**
|
||||
* @var Invoice
|
||||
|
28
app/Events/Quote/QuoteReminderWasEmailed.php
Normal file
28
app/Events/Quote/QuoteReminderWasEmailed.php
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
44
app/Exceptions/DuplicatePaymentException.php
Normal file
44
app/Exceptions/DuplicatePaymentException.php
Normal 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);
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
34
app/Exceptions/PeppolValidationException.php
Normal file
34
app/Exceptions/PeppolValidationException.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?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;
|
||||
|
||||
class PeppolValidationException extends Exception
|
||||
{
|
||||
|
||||
protected string $field = '';
|
||||
|
||||
public function __construct($message, $field, $code = 0, Exception $previous = null)
|
||||
{
|
||||
// Store the custom data
|
||||
$this->field = $field;
|
||||
|
||||
// Ensure that everything is assigned properly by calling the parent constructor
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
public function getInvalidField()
|
||||
{
|
||||
return $this->field;
|
||||
}
|
||||
}
|
@ -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();
|
||||
@ -102,8 +101,11 @@ class ActivityExport extends BaseExport
|
||||
$t = app('translator');
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
$this->date_format = DateFormat::find($this->company->settings->date_format_id)->format;
|
||||
/** @var \App\Models\DateFormat $df */
|
||||
$df = DateFormat::query()->find($this->company->settings->date_format_id);
|
||||
|
||||
$this->date_format = $df->format;
|
||||
|
||||
if (count($this->input['report_keys']) == 0) {
|
||||
$this->input['report_keys'] = array_values($this->entity_keys);
|
||||
}
|
||||
@ -111,7 +113,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 +132,9 @@ class ActivityExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($entity) {
|
||||
|
||||
/** @var \App\Models\Activity $entity */
|
||||
|
||||
$this->buildRow($entity);
|
||||
});
|
||||
|
||||
@ -143,10 +148,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
|
||||
|
@ -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,8 @@ class BaseExport
|
||||
'status' => 'task.status_id',
|
||||
'project' => 'task.project_id',
|
||||
'billable' => 'task.billable',
|
||||
'item_notes' => 'task.item_notes',
|
||||
'time_log' => 'task.time_log',
|
||||
];
|
||||
|
||||
protected array $forced_client_fields = [
|
||||
@ -838,12 +841,12 @@ class BaseExport
|
||||
return '';
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Apply Product Filters
|
||||
*
|
||||
* @param Builder $query
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
*
|
||||
* @return Builder
|
||||
*/
|
||||
public function applyProductFilters(Builder $query): Builder
|
||||
@ -863,13 +866,13 @@ class BaseExport
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add Client Filter
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param mixed $clients
|
||||
*
|
||||
*
|
||||
* @return Builder
|
||||
*/
|
||||
protected function addClientFilter(Builder $query, $clients): Builder
|
||||
@ -886,13 +889,13 @@ class BaseExport
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add Vendor Filter
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string $vendors
|
||||
*
|
||||
*
|
||||
* @return Builder
|
||||
*/
|
||||
protected function addVendorFilter(Builder$query, string $vendors): Builder
|
||||
@ -910,13 +913,13 @@ class BaseExport
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* AddProjectFilter
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string $projects
|
||||
*
|
||||
*
|
||||
* @return Builder
|
||||
*/
|
||||
protected function addProjectFilter(Builder $query, string $projects): Builder
|
||||
@ -934,13 +937,13 @@ class BaseExport
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add Category Filter
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string $expense_categories
|
||||
*
|
||||
*
|
||||
* @return Builder
|
||||
*/
|
||||
protected function addCategoryFilter(Builder $query, string $expense_categories): Builder
|
||||
@ -959,24 +962,25 @@ class BaseExport
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add Payment Status Filters
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string $status
|
||||
*
|
||||
*
|
||||
* @return Builder
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
$query->where(function ($query) use ($status_parameters) {
|
||||
$payment_filters = [];
|
||||
|
||||
@ -1016,26 +1020,31 @@ class BaseExport
|
||||
return $query;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add RecurringInvoice Status Filter
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string $status
|
||||
*
|
||||
* @return Builder
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
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){
|
||||
if (in_array('all', $status_parameters) || count($status_parameters) == 0) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
@ -1058,9 +1067,9 @@ class BaseExport
|
||||
/**
|
||||
* Add QuoteStatus Filter
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string $status
|
||||
*
|
||||
*
|
||||
* @return Builder
|
||||
*/
|
||||
protected function addQuoteStatusFilter(Builder $query, string $status): Builder
|
||||
@ -1124,14 +1133,15 @@ class BaseExport
|
||||
/**
|
||||
* Add PurchaseOrder Status Filter
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string $status
|
||||
*
|
||||
*
|
||||
* @return Builder
|
||||
*/
|
||||
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) {
|
||||
@ -1173,13 +1183,14 @@ class BaseExport
|
||||
/**
|
||||
* Add Invoice Status Filter
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string $status
|
||||
* @return Builder
|
||||
*/
|
||||
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) {
|
||||
@ -1234,20 +1245,21 @@ class BaseExport
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add Date Range
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\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'];
|
||||
}
|
||||
|
||||
@ -1578,7 +1590,7 @@ class BaseExport
|
||||
|
||||
public function queueDocuments(Builder $query)
|
||||
{
|
||||
|
||||
|
||||
if($query->getModel() instanceof Document) {
|
||||
$documents = $query->pluck('id')->toArray();
|
||||
} else {
|
||||
@ -1605,4 +1617,17 @@ class BaseExport
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
@ -127,10 +129,11 @@ class ClientExport extends BaseExport
|
||||
->withTrashed()
|
||||
->where('company_id', $this->company->id);
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false)
|
||||
if(!$this->input['include_deleted'] ?? false) {
|
||||
$query->where('is_deleted', 0);
|
||||
}
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
$query = $this->addDateRange($query, ' clients');
|
||||
|
||||
if($this->input['document_email_attachment'] ?? false) {
|
||||
$this->queueDocuments($query);
|
||||
@ -153,6 +156,8 @@ class ClientExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($client) {
|
||||
|
||||
/** @var \App\Models\Client $client */
|
||||
$this->csv->insertOne($this->buildRow($client));
|
||||
});
|
||||
|
||||
@ -242,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');
|
||||
// }
|
||||
}
|
||||
|
@ -59,11 +59,11 @@ class ContactExport extends BaseExport
|
||||
|
||||
$query = ClientContact::query()
|
||||
->where('company_id', $this->company->id)
|
||||
->whereHas('client', function ($q){
|
||||
->whereHas('client', function ($q) {
|
||||
$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'])) {
|
||||
|
@ -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();
|
||||
@ -102,13 +104,13 @@ class CreditExport extends BaseExport
|
||||
$query = Credit::query()
|
||||
->withTrashed()
|
||||
->with('client')
|
||||
->whereHas('client', function ($q){
|
||||
->whereHas('client', function ($q) {
|
||||
$q->where('is_deleted', false);
|
||||
})
|
||||
->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;
|
||||
|
@ -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));
|
||||
});
|
||||
|
||||
|
@ -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();
|
||||
@ -83,13 +85,13 @@ class ExpenseExport extends BaseExport
|
||||
->with('client')
|
||||
->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, '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));
|
||||
});
|
||||
|
||||
@ -220,17 +224,17 @@ class ExpenseExport extends BaseExport
|
||||
// $entity['expense.client'] = $expense->client ? $expense->client->present()->name() : '';
|
||||
// }
|
||||
|
||||
// if (in_array('expense.invoice_id', $this->input['report_keys'])) {
|
||||
// $entity['expense.invoice_id'] = $expense->invoice ? $expense->invoice->number : '';
|
||||
// }
|
||||
if (in_array('expense.invoice_id', $this->input['report_keys'])) {
|
||||
$entity['expense.invoice_id'] = $expense->invoice ? $expense->invoice->number : '';
|
||||
}
|
||||
|
||||
// if (in_array('expense.category', $this->input['report_keys'])) {
|
||||
// $entity['expense.category'] = $expense->category ? $expense->category->name : '';
|
||||
// }
|
||||
|
||||
// if (in_array('expense.vendor_id', $this->input['report_keys'])) {
|
||||
// $entity['expense.vendor'] = $expense->vendor ? $expense->vendor->name : '';
|
||||
// }
|
||||
if (in_array('expense.vendor_id', $this->input['report_keys'])) {
|
||||
$entity['expense.vendor'] = $expense->vendor ? $expense->vendor->name : '';
|
||||
}
|
||||
|
||||
// if (in_array('expense.payment_type_id', $this->input['report_keys'])) {
|
||||
// $entity['expense.payment_type_id'] = $expense->payment_type ? $expense->payment_type->name : '';
|
||||
@ -259,10 +263,16 @@ 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) {
|
||||
|
@ -57,17 +57,17 @@ class InvoiceExport extends BaseExport
|
||||
$query = Invoice::query()
|
||||
->withTrashed()
|
||||
->with('client')
|
||||
->whereHas('client', function ($q){
|
||||
->whereHas('client', function ($q) {
|
||||
$q->where('is_deleted', false);
|
||||
})
|
||||
->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;
|
||||
|
@ -70,16 +70,16 @@ class InvoiceItemExport extends BaseExport
|
||||
$query = Invoice::query()
|
||||
->withTrashed()
|
||||
->with('client')
|
||||
->whereHas('client', function ($q){
|
||||
->whereHas('client', function ($q) {
|
||||
$q->where('is_deleted', false);
|
||||
})
|
||||
->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);
|
||||
});
|
||||
|
||||
@ -225,10 +229,6 @@ class InvoiceItemExport extends BaseExport
|
||||
// $entity['currency'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code;
|
||||
// }
|
||||
|
||||
// if(array_key_exists('type', $entity)) {
|
||||
// $entity['type'] = $invoice->typeIdString($entity['type']);
|
||||
// }
|
||||
|
||||
// if(array_key_exists('tax_category', $entity)) {
|
||||
// $entity['tax_category'] = $invoice->taxTypeString($entity['tax_category']);
|
||||
// }
|
||||
@ -258,7 +258,11 @@ 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;
|
||||
|
@ -56,13 +56,13 @@ class PaymentExport extends BaseExport
|
||||
|
||||
$query = Payment::query()
|
||||
->withTrashed()
|
||||
->whereHas('client', function ($q){
|
||||
->whereHas('client', function ($q) {
|
||||
$q->where('is_deleted', false);
|
||||
})
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', 0);
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
$query = $this->addDateRange($query, 'payments');
|
||||
|
||||
$clients = &$this->input['client_id'];
|
||||
|
||||
@ -71,7 +71,7 @@ class PaymentExport extends BaseExport
|
||||
}
|
||||
|
||||
$query = $this->addPaymentStatusFilters($query, $this->input['status'] ?? '');
|
||||
|
||||
|
||||
if($this->input['document_email_attachment'] ?? false) {
|
||||
$this->queueDocuments($query);
|
||||
}
|
||||
@ -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));
|
||||
});
|
||||
|
||||
|
@ -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();
|
||||
@ -74,13 +76,12 @@ class ProductExport extends BaseExport
|
||||
$query = Product::query()
|
||||
->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,6 +105,8 @@ class ProductExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($entity) {
|
||||
|
||||
/** @var \App\Models\Product $entity */
|
||||
$this->csv->insertOne($this->buildRow($entity));
|
||||
});
|
||||
|
||||
@ -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;
|
||||
// }
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ class ProductSalesExport extends BaseExport
|
||||
{
|
||||
public string $date_key = 'created_at';
|
||||
|
||||
/** @var Collection<\App\Models\Product> $products*/
|
||||
protected Collection $products;
|
||||
|
||||
public Writer $csv;
|
||||
@ -65,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)
|
||||
{
|
||||
@ -80,20 +81,20 @@ class ProductSalesExport extends BaseExport
|
||||
|
||||
public function filterByProducts($query)
|
||||
{
|
||||
|
||||
|
||||
$product_keys = &$this->input['product_key'];
|
||||
|
||||
if ($product_keys && !empty($this->input['product_key'])) {
|
||||
|
||||
$keys = explode(",", $product_keys);
|
||||
$query->where(function ($q) use ($keys){
|
||||
$query->where(function ($q) use ($keys) {
|
||||
|
||||
foreach($keys as $key) {
|
||||
foreach($keys as $key) {
|
||||
$q->orWhereJsonContains('line_items', ['product_key' => $key]);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
return $query;
|
||||
@ -121,14 +122,14 @@ class ProductSalesExport extends BaseExport
|
||||
//insert the header
|
||||
$query = Invoice::query()
|
||||
->withTrashed()
|
||||
->whereHas('client', function ($q){
|
||||
->whereHas('client', function ($q) {
|
||||
$q->where('is_deleted', false);
|
||||
})
|
||||
->where('company_id', $this->company->id)
|
||||
->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);
|
||||
|
||||
@ -138,30 +139,29 @@ class ProductSalesExport extends BaseExport
|
||||
|
||||
$product_keys = &$this->input['product_key'];
|
||||
|
||||
if($product_keys){
|
||||
if($product_keys) {
|
||||
$product_keys = explode(",", $product_keys);
|
||||
}
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($invoice) use($product_keys) {
|
||||
->each(function ($invoice) use ($product_keys) {
|
||||
foreach ($invoice->line_items as $item) {
|
||||
|
||||
if($product_keys)
|
||||
{
|
||||
if(in_array($item->product_key, $product_keys))
|
||||
$this->csv->insertOne($this->buildRow($invoice, $item));
|
||||
}
|
||||
else {
|
||||
$this->csv->insertOne($this->buildRow($invoice, $item));
|
||||
}
|
||||
|
||||
if($product_keys) {
|
||||
if(in_array($item->product_key, $product_keys)) {
|
||||
$this->csv->insertOne($this->buildRow($invoice, $item));
|
||||
}
|
||||
} else {
|
||||
$this->csv->insertOne($this->buildRow($invoice, $item));
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$grouped = $this->sales->groupBy('product_key')->map(function ($key, $value) use($product_keys){
|
||||
$grouped = $this->sales->groupBy('product_key')->map(function ($key, $value) use ($product_keys) {
|
||||
|
||||
if($product_keys && !in_array($value, $product_keys)){
|
||||
if($product_keys && !in_array($value, $product_keys)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -185,7 +185,8 @@ class ProductSalesExport extends BaseExport
|
||||
|
||||
})->reject(function ($value) {
|
||||
return $value === false;
|
||||
});;
|
||||
});
|
||||
;
|
||||
|
||||
$this->csv->insertOne([]);
|
||||
$this->csv->insertOne([]);
|
||||
@ -327,10 +328,10 @@ class ProductSalesExport extends BaseExport
|
||||
* getProduct
|
||||
*
|
||||
* @param string $product_key
|
||||
* @return Product
|
||||
* @return ?\Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
private function getProduct(string $product_key): ?Product
|
||||
{
|
||||
return $this->products->firstWhere('product_key', $product_key);
|
||||
}
|
||||
// private function getProduct(string $product_key)
|
||||
// {
|
||||
// return $this->products->firstWhere('product_key', $product_key);
|
||||
// }
|
||||
}
|
||||
|
@ -58,22 +58,23 @@ class PurchaseOrderExport extends BaseExport
|
||||
$query = PurchaseOrder::query()
|
||||
->withTrashed()
|
||||
->with('vendor')
|
||||
->whereHas('vendor', function ($q){
|
||||
->whereHas('vendor', function ($q) {
|
||||
$q->where('is_deleted', false);
|
||||
})
|
||||
->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'];
|
||||
|
||||
if($clients)
|
||||
if($clients) {
|
||||
$query = $this->addClientFilter($query, $clients);
|
||||
}
|
||||
|
||||
$query = $this->addPurchaseOrderStatusFilter($query, $this->input['status'] ?? '');
|
||||
|
||||
@ -97,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();
|
||||
@ -118,6 +121,8 @@ class PurchaseOrderExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($purchase_order) {
|
||||
|
||||
/** @var \App\Models\PurchaseOrder $purchase_order */
|
||||
$this->csv->insertOne($this->buildRow($purchase_order));
|
||||
});
|
||||
|
||||
@ -166,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'])) {
|
||||
|
@ -62,16 +62,16 @@ class PurchaseOrderItemExport extends BaseExport
|
||||
|
||||
$query = PurchaseOrder::query()
|
||||
->withTrashed()
|
||||
->whereHas('vendor', function ($q){
|
||||
->whereHas('vendor', function ($q) {
|
||||
$q->where('is_deleted', false);
|
||||
})
|
||||
->with('vendor')->where('company_id', $this->company->id);
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false){
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false) {
|
||||
$query->where('is_deleted', 0);
|
||||
}
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
$query = $this->addDateRange($query, 'purchase_orders');
|
||||
|
||||
$clients = &$this->input['client_id'];
|
||||
|
||||
@ -101,6 +101,8 @@ class PurchaseOrderItemExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($resource) {
|
||||
|
||||
/** @var \App\Models\PurchaseOrder $resource */
|
||||
$this->iterateItems($resource);
|
||||
|
||||
foreach($this->storage_array as $row) {
|
||||
@ -127,6 +129,8 @@ class PurchaseOrderItemExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($purchase_order) {
|
||||
|
||||
/** @var \App\Models\PurchaseOrder $purchase_order */
|
||||
$this->iterateItems($purchase_order);
|
||||
});
|
||||
|
||||
@ -209,10 +213,6 @@ class PurchaseOrderItemExport extends BaseExport
|
||||
// $entity['currency'] = $purchase_order->vendor->currency() ? $purchase_order->vendor->currency()->code : $purchase_order->company->currency()->code;
|
||||
// }
|
||||
|
||||
// if(array_key_exists('type', $entity)) {
|
||||
// $entity['type'] = $purchase_order->typeIdString($entity['type']);
|
||||
// }
|
||||
|
||||
// if(array_key_exists('tax_category', $entity)) {
|
||||
// $entity['tax_category'] = $purchase_order->taxTypeString($entity['tax_category']);
|
||||
// }
|
||||
|
@ -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)
|
||||
{
|
||||
@ -64,16 +64,16 @@ class QuoteExport extends BaseExport
|
||||
$query = Quote::query()
|
||||
->withTrashed()
|
||||
->with('client')
|
||||
->whereHas('client', function ($q){
|
||||
->whereHas('client', function ($q) {
|
||||
$q->where('is_deleted', false);
|
||||
})
|
||||
->where('company_id', $this->company->id);
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false){
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false) {
|
||||
$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));
|
||||
});
|
||||
|
||||
|
@ -65,16 +65,16 @@ class QuoteItemExport extends BaseExport
|
||||
|
||||
$query = Quote::query()
|
||||
->withTrashed()
|
||||
->whereHas('client', function ($q){
|
||||
->whereHas('client', function ($q) {
|
||||
$q->where('is_deleted', false);
|
||||
})
|
||||
->with('client')->where('company_id', $this->company->id);
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false){
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false) {
|
||||
$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);
|
||||
});
|
||||
|
||||
|
@ -56,16 +56,16 @@ class RecurringInvoiceExport extends BaseExport
|
||||
$query = RecurringInvoice::query()
|
||||
->withTrashed()
|
||||
->with('client')
|
||||
->whereHas('client', function ($q){
|
||||
->whereHas('client', function ($q) {
|
||||
$q->where('is_deleted', false);
|
||||
})
|
||||
->where('company_id', $this->company->id);
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false){
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false) {
|
||||
$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();
|
||||
|
@ -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;
|
||||
|
||||
@ -69,22 +69,24 @@ class TaskExport extends BaseExport
|
||||
$query = Task::query()
|
||||
->withTrashed()
|
||||
->where('company_id', $this->company->id);
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false){
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false) {
|
||||
$query->where('is_deleted', 0);
|
||||
}
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
$query = $this->addDateRange($query, 'tasks');
|
||||
|
||||
$clients = &$this->input['client_id'];
|
||||
|
||||
if($clients)
|
||||
if($clients) {
|
||||
$query = $this->addClientFilter($query, $clients);
|
||||
}
|
||||
|
||||
$document_attachments = &$this->input['document_email_attachment'];
|
||||
|
||||
if($document_attachments)
|
||||
if($document_attachments) {
|
||||
$this->queueDocuments($query);
|
||||
}
|
||||
|
||||
return $query;
|
||||
|
||||
@ -104,6 +106,8 @@ class TaskExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($entity) {
|
||||
|
||||
/** @var \App\Models\Task $entity*/
|
||||
$this->buildRow($entity);
|
||||
});
|
||||
|
||||
@ -126,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) {
|
||||
@ -151,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', 'task.time_log'])) {
|
||||
$entity[$key] = '';
|
||||
} else {
|
||||
$entity[$key] = $this->decorator->transform($key, $task);
|
||||
@ -159,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);
|
||||
@ -170,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');
|
||||
}
|
||||
@ -208,6 +207,17 @@ class TaskExport extends BaseExport
|
||||
$seconds = $task->calcDuration();
|
||||
$entity['task.duration'] = $seconds;
|
||||
$entity['task.duration_words'] = $seconds > 86400 ? CarbonInterval::seconds($seconds)->locale($this->company->locale())->cascade()->forHumans() : now()->startOfDay()->addSeconds($seconds)->format('H:i:s');
|
||||
|
||||
$entity['task.time_log'] = (isset($item[1]) && $item[1] != 0) ? $item[1] - $item[0] : ctrans('texts.is_running');
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
@ -220,11 +230,13 @@ class TaskExport extends BaseExport
|
||||
$entity['task.end_time'] = '';
|
||||
$entity['task.duration'] = '';
|
||||
$entity['task.duration_words'] = '';
|
||||
$entity['task.billable'] = '';
|
||||
$entity['task.item_notes'] = '';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add Task Status Filter
|
||||
*
|
||||
@ -234,7 +246,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) {
|
||||
|
@ -63,12 +63,12 @@ class VendorExport extends BaseExport
|
||||
$query = Vendor::query()->with('contacts')
|
||||
->withTrashed()
|
||||
->where('company_id', $this->company->id);
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false){
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false) {
|
||||
$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,6 +109,8 @@ class VendorExport extends BaseExport
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($vendor) {
|
||||
|
||||
/** @var \App\Models\Vendor $vendor */
|
||||
$this->csv->insertOne($this->buildRow($vendor));
|
||||
});
|
||||
|
||||
@ -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');
|
||||
// }
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ class ContactDecorator implements DecoratorInterface
|
||||
$contact = $entity->contacts()->first();
|
||||
} elseif($entity->client) {
|
||||
$contact = $entity->client->primary_contact->first() ?? $entity->client->contacts()->whereNotNull('email')->first();
|
||||
} elseif($entity->vendor) {
|
||||
} elseif($entity->vendor) {
|
||||
$contact = $entity->vendor->primary_contact->first() ?? $entity->vendor->contacts()->whereNotNull('email')->first();
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
|
@ -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();
|
||||
|
@ -23,14 +23,13 @@ class BankIntegrationFactory
|
||||
$bank_integration->company_id = $company_id;
|
||||
|
||||
$bank_integration->provider_name = '';
|
||||
$bank_integration->bank_account_id = '';
|
||||
$bank_integration->bank_account_name = '';
|
||||
$bank_integration->bank_account_number = '';
|
||||
$bank_integration->bank_account_status = '';
|
||||
$bank_integration->bank_account_type = '';
|
||||
$bank_integration->balance = 0;
|
||||
$bank_integration->currency = '';
|
||||
$bank_integration->auto_sync = 1;
|
||||
$bank_integration->auto_sync = true;
|
||||
|
||||
return $bank_integration;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ class ClientFactory
|
||||
$client->balance = 0;
|
||||
$client->paid_to_date = 0;
|
||||
$client->country_id = null;
|
||||
$client->is_deleted = 0;
|
||||
$client->is_deleted = false;
|
||||
$client->client_hash = Str::random(40);
|
||||
$client->settings = ClientSettings::defaults();
|
||||
$client->classification = '';
|
||||
|
@ -33,7 +33,7 @@ class CloneQuoteToProjectFactory
|
||||
$project->custom_value2 = '';
|
||||
$project->custom_value3 = '';
|
||||
$project->custom_value4 = '';
|
||||
$project->is_deleted = 0;
|
||||
$project->is_deleted = false;
|
||||
|
||||
return $project;
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ class CompanyFactory
|
||||
$company->markdown_email_enabled = true;
|
||||
$company->markdown_enabled = false;
|
||||
$company->tax_data = new TaxModel();
|
||||
$company->first_month_of_year = 1;
|
||||
$company->first_month_of_year = '1';
|
||||
$company->smtp_encryption = 'tls';
|
||||
$company->smtp_host = '';
|
||||
$company->smtp_local_domain = '';
|
||||
@ -56,7 +56,7 @@ class CompanyFactory
|
||||
$company->smtp_port = '';
|
||||
$company->smtp_username = '';
|
||||
$company->smtp_verify_peer = true;
|
||||
|
||||
|
||||
return $company;
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ class CompanyGatewayFactory
|
||||
$company_gateway->require_shipping_address = false;
|
||||
$company_gateway->config = encrypt(json_encode(new \stdClass()));
|
||||
$company_gateway->always_show_required_fields = true;
|
||||
|
||||
|
||||
return $company_gateway;
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ use App\Models\Credit;
|
||||
|
||||
class CreditFactory
|
||||
{
|
||||
public static function create(int $company_id, int $user_id, object $settings = null, Client $client = null): Credit
|
||||
public static function create(int $company_id, int $user_id): Credit
|
||||
{
|
||||
$credit = new Credit();
|
||||
$credit->status_id = Credit::STATUS_DRAFT;
|
||||
|
@ -76,6 +76,26 @@ class InvoiceItemFactory
|
||||
$data[] = $item;
|
||||
}
|
||||
|
||||
|
||||
$item = self::create();
|
||||
$item->quantity = $faker->numberBetween(1, 10);
|
||||
$item->cost = $faker->randomFloat(2, 1, 1000);
|
||||
$item->line_total = $item->quantity * $item->cost;
|
||||
$item->is_amount_discount = true;
|
||||
$item->discount = $faker->numberBetween(1, 10);
|
||||
$item->notes = str_replace(['"',"'"], ['',""], $faker->realText(20));
|
||||
$item->product_key = $faker->word();
|
||||
// $item->custom_value1 = $faker->realText(10);
|
||||
// $item->custom_value2 = $faker->realText(10);
|
||||
// $item->custom_value3 = $faker->realText(10);
|
||||
// $item->custom_value4 = $faker->realText(10);
|
||||
$item->tax_name1 = 'GST';
|
||||
$item->tax_rate1 = 10.00;
|
||||
$item->type_id = '2';
|
||||
|
||||
$data[] = $item;
|
||||
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ class PurchaseOrderFactory
|
||||
$purchase_order->exchange_rate = 1;
|
||||
$purchase_order->total_taxes = 0;
|
||||
$purchase_order->uses_inclusive_taxes = false;
|
||||
|
||||
|
||||
return $purchase_order;
|
||||
}
|
||||
}
|
||||
|
@ -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,45 +148,45 @@ class RecurringExpenseToExpenseFactory
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Str::contains($match, '|')) {
|
||||
$parts = explode('|', $match); // [ '[MONTH', 'MONTH+2]' ]
|
||||
// if (Str::contains($match, '|')) {
|
||||
$parts = explode('|', $match); // [ '[MONTH', 'MONTH+2]' ]
|
||||
|
||||
$left = substr($parts[0], 1); // 'MONTH'
|
||||
$right = substr($parts[1], 0, -1); // MONTH+2
|
||||
$left = substr($parts[0], 1); // 'MONTH'
|
||||
$right = substr($parts[1], 0, -1); // MONTH+2
|
||||
|
||||
// If left side is not part of replacements, skip.
|
||||
if (! array_key_exists($left, $replacements['ranges'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$_left = Carbon::createFromDate(now()->year, now()->month)->translatedFormat('F Y');
|
||||
$_right = '';
|
||||
|
||||
// If right side doesn't have any calculations, replace with raw ranges keyword.
|
||||
if (! Str::contains($right, ['-', '+', '/', '*'])) {
|
||||
$_right = Carbon::createFromDate(now()->year, now()->month)->translatedFormat('F Y');
|
||||
}
|
||||
|
||||
// If right side contains one of math operations, calculate.
|
||||
if (Str::contains($right, ['+'])) {
|
||||
$operation = preg_match_all('/(?!^-)[+*\/-](\s?-)?/', $right, $_matches);
|
||||
|
||||
$_operation = array_shift($_matches)[0]; // + -
|
||||
|
||||
$_value = explode($_operation, $right); // [MONTHYEAR, 4]
|
||||
|
||||
$_right = Carbon::createFromDate(now()->year, now()->month)->addMonths($_value[1])->translatedFormat('F Y');
|
||||
}
|
||||
|
||||
$replacement = sprintf('%s to %s', $_left, $_right);
|
||||
|
||||
$value = preg_replace(
|
||||
sprintf('/%s/', preg_quote($match)),
|
||||
$replacement,
|
||||
$value,
|
||||
1
|
||||
);
|
||||
// If left side is not part of replacements, skip.
|
||||
if (! array_key_exists($left, $replacements['ranges'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$_left = Carbon::createFromDate(now()->year, now()->month)->translatedFormat('F Y');
|
||||
$_right = '';
|
||||
|
||||
// If right side doesn't have any calculations, replace with raw ranges keyword.
|
||||
if (! Str::contains($right, ['-', '+', '/', '*'])) {
|
||||
$_right = Carbon::createFromDate(now()->year, now()->month)->translatedFormat('F Y');
|
||||
}
|
||||
|
||||
// If right side contains one of math operations, calculate.
|
||||
if (Str::contains($right, ['+'])) {
|
||||
$operation = preg_match_all('/(?!^-)[+*\/-](\s?-)?/', $right, $_matches);
|
||||
|
||||
$_operation = array_shift($_matches)[0]; // + -
|
||||
|
||||
$_value = explode($_operation, $right); // [MONTHYEAR, 4]
|
||||
|
||||
$_right = Carbon::createFromDate(now()->year, now()->month)->addMonths($_value[1])->translatedFormat('F Y'); //@phpstan-ignore-line
|
||||
}
|
||||
|
||||
$replacement = sprintf('%s to %s', $_left, $_right);
|
||||
|
||||
$value = preg_replace(
|
||||
sprintf('/%s/', preg_quote($match)),
|
||||
$replacement,
|
||||
$value,
|
||||
1
|
||||
);
|
||||
// }
|
||||
}
|
||||
|
||||
// Second case with more common calculations.
|
||||
|
@ -23,7 +23,7 @@ class SubscriptionFactory
|
||||
$billing_subscription->company_id = $company_id;
|
||||
$billing_subscription->user_id = $user_id;
|
||||
$billing_subscription->steps = collect(Purchase::defaultSteps())
|
||||
->map(fn($step) => StepService::mapClassNameToString($step))
|
||||
->map(fn ($step) => StepService::mapClassNameToString($step))
|
||||
->implode(',');
|
||||
|
||||
return $billing_subscription;
|
||||
|
@ -20,7 +20,7 @@ class TaxRateFactory
|
||||
$tax_rate = new TaxRate();
|
||||
|
||||
$tax_rate->name = '';
|
||||
$tax_rate->rate = '';
|
||||
$tax_rate->rate = 0;
|
||||
$tax_rate->company_id = $company_id;
|
||||
$tax_rate->user_id = $user_id;
|
||||
|
||||
|
@ -26,7 +26,7 @@ class VendorFactory
|
||||
$vendor->private_notes = '';
|
||||
$vendor->public_notes = '';
|
||||
$vendor->country_id = 4;
|
||||
$vendor->is_deleted = 0;
|
||||
$vendor->is_deleted = false;
|
||||
$vendor->vendor_hash = Str::random(40);
|
||||
// $vendor->classification = '';
|
||||
|
||||
|
@ -68,7 +68,7 @@ class BankTransactionFilters extends QueryFilters
|
||||
*/
|
||||
public function client_status(string $value = ''): Builder
|
||||
{
|
||||
if (strlen($value) == 0) {
|
||||
if (strlen($value ?? '') == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
@ -108,13 +108,47 @@ class BankTransactionFilters extends QueryFilters
|
||||
}
|
||||
|
||||
if (count($debit_or_withdrawal_array) >= 1) {
|
||||
$query->orWhereIn('base_type', $debit_or_withdrawal_array);
|
||||
$query->whereIn('base_type', $debit_or_withdrawal_array);
|
||||
}
|
||||
});
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
public function active_banks(string $value = ''): Builder
|
||||
{
|
||||
|
||||
if (strlen($value) == 0 || $value != 'true') {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
return $this->builder->whereHas('bank_integration', function ($query){
|
||||
$query->where('is_deleted', 0)->whereNull('deleted_at');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the list based on Bank Accounts.
|
||||
*
|
||||
* @param string $ids Comma Separated List of bank account ids
|
||||
* @return Builder
|
||||
*/
|
||||
public function bank_integration_ids(string $ids = ''): Builder
|
||||
{
|
||||
if(strlen($ids) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
$ids = $this->transformKeys(explode(",", $ids));
|
||||
|
||||
$this->builder->where(function ($query) use ($ids) {
|
||||
$query->whereIn('bank_integration_id', $ids);
|
||||
});
|
||||
|
||||
return $this->builder;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the list based on $sort.
|
||||
*
|
||||
@ -132,11 +166,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') {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
@ -160,8 +160,9 @@ class ClientFilters extends QueryFilters
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
if($sort_col[0] == 'documents')
|
||||
if($sort_col[0] == 'documents') {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
if ($sort_col[0] == 'display_name') {
|
||||
$sort_col[0] = 'name';
|
||||
|
@ -97,7 +97,15 @@ class CreditFilters extends QueryFilters
|
||||
$q->where('first_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('last_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('email', 'like', '%'.$filter.'%');
|
||||
});
|
||||
})
|
||||
->orWhereRaw("
|
||||
JSON_UNQUOTE(JSON_EXTRACT(
|
||||
JSON_ARRAY(
|
||||
JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')),
|
||||
JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].product_key'))
|
||||
), '$[*]')
|
||||
) LIKE ?", ['%'.$filter.'%']);
|
||||
// ->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -58,10 +58,11 @@ class DesignFilters extends QueryFilters
|
||||
|
||||
public function entities(string $entities = ''): Builder
|
||||
{
|
||||
|
||||
if(stripos($entities, 'statement') !== false)
|
||||
|
||||
if(stripos($entities, 'statement') !== false) {
|
||||
$entities = 'client';
|
||||
|
||||
}
|
||||
|
||||
if (strlen($entities) == 0 || str_contains($entities, ',')) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
@ -49,22 +49,22 @@ class DocumentFilters extends QueryFilters
|
||||
*/
|
||||
public function client_id(string $client_id = ''): Builder
|
||||
{
|
||||
|
||||
|
||||
return $this->builder->where(function ($query) use ($client_id) {
|
||||
$query->whereHasMorph('documentable', [
|
||||
\App\Models\Invoice::class,
|
||||
\App\Models\Quote::class,
|
||||
\App\Models\Credit::class,
|
||||
\App\Models\Expense::class,
|
||||
\App\Models\Payment::class,
|
||||
\App\Models\Invoice::class,
|
||||
\App\Models\Quote::class,
|
||||
\App\Models\Credit::class,
|
||||
\App\Models\Expense::class,
|
||||
\App\Models\Payment::class,
|
||||
\App\Models\Task::class,
|
||||
\App\Models\RecurringExpense::class,
|
||||
\App\Models\RecurringInvoice::class,
|
||||
\App\Models\Project::class,
|
||||
], function ($q2) use ($client_id) {
|
||||
$q2->where('client_id', $this->decodePrimaryKey($client_id));
|
||||
})->orWhereHasMorph('documentable', [\App\Models\Client::class], function ($q3) use ($client_id) {
|
||||
$q3->where('id', $this->decodePrimaryKey($client_id));
|
||||
$q2->where('client_id', $this->decodePrimaryKey($client_id));
|
||||
})->orWhereHasMorph('documentable', [\App\Models\Client::class], function ($q3) use ($client_id) {
|
||||
$q3->where('id', $this->decodePrimaryKey($client_id));
|
||||
});
|
||||
});
|
||||
|
||||
@ -74,8 +74,7 @@ class DocumentFilters extends QueryFilters
|
||||
{
|
||||
$types = explode(',', $types);
|
||||
|
||||
foreach ($types as $type)
|
||||
{
|
||||
foreach ($types as $type) {
|
||||
match($type) {
|
||||
'private' => $this->builder->where('is_public', 0),
|
||||
'public' => $this->builder->where('is_public', 1),
|
||||
@ -87,7 +86,7 @@ class DocumentFilters extends QueryFilters
|
||||
}
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the list based on $sort.
|
||||
|
@ -44,6 +44,9 @@ class ExpenseFilters extends QueryFilters
|
||||
})
|
||||
->orWhereHas('vendor', function ($q) use ($filter) {
|
||||
$q->where('name', 'like', '%'.$filter.'%');
|
||||
})
|
||||
->orWhereHas('client', function ($q) use ($filter) {
|
||||
$q->where('name', 'like', '%'.$filter.'%');
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -76,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);
|
||||
@ -96,6 +99,12 @@ class ExpenseFilters extends QueryFilters
|
||||
});
|
||||
}
|
||||
|
||||
if (in_array('uninvoiced', $status_parameters)) {
|
||||
$query->orWhere(function ($query) {
|
||||
$query->whereNull('invoice_id');
|
||||
});
|
||||
}
|
||||
|
||||
if (in_array('paid', $status_parameters)) {
|
||||
$query->orWhere(function ($query) {
|
||||
$query->whereNotNull('payment_date');
|
||||
@ -108,8 +117,8 @@ class ExpenseFilters extends QueryFilters
|
||||
});
|
||||
}
|
||||
|
||||
if(in_array('uncategorized', $status_parameters)){
|
||||
$query->orWhere(function ($query){
|
||||
if(in_array('uncategorized', $status_parameters)) {
|
||||
$query->orWhere(function ($query) {
|
||||
$query->whereNull('category_id');
|
||||
});
|
||||
}
|
||||
@ -155,6 +164,19 @@ class ExpenseFilters extends QueryFilters
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
public function categories(string $categories = ''): Builder
|
||||
{
|
||||
$categories_exploded = explode(",", $categories);
|
||||
|
||||
if(empty($categories) || count(array_filter($categories_exploded)) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
$categories_keys = $this->transformKeys($categories_exploded);
|
||||
|
||||
return $this->builder->whereIn('category_id', $categories_keys);
|
||||
}
|
||||
|
||||
public function number(string $number = ''): Builder
|
||||
{
|
||||
if (strlen($number) == 0) {
|
||||
@ -202,6 +224,11 @@ class ExpenseFilters extends QueryFilters
|
||||
->whereColumn('expense_categories.id', 'expenses.category_id'), $sort_col[1]);
|
||||
}
|
||||
|
||||
if ($sort_col[0] == 'payment_date' && in_array($sort_col[1], ['asc', 'desc'])) {
|
||||
return $this->builder
|
||||
->orderByRaw('ISNULL(payment_date), payment_date '. $sort_col[1]);
|
||||
}
|
||||
|
||||
if($sort_col[0] == 'number') {
|
||||
return $this->builder->orderByRaw("REGEXP_REPLACE(number,'[^0-9]+','')+0 " . $dir);
|
||||
}
|
||||
|
@ -124,7 +124,15 @@ class InvoiceFilters extends QueryFilters
|
||||
$q->where('first_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('last_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('email', 'like', '%'.$filter.'%');
|
||||
});
|
||||
})
|
||||
->orWhereRaw("
|
||||
JSON_UNQUOTE(JSON_EXTRACT(
|
||||
JSON_ARRAY(
|
||||
JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')),
|
||||
JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].product_key'))
|
||||
), '$[*]')
|
||||
) LIKE ?", ['%'.$filter.'%']);
|
||||
// ->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
|
||||
});
|
||||
}
|
||||
|
||||
@ -228,10 +236,9 @@ class InvoiceFilters extends QueryFilters
|
||||
$date = Carbon::createFromTimestamp((int)$date);
|
||||
} else {
|
||||
|
||||
try{
|
||||
try {
|
||||
$date = Carbon::parse($date);
|
||||
}
|
||||
catch(\Exception $e){
|
||||
} catch(\Exception $e) {
|
||||
return $this->builder;
|
||||
}
|
||||
}
|
||||
@ -259,59 +266,6 @@ class InvoiceFilters extends QueryFilters
|
||||
return $this->builder->where('due_date', '>=', $date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by date range
|
||||
*
|
||||
* @param string $date_range
|
||||
* @return Builder
|
||||
*/
|
||||
public function date_range(string $date_range = ''): Builder
|
||||
{
|
||||
$parts = explode(",", $date_range);
|
||||
|
||||
if (count($parts) != 2) {
|
||||
return $this->builder;
|
||||
}
|
||||
try {
|
||||
|
||||
$start_date = Carbon::parse($parts[0]);
|
||||
$end_date = Carbon::parse($parts[1]);
|
||||
|
||||
return $this->builder->whereBetween('date', [$start_date, $end_date]);
|
||||
} catch(\Exception $e) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by due date range
|
||||
*
|
||||
* @param string $date_range
|
||||
* @return Builder
|
||||
*/
|
||||
public function due_date_range(string $date_range = ''): Builder
|
||||
{
|
||||
$parts = explode(",", $date_range);
|
||||
|
||||
if (count($parts) != 2) {
|
||||
return $this->builder;
|
||||
}
|
||||
try {
|
||||
|
||||
$start_date = Carbon::parse($parts[0]);
|
||||
$end_date = Carbon::parse($parts[1]);
|
||||
|
||||
return $this->builder->whereBetween('due_date', [$start_date, $end_date]);
|
||||
} catch(\Exception $e) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sorts the list based on $sort.
|
||||
*
|
||||
@ -322,7 +276,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;
|
||||
}
|
||||
|
||||
@ -339,10 +293,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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -204,7 +204,6 @@ class PaymentFilters extends QueryFilters
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,7 +60,7 @@ class ProjectFilters 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('projects'))) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,14 @@ class PurchaseOrderFilters extends QueryFilters
|
||||
->orWhere('custom_value4', 'like', '%'.$filter.'%')
|
||||
->orWhereHas('vendor', function ($q) use ($filter) {
|
||||
$q->where('name', 'like', '%'.$filter.'%');
|
||||
});
|
||||
})
|
||||
->orWhereRaw("
|
||||
JSON_UNQUOTE(JSON_EXTRACT(
|
||||
JSON_ARRAY(
|
||||
JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')),
|
||||
JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].product_key'))
|
||||
), '$[*]')
|
||||
) LIKE ?", ['%'.$filter.'%']);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -331,4 +331,61 @@ abstract class QueryFilters
|
||||
->orderByRaw("{$this->with_property} = ? DESC", [$value])
|
||||
->company();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter by date range
|
||||
*
|
||||
* @param string $date_range
|
||||
* @return Builder
|
||||
*/
|
||||
public function date_range(string $date_range = ''): Builder
|
||||
{
|
||||
$parts = explode(",", $date_range);
|
||||
|
||||
if (count($parts) != 2 || !in_array('date', \Illuminate\Support\Facades\Schema::getColumnListing($this->builder->getModel()->getTable()))) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
$start_date = Carbon::parse($parts[0]);
|
||||
$end_date = Carbon::parse($parts[1]);
|
||||
|
||||
return $this->builder->whereBetween('date', [$start_date, $end_date]);
|
||||
} catch(\Exception $e) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by due date range
|
||||
*
|
||||
* @param string $date_range
|
||||
* @return Builder
|
||||
*/
|
||||
public function due_date_range(string $date_range = ''): Builder
|
||||
{
|
||||
|
||||
$parts = explode(",", $date_range);
|
||||
|
||||
if (count($parts) != 2 || !in_array('due_date', \Illuminate\Support\Facades\Schema::getColumnListing($this->builder->getModel()->getTable()))) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
$start_date = Carbon::parse($parts[0]);
|
||||
$end_date = Carbon::parse($parts[1]);
|
||||
|
||||
return $this->builder->whereBetween('due_date', [$start_date, $end_date]);
|
||||
} catch(\Exception $e) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -45,7 +45,15 @@ class QuoteFilters extends QueryFilters
|
||||
$q->where('first_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('last_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('email', 'like', '%'.$filter.'%');
|
||||
});
|
||||
})
|
||||
->orWhereRaw("
|
||||
JSON_UNQUOTE(JSON_EXTRACT(
|
||||
JSON_ARRAY(
|
||||
JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')),
|
||||
JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].product_key'))
|
||||
), '$[*]')
|
||||
) LIKE ?", ['%'.$filter.'%']);
|
||||
// ->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
|
||||
});
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user