Merge branch 'v5-develop' into feature-nordigen-payment-provider

This commit is contained in:
paulwer 2023-12-10 08:18:31 +01:00
commit 89a31d86c0
2190 changed files with 1137495 additions and 719814 deletions

View File

@ -21,3 +21,6 @@ COMPOSER_AUTH='{"github-oauth": {"github.com": "${{ secrets.GITHUB_TOKEN }}"}}'
TRAVIS=true
API_SECRET=superdoopersecrethere
PHANTOMJS_PDF_GENERATION=false
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis

View File

@ -4,6 +4,7 @@ APP_KEY=base64:RR++yx2rJ9kdxbdh3+AmbHLDQu+Q76i++co9Y8ybbno=
APP_DEBUG=false
APP_URL=http://localhost
REACT_URL=http://localhost:3001
DB_CONNECTION=mysql
MULTI_DB_ENABLED=false
@ -33,8 +34,8 @@ MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS='user@example.com'
MAIL_FROM_NAME='Self Hosted User'
MAIL_FROM_ADDRESS="user@example.com"
MAIL_FROM_NAME="Self Hosted User"
POSTMARK_API_TOKEN=
REQUIRE_HTTPS=false
@ -67,4 +68,4 @@ MICROSOFT_REDIRECT_URI=
APPLE_CLIENT_ID=
APPLE_CLIENT_SECRET=
APPLE_REDIRECT_URI=
APPLE_REDIRECT_URI=

View File

@ -14,6 +14,11 @@ https://invoiceninja.github.io/docs/self-host-troubleshooting/ -->
- Version: <!-- i.e. v4.5.25 / v5.0.30 -->
- Environment: <!-- Docker/Shared Hosting/ZIP/Other -->
## Interface
- Flutter: []
- React: []
- Both: []
## Checklist
- Can you replicate the issue on our v5 demo site https://demo.invoiceninja.com or https://react.invoicing.co/demo?
- Have you searched existing issues?

View File

@ -2,6 +2,7 @@ on:
push:
branches:
- v5-develop
- v5-stable
pull_request:
branches:
- v5-develop
@ -21,6 +22,7 @@ jobs:
dependency-version: [prefer-stable]
env:
DB_CONNECTION: mysql
DB_DATABASE1: ninja
DB_USERNAME1: root
DB_PASSWORD1: ninja
@ -42,7 +44,7 @@ jobs:
services:
mariadb:
image: mariadb:latest
image: mariadb:10.6
ports:
- 32768:3306
env:
@ -79,9 +81,9 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: mysql, mysqlnd, sqlite3, bcmath, gmp, gd, curl, zip, openssl, mbstring, xml, redis
extensions: mysql, mysqlnd, sqlite3, bcmath, gmp, gd, curl, zip, openssl, mbstring, xml, redis, :psr
- uses: actions/checkout@v1
- uses: actions/checkout@v4
with:
ref: v5-develop
fetch-depth: 1
@ -94,19 +96,13 @@ jobs:
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- uses: actions/cache@v2
- uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-${{ matrix.php }}-composer-
# - name: Cache dependencies actions/cache@v3
# uses: actions/cache@v3
# with:
# path: ~/.composer/cache/files
# key: dependencies-${{ matrix.dependency-version }}-laravel-${{ matrix.laravel }}-php-${{ matrix.php-versions }}-composer-${{ hashFiles('composer.json') }}
- name: Install composer dependencies
run: |
composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }}
@ -117,11 +113,10 @@ jobs:
REDIS_PORT: ${{ job.services.redis.ports['6379'] }}
run: |
php artisan key:generate
php artisan optimize
php artisan cache:clear
php artisan config:cache
php artisan config:clear
php artisan ninja:post-update
php artisan optimize
- name: Migrate Database
run: |
php artisan migrate:fresh --seed --force && php artisan db:seed --force

View File

@ -18,12 +18,12 @@ jobs:
- name: Checkout code
uses: actions/checkout@v1
with:
ref: v5-stable
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 }}
@ -32,9 +32,9 @@ jobs:
- name: Prepare Laravel Application
run: |
cp .env.example .env
php artisan key:generate
php artisan key:generate --force
php artisan optimize
php artisan storage:link
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 {} \;
@ -46,7 +46,13 @@ jobs:
git checkout main
npm i
npm run build
cp -r dist/react/* ../public/react
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/
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
@ -65,8 +71,9 @@ jobs:
- name: Build project
run: |
zip -r ./invoiceninja.zip .* -x "../*"
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/')
@ -74,4 +81,5 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: |
invoiceninja.zip
/home/runner/work/invoiceninja/invoiceninja.tar
/home/runner/work/invoiceninja/invoiceninja.zip

8
.gitignore vendored
View File

@ -5,7 +5,9 @@
/storage/*.key
/storage/debugbar
/storage/*
/tests/bootstrap/
/vendor
/app/Console/Commands/vendor/
/.idea
/.vscode
/.vagrant
@ -17,10 +19,12 @@ yarn-error.log
local_version.txt
.env
.phpunit.result.cache
_ide_helper.php
/resources/assets/bower
/public/logo
.env.dusk.local
.env.cypress
/public/vendors/*
*.log
@ -32,3 +36,7 @@ nbproject
public/test.pdf
public/storage/test.pdf
/Modules
_ide_helper_models.php
_ide_helper.php
/composer.phar
.tx/

19
.php_cs
View File

@ -1,19 +0,0 @@
<?php
$finder = Symfony\Component\Finder\Finder::create()
->notPath('vendor')
->notPath('bootstrap')
->notPath('storage')
->notPath('node_modules')
->in(__DIR__)
->name('*.php')
->notName('*.blade.php');
return PhpCsFixer\Config::create()
->setRules([
'@PSR2' => true,
'array_syntax' => ['syntax' => 'short'],
'ordered_imports' => ['sortAlgorithm' => 'alpha'],
'no_unused_imports' => true,
])
->setFinder($finder);

View File

@ -10,9 +10,7 @@
## [Hosted](https://www.invoiceninja.com) | [Self-Hosted](https://www.invoiceninja.org)
Join us on [Slack](http://slack.invoiceninja.com), [Discourse](https://forum.invoiceninja.com) -
or [StackOverflow](https://stackoverflow.com/tags/invoice-ninja/) if you like,
just make sure to add the `invoice-ninja` tag to your question.
Join us on [Slack](http://slack.invoiceninja.com), [Discord](https://discord.gg/ZwEdtfCwXA), [Support Forum](https://forum.invoiceninja.com)
## Introduction
@ -24,16 +22,16 @@ All Pro and Enterprise features from the hosted app are included in the open cod
We offer a $30 per year white-label license to remove the Invoice Ninja branding from client facing parts of the app.
* [Videos](https://www.youtube.com/@appinvoiceninja)
* [API Documentation](https://app.swaggerhub.com/apis/invoiceninja/invoiceninja)
* [API Documentation](https://api-docs.invoicing.co/)
* [APP Documentation](https://invoiceninja.github.io/)
* [Support Forum](https://forum.invoiceninja.com)
* [StackOverflow](https://stackoverflow.com/tags/invoice-ninja/)
## Setup
### Mobile Apps
* [iPhone](https://apps.apple.com/app/id1503970375?platform=iphone)
* [Android](https://play.google.com/store/apps/details?id=com.invoiceninja.app)
* [F-Droid](https://f-droid.org/en/packages/com.invoiceninja.app)
### Desktop Apps
* [macOS](https://apps.apple.com/app/id1503970375?platform=mac)
@ -52,10 +50,9 @@ We offer a $30 per year white-label license to remove the Invoice Ninja branding
## Quick Hosting Setup
```sh
git clone https://github.com/invoiceninja/invoiceninja.git
git checkout v5-stable
git clone --single-branch --branch v5-stable https://github.com/invoiceninja/invoiceninja.git
cp .env.example .env
composer update
composer i -o --no-dev
php artisan key:generate
```
@ -85,6 +82,66 @@ http://localhost:8000/client/login - For Client Portal
user: user@example.com
pass: password
```
## Developers Guide
### App Design
The API and client portal have been developed using [Laravel](https://laravel.com) if you wish to contribute to this project familiarity with Laravel is essential.
When inspecting functionality of the API, the best place to start would be in the routes/api.php file which describes all of the availabe API endpoints. The controller methods then describe all the entry points into each domain of the application, ie InvoiceController / QuoteController
The average API request follows this path into the application.
* Middleware processes the request initially inspecting the domain being requested + provides the authentication layer.
* The request then passes into a Form Request (Type hinted in the controller methods) which is used to provide authorization and also validation of the request. If successful, the request is then passed into the controller method where it is digested, here is an example:
```php
public function store(StoreInvoiceRequest $request)
{
$invoice = $this->invoice_repo->save($request->all(), InvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id));
$invoice = $invoice->service()
->fillDefaults()
->triggeredActions($request)
->adjustInventory()
->save();
event(new InvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($invoice);
}
```
Here for example we are storing a new invoice, we pass the validated request along with a factory into the invoice repository where it is processed and saved.
The returned invoice then passes through its service class (app/Services/Invoice) where various actions are performed.
A event is then fired which notifies listeners in the application (app/Providers/EventServiceProvider) which perform non blocking sub tasks
Finally the invoice is transformed (app/Transformers/) and returned as a response via Fractal.
### Developer environment
Using the Quick Hosting Setup describe above you can quickly get started building out your development environment. Instead of using
```
composer i -o --no-dev
```
use
```
composer i -o
```
This provides the developer tools including phpunit which allows the test suite to be run.
If you are considering contributing back to the main repository, please add in any tests for new functionality / modifications. This will greatly increase the chances of your PR being accepted
Also, if you plan any additions for the main repository, you may want to discuss this with us first on Slack where we can assist with any technical information and provide advice.
## Credits
* [Hillel Coren](https://hillelcoren.com/)

View File

@ -1 +1 @@
5.5.65
5.7.59

17
_ide_helper_custom.php Normal file
View File

@ -0,0 +1,17 @@
<?php
namespace Illuminate\Contracts\Mail
{
class Mailer
{
public function postmark_config(string $key)
{
return true;
}
public function mailgun_config(string $key)
{
return true;
}
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class EncryptedCast implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes)
{
return is_string($value) && strlen($value) > 1 ? decrypt($value) : null;
}
public function set($model, string $key, $value, array $attributes)
{
return [$key => ! is_null($value) ? encrypt($value) : null];
}
}

View File

@ -13,12 +13,13 @@ namespace App\Console\Commands;
use App\Libraries\MultiDB;
use App\Models\Backup;
use App\Models\Client;
use App\Models\Company;
use App\Models\Design;
use App\Models\Document;
use App\Models\GroupSetting;
use App\Utils\Ninja;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use stdClass;
class BackupUpdate extends Command
{
@ -55,12 +56,15 @@ class BackupUpdate extends Command
{
//always return state to first DB
if(Ninja::isSelfHost()) {
return;
}
$current_db = config('database.default');
if (! config('ninja.db.multi_db_enabled')) {
$this->handleOnDb();
} else {
//multiDB environment, need to
foreach (MultiDB::$dbs as $db) {
MultiDB::setDB($db);
@ -78,50 +82,126 @@ class BackupUpdate extends Command
//logos
Company::cursor()
->each(function ($company){
->each(function ($company) {
$company_logo_path = $company->settings->company_logo;
$company_logo = $company->present()->logo();
if ($company_logo_path == config('ninja.app_logo') || $company_logo_path == '') {
return;
}
if($company_logo == 'https://invoicing.co/images/new_logo.png')
return;
$logo = @file_get_contents($company_logo_path);
$extension = @pathinfo($company->settings->company_logo, PATHINFO_EXTENSION);
if ($logo && $extension) {
$path = "{$company->company_key}/{$company->company_key}.{$extension}";
$logo = @file_get_contents($company_logo);
Storage::disk($this->option('disk'))->put($path, $logo);
if($logo){
$url = Storage::disk($this->option('disk'))->url($path);
$path = str_replace("https://objects.invoicing.co/", "", $company->present()->logo());
$path = str_replace("https://v5-at-backup.us-southeast-1.linodeobjects.com/", "", $path);
Storage::disk($this->option('disk'))->put($path, $logo);
}
nlog("Company - Moving {$company_logo_path} logo to {$this->option('disk')} final URL = {$url}}");
$settings = $company->settings;
$settings->company_logo = $url;
$company->settings = $settings;
;
$company->save();
}
});
Client::withTrashed()
->whereNotNull('settings->company_logo')
->cursor()
->each(function ($client) {
$company_logo_path = $client->settings->company_logo;
$logo = @file_get_contents($company_logo_path);
$extension = @pathinfo($company_logo_path, PATHINFO_EXTENSION);
if ($logo && $extension) {
$path = "{$client->company->company_key}/{$client->client_hash}.{$extension}";
Storage::disk($this->option('disk'))->put($path, $logo);
$url = Storage::disk($this->option('disk'))->url($path);
nlog("Client - Moving {$company_logo_path} logo to {$this->option('disk')} final URL = {$url}}");
$settings = $client->settings;
$settings->company_logo = $url;
$client->settings = $settings;
;
$client->saveQuietly();
}
});
GroupSetting::withTrashed()
->whereNotNull('settings->company_logo')
->orWhere('settings->company_logo', '!=', '')
->cursor()
->each(function ($group) {
$company_logo_path = $group->settings->company_logo;
if (!$company_logo_path) {
return;
}
$logo = @file_get_contents($company_logo_path);
$extension = @pathinfo($company_logo_path, PATHINFO_EXTENSION);
if ($logo && $extension) {
$path = "{$group->company->company_key}/{$group->hashed_id}.{$extension}";
Storage::disk($this->option('disk'))->put($path, $logo);
$url = Storage::disk($this->option('disk'))->url($path);
nlog("Group - Moving {$company_logo_path} logo to {$this->option('disk')} final URL = {$url}}");
$settings = $group->settings;
$settings->company_logo = $url;
$group->settings = $settings;
;
$group->saveQuietly();
}
});
//documents
Document::cursor()
->each(function ($document){
->each(function (Document $document) {
$doc_bin = false;
$doc_bin = $document->getFile();
try {
$doc_bin = $document->getFile();
} catch(\Exception $e) {
nlog($e->getMessage());
}
if($doc_bin)
if ($doc_bin) {
Storage::disk($this->option('disk'))->put($document->url, $doc_bin);
$document->disk = $this->option('disk');
$document->saveQuietly();
nlog("Documents - Moving {$document->url} to {$this->option('disk')}");
}
});
//backups
Backup::cursor()
->each(function ($backup){
//backups
Backup::whereNotNull('filename')
->where('filename', '!=', '')
->cursor()
->each(function ($backup) {
$backup_bin = Storage::disk('s3')->get($backup->filename);
$backup_bin = Storage::disk('s3')->get($backup->filename);
if($backup_bin)
if ($backup_bin) {
Storage::disk($this->option('disk'))->put($backup->filename, $backup_bin);
nlog("Backups - Moving {$backup->filename} to {$this->option('disk')}");
}
});
}
}

File diff suppressed because it is too large Load Diff

View File

@ -11,8 +11,6 @@
namespace App\Console\Commands;
use App;
use App\Factory\ClientContactFactory;
use App\Models\Account;
use App\Models\Activity;
use App\Models\Backup;
@ -24,7 +22,6 @@ use App\Models\CompanyGateway;
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\Design;
@ -53,13 +50,7 @@ use App\Models\User;
use App\Models\Vendor;
use App\Models\VendorContact;
use App\Models\Webhook;
use App\Utils\Ninja;
use DB;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use Mail;
use Symfony\Component\Console\Input\InputOption;
/**
* Class CheckDb.

View File

@ -11,41 +11,20 @@
namespace App\Console\Commands;
use App\DataMapper\ClientRegistrationFields;
use App\DataMapper\CompanySettings;
use App\DataMapper\FeesAndLimits;
use App\Events\Invoice\InvoiceWasCreated;
use App\Factory\InvoiceFactory;
use App\Factory\InvoiceItemFactory;
use App\Helpers\Invoice\InvoiceSum;
use App\Jobs\Company\CreateCompanyPaymentTerms;
use App\Jobs\Company\CreateCompanyTaskStatuses;
use App\Jobs\Util\VersionCheck;
use App\Models\Account;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\CompanyToken;
use App\Models\Country;
use App\Models\Credit;
use App\Models\Expense;
use App\Models\Product;
use App\Models\Project;
use App\Models\Quote;
use App\Models\Task;
use App\Models\User;
use App\Models\Vendor;
use App\Models\VendorContact;
use App\Repositories\InvoiceRepository;
use App\Utils\Ninja;
use App\Utils\Traits\GeneratesCounter;
use App\Utils\Traits\MakesHash;
use Carbon\Carbon;
use Faker\Factory;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
class CreateAccount extends Command
@ -62,11 +41,6 @@ class CreateAccount extends Command
*/
protected $signature = 'ninja:create-account {--email=} {--password=}';
/**
* Create a new command instance.
*
* @param InvoiceRepository $invoice_repo
*/
public function __construct()
{
parent::__construct();
@ -88,14 +62,25 @@ class CreateAccount extends Command
private function createAccount()
{
$settings = CompanySettings::defaults();
$settings->name = "Untitled Company";
$settings->currency_id = '1';
$settings->language_id = '1';
$account = Account::factory()->create();
$company = Company::factory()->create([
'account_id' => $account->id,
'portal_domain' => config('ninja.app_url'),
'portal_mode' => 'domain',
'settings' => $settings,
]);
$company->client_registration_fields = ClientRegistrationFields::generate();
$company->save();
$account->default_company_id = $company->id;
$account->set_react_as_default_ap = true;
$account->save();
$email = $this->option('email') ?? 'admin@example.com';
@ -136,7 +121,6 @@ class CreateAccount extends Command
(new VersionCheck())->handle();
$this->warmCache();
}
private function warmCache()
@ -158,7 +142,6 @@ class CreateAccount extends Command
if ($tableData->count()) {
Cache::forever($name, $tableData);
}
}
}
}

View File

@ -36,11 +36,13 @@ use App\Models\CompanyToken;
use App\Models\Country;
use App\Models\Credit;
use App\Models\Expense;
use App\Models\Invoice;
use App\Models\Product;
use App\Models\Project;
use App\Models\Quote;
use App\Models\RecurringInvoice;
use App\Models\Task;
use App\Models\TaskStatus;
use App\Models\TaxRate;
use App\Models\User;
use App\Models\Vendor;
@ -50,13 +52,11 @@ use App\Utils\Ninja;
use App\Utils\Traits\GeneratesCounter;
use App\Utils\Traits\MakesHash;
use Carbon\Carbon;
use Database\Factories\BankTransactionRuleFactory;
use Faker\Factory;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use stdClass;
class CreateSingleAccount extends Command
@ -73,6 +73,7 @@ class CreateSingleAccount extends Command
protected $gateway;
public $faker;
/**
* Execute the console command.
*
@ -80,9 +81,11 @@ class CreateSingleAccount extends Command
*/
public function handle()
{
$this->faker = Factory::create();
if (Ninja::isHosted() || config('ninja.is_docker') || !$this->confirm('Are you sure you want to inject dummy data?'))
if (Ninja::isHosted() || config('ninja.is_docker') || !$this->confirm('Are you sure you want to inject dummy data?')) {
return;
}
$this->invoice_repo = new InvoiceRepository();
@ -103,8 +106,7 @@ class CreateSingleAccount extends Command
{
$this->info('Creating Small Account and Company');
if($user = User::where('email','small@example.com')->first())
{
if ($user = User::where('email', 'small@example.com')->first()) {
$user->account->delete();
}
@ -117,12 +119,33 @@ class CreateSingleAccount extends Command
'portal_domain' => 'http://ninja.test:8000',
'track_inventory' => true
]);
$faker = \Faker\Factory::create();
$settings = $company->settings;
$settings->invoice_terms = 'Default company invoice terms';
$settings->quote_terms = 'Default company quote terms';
$settings->invoice_footer = 'Default invoice footer';
$settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png';
$settings->name = $faker->name();
$settings->email = $faker->safeEmail();
$settings->phone = $faker->phoneNumber();
$settings->website = $faker->url();
$settings->address1 = $faker->streetName();
$settings->address2 = $faker->streetAddress();
$settings->city = $faker->city();
$settings->state = $faker->state();
$settings->postal_code = $faker->postcode();
$settings->country_id = '840';
$settings->vat_number = 'vat number';
$settings->id_number = 'id number';
$settings->use_credits_payment = 'always';
$settings->timezone_id = '1';
$settings->entity_send_time = 0;
$settings->name = $faker->name();
$company->settings = $settings;
$company->client_registration_fields = ClientRegistrationFields::generate();
$company->save();
@ -201,7 +224,7 @@ class CreateSingleAccount extends Command
$btr = BankTransactionRule::factory()->create([
'user_id' => $user->id,
'company_id' => $company->id,
'applies_to' => (bool)rand(0,1) ? 'CREDIT' : 'DEBIT',
'applies_to' => (bool)rand(0, 1) ? 'CREDIT' : 'DEBIT',
]);
$client = Client::factory()->create([
@ -285,6 +308,62 @@ class CreateSingleAccount extends Command
$this->createGateways($company, $user);
$this->createSubsData($company, $user);
$repo = new \App\Repositories\TaskRepository();
Task::query()->cursor()->each(function ($t) use ($repo) {
$repo->save([], $t);
});
$repo = new \App\Repositories\ExpenseRepository();
Expense::query()->cursor()->each(function ($t) use ($repo) {
$repo->save([], $t);
});
$repo = new \App\Repositories\VendorRepository(new \App\Repositories\VendorContactRepository());
Vendor::query()->cursor()->each(function ($t) use ($repo) {
$repo->save([], $t);
});
$repo = new \App\Repositories\ClientRepository(new \App\Repositories\ClientContactRepository());
Client::query()->cursor()->each(function ($t) use ($repo) {
$repo->save([], $t);
});
$repo = new \App\Repositories\RecurringInvoiceRepository();
RecurringInvoice::query()->cursor()->each(function ($t) use ($repo) {
$repo->save([], $t);
});
$repo = new \App\Repositories\InvoiceRepository();
Invoice::query()->cursor()->each(function ($t) use ($repo) {
$repo->save([], $t);
});
$repo = new \App\Repositories\QuoteRepository();
Quote::query()->cursor()->each(function ($t) use ($repo) {
$repo->save([], $t);
});
$repo = new \App\Repositories\CreditRepository();
Credit::query()->cursor()->each(function ($t) use ($repo) {
$repo->save([], $t);
});
Project::query()->with('client')->whereNotNull('client_id')->cursor()->each(function ($p) {
if($p && $p->client && !isset($p->number)) {
$p->number = $this->getNextProjectNumber($p);
$p->save();
}
});
$this->info("finished");
}
private function createSubsData($company, $user)
@ -359,7 +438,6 @@ class CreateSingleAccount extends Command
private function createClient($company, $user)
{
// dispatch(function () use ($company, $user) {
// });
@ -386,7 +464,7 @@ class CreateSingleAccount extends Command
$settings = $client->settings;
$settings->currency_id = "1";
// $settings->use_credits_payment = "always";
// $settings->use_credits_payment = "always";
$client->settings = $settings;
@ -429,19 +507,61 @@ class CreateSingleAccount extends Command
private function createTask($client)
{
$vendor = Task::factory()->create([
$time_log = $this->createTimeLog(rand(1, 20));
$status = TaskStatus::where('company_id', $client->company_id)->get()->random();
return Task::factory()->create([
'user_id' => $client->user->id,
'company_id' => $client->company->id,
'time_log' => $time_log,
'description' => $this->faker->paragraph,
'status_id' => $status->id ?? null,
'number' => rand(10000, 100000000),
'rate' => rand(1, 150),
'client_id' => $client->id
]);
}
private function createTimeLog(int $count)
{
$time_log = [];
$min = 0;
for ($x = 0; $x < $count; $x++) {
$rando = rand(300, 87000);
$time_log[] = [
Carbon::now()->addSeconds($min)->timestamp,
Carbon::now()->addSeconds($min += $rando)->timestamp,
$this->faker->sentence,
rand(0, 1) === 0 ? false : true
];
$min += 300;
}
return json_encode($time_log);
}
private function createProject($client)
{
$vendor = Project::factory()->create([
$project = Project::factory()->create([
'user_id' => $client->user->id,
'company_id' => $client->company->id,
'client_id' => $client->id,
'due_date' => now()->addSeconds(rand(100000, 1000000))->format('Y-m-d'),
'budgeted_hours' => rand(100, 1000),
'task_rate' => rand(1, 200),
]);
for($x=0; $x < rand(2, 5); $x++) {
$task = $this->createTask($client);
$task->project_id = $project->id;
$task->save();
}
}
private function createInvoice($client)
@ -485,6 +605,7 @@ class CreateSingleAccount extends Command
$invoice->amount = 100; // Braintree sandbox only allows payments under 2,000 to complete successfully.
}
/** @var \App\Models\Invoice $invoice */
$invoice->save();
$invoice->service()->createInvitations()->markSent();
@ -512,6 +633,7 @@ class CreateSingleAccount extends Command
$credit = $invoice_calc->getCredit();
/** @var \App\Models\Credit $credit */
$credit->save();
$credit->service()->markSent()->save();
$credit->service()->createInvitations();
@ -554,6 +676,7 @@ class CreateSingleAccount extends Command
$quote->save();
/** @var \App\Models\Quote $quote */
$quote->service()->markSent()->save();
$quote->service()->createInvitations();
}
@ -630,30 +753,29 @@ class CreateSingleAccount extends Command
$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);
}
// 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')) {
$cg = new CompanyGateway;
$cg->company_id = $company->id;
$cg->user_id = $user->id;
@ -672,8 +794,6 @@ class CreateSingleAccount extends Command
$cg->fees_and_limits = $fees_and_limits;
$cg->save();
}
if (config('ninja.testvars.paypal') && ($this->gateway == 'all' || $this->gateway == 'paypal')) {
@ -697,6 +817,29 @@ class CreateSingleAccount extends Command
$cg->save();
}
if (config('ninja.testvars.paypal_rest') && ($this->gateway == 'all' || $this->gateway == 'paypal_rest')) {
$cg = new CompanyGateway;
$cg->company_id = $company->id;
$cg->user_id = $user->id;
$cg->gateway_key = '80af24a6a691230bbec33e930ab40665';
$cg->require_cvv = true;
$cg->require_billing_address = true;
$cg->require_shipping_address = true;
$cg->update_details = true;
$cg->config = encrypt(config('ninja.testvars.paypal_rest'));
$cg->save();
// $gateway_types = $cg->driver()->gatewayTypes();
$fees_and_limits = new stdClass;
$fees_and_limits->{3} = new FeesAndLimits;
$cg->fees_and_limits = $fees_and_limits;
$cg->save();
}
if (config('ninja.testvars.checkout') && ($this->gateway == 'all' || $this->gateway == 'checkout')) {
$cg = new CompanyGateway;
$cg->company_id = $company->id;
@ -848,11 +991,11 @@ class CreateSingleAccount extends Command
}
}
private function createRecurringInvoice($client)
private function createRecurringInvoice(Client $client)
{
$faker = Factory::create();
$invoice = RecurringInvoiceFactory::create($client->company->id, $client->user->id); //stub the company and user_id
$invoice = RecurringInvoiceFactory::create($client->company_id, $client->user_id); //stub the company and user_id
$invoice->client_id = $client->id;
$dateable = Carbon::now()->subDays(rand(0, 90));
$invoice->date = $dateable;

View File

@ -61,6 +61,8 @@ class CreateTestData extends Command
protected $invoice_repo;
protected $count;
/**
* Execute the console command.
*
@ -380,7 +382,6 @@ class CreateTestData extends Command
private function createClient($company, $user)
{
// dispatch(function () use ($company, $user) {
// });
@ -495,7 +496,7 @@ class CreateTestData extends Command
$invoice = InvoiceFactory::create($client->company->id, $client->user->id); //stub the company and user_id
$invoice->client_id = $client->id;
// $invoice->date = $faker->date();
// $invoice->date = $faker->date();
$dateable = Carbon::now()->subDays(rand(0, 90));
$invoice->date = $dateable;

View File

@ -188,6 +188,7 @@ class DemoMode extends Command
$company_token->account_id = $account->id;
$company_token->name = 'test token';
$company_token->token = 'TOKEN';
$company_token->is_system = true;
$company_token->save();
$u2->companies()->attach($company->id, [
@ -257,7 +258,6 @@ class DemoMode extends Command
private function createClient($company, $user, $assigned_user_id = null)
{
// dispatch(function () use ($company, $user) {
// });

View File

@ -56,12 +56,11 @@ class DesignUpdate extends Command
if (! config('ninja.db.multi_db_enabled')) {
$this->handleOnDb();
} else {
//multiDB environment, need to
foreach (MultiDB::$dbs as $db) {
MultiDB::setDB($db);
$this->handleOnDb($db);
$this->handleOnDb();
}
MultiDB::setDB($current_db);

View File

@ -11,27 +11,19 @@
namespace App\Console\Commands;
use App\DataMapper\CompanySettings;
use App\Exceptions\MigrationValidatorFailed;
use App\Exceptions\NonExistingMigrationFile;
use App\Exceptions\ProcessingMigrationArchiveFailed;
use App\Exceptions\ResourceDependencyMissing;
use App\Exceptions\ResourceNotAvailableForMigration;
use App\Jobs\Util\Import;
use App\Jobs\Util\StartMigration;
use App\Libraries\MultiDB;
use App\Mail\MigrationFailed;
use App\Models\Account;
use App\Models\Company;
use App\Models\CompanyToken;
use App\Models\User;
use App\Utils\Traits\AppSetup;
use App\Utils\Traits\MakesHash;
use DirectoryIterator;
use Faker\Factory;
use Faker\Generator;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use ZipArchive;
class HostedMigrations extends Command
@ -113,7 +105,7 @@ class HostedMigrations extends Command
Import::dispatch($import_file, $user->companies()->first(), $user);
} catch (NonExistingMigrationFile | ProcessingMigrationArchiveFailed | ResourceNotAvailableForMigration | MigrationValidatorFailed | ResourceDependencyMissing $e) {
\Mail::to($this->user)->send(new MigrationFailed($e, $e->getMessage()));
\Mail::to($user)->send(new MigrationFailed($e, $company));
if (app()->environment() !== 'production') {
info($e->getMessage());

View File

@ -11,9 +11,7 @@
namespace App\Console\Commands;
use App\Models\ClientContact;
use App\Models\Company;
use App\Models\User;
use App\Utils\Ninja;
use Illuminate\Console\Command;
@ -46,7 +44,6 @@ class HostedUsers extends Command
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{

View File

@ -18,7 +18,6 @@ use App\Exceptions\ProcessingMigrationArchiveFailed;
use App\Exceptions\ResourceDependencyMissing;
use App\Exceptions\ResourceNotAvailableForMigration;
use App\Jobs\Util\Import;
use App\Jobs\Util\StartMigration;
use App\Mail\MigrationFailed;
use App\Models\Account;
use App\Models\Company;
@ -64,8 +63,6 @@ class ImportMigrations extends Command
*/
public function __construct()
{
$this->faker = Factory::create();
parent::__construct();
}
@ -76,6 +73,8 @@ class ImportMigrations extends Command
*/
public function handle()
{
$this->faker = Factory::create();
$this->buildCache();
$path = $this->option('path') ?? public_path('storage/migrations/import');
@ -105,9 +104,9 @@ class ImportMigrations extends Command
$import_file = public_path("storage/migrations/$filename/migration.json");
Import::dispatch($import_file, $this->getUser()->companies()->first(), $this->getUser());
// StartMigration::dispatch($file->getRealPath(), $this->getUser(), $this->getUser()->companies()->first());
} catch (NonExistingMigrationFile | ProcessingMigrationArchiveFailed | ResourceNotAvailableForMigration | MigrationValidatorFailed | ResourceDependencyMissing $e) {
\Mail::to($this->user)->send(new MigrationFailed($e, $e->getMessage()));
\Mail::to($user)->send(new MigrationFailed($e, $company));
if (app()->environment() !== 'production') {
info($e->getMessage());

View File

@ -11,8 +11,6 @@
namespace App\Console\Commands;
use App\Models\Company;
use App\Models\User;
use App\Utils\CurlUtils;
use Illuminate\Console\Command;
@ -63,12 +61,14 @@ class MobileLocalization extends Command
private function laravelResources()
{
$resources = $this->getResources();
$resources =(array)$this->getResources();
foreach ($resources as $key => $val) {
$transKey = "texts.{$key}";
if (trans($transKey) == $transKey) {
echo "'$key' => '$val',\n";
if(is_iterable($resources)) {
foreach ($resources as $key => $val) {
$transKey = "texts.{$key}";
if (trans($transKey) == $transKey) {
echo "'$key' => '$val',\n";
}
}
}
}

View File

@ -0,0 +1,118 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Console\Commands;
use DirectoryIterator;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
class OpenApiYaml extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ninja:openapi';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Build OpenApi YAML';
private array $directories = [
'/components/schemas',
'/paths/'
];
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$path = base_path('openapi');
$directory = new DirectoryIterator($path);
$this->info($directory);
foreach ($directory as $file) {
$this->info($file);
}
Storage::disk('base')->delete('/openapi/api-docs.yaml');
Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents($path.'/info.yaml'));
Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents($path.'/paths.yaml'));
//iterate paths
$directory = new DirectoryIterator($path . '/paths/');
foreach ($directory as $file) {
if ($file->isFile() && ! $file->isDot()) {
Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents("{$path}/paths/{$file->getFilename()}"));
}
}
Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents($path.'/components.yaml'));
Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents($path.'/components/responses.yaml'));
Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents($path.'/components/examples.yaml'));
$directory = new DirectoryIterator($path . '/components/responses/');
foreach ($directory as $file) {
if ($file->isFile() && ! $file->isDot()) {
Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents("{$path}/components/responses/{$file->getFilename()}"));
}
}
Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents($path.'/components/parameters.yaml'));
$directory = new DirectoryIterator($path . '/components/parameters/');
foreach ($directory as $file) {
if ($file->isFile() && ! $file->isDot()) {
Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents("{$path}/components/parameters/{$file->getFilename()}"));
}
}
Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents($path.'/components/schemas.yaml'));
//iterate schemas
$directory = new DirectoryIterator($path . '/components/schemas/');
foreach ($directory as $file) {
if ($file->isFile() && ! $file->isDot()) {
Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents("{$path}/components/schemas/{$file->getFilename()}"));
}
}
// Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents($path.'/components/schemas/account.yaml'));
Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents($path.'/misc/misc.yaml'));
}
}

View File

@ -12,7 +12,6 @@
namespace App\Console\Commands;
use App\Jobs\Util\VersionCheck;
use App\Utils\Ninja;
use App\Utils\Traits\AppSetup;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
@ -63,12 +62,13 @@ class PostUpdate extends Command
info('finished running composer install ');
try {
Artisan::call('optimize');
// Artisan::call('optimize');
Artisan::call('config:clear');
} catch (\Exception $e) {
info("I wasn't able to optimize.");
info("I wasn't able to clear config.");
}
info('optimized');
info('cleared config');
try {
Artisan::call('view:clear');

View File

@ -11,12 +11,7 @@
namespace App\Console\Commands;
use App\Libraries\MultiDB;
use App\Models\Backup;
use App\Models\Design;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use stdClass;
class ReactBuilder extends Command
{
@ -53,19 +48,26 @@ class ReactBuilder extends Command
{
$includes = '';
$directoryIterator = new \RecursiveDirectoryIterator(public_path('react'), \RecursiveDirectoryIterator::SKIP_DOTS);
$directoryIterator = false;
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 (str_contains($file->getFileName(), '.js') && !strpos($file->getFileName(), '.json')) {
if (str_contains($file->getFileName(), 'index.')) {
$includes .= '<script type="module" crossorigin src="/react/'.$file->getFileName().'"></script>'."\n";
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/'.$file->getFileName().'">'."\n";
$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/'.$file->getFileName().'">'."\n";
$includes .= '<link rel="stylesheet" href="/react/v'.config('ninja.app_version').'/'.$file->getFileName().'">'."\n";
}
}

View File

@ -47,7 +47,6 @@ class S3Cleanup extends Command
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{

View File

@ -14,7 +14,6 @@ namespace App\Console\Commands;
use App\DataMapper\InvoiceItem;
use App\Events\Invoice\InvoiceWasEmailed;
use App\Jobs\Entity\EmailEntity;
use App\Jobs\Ninja\SendReminders;
use App\Jobs\Util\WebhookHandler;
use App\Libraries\MultiDB;
use App\Models\Invoice;
@ -58,7 +57,6 @@ class SendRemindersCron extends Command
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
@ -97,7 +95,6 @@ class SendRemindersCron extends Command
$invoice->save();
}
});
}
private function calcLateFee($invoice, $template) :Invoice
@ -175,15 +172,13 @@ class SendRemindersCron extends Command
/**Refresh Invoice values*/
$invoice->calc()->getInvoice()->save();
$invoice->fresh();
$invoice->service()->deletePdf()->save();
$invoice = $invoice->fresh();
/* Refresh the client here to ensure the balance is fresh */
$client = $invoice->client;
$client = $client->fresh();
nlog('adjusting client balance and invoice balance by '.($invoice->balance - $temp_invoice_balance));
$client->service()->updateBalance($invoice->balance - $temp_invoice_balance)->save();
$client->service()->calculateBalance();
$invoice->ledger()->updateInvoiceBalance($invoice->balance - $temp_invoice_balance, "Late Fee Adjustment for invoice {$invoice->number}");
return $invoice;

View File

@ -11,22 +11,9 @@
namespace App\Console\Commands;
use App\DataMapper\CompanySettings;
use App\DataMapper\DefaultSettings;
use App\Factory\ClientFactory;
use App\Factory\InvoiceFactory;
use App\Factory\InvoiceInvitationFactory;
use App\Jobs\Invoice\CreateEntityPdf;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Mail\Migration\MaxCompanies;
use App\Mail\TemplateEmail;
use App\Models\Account;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Company;
use App\Mail\TestMailServer;
use App\Models\User;
use Faker\Factory;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;
@ -63,39 +50,26 @@ class SendTestEmails extends Command
*/
public function handle()
{
$faker = Factory::create();
$account = Account::factory()->create();
$user = User::factory()->create([
'account_id' => $account->id,
'confirmation_code' => '123',
'email' => $faker->safeEmail(),
'first_name' => 'John',
'last_name' => 'Doe',
]);
$company = Company::factory()->create([
'account_id' => $account->id,
]);
$user->companies()->attach($company->id, [
'account_id' => $account->id,
'is_owner' => 1,
'is_admin' => 1,
'is_locked' => 0,
'permissions' => '',
'notifications' => CompanySettings::notificationDefaults(),
//'settings' => DefaultSettings::userSettings(),
'settings' => null,
]);
$to_user = User::first();
$nmo = new NinjaMailerObject;
$nmo->mailable = new MaxCompanies($user->account->companies()->first());
$nmo->company = $user->account->companies()->first();
$nmo->settings = $user->account->companies()->first()->settings;
$nmo->to_user = $user;
$nmo->mailable = new TestMailServer('Email Server Works!', config('mail.from.address'));
$nmo->company = $to_user->account->companies()->first();
$nmo->settings = $to_user->account->companies()->first()->settings;
$nmo->to_user = $to_user;
(new NinjaMailerJob($nmo))->handle();
try {
Mail::raw("Test Message", function ($message) {
$message->to(config('mail.from.address'))
->from(config('mail.from.address'), config('mail.from.name'))
->subject('Test Email');
});
} catch(\Exception $e) {
$this->info("Error sending email: " . $e->getMessage());
}
}
}

View File

@ -11,14 +11,10 @@
namespace App\Console\Commands;
use App\Libraries\MultiDB;
use App\Models\Backup;
use App\Models\Design;
use Illuminate\Console\Command;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Storage;
use stdClass;
class TranslationsExport extends Command
{
@ -55,10 +51,13 @@ class TranslationsExport extends Command
'fi',
'fr',
'fr_CA',
'fr_CH',
'he',
'hr',
'hu',
'it',
'ja',
'km_KH',
'lt',
'lv_LV',
'mk_MK',
@ -98,42 +97,35 @@ class TranslationsExport extends Command
{
$type =$this->option('type') ?? 'export';
if($type == 'import')
if ($type == 'import') {
$this->import();
}
if($type == 'export')
if ($type == 'export') {
$this->export();
}
}
private function import()
{
//loop and
foreach($this->langs as $lang)
{
foreach ($this->langs as $lang) {
$import_file = "textsphp_{$lang}.php";
$dir = $this->option('path') ?? storage_path('lang_import/');
$path = $dir.$import_file;
if(file_exists($path)){
if (file_exists($path)) {
$this->logMessage($path);
$trans = file_get_contents($path);
file_put_contents(lang_path("/{$lang}/texts.php"), $trans);
}
else{
} else {
$this->logMessage("Could not open file");
$this->logMessage($path);
}
}
}
@ -145,9 +137,8 @@ class TranslationsExport extends Command
Storage::disk('local')->makeDirectory("lang/{$lang}");
$translations = Lang::getLoader()->load($lang, 'texts');
Storage::disk('local')->put("lang/{$lang}/{$lang}.json", json_encode(Arr::dot($translations), JSON_UNESCAPED_UNICODE));
}
}
}
private function logMessage($str)
@ -156,5 +147,4 @@ class TranslationsExport extends Command
$this->info($str);
$this->log .= $str."\n";
}
}

View File

@ -11,17 +11,11 @@
namespace App\Console\Commands;
use App\Http\ValidationRules\ValidClientGroupSettingsRule;
use App\Libraries\MultiDB;
use App\Models\Backup;
use App\Models\Client;
use App\Models\Company;
use App\Models\Design;
use App\Utils\Traits\ClientGroupSettingsSaver;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use stdClass;
class TypeCheck extends Command
{
@ -128,7 +122,7 @@ class TypeCheck extends Command
$client->save();
});
Company::cursor()->each(function ($company) {
Company::query()->cursor()->each(function ($company) {
$this->logMessage("Checking company {$company->id}");
$company->saveSettings($company->settings, $company);
});

View File

@ -15,20 +15,20 @@ use App\Jobs\Cron\AutoBillCron;
use App\Jobs\Cron\RecurringExpensesCron;
use App\Jobs\Cron\RecurringInvoicesCron;
use App\Jobs\Cron\SubscriptionCron;
use App\Jobs\Ledger\LedgerBalanceUpdate;
use App\Jobs\Cron\UpdateCalculatedFields;
use App\Jobs\Invoice\InvoiceCheckLateWebhook;
use App\Jobs\Ninja\AdjustEmailQuota;
use App\Jobs\Ninja\BankTransactionSync;
use App\Jobs\Ninja\CheckACHStatus;
use App\Jobs\Ninja\CompanySizeCheck;
use App\Jobs\Ninja\QueueSize;
use App\Jobs\Ninja\SystemMaintenance;
use App\Jobs\Ninja\TaskScheduler;
use App\Jobs\Invoice\InvoiceCheckLateWebhook;
use App\Jobs\Quote\QuoteCheckExpired;
use App\Jobs\Subscription\CleanStaleInvoiceOrder;
use App\Jobs\Util\DiskCleanup;
use App\Jobs\Util\ReminderJob;
use App\Jobs\Util\SchedulerCheck;
use App\Jobs\Util\SendFailedEmails;
use App\Jobs\Util\UpdateExchangeRates;
use App\Jobs\Util\VersionCheck;
use App\Models\Account;
@ -49,14 +49,23 @@ class Kernel extends ConsoleKernel
/* Check for the latest version of Invoice Ninja */
$schedule->job(new VersionCheck)->daily();
/* Checks and cleans redundant files */
$schedule->job(new DiskCleanup)->dailyAt('02:10')->withoutOverlapping()->name('disk-cleanup-job')->onOneServer();
/* Returns the number of jobs in the queue */
$schedule->job(new QueueSize)->everyFiveMinutes()->withoutOverlapping()->name('queue-size-job')->onOneServer();
/* Send reminders */
$schedule->job(new ReminderJob)->hourly()->withoutOverlapping()->name('reminder-job')->onOneServer();
/* Returns the number of jobs in the queue */
$schedule->job(new QueueSize)->everyFiveMinutes()->withoutOverlapping()->name('queue-size-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();
/* Stale Invoice Cleanup*/
$schedule->job(new UpdateCalculatedFields)->hourlyAt(40)->withoutOverlapping()->name('update-calculated-fields-job')->onOneServer();
/* Checks for large companies and marked them as is_large */
$schedule->job(new CompanySizeCheck)->dailyAt('23:20')->withoutOverlapping()->name('company-size-job')->onOneServer();
@ -67,67 +76,54 @@ class Kernel extends ConsoleKernel
/* Runs cleanup code for subscriptions */
$schedule->job(new SubscriptionCron)->dailyAt('00:01')->withoutOverlapping()->name('subscription-job')->onOneServer();
/* Sends recurring invoices*/
$schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping()->name('recurring-invoice-job')->onOneServer();
/* Stale Invoice Cleanup*/
$schedule->job(new CleanStaleInvoiceOrder)->hourlyAt(30)->withoutOverlapping()->name('stale-invoice-job')->onOneServer();
/* Sends recurring invoices*/
$schedule->job(new RecurringExpensesCron)->dailyAt('00:10')->withoutOverlapping()->name('recurring-expense-job')->onOneServer();
/* Fires notifications for expired Quotes */
$schedule->job(new QuoteCheckExpired)->dailyAt('05:10')->withoutOverlapping()->name('quote-expired-job')->onOneServer();
/* Fires webhooks for overdue Invoice */
$schedule->job(new InvoiceCheckLateWebhook)->dailyAt('07:00')->withoutOverlapping()->name('invoice-overdue-job')->onOneServer();
/* Performs auto billing */
$schedule->job(new AutoBillCron)->dailyAt('06:20')->withoutOverlapping()->name('auto-bill-job')->onOneServer();
/* Checks the status of the scheduler */
$schedule->job(new SchedulerCheck)->dailyAt('01:10')->withoutOverlapping();
/* Checks for scheduled tasks */
$schedule->job(new TaskScheduler())->hourlyAt(10)->withoutOverlapping()->name('task-scheduler-job')->onOneServer();
/* Checks and cleans redundant files */
$schedule->job(new DiskCleanup)->dailyAt('02:10')->withoutOverlapping()->name('disk-cleanup-job')->onOneServer();
/* Performs system maintenance such as pruning the backup table */
$schedule->job(new SystemMaintenance)->sundays()->at('02:30')->withoutOverlapping()->name('system-maintenance-job')->onOneServer();
/* Fires notifications for expired Quotes */
$schedule->job(new QuoteCheckExpired)->dailyAt('05:10')->withoutOverlapping()->name('quote-expired-job')->onOneServer();
/* Performs auto billing */
$schedule->job(new AutoBillCron)->dailyAt('06:20')->withoutOverlapping()->name('auto-bill-job')->onOneServer();
/* Fires webhooks for overdue Invoice */
$schedule->job(new InvoiceCheckLateWebhook)->dailyAt('07:00')->withoutOverlapping()->name('invoice-overdue-job')->onOneServer();
if (Ninja::isSelfHost()) {
$schedule->call(function () {
Account::whereNotNull('id')->update(['is_scheduler_running' => true]);
})->everyFiveMinutes();
}
/* Run hosted specific jobs */
if (Ninja::isHosted()) {
$schedule->job(new AdjustEmailQuota)->dailyAt('23:30')->withoutOverlapping();
/* Pulls in bank transactions from third party services */
$schedule->job(new BankTransactionSync)->dailyAt('04:10')->withoutOverlapping()->name('bank-trans-sync-job')->onOneServer();
$schedule->job(new BankTransactionSync)->everyFourHours()->withoutOverlapping()->name('bank-trans-sync-job')->onOneServer();
//not used @deprecate
// $schedule->job(new SendFailedEmails)->daily()->withoutOverlapping();
/* Checks ACH verification status and updates state to authorize when verified */
$schedule->job(new CheckACHStatus)->everySixHours()->withoutOverlapping()->name('ach-status-job')->onOneServer();
$schedule->command('ninja:check-data --database=db-ninja-01')->dailyAt('02:10')->withoutOverlapping()->name('check-data-db-1-job')->onOneServer();
$schedule->command('ninja:check-data --database=db-ninja-02')->dailyAt('02:20')->withoutOverlapping()->name('check-data-db-2-job')->onOneServer();
$schedule->command('ninja:s3-cleanup')->dailyAt('23:15')->withoutOverlapping()->name('s3-cleanup-job')->onOneServer();
}
if (config('queue.default') == 'database' && Ninja::isSelfHost() && config('ninja.internal_queue_enabled') && ! config('ninja.is_docker')) {
$schedule->command('queue:work database --stop-when-empty --memory=256')->everyMinute()->withoutOverlapping();
$schedule->command('queue:restart')->everyFiveMinutes()->withoutOverlapping();
}
}

View File

@ -37,7 +37,6 @@ class AccountCreated extends GenericCounter
*
* date("Y-m-d H:i:s")
*
* @var DateTime
*/
public $datetime;

View File

@ -37,7 +37,6 @@ class AccountDeleted extends GenericCounter
*
* date("Y-m-d H:i:s")
*
* @var DateTime
*/
public $datetime;

View File

@ -37,7 +37,6 @@ class AccountPlatform extends GenericMixedMetric
*
* date("Y-m-d H:i:s")
*
* @var DateTime
*/
public $datetime;

View File

@ -37,7 +37,6 @@ class AccountSignup extends GenericMixedMetric
*
* date("Y-m-d H:i:s")
*
* @var DateTime
*/
public $datetime;
@ -61,7 +60,7 @@ class AccountSignup extends GenericMixedMetric
* The counter
* set to 1.
*
* @var string
* @var int
*/
public $int_metric1 = 1;

View File

@ -37,7 +37,6 @@ class BankAccountsCreated extends GenericMixedMetric
*
* date("Y-m-d H:i:s")
*
* @var DateTime
*/
public $datetime;

View File

@ -37,7 +37,6 @@ class DbQuery extends GenericMixedMetric
*
* date("Y-m-d H:i:s")
*
* @var DateTime
*/
public $datetime;
@ -57,7 +56,7 @@ class DbQuery extends GenericMixedMetric
* The counter
* set to 1.
*
* @var string
* @var int
*/
public $int_metric1 = 1;

View File

@ -37,7 +37,6 @@ class EmailCount extends GenericMixedMetric
*
* date("Y-m-d H:i:s")
*
* @var DateTime
*/
public $datetime;

View File

@ -37,7 +37,6 @@ class EmailFailure extends GenericMixedMetric
*
* date("Y-m-d H:i:s")
*
* @var DateTime
*/
public $datetime;
@ -61,7 +60,6 @@ class EmailFailure extends GenericMixedMetric
* The counter
* set to 1.
*
* @var string
*/
public $int_metric1 = 1;

View File

@ -37,7 +37,6 @@ class EmailInvoiceFailure extends GenericMixedMetric
*
* date("Y-m-d H:i:s")
*
* @var DateTime
*/
public $datetime;

View File

@ -37,7 +37,6 @@ class EmailSuccess extends GenericMixedMetric
*
* date("Y-m-d H:i:s")
*
* @var DateTime
*/
public $datetime;
@ -61,7 +60,6 @@ class EmailSuccess extends GenericMixedMetric
* The counter
* set to 1.
*
* @var string
*/
public $int_metric1 = 1;
@ -71,8 +69,15 @@ class EmailSuccess extends GenericMixedMetric
*/
public $string_metric7 = '';
public function __construct($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;
}
}

View File

@ -37,7 +37,6 @@ class LivePreview extends GenericCounter
*
* date("Y-m-d H:i:s")
*
* @var DateTime
*/
public $datetime;

View File

@ -37,7 +37,6 @@ class LoginFailure extends GenericCounter
*
* date("Y-m-d H:i:s")
*
* @var DateTime
*/
public $datetime;

View File

@ -37,7 +37,6 @@ class LoginSuccess extends GenericCounter
*
* date("Y-m-d H:i:s")
*
* @var DateTime
*/
public $datetime;

View File

@ -37,7 +37,6 @@ class EmailBounce extends GenericMixedMetric
*
* date("Y-m-d H:i:s")
*
* @var DateTime
*/
public $datetime;
@ -67,7 +66,6 @@ class EmailBounce extends GenericMixedMetric
* The counter
* set to 1.
*
* @var string
*/
public $int_metric1 = 1;

View File

@ -37,7 +37,7 @@ class EmailSpam extends GenericMixedMetric
*
* date("Y-m-d H:i:s")
*
* @var DateTime
* @var \DateTime
*/
public $datetime;

View File

@ -37,7 +37,7 @@ class MigrationFailure extends GenericMixedMetric
*
* date("Y-m-d H:i:s")
*
* @var DateTime
* @var \DateTime
*/
public $datetime;

View File

@ -37,7 +37,7 @@ class QueueSize extends GenericMixedMetric
*
* date("Y-m-d H:i:s")
*
* @var DateTime
* @var \DateTime
*/
public $datetime;

View File

@ -0,0 +1,101 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Analytics;
use Turbo124\Beacon\ExampleMetric\GenericMixedMetric;
class RevenueTrack 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 = 'app.revenue';
/**
* The datetime of the counter measurement.
*
* date("Y-m-d H:i:s")
*
*/
public $datetime;
/**
* The Client email
*
* @var string
*/
public $string_metric5 = 'email';
/**
* The AccountKey email
*
* @var string
*/
public $string_metric6 = 'key';
/**
* Product Type
*
* @var string
*/
public $string_metric7 = 'product';
/**
* Gateway Reference
*
* @var string
*/
public $string_metric8 = 'gateway_reference';
public $string_metric9 = 'entity_reference';
public $string_metric10 = 'gateway_type';
/**
* The counter
* set to 1.
*
* @var int
*/
public $int_metric1 = 1;
/**
* Amount Received
*
* @var double
*/
public $double_metric2 = 0;
public function __construct($string_metric5, $string_metric6, $int_metric1, $double_metric2, $string_metric7, $string_metric8, $string_metric9, $string_metric10)
{
$this->int_metric1 = $int_metric1;
$this->double_metric2 = $double_metric2;
$this->string_metric5 = $string_metric5;
$this->string_metric6 = $string_metric6;
$this->string_metric7 = $string_metric7;
$this->string_metric8 = $string_metric8;
$this->string_metric9 = $string_metric9;
$this->string_metric10 = $string_metric10;
}
}

View File

@ -37,7 +37,7 @@ class SendRecurringFailure extends GenericMixedMetric
*
* date("Y-m-d H:i:s")
*
* @var DateTime
* @var \DateTime
*/
public $datetime;

View File

@ -37,7 +37,7 @@ class TrialFinished extends GenericCounter
*
* date("Y-m-d H:i:s")
*
* @var DateTime
* @var \DateTime
*/
public $datetime;

View File

@ -37,7 +37,7 @@ class TrialStarted extends GenericCounter
*
* date("Y-m-d H:i:s")
*
* @var DateTime
* @var \DateTime
*/
public $datetime;

View File

@ -16,13 +16,13 @@ namespace App\DataMapper;
*/
class BaseSettings
{
//@deprecated
public function __construct($obj)
{
// foreach ($obj as $key => $value) {
// $obj->{$key} = $value;
// }
}
// //@deprecated
// public function __construct($obj)
// {
// // foreach ($obj as $key => $value) {
// // $obj->{$key} = $value;
// // }
// }
public static function setCasts($obj, $casts)
{
@ -57,5 +57,4 @@ class BaseSettings
return $value;
}
}
}

View File

@ -20,82 +20,102 @@ class ClientRegistrationFields
[
'key' => 'first_name',
'required' => true,
'visible' => true,
],
[
'key' => 'last_name',
'required' => true,
'visible' => true,
],
[
'key' => 'email',
'required' => true,
'visible' => true,
],
[
'key' => 'phone',
'required' => false,
'visible' => true,
],
[
'key' => 'password',
'required' => true,
'visible' => true,
],
[
'key' => 'name',
'required' => false,
'visible' => false,
],
[
'key' => 'website',
'required' => false,
'visible' => false,
],
[
'key' => 'address1',
'required' => false,
'visible' => false,
],
[
'key' => 'address2',
'required' => false,
'visible' => false,
],
[
'key' => 'city',
'required' => false,
'visible' => false,
],
[
'key' => 'state',
'required' => false,
'visible' => false,
],
[
'key' => 'postal_code',
'required' => false,
'visible' => false,
],
[
'key' => 'country_id',
'required' => false,
'visible' => false,
],
[
'key' => 'custom_value1',
'required' => false,
'visible' => false,
],
[
'key' => 'custom_value2',
'required' => false,
'visible' => false,
],
[
'key' => 'custom_value3',
'required' => false,
'visible' => false,
],
[
'key' => 'custom_value4',
'required' => false,
'visible' => false,
],
[
'key' => 'public_notes',
'required' => false,
'visible' => false,
],
[
'key' => 'vat_number',
'required' => false,
'visible' => false,
],
[
'key' => 'currency_id',
'required' => false,
'visible' => false,
],
];

View File

@ -47,17 +47,17 @@ class ClientSettings extends BaseSettings
'send_reminders' => 'bool',
];
/**
* Cast object values and return entire class
* prevents missing properties from not being returned
* and always ensure an up to date class is returned.
*
* @param $obj
*/
public function __construct($obj)
{
parent::__construct($obj);
}
// /**
// * Cast object values and return entire class
// * prevents missing properties from not being returned
// * and always ensure an up to date class is returned.
// *
// * @param $obj
// */
// public function __construct($obj)
// {
// // parent::__construct($obj);
// }
/**
* Default Client Settings scaffold.
@ -78,8 +78,8 @@ class ClientSettings extends BaseSettings
/**
* Merges settings from Company to Client.
*
* @param stdClass $company_settings
* @param stdClass $client_settings
* @param $company_settings
* @param $client_settings
* @return stdClass of merged settings
*/
public static function buildClientSettings($company_settings, $client_settings)
@ -88,6 +88,10 @@ class ClientSettings extends BaseSettings
return $company_settings;
}
if (is_array($client_settings)) {
$client_settings = (object)$client_settings;
}
foreach ($company_settings as $key => $value) {
/* pseudo code
if the property exists and is a string BUT has no length, treat it as TRUE

View File

@ -229,7 +229,7 @@ class CompanySettings extends BaseSettings
public $require_quote_signature = false; //@TODO ben to confirm
//email settings
public $email_sending_method = 'default'; //enum 'default','gmail','office365' 'client_postmark', 'client_mailgun'//@implemented
public $email_sending_method = 'default'; //enum 'default','gmail','office365' 'client_postmark', 'client_mailgun' //@implemented
public $gmail_sending_user_id = '0'; //@implemented
@ -383,7 +383,7 @@ class CompanySettings extends BaseSettings
public $page_layout = 'portrait';
public $font_size = 7; //@implemented
public $font_size = 16; //@implemented
public $primary_font = 'Roboto';
@ -442,11 +442,15 @@ class CompanySettings extends BaseSettings
public $send_email_on_mark_paid = false;
public $postmark_secret = '';
public $custom_sending_email = '';
public $mailgun_secret = '';
public $mailgun_domain = '';
public $mailgun_endpoint = 'api.mailgun.net'; //api.eu.mailgun.net
public $auto_bill_standard_invoices = false;
public $email_alignment = 'center'; // center , left, right
@ -459,7 +463,55 @@ class CompanySettings extends BaseSettings
public $show_shipping_address = false;
public $accept_client_input_quote_approval = false;
public $allow_billable_task_items = true;
public $show_task_item_description = false;
public $client_initiated_payments = false;
public $client_initiated_payments_minimum = 0;
public $sync_invoice_quote_columns = true;
public $e_invoice_type = 'EN16931';
public $default_expense_payment_type_id = '0';
public $enable_e_invoice = false;
public $delivery_note_design_id = '';
public $statement_design_id = '';
public $payment_receipt_design_id = '';
public $payment_refund_design_id = '';
public $classification = ''; // individual, business, partnership, trust, charity, government, other
public $payment_email_all_contacts = false;
public static $casts = [
'payment_email_all_contacts' => 'bool',
'statement_design_id' => 'string',
'delivery_note_design_id' => 'string',
'payment_receipt_design_id' => 'string',
'payment_refund_design_id' => 'string',
'classification' => 'string',
'enable_e_invoice' => 'bool',
'classification' => 'string',
'default_expense_payment_type_id' => 'string',
'e_invoice_type' => 'string',
'mailgun_endpoint' => 'string',
'client_initiated_payments' => 'bool',
'client_initiated_payments_minimum' => 'float',
'sync_invoice_quote_columns' => 'bool',
'show_task_item_description' => 'bool',
'allow_billable_task_items' => 'bool',
'accept_client_input_quote_approval' => 'bool',
'custom_sending_email' => 'string',
'show_paid_stamp' => 'bool',
'show_shipping_address' => 'bool',
'company_logo_size' => 'string',
@ -481,7 +533,6 @@ class CompanySettings extends BaseSettings
'purchase_order_design_id' => 'string',
'purchase_order_footer' => 'string',
'purchase_order_number_pattern' => 'string',
'purchase_order_number_counter' => 'int',
'page_numbering_alignment' => 'string',
'page_numbering' => 'bool',
'auto_archive_invoice_cancelled' => 'bool',
@ -513,7 +564,6 @@ class CompanySettings extends BaseSettings
'reminder_send_time' => 'int',
'email_sending_method' => 'string',
'gmail_sending_user_id' => 'string',
'currency_id' => 'string',
'counter_number_applied' => 'string',
'quote_number_applied' => 'string',
'email_subject_custom1' => 'string',
@ -734,20 +784,22 @@ class CompanySettings extends BaseSettings
'quote_design_id',
'credit_design_id',
'purchase_order_design_id',
'statement_design_id',
'delivery_note_design_id',
];
/**
* Cast object values and return entire class
* prevents missing properties from not being returned
* and always ensure an up to date class is returned.
*
* @param $obj
* @deprecated
*/
public function __construct()
{
// parent::__construct($obj);
}
// /**
// * Cast object values and return entire class
// * prevents missing properties from not being returned
// * and always ensure an up to date class is returned.
// *
// * @param $obj
// * @deprecated
// */
// public function __construct()
// {
// // parent::__construct($obj);
// }
/**
* Provides class defaults on init.
@ -756,7 +808,6 @@ class CompanySettings extends BaseSettings
*/
public static function defaults(): stdClass
{
$data = (object) get_class_vars(self::class);
unset($data->casts);
@ -782,14 +833,14 @@ class CompanySettings extends BaseSettings
* need to provide a fallback catch on old settings objects which will
* set new properties to the object prior to being returned.
*
* @param $settings
* @param \stdClass $settings
*
* @return stdClass
*/
public static function setProperties($settings): stdClass
{
$company_settings = (object) get_class_vars(self::class);
foreach ($company_settings as $key => $value) {
if (! property_exists($settings, $key)) {
$settings->{$key} = self::castAttribute($key, $company_settings->{$key});
@ -808,11 +859,30 @@ class CompanySettings extends BaseSettings
{
$notification = new stdClass;
$notification->email = [];
$notification->email = ['invoice_sent_all'];
// $notification->email = ['all_notifications'];
return $notification;
}
/**
* Stubs the notification defaults
*
* @return stdClass
*/
public static function notificationAdminDefaults() :stdClass
{
$notification = new stdClass;
$notification->email = [];
$notification->email = ['invoice_sent_all'];
return $notification;
}
/**
* Defines entity variables for PDF generation
*
@ -828,7 +898,6 @@ class CompanySettings extends BaseSettings
'$client.address1',
'$client.address2',
'$client.city_state_postal',
'$client.postal_city',
'$client.country',
'$client.phone',
'$contact.email',
@ -840,7 +909,6 @@ class CompanySettings extends BaseSettings
'$vendor.address1',
'$vendor.address2',
'$vendor.city_state_postal',
'$vendor.postal_city',
'$vendor.country',
'$vendor.phone',
'$contact.email',
@ -865,7 +933,6 @@ class CompanySettings extends BaseSettings
'$company.address1',
'$company.address2',
'$company.city_state_postal',
'$company.postal_city',
'$company.country',
],
'invoice_details' => [
@ -899,6 +966,15 @@ class CompanySettings extends BaseSettings
'$product.tax',
'$product.line_total',
],
'product_quote_columns' => [
'$product.item',
'$product.description',
'$product.unit_cost',
'$product.quantity',
'$product.discount',
'$product.tax',
'$product.line_total',
],
'task_columns' =>[
'$task.service',
'$task.description',
@ -935,6 +1011,21 @@ class CompanySettings extends BaseSettings
'$method',
'$statement_amount',
],
'statement_credit_columns' => [
'$credit.number',
'$credit.date',
'$total',
'$credit.balance',
],
'statement_details' => [
'$statement_date',
'$balance'
],
'delivery_note_columns' => [
'$product.item',
'$product.description',
'$product.quantity',
],
];
return json_decode(json_encode($variables));

View File

@ -34,13 +34,4 @@ class DefaultSettings extends BaseSettings
];
}
/**
* @return stdClass
*/
private static function userSettingsObject() : stdClass
{
return (object) [
// 'per_page' => self::$per_page,
];
}
}

View File

@ -20,100 +20,100 @@ class EmailTemplateDefaults
App::setLocale($locale);
switch ($template) {
/* Template */
case 'email_template_invoice':
return self::emailInvoiceTemplate();
break;
case 'email_template_quote':
return self::emailQuoteTemplate();
break;
case 'email_template_credit':
return self::emailCreditTemplate();
break;
case 'email_template_payment':
return self::emailPaymentTemplate();
break;
case 'email_template_payment_partial':
return self::emailPaymentPartialTemplate();
break;
case 'email_template_statement':
return self::emailStatementTemplate();
break;
case 'email_template_reminder1':
return self::emailReminder1Template();
break;
case 'email_template_reminder2':
return self::emailReminder2Template();
break;
case 'email_template_reminder3':
return self::emailReminder3Template();
break;
case 'email_template_reminder_endless':
return self::emailReminderEndlessTemplate();
break;
case 'email_template_custom1':
return self::emailInvoiceTemplate();
break;
case 'email_template_custom2':
return self::emailInvoiceTemplate();
break;
case 'email_template_custom3':
return self::emailInvoiceTemplate();
case 'email_template_purchase_order':
return self::emailPurchaseOrderTemplate();
break;
/* Subject */
/* Subject */
case 'email_subject_purchase_order':
return self::emailPurchaseOrderSubject();
case 'email_subject_invoice':
return self::emailInvoiceSubject();
break;
case 'email_subject_quote':
return self::emailQuoteSubject();
break;
case 'email_subject_credit':
return self::emailCreditSubject();
break;
case 'email_subject_payment':
return self::emailPaymentSubject();
break;
case 'email_subject_payment_partial':
return self::emailPaymentPartialSubject();
break;
case 'email_subject_statement':
return self::emailStatementSubject();
break;
case 'email_subject_reminder1':
return self::emailReminder1Subject();
break;
case 'email_subject_reminder2':
return self::emailReminder2Subject();
break;
case 'email_subject_reminder3':
return self::emailReminder3Subject();
break;
case 'email_subject_reminder_endless':
return self::emailReminderEndlessSubject();
break;
case 'email_subject_custom1':
return self::emailInvoiceSubject();
break;
case 'email_subject_custom2':
return self::emailInvoiceSubject();
break;
case 'email_subject_custom3':
return self::emailInvoiceSubject();
break;
case 'email_vendor_notification_subject':
return self::emailVendorNotificationSubject();
case 'email_vendor_notification_body':
return self::emailVendorNotificationBody();
default:
return self::emailInvoiceTemplate();
break;
}
}
public static function emailVendorNotificationSubject()
{
return self::transformText('vendor_notification_subject');
}
public static function emailVendorNotificationBody()
{
return self::transformText('vendor_notification_body');
}
public static function emailInvoiceSubject()
{
return ctrans('texts.invoice_subject', ['number'=>'$number', 'account'=>'$company.name']);
@ -240,7 +240,6 @@ class EmailTemplateDefaults
public static function emailStatementTemplate()
{
$statement_message = '<p>$client<br><br>'.self::transformText('client_statement_body').'<br></p>';
return $statement_message;

View File

@ -44,7 +44,7 @@ class FreeCompanySettings extends BaseSettings
public $date_format_id = '';
// public $enabled_item_tax_rates = 0;
// public $enabled_item_tax_rates = 0;
public $expense_number_pattern = '';
public $expense_number_counter = 1;
@ -141,7 +141,6 @@ class FreeCompanySettings extends BaseSettings
public static $casts = [
'portal_design_id' => 'string',
'currency_id' => 'string',
'task_number_pattern' => 'string',
'task_number_counter' => 'int',
'expense_number_pattern' => 'string',
@ -191,16 +190,16 @@ class FreeCompanySettings extends BaseSettings
'website' => 'string',
];
/**
* Cast object values and return entire class
* prevents missing properties from not being returned
* and always ensure an up to date class is returned.
*
* @param $obj
*/
public function __construct($obj)
{
}
// /**
// * Cast object values and return entire class
// * prevents missing properties from not being returned
// * and always ensure an up to date class is returned.
// *
// * @param $obj
// */
// public function __construct($obj)
// {
// }
/**
* Provides class defaults on init.
@ -223,7 +222,7 @@ class FreeCompanySettings extends BaseSettings
$data->date_format_id = (string) config('ninja.i18n.date_format_id');
$data->country_id = (string) config('ninja.i18n.country_id');
$data->translations = (object) [];
$data->pdf_variables = (object) self::getEntityVariableDefaults();
// $data->pdf_variables = (object) self::getEntityVariableDefaults();
return self::setCasts($data, self::$casts);
}

View File

@ -59,7 +59,16 @@ class InvoiceItem
public $type_id = '1'; //1 = product, 2 = service, 3 unpaid gateway fee, 4 paid gateway fee, 5 late fee, 6 expense
public $tax_id = '';
public $task_id = '';
public $expense_id = '';
public static $casts = [
'task_id' => 'string',
'expense_id' => 'string',
'tax_id' => 'string',
'type_id' => 'string',
'quantity' => 'float',
'cost' => 'float',

View File

@ -0,0 +1,36 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Schedule;
class EmailRecord
{
/**
* Defines the template name
*
* @var string
*/
public string $template = 'email_record';
/**
* Defines the template name
*
* @var string
*/
public string $entity = ''; // invoice, credit, quote, purchase_order
/**
* Defines the template name
*
* @var string
*/
public string $entity_id = '';
}

View File

@ -0,0 +1,111 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Schedule;
class EmailReport
{
/**
* Defines the template name
*
* @var string
*/
public string $template = 'email_report';
/**
* An array of clients hashed_ids
*
* Leave blank if this action should apply to all clients
*
* @var array
*/
public array $clients = [];
/**
* The consts to be used to define the date_range variable of the statement
*/
public const LAST7 = "last7_days";
public const LAST30 = "last30_days";
public const LAST365 = "last365_days";
public const THIS_MONTH = "this_month";
public const LAST_MONTH = "last_month";
public const THIS_QUARTER = "this_quarter";
public const LAST_QUARTER = "last_quarter";
public const THIS_YEAR = "this_year";
public const LAST_YEAR = "last_year";
public const CUSTOM_RANGE = "custom";
/**
* The date range the report should include
*
* @var string
*/
public string $date_range = 'this_month';
/**
* If a custom range is select for the date range then
* the start_date should be supplied in Y-m-d format
*
* @var string
*/
public string $start_date = '';
/**
* If a custom range is select for the date range then
* the end_date should be supplied in Y-m-d format
*
* @var string
*/
public string $end_date = '';
/******************************* Parameters **********************************/
/** @var string $report_name */
public string $report_name = '';
/**
* Optional array of report keys for
* filter the columns of the report
*
* @var array $report_keys
*
* */
public array $report_keys = [];
/** Profit Loss Parameters */
public bool $is_income_billed = true;
public bool $is_expense_billed = true;
public bool $include_tax = true;
/**
* Comma separated string of statuses for filtering the Invoice report
*
* all
* draft
* sent
* paid
* unpaid
* overdue
* viewed
*
* */
public string $status = '';
/**
* Comma separated list of product.product_keys
* to filter the report by
*/
public string $product_key = '';
}

View File

@ -11,24 +11,20 @@
namespace App\DataMapper\Schedule;
use App\Models\Client;
use stdClass;
class ClientStatement
class EmailStatement
{
/**
* Defines the template name
*
*
* @var string
*/
public string $template = 'client_statement';
public string $template = 'email_statement';
/**
* An array of clients hashed_ids
*
* Leave blank if this action should apply to all clients
*
*
* @var array
*/
public array $clients = [];
@ -36,17 +32,22 @@ class ClientStatement
/**
* The consts to be used to define the date_range variable of the statement
*/
public const THIS_MONTH = 'this_month';
public const THIS_QUARTER = 'this_quarter';
public const THIS_YEAR = 'this_year';
public const PREVIOUS_MONTH = 'previous_month';
public const PREVIOUS_QUARTER = 'previous_quarter';
public const PREVIOUS_YEAR = 'previous_year';
public const CUSTOM_RANGE = "custom_range";
public const LAST7 = "last7_days";
public const LAST30 = "last30_days";
public const LAST365 = "last365_days";
public const THIS_MONTH = "this_month";
public const LAST_MONTH = "last_month";
public const THIS_QUARTER = "this_quarter";
public const LAST_QUARTER = "last_quarter";
public const THIS_YEAR = "this_year";
public const LAST_YEAR = "last_year";
public const ALL_TIME = "all_time";
public const CUSTOM_RANGE = "custom";
/**
* The date range the statement should include
*
*
* @var string
*/
public string $date_range = 'this_month';
@ -54,7 +55,7 @@ class ClientStatement
/**
* If a custom range is select for the date range then
* the start_date should be supplied in Y-m-d format
*
*
* @var string
*/
public string $start_date = '';
@ -62,7 +63,7 @@ class ClientStatement
/**
* If a custom range is select for the date range then
* the end_date should be supplied in Y-m-d format
*
*
* @var string
*/
public string $end_date = '';
@ -87,10 +88,8 @@ class ClientStatement
* String const which defines whether
* the invoices to be shown are either
* paid or unpaid
*
*
* @var string
*/
public string $status = 'paid'; // paid | unpaid
}
}

View File

@ -0,0 +1,516 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Settings;
class SettingsData
{
public bool $auto_archive_invoice = false; // @implemented
public string $qr_iban = ''; //@implemented
public string $besr_id = ''; //@implemented
public string $lock_invoices = 'off'; // off, when_sent, when_paid //@implemented
public bool $enable_client_portal_tasks = false; //@ben to implement
public string $show_all_tasks_client_portal = 'invoiced'; // all, uninvoiced, invoiced
public bool $enable_client_portal_password = false; //@implemented
public bool $enable_client_portal = true; //@implemented
public bool $enable_client_portal_dashboard = false; // @TODO There currently is no dashboard, so this is pending
public bool $signature_on_pdf = false; //@implemented
public bool $document_email_attachment = false; //@TODO I assume this is 3rd party attachments on the entity to be included
public string $portal_design_id = '1'; //? @deprecated
public string $timezone_id = ''; //@implemented
public string $date_format_id = ''; //@implemented
public bool $military_time = false; // @TODO Implemented in Tasks only?
public string $language_id = ''; //@implemented
public bool $show_currency_code = false; //@implemented
public string $company_gateway_ids = ''; //@implemented
public string $currency_id = '1'; //@implemented
public string $custom_value1 = ''; //@implemented
public string $custom_value2 = ''; //@implemented
public string $custom_value3 = ''; //@implemented
public string $custom_value4 = ''; //@implemented
public float $default_task_rate = 0; // @TODO Where do we inject this?
public string $payment_terms = ''; //@implemented
public bool $send_reminders = true; //@TODO
public string $custom_message_dashboard = ''; // @TODO There currently is no dashboard, so this is pending
public string $custom_message_unpaid_invoice = '';
public string $custom_message_paid_invoice = '';
public string $custom_message_unapproved_quote = '';
public bool $auto_archive_quote = false; //@implemented
public bool $auto_convert_quote = true; //@implemented
public bool $auto_email_invoice = true; //@only used for Recurring Invoices, if set to false, we never send?
public int $entity_send_time = 6;
public bool $inclusive_taxes = false; //@implemented
public string $quote_footer = ''; //@implemented
public object $translations;
public string $counter_number_applied = 'when_saved'; // when_saved, when_sent //@implemented
public string $quote_number_applied = 'when_saved'; // when_saved, when_sent //@implemented
public string $invoice_number_pattern = ''; //@implemented
public int $invoice_number_counter = 1; //@implemented
public string $recurring_invoice_number_pattern = ''; //@implemented
public int $recurring_invoice_number_counter = 1; //@implemented
public string $quote_number_pattern = ''; //@implemented
public int $quote_number_counter = 1; //@implemented
public string $client_number_pattern = ''; //@implemented
public int $client_number_counter = 1; //@implemented
public string $credit_number_pattern = ''; //@implemented
public int $credit_number_counter = 1; //@implemented
public string $task_number_pattern = ''; //@implemented
public int $task_number_counter = 1; //@implemented
public string $expense_number_pattern = ''; //@implemented
public int $expense_number_counter = 1; //@implemented
public string $recurring_expense_number_pattern = '';
public int $recurring_expense_number_counter = 1;
public string $recurring_quote_number_pattern = '';
public int $recurring_quote_number_counter = 1;
public string $vendor_number_pattern = ''; //@implemented
public int $vendor_number_counter = 1; //@implemented
public string $ticket_number_pattern = ''; //@implemented
public int $ticket_number_counter = 1; //@implemented
public string $payment_number_pattern = ''; //@implemented
public int $payment_number_counter = 1; //@implemented
public string $project_number_pattern = ''; //@implemented
public int $project_number_counter = 1; //@implemented
public string $purchase_order_number_pattern = ''; //@implemented
public int $purchase_order_number_counter = 1; //@implemented
public bool $shared_invoice_quote_counter = false; //@implemented
public bool $shared_invoice_credit_counter = false; //@implemented
public string $recurring_number_prefix = ''; //@implemented
public string $reset_counter_frequency_id = '0'; //@implemented
public string $reset_counter_date = ''; //@implemented
public int $counter_padding = 4; //@implemented
public string $auto_bill = 'off'; // off, always, opt-in, opt-out //@implemented
public string $auto_bill_date = 'on_due_date'; // on_due_date, on_send_date //@implemented
public string $invoice_terms = ''; //@implemented
public string $quote_terms = ''; //@implemented
public int $invoice_taxes = 0; // ? used in AP only?
public string $invoice_design_id = 'Wpmbk5ezJn'; //@implemented
public string $quote_design_id = 'Wpmbk5ezJn'; //@implemented
public string $credit_design_id = 'Wpmbk5ezJn'; //@implemented
public string $purchase_order_design_id = 'Wpmbk5ezJn';
public string $purchase_order_footer = ''; //@implemented
public string $purchase_order_terms = ''; //@implemented
public string $purchase_order_public_notes = ''; //@implemented
public bool $require_purchase_order_signature = false; //@TODO ben to confirm
public string $invoice_footer = ''; //@implemented
public string $credit_footer = ''; //@implemented
public string $credit_terms = ''; //@implemented
public string $invoice_labels = ''; //@TODO used in AP only?
public string $tax_name1 = ''; //@TODO where do we use this?
public float $tax_rate1 = 0; //@TODO where do we use this?
public string $tax_name2 = ''; //@TODO where do we use this?
public float $tax_rate2 = 0; //@TODO where do we use this?
public string $tax_name3 = ''; //@TODO where do we use this?
public float $tax_rate3 = 0; //@TODO where do we use this?
public string $payment_type_id = '0'; //@TODO where do we use this?
public string $valid_until = ''; //@implemented
public bool $show_accept_invoice_terms = false; //@TODO ben to confirm
public bool $show_accept_quote_terms = false; //@TODO ben to confirm
public string $email_sending_method = 'default'; // enum 'default', 'gmail', 'office365', 'client_postmark', 'client_mailgun' //@implemented
public string $gmail_sending_user_id = '0'; //@implemented
public string $reply_to_email = ''; //@implemented
public string $reply_to_name = ''; //@implemented
public string $bcc_email = ''; //@TODO
public bool $pdf_email_attachment = false; //@implemented
public bool $ubl_email_attachment = false; //@implemented
public string $email_style = 'light'; // plain, light, dark, custom //@implemented
public string $email_style_custom = ''; // the template itself //@implemented
public string $email_subject_invoice = ''; //@implemented
public string $email_subject_quote = ''; //@implemented
public string $email_subject_credit = ''; //@implemented
public string $email_subject_payment = ''; //@implemented
public string $email_subject_payment_partial = ''; //@implemented
public string $email_subject_statement = ''; //@implemented
public string $email_subject_purchase_order = ''; //@implemented
public string $email_template_purchase_order = ''; //@implemented
public string $email_template_invoice = ''; //@implemented
public string $email_template_credit = ''; //@implemented
public string $email_template_quote = ''; //@implemented
public string $email_template_payment = ''; //@implemented
public string $email_template_payment_partial = ''; //@implemented
public string $email_template_statement = ''; //@implemented
public string $email_subject_reminder1 = ''; //@implemented
public string $email_subject_reminder2 = ''; //@implemented
public string $email_subject_reminder3 = ''; //@implemented
public string $email_subject_reminder_endless = ''; //@implemented
public string $email_template_reminder1 = ''; //@implemented
public string $email_template_reminder2 = ''; //@implemented
public string $email_template_reminder3 = ''; //@implemented
public string $email_template_reminder_endless = ''; //@implemented
public string $email_signature = ''; //@implemented
public bool $enable_email_markup = true; //@TODO -
public string $email_subject_custom1 = ''; //@TODO
public string $email_subject_custom2 = ''; //@TODO
public string $email_subject_custom3 = ''; //@TODO
public string $email_template_custom1 = ''; //@TODO
public string $email_template_custom2 = ''; //@TODO
public string $email_template_custom3 = ''; //@TODO
public bool $enable_reminder1 = false; //@implmemented
public bool $enable_reminder2 = false; //@implmemented
public bool $enable_reminder3 = false; //@implmemented
public bool $enable_reminder_endless = false; //@implmemented
public int $num_days_reminder1 = 0; //@implmemented
public int $num_days_reminder2 = 0; //@implmemented
public int $num_days_reminder3 = 0; //@implmemented
public string $schedule_reminder1 = ''; // (enum: after_invoice_date, before_due_date, after_due_date) implmemented
public string $schedule_reminder2 = ''; // (enum: after_invoice_date, before_due_date, after_due_date) implmemented
public string $schedule_reminder3 = ''; // (enum: after_invoice_date, before_due_date, after_due_date) implmemented
public int $reminder_send_time = 0; // number of seconds from UTC +0 to send reminders @TODO
public float $late_fee_amount1 = 0; //@implemented
public float $late_fee_amount2 = 0; //@implemented
public float $late_fee_amount3 = 0; //@implemented
public float $late_fee_percent1 = 0; //@implemented
public float $late_fee_percent2 = 0; //@implemented
public float $late_fee_percent3 = 0; //@implemented
public string $endless_reminder_frequency_id = '0'; //@implemented
public float $late_fee_endless_amount = 0; //@implemented
public float $late_fee_endless_percent = 0; //@implemented
public bool $client_online_payment_notification = true; //@todo implement in notifications check this bool prior to sending payment notification to client
public bool $client_manual_payment_notification = true; //@todo implement in notifications check this bool prior to sending manual payment notification to client
public string $name = ''; //@implemented
public string $company_logo = ''; //@implemented
public string $website = ''; //@implemented
public string $address1 = ''; //@implemented
public string $address2 = ''; //@implemented
public string $city = ''; //@implemented
public string $state = ''; //@implemented
public string $postal_code = ''; //@implemented
public string $phone = ''; //@implemented
public string $email = ''; //@implemented
public string $country_id = ''; //@implemented
public string $vat_number = ''; //@implemented
public string $id_number = ''; //@implemented
public string $page_size = 'A4'; // Letter, Legal, Tabloid, Ledger, A0, A1, A2, A3, A4, A5, A6
public string $page_layout = 'portrait';
public int $font_size = 16; //@implemented
public string $primary_font = 'Roboto';
public string $secondary_font = 'Roboto';
public string $primary_color = '#298AAB';
public string $secondary_color = '#7081e0';
public bool $page_numbering = false;
public string $page_numbering_alignment = 'C'; // C, R, L
public bool $hide_paid_to_date = false; //@TODO where?
public bool $embed_documents = false; //@TODO where?
public bool $all_pages_header = false; //@deprecated 31-05-2021
public bool $all_pages_footer = false; //@deprecated 31-05-2021
public string $pdf_variables = ''; //@implemented
public string $portal_custom_head = ''; //@TODO @BEN
public string $portal_custom_css = ''; //@TODO @BEN
public string $portal_custom_footer = ''; //@TODO @BEN
public string $portal_custom_js = ''; //@TODO @BEN
public bool $client_can_register = false; //@deprecated 04/06/2021
public string $client_portal_terms = ''; //@TODO @BEN
public string $client_portal_privacy_policy = ''; //@TODO @BEN
public bool $client_portal_enable_uploads = false; //@implemented
public bool $client_portal_allow_under_payment = false; //@implemented
public float $client_portal_under_payment_minimum = 0; //@implemented
public bool $client_portal_allow_over_payment = false; //@implemented
public string $use_credits_payment = 'off'; // always, option, off //@implemented
public bool $hide_empty_columns_on_pdf = false;
public string $email_from_name = '';
public bool $auto_archive_invoice_cancelled = false;
public bool $vendor_portal_enable_uploads = false;
public bool $send_email_on_mark_paid = false;
public string $postmark_secret = '';
public string $custom_sending_email = '';
public string $mailgun_secret = '';
public string $mailgun_domain = '';
public string $mailgun_endpoint = 'api.mailgun.net'; // api.eu.mailgun.net
public bool $auto_bill_standard_invoices = false;
public string $email_alignment = 'center'; // center, left, right
public bool $show_email_footer = true;
public string $company_logo_size = '';
public bool $show_paid_stamp = false;
public bool $show_shipping_address = false;
public bool $accept_client_input_quote_approval = false;
public bool $allow_billable_task_items = true;
public bool $show_task_item_description = false;
public bool $client_initiated_payments = false;
public float $client_initiated_payments_minimum = 0;
public bool $sync_invoice_quote_columns = true;
public string $e_invoice_type = 'EN16931';
public string $default_expense_payment_type_id = '0';
public bool $enable_e_invoice = false;
public string $classification = '';
private mixed $object;
public function cast(mixed $object)
{
if(is_array($object)) {
$object = (object)$object;
}
if (is_object($object)) {
foreach ($object as $key => $value) {
try {
settype($object->{$key}, gettype($this->{$key}));
} catch(\Exception | \Error | \Throwable $e) {
if(property_exists($this, $key)) {
$object->{$key} = $this->{$key};
} else {
unset($object->{$key});
}
}
// if(!property_exists($this, $key)) {
// unset($object->{$key});
// }
// elseif(is_array($object->{$key}) && gettype($this->{$key} != 'array')){
// $object->{$key} = $this->{$key};
// }
// else {
// settype($object->{$key}, gettype($this->{$key}));
// }
}
}
$this->object = $object;
return $this;
}
public function toObject(): object
{
return (object)$this->object;
}
public function toArray(): array
{
return (array)$this->object;
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\AT;
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 = 'USt';
}

View File

@ -0,0 +1,260 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\AU;
use App\DataMapper\Tax\BaseRule;
use App\DataMapper\Tax\RuleInterface;
use App\Models\Product;
class Rule extends BaseRule implements RuleInterface
{
/** @var string $seller_region */
public string $seller_region = 'AU';
/** @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;
/**
* Initializes the rules and builds any required data.
*
* @return self
*/
public function init(): self
{
$this->calculateRates();
return $this;
}
/**
* Sets the correct tax rate based on the product type.
*
* @param mixed $item
* @return self
*/
public function taxByType($item): self
{
if ($this->client->is_tax_exempt || !property_exists($item, 'tax_id')) {
return $this->taxExempt($item);
}
match(intval($item->tax_id)) {
Product::PRODUCT_TYPE_EXEMPT => $this->taxExempt($item),
Product::PRODUCT_TYPE_DIGITAL => $this->taxDigital($item),
Product::PRODUCT_TYPE_SERVICE => $this->taxService($item),
Product::PRODUCT_TYPE_SHIPPING => $this->taxShipping($item),
Product::PRODUCT_TYPE_PHYSICAL => $this->taxPhysical($item),
Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced($item),
Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override($item),
Product::PRODUCT_TYPE_ZERO_RATED => $this->zeroRated($item),
Product::PRODUCT_TYPE_REVERSE_TAX => $this->reverseTax($item),
default => $this->default($item),
};
return $this;
}
/**
* Calculates the tax rate for a reduced tax product
*
* @return self
*/
public function reverseTax($item): self
{
$this->tax_rate1 = 10;
$this->tax_name1 = 'GST';
return $this;
}
/**
* Calculates the tax rate for a reduced tax product
*
* @return self
*/
public function taxReduced($item): self
{
$this->tax_rate1 = $this->reduced_tax_rate;
$this->tax_name1 = 'GST';
return $this;
}
/**
* Calculates the tax rate for a zero rated tax product
*
* @return self
*/
public function zeroRated($item): self
{
$this->tax_rate1 = 0;
$this->tax_name1 = 'GST';
return $this;
}
/**
* Calculates the tax rate for a tax exempt product
*
* @return self
*/
public function taxExempt($item): self
{
$this->tax_name1 = '';
$this->tax_rate1 = 0;
return $this;
}
/**
* Calculates the tax rate for a digital product
*
* @return self
*/
public function taxDigital($item): self
{
$this->tax_rate1 = $this->tax_rate;
$this->tax_name1 = 'GST';
return $this;
}
/**
* Calculates the tax rate for a service product
*
* @return self
*/
public function taxService($item): self
{
$this->tax_rate1 = $this->tax_rate;
$this->tax_name1 = 'GST';
return $this;
}
/**
* Calculates the tax rate for a shipping product
*
* @return self
*/
public function taxShipping($item): self
{
$this->tax_rate1 = $this->tax_rate;
$this->tax_name1 = 'GST';
return $this;
}
/**
* Calculates the tax rate for a physical product
*
* @return self
*/
public function taxPhysical($item): self
{
$this->tax_rate1 = $this->tax_rate;
$this->tax_name1 = 'GST';
return $this;
}
/**
* Calculates the tax rate for a default product
*
* @return self
*/
public function default($item): self
{
$this->tax_name1 = '';
$this->tax_rate1 = 0;
return $this;
}
/**
* Calculates the tax rate for an override product
*
* @return self
*/
public function override($item): self
{
return $this;
}
/**
* Calculates the tax rates based on the client's location.
*
* @return self
*/
public function calculateRates(): self
{
if ($this->client->is_tax_exempt) {
$this->tax_rate = 0;
$this->reduced_tax_rate = 0;
return $this;
}
// } 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) {
// nlog("euro zone and tax exempt");
// $this->tax_rate = 0;
// $this->reduced_tax_rate = 0;
// } elseif(!in_array($this->client_subregion, $this->eu_country_codes) && ($this->foreign_consumer_tax_exempt || $this->foreign_business_tax_exempt)) { //foreign + tax exempt
// nlog("foreign and tax exempt");
// $this->tax_rate = 0;
// $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->has_valid_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) {
// 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;
// $this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->reduced_tax_rate;
// } else {
// nlog("EU with intra-community supply ie DE to DE");
// $this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->tax_rate;
// $this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->reduced_tax_rate;
// }
$this->tax_rate = $this->client->company->tax_data->regions->AU->subregions->{$this->client->company->country()->iso_3166_2}->tax_rate ?? 0;
$this->reduced_tax_rate = $this->client->company->tax_data->regions->AU->subregions->{$this->client->company->country()->iso_3166_2}->tax_rate ?? 0;
return $this;
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\BE;
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 = 'BTW';
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\BG;
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 = 'НДС';
}

View File

@ -0,0 +1,393 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax;
use App\DataMapper\Tax\ZipTax\Response;
use App\DataProviders\USStates;
use App\Models\Client;
use App\Models\Invoice;
use App\Models\Product;
class BaseRule implements RuleInterface
{
/** EU TAXES */
public bool $consumer_tax_exempt = false;
public bool $business_tax_exempt = true;
public bool $eu_business_tax_exempt = true;
public bool $foreign_business_tax_exempt = true;
public bool $foreign_consumer_tax_exempt = true;
public string $seller_region = '';
public string $client_region = '';
public string $client_subregion = '';
public array $eu_country_codes = [
'AT', // Austria
'BE', // Belgium
'BG', // Bulgaria
'CY', // Cyprus
'CZ', // Czech Republic
'DE', // Germany
'DK', // Denmark
'EE', // Estonia
'ES', // Spain
'FI', // Finland
'FR', // France
'GR', // Greece
'HR', // Croatia
'HU', // Hungary
'IE', // Ireland
'IT', // Italy
'LT', // Lithuania
'LU', // Luxembourg
'LV', // Latvia
'MT', // Malta
'NL', // Netherlands
'PL', // Poland
'PT', // Portugal
'RO', // Romania
'SE', // Sweden
'SI', // Slovenia
'SK', // Slovakia
];
public array $region_codes = [
'AT' => 'EU', // Austria
'BE' => 'EU', // Belgium
'BG' => 'EU', // Bulgaria
'CY' => 'EU', // Cyprus
'CZ' => 'EU', // Czech Republic
'DE' => 'EU', // Germany
'DK' => 'EU', // Denmark
'EE' => 'EU', // Estonia
'ES' => 'EU', // Spain
'FI' => 'EU', // Finland
'FR' => 'EU', // France
'GR' => 'EU', // Greece
'HR' => 'EU', // Croatia
'HU' => 'EU', // Hungary
'IE' => 'EU', // Ireland
'IT' => 'EU', // Italy
'LT' => 'EU', // Lithuania
'LU' => 'EU', // Luxembourg
'LV' => 'EU', // Latvia
'MT' => 'EU', // Malta
'NL' => 'EU', // Netherlands
'PL' => 'EU', // Poland
'PT' => 'EU', // Portugal
'RO' => 'EU', // Romania
'SE' => 'EU', // Sweden
'SI' => 'EU', // Slovenia
'SK' => 'EU', // Slovakia
'US' => 'US', // United States
'AU' => 'AU', // Australia
];
/** EU TAXES */
public string $tax_name1 = '';
public float $tax_rate1 = 0;
public string $tax_name2 = '';
public float $tax_rate2 = 0;
public string $tax_name3 = '';
public float $tax_rate3 = 0;
protected ?Client $client;
public ?Response $tax_data;
public mixed $invoice;
private bool $should_calc_tax = true;
public function __construct()
{
}
public function init(): self
{
return $this;
}
public function shouldCalcTax(): bool
{
return $this->should_calc_tax;
}
/**
* Initializes the tax rule for the entity.
*
* @param mixed $invoice
* @return self
*/
public function setEntity(mixed $invoice): self
{
$this->invoice = $invoice;
$this->client = $invoice->client;
$this->resolveRegions();
if(!$this->isTaxableRegion()) {
return $this;
}
$this->configTaxData();
$this->tax_data = new Response($this->invoice->tax_data);
return $this;
}
/**
* Configigures the Tax Data for the entity
*
* @return self
*/
private function configTaxData(): self
{
/* We should only apply taxes for configured states */
if(!array_key_exists($this->client->country->iso_3166_2, $this->region_codes)) {
nlog('Automatic tax calculations not supported for this country - defaulting to company country');
nlog("With new logic, we should never see this");
}
/** Harvest the client_region */
/** If the tax data is already set and the invoice is marked as sent, do not adjust the rates */
if($this->invoice->tax_data && $this->invoice->status_id > 1) {
return $this;
}
/**
* Origin - Company Tax Data
* Destination - Client Tax Data
*
*/
$tax_data = false;
if($this->seller_region == 'US' && $this->client_region == 'US') {
$company = $this->invoice->company;
/** If no company tax data has been configured, lets do that now. */
/** We should never encounter this scenario */
if(!$company->origin_tax_data) {
$this->should_calc_tax = false;
return $this;
}
/** If we are in a Origin based state, force the company tax here */
if($company->origin_tax_data->originDestination == 'O' && ($company->tax_data?->seller_subregion == $this->client_subregion)) {
$tax_data = $company->origin_tax_data;
} elseif($this->client->tax_data) {
$tax_data = $this->client->tax_data;
}
}
/** Applies the tax data to the invoice */
if($this->invoice instanceof Invoice && $tax_data) {
$this->invoice->tax_data = $tax_data;
if(\DB::transactionLevel() == 0) {
try {
$this->invoice->saveQuietly();
} catch(\Exception $e) {
}
}
}
return $this;
}
/**
* Resolve Regions & Subregions
*
* @return self
*/
private function resolveRegions(): self
{
$this->client_region = $this->region_codes[$this->client->country->iso_3166_2];
match($this->client_region) {
'US' => $this->client_subregion = isset($this->invoice?->client?->tax_data?->geoState) ? $this->invoice->client->tax_data->geoState : $this->getUSState(),
'EU' => $this->client_subregion = $this->client->country->iso_3166_2,
'AU' => $this->client_subregion = 'AU',
default => $this->client_subregion = $this->client->country->iso_3166_2,
};
return $this;
}
private function getUSState(): string
{
try {
$states = USStates::$states;
if(isset($states[$this->client->state])) {
return $this->client->state;
}
return USStates::getState(strlen($this->client->postal_code) > 1 ? $this->client->postal_code : $this->client->shipping_postal_code);
} catch (\Exception $e) {
return $this->client->company->country()->iso_3166_2 == 'US' ? $this->client->company->tax_data->seller_subregion : 'CA';
}
}
public function isTaxableRegion(): bool
{
return $this->client->company->tax_data->regions->{$this->client_region}->tax_all_subregions ||
(property_exists($this->client->company->tax_data->regions->{$this->client_region}->subregions, $this->client_subregion) && $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->apply_tax);
}
public function defaultForeign(): self
{
if($this->client_region == 'US' && isset($this->tax_data?->taxSales)) {
$this->tax_rate1 = $this->tax_data->taxSales * 100;
$this->tax_name1 = "{$this->tax_data->geoState} Sales Tax";
return $this;
} elseif($this->client_region == 'AU') { //these are defaults and are only stubbed out for now, for AU we can actually remove these
$this->tax_rate1 = $this->client->company->tax_data->regions->AU->subregions->AU->tax_rate;
$this->tax_name1 = $this->client->company->tax_data->regions->AU->subregions->AU->tax_name;
return $this;
}
if(isset($this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion})) {
$this->tax_rate1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_rate;
$this->tax_name1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_name;
}
return $this;
}
public function tax($item = null): self
{
if ($this->client->is_tax_exempt || !property_exists($item, 'tax_id')) {
return $this->taxExempt($item);
} elseif($this->client_region == $this->seller_region && $this->isTaxableRegion()) {
$this->taxByType($item);
return $this;
} elseif($this->isTaxableRegion()) { //other regions outside of US
match(intval($item->tax_id)) {
Product::PRODUCT_TYPE_EXEMPT => $this->taxExempt($item),
Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced($item),
Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override($item),
default => $this->defaultForeign(),
};
}
return $this;
}
public function taxByType(mixed $type): self
{
return $this;
}
public function taxReduced($item): self
{
return $this;
}
public function taxExempt($item): self
{
return $this;
}
public function taxDigital($item): self
{
return $this;
}
public function taxService($item): self
{
return $this;
}
public function taxShipping($item): self
{
return $this;
}
public function taxPhysical($item): self
{
return $this;
}
public function default($item): self
{
return $this;
}
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;
}
public function calculateRates(): self
{
return $this;
}
public function regionWithNoTaxCoverage(string $iso_3166_2): bool
{
return ! in_array($iso_3166_2, array_merge($this->eu_country_codes, array_keys($this->region_codes)));
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\CY;
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 = 'ΦΠΑ';
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\CZ;
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 = 'DPH';
}

View File

@ -0,0 +1,254 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\DE;
use App\DataMapper\Tax\BaseRule;
use App\DataMapper\Tax\RuleInterface;
use App\Models\Product;
class Rule extends BaseRule implements RuleInterface
{
/** @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 = 'MwSt.';
/**
* Initializes the rules and builds any required data.
*
* @return self
*/
public function init(): self
{
$this->calculateRates();
return $this;
}
/**
* Sets the correct tax rate based on the product type.
*
* @param mixed $item
* @return self
*/
public function taxByType($item): self
{
if ($this->client->is_tax_exempt || !property_exists($item, 'tax_id')) {
return $this->taxExempt($item);
}
match(intval($item->tax_id)) {
Product::PRODUCT_TYPE_EXEMPT => $this->taxExempt($item),
Product::PRODUCT_TYPE_DIGITAL => $this->taxDigital($item),
Product::PRODUCT_TYPE_SERVICE => $this->taxService($item),
Product::PRODUCT_TYPE_SHIPPING => $this->taxShipping($item),
Product::PRODUCT_TYPE_PHYSICAL => $this->taxPhysical($item),
Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced($item),
Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override($item),
Product::PRODUCT_TYPE_ZERO_RATED => $this->zeroRated($item),
Product::PRODUCT_TYPE_REVERSE_TAX => $this->reverseTax($item),
default => $this->default($item),
};
return $this;
}
/**
* Calculates the tax rate for a reduced tax product
*
* @return self
*/
public function reverseTax($item): self
{
$this->tax_rate1 = 0;
return $this;
}
/**
* Calculates the tax rate for a reduced tax product
*
* @return self
*/
public function taxReduced($item): self
{
$this->tax_rate1 = $this->reduced_tax_rate;
return $this;
}
/**
* Calculates the tax rate for a zero rated tax product
*
* @return self
*/
public function zeroRated($item): self
{
$this->tax_rate1 = 0;
return $this;
}
/**
* Calculates the tax rate for a tax exempt product
*
* @return self
*/
public function taxExempt($item): self
{
$this->tax_name1 = '';
$this->tax_rate1 = 0;
return $this;
}
/**
* Calculates the tax rate for a digital product
*
* @return self
*/
public function taxDigital($item): self
{
$this->tax_rate1 = $this->tax_rate;
return $this;
}
/**
* Calculates the tax rate for a service product
*
* @return self
*/
public function taxService($item): self
{
$this->tax_rate1 = $this->tax_rate;
return $this;
}
/**
* Calculates the tax rate for a shipping product
*
* @return self
*/
public function taxShipping($item): self
{
$this->tax_rate1 = $this->tax_rate;
return $this;
}
/**
* Calculates the tax rate for a physical product
*
* @return self
*/
public function taxPhysical($item): self
{
$this->tax_rate1 = $this->tax_rate;
return $this;
}
/**
* Calculates the tax rate for a default product
*
* @return self
*/
public function default($item): self
{
$this->tax_name1 = '';
$this->tax_rate1 = 0;
return $this;
}
/**
* Calculates the tax rate for an override product
*
* @return self
*/
public function override($item): self
{
return $this;
}
/**
* Calculates the tax rates based on the client's location.
*
* @return self
*/
public function calculateRates(): self
{
if ($this->client->is_tax_exempt) {
// 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)
// nlog("euro zone and tax exempt");
$this->tax_rate = 0;
$this->reduced_tax_rate = 0;
} elseif(!in_array($this->client_subregion, $this->eu_country_codes) && ($this->foreign_consumer_tax_exempt || $this->foreign_business_tax_exempt)) { //foreign + tax exempt
// nlog("foreign and tax exempt");
$this->tax_rate = 0;
$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) {
// 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;
$this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->reduced_tax_rate;
} else {
// nlog("EU with intra-community supply ie DE to DE");
$this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->tax_rate;
$this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->reduced_tax_rate;
}
} else {
// nlog("default tax");
$this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->tax_rate;
$this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->reduced_tax_rate;
}
return $this;
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\DK;
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 = 'Moms';
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\EE;
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 = 'KM';
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\ES;
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 = 'IVA';
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\FI;
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 = 'ALV';
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\FR;
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 = 'TVA';
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\GR;
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 = 'IVA';
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\HR;
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 = 'PDV';
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\HU;
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 = 'ÁFA';
}

View File

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

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\IT;
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 = 'IVA';
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\LT;
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 = 'PVM';
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\LU;
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 = 'TVA';
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\LV;
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 = 'PVN';
}

View File

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

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\NL;
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 = 'BTW';
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\PT;
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 = 'IVA';
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\RO;
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 = 'TVA';
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax;
interface RuleInterface
{
public function init();
public function tax($item);
public function taxByType($type);
public function taxExempt($item);
public function taxDigital($item);
public function taxService($item);
public function taxShipping($item);
public function taxPhysical($item);
public function taxReduced($item);
public function default($item);
public function override($item);
public function calculateRates();
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\SE;
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 = 'Moms';
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\SI;
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 = 'DDV';
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\SK;
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 = 'DPH';
}

View File

@ -0,0 +1,31 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax;
use App\DataMapper\Tax\ZipTax\Response;
/**
* InvoiceTaxData
*
* Definition for the invoice tax data structure
*/
class TaxData
{
public int $updated_at;
public function __construct(public Response $origin)
{
foreach($origin as $key => $value) {
$this->{$key} = $value;
}
}
}

View File

@ -0,0 +1,512 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax;
class TaxModel
{
/** @var string $seller_subregion */
public string $seller_subregion = 'CA';
/** @var string $version */
public string $version = 'alpha';
/** @var object $regions */
public object $regions;
/**
* __construct
*
* @param TaxModel $model
* @return void
*/
public function __construct(public ?TaxModel $model = null)
{
if(!$this->model) {
$this->regions = $this->init();
} else {
$this->regions = $model;
}
}
/**
* Initializes the rules and builds any required data.
*
* @return object
*/
public function init(): object
{
$this->regions = new \stdClass();
$this->regions->US = new \stdClass();
$this->regions->EU = new \stdClass();
$this->usRegion()
->euRegion()
->auRegion();
return $this->regions;
}
/**
* Builds the model for Australian Taxes
*
* @return self
*/
private function auRegion(): self
{
$this->regions->AU = new \stdClass();
$this->regions->AU->has_sales_above_threshold = false;
$this->regions->AU->tax_all_subregions = false;
$this->regions->AU->tax_threshold = 75000;
$this->auSubRegions();
return $this;
}
/**
* Builds the model for Australian Subregions
*
* @return self
*/
private function auSubRegions(): self
{
$this->regions->AU->subregions = new \stdClass();
$this->regions->AU->subregions->AU = new \stdClass();
$this->regions->AU->subregions->AU->apply_tax = false;
$this->regions->AU->subregions->AU->tax_rate = 10;
$this->regions->AU->subregions->AU->tax_name = 'GST';
return $this;
}
/**
* Builds the model for US Taxes
*
* @return self
*/
private function usRegion(): self
{
$this->regions->US->has_sales_above_threshold = false;
$this->regions->US->tax_all_subregions = false;
$this->usSubRegions();
return $this;
}
/**
* Builds the model for EU Taxes
*
* @return self
*/
private function euRegion(): self
{
$this->regions->EU->has_sales_above_threshold = false;
$this->regions->EU->tax_all_subregions = false;
$this->regions->EU->tax_threshold = 10000;
$this->euSubRegions();
return $this;
}
/**
* Builds the model for US States
*
* @return self
*/
private function usSubRegions(): self
{
$this->regions->US->subregions = new \stdClass();
$this->regions->US->subregions->AL = new \stdClass();
$this->regions->US->subregions->AL->apply_tax = false;
$this->regions->US->subregions->AL->tax_rate = 4;
$this->regions->US->subregions->AL->tax_name = 'Sales Tax';
$this->regions->US->subregions->AK = new \stdClass();
$this->regions->US->subregions->AK->apply_tax = false;
$this->regions->US->subregions->AK->tax_rate = 0;
$this->regions->US->subregions->AK->tax_name = 'Sales Tax';
$this->regions->US->subregions->AZ = new \stdClass();
$this->regions->US->subregions->AZ->apply_tax = false;
$this->regions->US->subregions->AZ->tax_rate = 5.6;
$this->regions->US->subregions->AZ->tax_name = 'Sales Tax';
$this->regions->US->subregions->AR = new \stdClass();
$this->regions->US->subregions->AR->apply_tax = false;
$this->regions->US->subregions->AR->tax_rate = 6.5;
$this->regions->US->subregions->AR->tax_name = 'Sales Tax';
$this->regions->US->subregions->CA = new \stdClass();
$this->regions->US->subregions->CA->apply_tax = false;
$this->regions->US->subregions->CA->tax_rate = 7.25;
$this->regions->US->subregions->CA->tax_name = 'Sales Tax';
$this->regions->US->subregions->CO = new \stdClass();
$this->regions->US->subregions->CO->apply_tax = false;
$this->regions->US->subregions->CO->tax_rate = 2.9;
$this->regions->US->subregions->CO->tax_name = 'Sales Tax';
$this->regions->US->subregions->CT = new \stdClass();
$this->regions->US->subregions->CT->apply_tax = false;
$this->regions->US->subregions->CT->tax_rate = 6.35;
$this->regions->US->subregions->CT->tax_name = 'Sales Tax';
$this->regions->US->subregions->DE = new \stdClass();
$this->regions->US->subregions->DE->apply_tax = false;
$this->regions->US->subregions->DE->tax_rate = 0;
$this->regions->US->subregions->DE->tax_name = 'Sales Tax';
$this->regions->US->subregions->FL = new \stdClass();
$this->regions->US->subregions->FL->apply_tax = false;
$this->regions->US->subregions->FL->tax_rate = 6;
$this->regions->US->subregions->FL->tax_name = 'Sales Tax';
$this->regions->US->subregions->GA = new \stdClass();
$this->regions->US->subregions->GA->apply_tax = false;
$this->regions->US->subregions->GA->tax_rate = 4;
$this->regions->US->subregions->GA->tax_name = 'Sales Tax';
$this->regions->US->subregions->HI = new \stdClass();
$this->regions->US->subregions->HI->apply_tax = false;
$this->regions->US->subregions->HI->tax_rate = 4;
$this->regions->US->subregions->HI->tax_name = 'Sales Tax';
$this->regions->US->subregions->ID = new \stdClass();
$this->regions->US->subregions->ID->apply_tax = false;
$this->regions->US->subregions->ID->tax_rate = 6;
$this->regions->US->subregions->ID->tax_name = 'Sales Tax';
$this->regions->US->subregions->IL = new \stdClass();
$this->regions->US->subregions->IL->apply_tax = false;
$this->regions->US->subregions->IL->tax_rate = 6.25;
$this->regions->US->subregions->IL->tax_name = 'Sales Tax';
$this->regions->US->subregions->IN = new \stdClass();
$this->regions->US->subregions->IN->apply_tax = false;
$this->regions->US->subregions->IN->tax_rate = 7;
$this->regions->US->subregions->IN->tax_name = 'Sales Tax';
$this->regions->US->subregions->IA = new \stdClass();
$this->regions->US->subregions->IA->apply_tax = false;
$this->regions->US->subregions->IA->tax_rate = 6;
$this->regions->US->subregions->IA->tax_name = 'Sales Tax';
$this->regions->US->subregions->KS = new \stdClass();
$this->regions->US->subregions->KS->apply_tax = false;
$this->regions->US->subregions->KS->tax_rate = 6.5;
$this->regions->US->subregions->KS->tax_name = 'Sales Tax';
$this->regions->US->subregions->KY = new \stdClass();
$this->regions->US->subregions->KY->apply_tax = false;
$this->regions->US->subregions->KY->tax_rate = 6;
$this->regions->US->subregions->KY->tax_name = 'Sales Tax';
$this->regions->US->subregions->LA = new \stdClass();
$this->regions->US->subregions->LA->apply_tax = false;
$this->regions->US->subregions->LA->tax_rate = 4.45;
$this->regions->US->subregions->LA->tax_name = 'Sales Tax';
$this->regions->US->subregions->ME = new \stdClass();
$this->regions->US->subregions->ME->apply_tax = false;
$this->regions->US->subregions->ME->tax_rate = 5.5;
$this->regions->US->subregions->ME->tax_name = 'Sales Tax';
$this->regions->US->subregions->MD = new \stdClass();
$this->regions->US->subregions->MD->apply_tax = false;
$this->regions->US->subregions->MD->tax_rate = 6;
$this->regions->US->subregions->MD->tax_name = 'Sales Tax';
$this->regions->US->subregions->MA = new \stdClass();
$this->regions->US->subregions->MA->apply_tax = false;
$this->regions->US->subregions->MA->tax_rate = 6.25;
$this->regions->US->subregions->MA->tax_name = 'Sales Tax';
$this->regions->US->subregions->MI = new \stdClass();
$this->regions->US->subregions->MI->apply_tax = false;
$this->regions->US->subregions->MI->tax_rate = 6;
$this->regions->US->subregions->MI->tax_name = 'Sales Tax';
$this->regions->US->subregions->MN = new \stdClass();
$this->regions->US->subregions->MN->apply_tax = false;
$this->regions->US->subregions->MN->tax_rate = 6.875;
$this->regions->US->subregions->MN->tax_name = 'Sales Tax';
$this->regions->US->subregions->MS = new \stdClass();
$this->regions->US->subregions->MS->apply_tax = false;
$this->regions->US->subregions->MS->tax_rate = 7;
$this->regions->US->subregions->MS->tax_name = 'Sales Tax';
$this->regions->US->subregions->MO = new \stdClass();
$this->regions->US->subregions->MO->apply_tax = false;
$this->regions->US->subregions->MO->tax_rate = 4.225;
$this->regions->US->subregions->MO->tax_name = 'Sales Tax';
$this->regions->US->subregions->MT = new \stdClass();
$this->regions->US->subregions->MT->apply_tax = false;
$this->regions->US->subregions->MT->tax_rate = 0;
$this->regions->US->subregions->MT->tax_name = 'Sales Tax';
$this->regions->US->subregions->NE = new \stdClass();
$this->regions->US->subregions->NE->apply_tax = false;
$this->regions->US->subregions->NE->tax_rate = 5.5;
$this->regions->US->subregions->NE->tax_name = 'Sales Tax';
$this->regions->US->subregions->NV = new \stdClass();
$this->regions->US->subregions->NV->apply_tax = false;
$this->regions->US->subregions->NV->tax_rate = 6.85;
$this->regions->US->subregions->NV->tax_name = 'Sales Tax';
$this->regions->US->subregions->NH = new \stdClass();
$this->regions->US->subregions->NH->apply_tax = false;
$this->regions->US->subregions->NH->tax_rate = 0;
$this->regions->US->subregions->NH->tax_name = 'Sales Tax';
$this->regions->US->subregions->NJ = new \stdClass();
$this->regions->US->subregions->NJ->apply_tax = false;
$this->regions->US->subregions->NJ->tax_rate = 6.625;
$this->regions->US->subregions->NJ->tax_name = 'Sales Tax';
$this->regions->US->subregions->NM = new \stdClass();
$this->regions->US->subregions->NM->apply_tax = false;
$this->regions->US->subregions->NM->tax_rate = 5.125;
$this->regions->US->subregions->NM->tax_name = 'Sales Tax';
$this->regions->US->subregions->NY = new \stdClass();
$this->regions->US->subregions->NY->apply_tax = false;
$this->regions->US->subregions->NY->tax_rate = 4;
$this->regions->US->subregions->NY->tax_name = 'Sales Tax';
$this->regions->US->subregions->NC = new \stdClass();
$this->regions->US->subregions->NC->apply_tax = false;
$this->regions->US->subregions->NC->tax_rate = 4.75;
$this->regions->US->subregions->NC->tax_name = 'Sales Tax';
$this->regions->US->subregions->ND = new \stdClass();
$this->regions->US->subregions->ND->apply_tax = false;
$this->regions->US->subregions->ND->tax_rate = 5;
$this->regions->US->subregions->ND->tax_name = 'Sales Tax';
$this->regions->US->subregions->OH = new \stdClass();
$this->regions->US->subregions->OH->apply_tax = false;
$this->regions->US->subregions->OH->tax_rate = 5.75;
$this->regions->US->subregions->OH->tax_name = 'Sales Tax';
$this->regions->US->subregions->OK = new \stdClass();
$this->regions->US->subregions->OK->apply_tax = false;
$this->regions->US->subregions->OK->tax_rate = 4.5;
$this->regions->US->subregions->OK->tax_name = 'Sales Tax';
$this->regions->US->subregions->OR = new \stdClass();
$this->regions->US->subregions->OR->apply_tax = false;
$this->regions->US->subregions->OR->tax_rate = 0;
$this->regions->US->subregions->OR->tax_name = 'Sales Tax';
$this->regions->US->subregions->PA = new \stdClass();
$this->regions->US->subregions->PA->apply_tax = false;
$this->regions->US->subregions->PA->tax_rate = 6;
$this->regions->US->subregions->PA->tax_name = 'Sales Tax';
$this->regions->US->subregions->RI = new \stdClass();
$this->regions->US->subregions->RI->apply_tax = false;
$this->regions->US->subregions->RI->tax_rate = 7;
$this->regions->US->subregions->RI->tax_name = 'Sales Tax';
$this->regions->US->subregions->SC = new \stdClass();
$this->regions->US->subregions->SC->apply_tax = false;
$this->regions->US->subregions->SC->tax_rate = 6;
$this->regions->US->subregions->SC->tax_name = 'Sales Tax';
$this->regions->US->subregions->SD = new \stdClass();
$this->regions->US->subregions->SD->apply_tax = false;
$this->regions->US->subregions->SD->tax_rate = 4.5;
$this->regions->US->subregions->SD->tax_name = 'Sales Tax';
$this->regions->US->subregions->TN = new \stdClass();
$this->regions->US->subregions->TN->apply_tax = false;
$this->regions->US->subregions->TN->tax_rate = 7;
$this->regions->US->subregions->TN->tax_name = 'Sales Tax';
$this->regions->US->subregions->TX = new \stdClass();
$this->regions->US->subregions->TX->apply_tax = false;
$this->regions->US->subregions->TX->tax_rate = 6.25;
$this->regions->US->subregions->TX->tax_name = 'Sales Tax';
$this->regions->US->subregions->UT = new \stdClass();
$this->regions->US->subregions->UT->apply_tax = false;
$this->regions->US->subregions->UT->tax_rate = 5.95;
$this->regions->US->subregions->UT->tax_name = 'Sales Tax';
$this->regions->US->subregions->VT = new \stdClass();
$this->regions->US->subregions->VT->apply_tax = false;
$this->regions->US->subregions->VT->tax_rate = 6;
$this->regions->US->subregions->VT->tax_name = 'Sales Tax';
$this->regions->US->subregions->VA = new \stdClass();
$this->regions->US->subregions->VA->apply_tax = false;
$this->regions->US->subregions->VA->tax_rate = 5.3;
$this->regions->US->subregions->VA->tax_name = 'Sales Tax';
$this->regions->US->subregions->WA = new \stdClass();
$this->regions->US->subregions->WA->apply_tax = false;
$this->regions->US->subregions->WA->tax_rate = 6.5;
$this->regions->US->subregions->WA->tax_name = 'Sales Tax';
$this->regions->US->subregions->WV = new \stdClass();
$this->regions->US->subregions->WV->apply_tax = false;
$this->regions->US->subregions->WV->tax_rate = 6;
$this->regions->US->subregions->WV->tax_name = 'Sales Tax';
$this->regions->US->subregions->WI = new \stdClass();
$this->regions->US->subregions->WI->apply_tax = false;
$this->regions->US->subregions->WI->tax_rate = 5;
$this->regions->US->subregions->WI->tax_name = 'Sales Tax';
$this->regions->US->subregions->WY = new \stdClass();
$this->regions->US->subregions->WY->apply_tax = false;
$this->regions->US->subregions->WY->tax_rate = 4;
$this->regions->US->subregions->WY->tax_name = 'Sales Tax';
return $this;
}
/**
* Create the EU member countries
*
* @return self
*/
private function euSubRegions(): self
{
$this->regions->EU->subregions = new \stdClass();
$this->regions->EU->subregions->AT = new \stdClass();
$this->regions->EU->subregions->AT->tax_rate = 20;
$this->regions->EU->subregions->AT->tax_name = 'USt';
$this->regions->EU->subregions->AT->reduced_tax_rate = 10;
$this->regions->EU->subregions->AT->apply_tax = false;
$this->regions->EU->subregions->BE = new \stdClass();
$this->regions->EU->subregions->BE->tax_rate = 21;
$this->regions->EU->subregions->BE->tax_name = 'BTW';
$this->regions->EU->subregions->BE->reduced_tax_rate = 6;
$this->regions->EU->subregions->BE->apply_tax = false;
$this->regions->EU->subregions->BG = new \stdClass();
$this->regions->EU->subregions->BG->tax_rate = 20;
$this->regions->EU->subregions->BG->tax_name = 'НДС';
$this->regions->EU->subregions->BG->reduced_tax_rate = 9;
$this->regions->EU->subregions->BG->apply_tax = false;
$this->regions->EU->subregions->CY = new \stdClass();
$this->regions->EU->subregions->CY->tax_rate = 19;
$this->regions->EU->subregions->CY->tax_name = 'ΦΠΑ';
$this->regions->EU->subregions->CY->reduced_tax_rate = 9;
$this->regions->EU->subregions->CY->apply_tax = false;
$this->regions->EU->subregions->CZ = new \stdClass();
$this->regions->EU->subregions->CZ->tax_rate = 21;
$this->regions->EU->subregions->CZ->tax_name = 'DPH';
$this->regions->EU->subregions->CZ->reduced_tax_rate = 15;
$this->regions->EU->subregions->CZ->apply_tax = false;
$this->regions->EU->subregions->DE = new \stdClass();
$this->regions->EU->subregions->DE->tax_rate = 19;
$this->regions->EU->subregions->DE->tax_name = 'MwSt';
$this->regions->EU->subregions->DE->reduced_tax_rate = 7;
$this->regions->EU->subregions->DE->apply_tax = false;
$this->regions->EU->subregions->DK = new \stdClass();
$this->regions->EU->subregions->DK->tax_rate = 25;
$this->regions->EU->subregions->DK->tax_name = 'Moms';
$this->regions->EU->subregions->DK->reduced_tax_rate = 0;
$this->regions->EU->subregions->DK->apply_tax = false;
$this->regions->EU->subregions->EE = new \stdClass();
$this->regions->EU->subregions->EE->tax_rate = 20;
$this->regions->EU->subregions->EE->tax_name = 'KM';
$this->regions->EU->subregions->EE->reduced_tax_rate = 9;
$this->regions->EU->subregions->EE->apply_tax = false;
$this->regions->EU->subregions->ES = new \stdClass();
$this->regions->EU->subregions->ES->tax_rate = 21;
$this->regions->EU->subregions->ES->tax_name = 'IVA';
$this->regions->EU->subregions->ES->reduced_tax_rate = 10;
$this->regions->EU->subregions->ES->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';
$this->regions->EU->subregions->FI->reduced_tax_rate = 14;
$this->regions->EU->subregions->FI->apply_tax = false;
$this->regions->EU->subregions->FR = new \stdClass();
$this->regions->EU->subregions->FR->tax_rate = 20;
$this->regions->EU->subregions->FR->tax_name = 'TVA';
$this->regions->EU->subregions->FR->reduced_tax_rate = 5.5;
$this->regions->EU->subregions->FR->apply_tax = false;
// $this->regions->EU->subregions->GB = new \stdClass();
// $this->regions->EU->subregions->GB->tax_rate = 20;
// $this->regions->EU->subregions->GB->reduced_tax_rate = 0;
// $this->regions->EU->subregions->GB->apply_tax = false;
$this->regions->EU->subregions->GR = new \stdClass();
$this->regions->EU->subregions->GR->tax_rate = 24;
$this->regions->EU->subregions->GR->tax_name = 'ΦΠΑ';
$this->regions->EU->subregions->GR->reduced_tax_rate = 13;
$this->regions->EU->subregions->GR->apply_tax = false;
$this->regions->EU->subregions->HR = new \stdClass();
$this->regions->EU->subregions->HR->tax_rate = 25;
$this->regions->EU->subregions->HR->tax_name = 'PDV';
$this->regions->EU->subregions->HR->reduced_tax_rate = 5;
$this->regions->EU->subregions->HR->apply_tax = false;
$this->regions->EU->subregions->HU = new \stdClass();
$this->regions->EU->subregions->HU->tax_rate = 27;
$this->regions->EU->subregions->HU->tax_name = 'ÁFA';
$this->regions->EU->subregions->HU->reduced_tax_rate = 5;
$this->regions->EU->subregions->HU->apply_tax = false;
$this->regions->EU->subregions->IE = new \stdClass();
$this->regions->EU->subregions->IE->tax_rate = 23;
$this->regions->EU->subregions->IE->tax_name = 'VAT';
$this->regions->EU->subregions->IE->reduced_tax_rate = 0;
$this->regions->EU->subregions->IE->apply_tax = false;
$this->regions->EU->subregions->IT = new \stdClass();
$this->regions->EU->subregions->IT->tax_rate = 22;
$this->regions->EU->subregions->IT->tax_name = 'IVA';
$this->regions->EU->subregions->IT->reduced_tax_rate = 10;
$this->regions->EU->subregions->IT->apply_tax = false;
$this->regions->EU->subregions->LT = new \stdClass();
$this->regions->EU->subregions->LT->tax_rate = 21;
$this->regions->EU->subregions->LT->tax_name = 'PVM';
$this->regions->EU->subregions->LT->reduced_tax_rate = 9;
$this->regions->EU->subregions->LT->apply_tax = false;
$this->regions->EU->subregions->LU = new \stdClass();
$this->regions->EU->subregions->LU->tax_rate = 17;
$this->regions->EU->subregions->LU->tax_name = 'TVA';
$this->regions->EU->subregions->LU->reduced_tax_rate = 3;
$this->regions->EU->subregions->LU->apply_tax = false;
$this->regions->EU->subregions->LV = new \stdClass();
$this->regions->EU->subregions->LV->tax_rate = 21;
$this->regions->EU->subregions->LV->tax_name = 'PVN';
$this->regions->EU->subregions->LV->reduced_tax_rate = 12;
$this->regions->EU->subregions->LV->apply_tax = false;
$this->regions->EU->subregions->MT = new \stdClass();
$this->regions->EU->subregions->MT->tax_rate = 18;
$this->regions->EU->subregions->MT->tax_name = 'VAT';
$this->regions->EU->subregions->MT->reduced_tax_rate = 5;
$this->regions->EU->subregions->MT->apply_tax = false;
$this->regions->EU->subregions->NL = new \stdClass();
$this->regions->EU->subregions->NL->tax_rate = 21;
$this->regions->EU->subregions->NL->tax_name = 'BTW';
$this->regions->EU->subregions->NL->reduced_tax_rate = 9;
$this->regions->EU->subregions->NL->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';
$this->regions->EU->subregions->PT->reduced_tax_rate = 6;
$this->regions->EU->subregions->PT->apply_tax = false;
$this->regions->EU->subregions->RO = new \stdClass();
$this->regions->EU->subregions->RO->tax_rate = 19;
$this->regions->EU->subregions->RO->tax_name = 'TVA';
$this->regions->EU->subregions->RO->reduced_tax_rate = 5;
$this->regions->EU->subregions->RO->apply_tax = false;
$this->regions->EU->subregions->SE = new \stdClass();
$this->regions->EU->subregions->SE->tax_rate = 25;
$this->regions->EU->subregions->SE->tax_name = 'Moms';
$this->regions->EU->subregions->SE->reduced_tax_rate = 12;
$this->regions->EU->subregions->SE->apply_tax = false;
$this->regions->EU->subregions->SI = new \stdClass();
$this->regions->EU->subregions->SI->tax_rate = 22;
$this->regions->EU->subregions->SI->tax_name = 'DDV';
$this->regions->EU->subregions->SI->reduced_tax_rate = 9.5;
$this->regions->EU->subregions->SI->apply_tax = false;
$this->regions->EU->subregions->SK = new \stdClass();
$this->regions->EU->subregions->SK->tax_rate = 20;
$this->regions->EU->subregions->SK->tax_name = 'DPH';
$this->regions->EU->subregions->SK->reduced_tax_rate = 10;
$this->regions->EU->subregions->SK->apply_tax = false;
return $this;
}
}

View File

@ -0,0 +1,226 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\US;
use App\DataMapper\Tax\BaseRule;
use App\DataMapper\Tax\RuleInterface;
use App\Models\Product;
/**
* The rules apply US => US taxes using the tax calculator.
*
* US => Foreign taxes we check the product types still for exemptions, and we all back to the client country tax rate.
*/
class Rule extends BaseRule implements RuleInterface
{
/** @var string $seller_region */
public string $seller_region = 'US';
/**
* Initializes the rules and builds any required data.
*
* @return self
*/
public function init(): self
{
$this->calculateRates();
return $this;
}
/**
* Override tax class, we use this when we do not modify the input taxes
*
* @param mixed $item
* @return self
*/
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;
}
/**
* Sets the correct tax rate based on the product type.
*
* @param mixed $item
* @return self
*/
public function taxByType($item): self
{
match(intval($item->tax_id)) {
Product::PRODUCT_TYPE_EXEMPT => $this->taxExempt($item),
Product::PRODUCT_TYPE_DIGITAL => $this->taxDigital($item),
Product::PRODUCT_TYPE_SERVICE => $this->taxService($item),
Product::PRODUCT_TYPE_SHIPPING => $this->taxShipping($item),
Product::PRODUCT_TYPE_PHYSICAL => $this->taxPhysical($item),
Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced($item),
Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override($item),
Product::PRODUCT_TYPE_ZERO_RATED => $this->zeroRated($item),
default => $this->default($item),
};
return $this;
}
/**
* Sets the tax as exempt (0)
* @param mixed $item
*
* @return self
*/
public function taxExempt($item): self
{
$this->tax_name1 = '';
$this->tax_rate1 = 0;
return $this;
}
/**
* Calculates the tax rate for a digital product
* @param mixed $item
*
* @return self
*/
public function taxDigital($item): self
{
$this->default($item);
return $this;
}
/**
* Calculates the tax rate for a service product
* @param mixed $item
*
* @return self
*/
public function taxService($item): self
{
if(in_array($this->tax_data?->txbService, ['Y','L'])) {
$this->default($item);
} else {
$this->taxExempt($item);
}
return $this;
}
/**
* Calculates the tax rate for a shipping product
* @param mixed $item
*
* @return self
*/
public function taxShipping($item): self
{
if($this->tax_data?->txbFreight == 'Y') {
return $this->default($item);
}
$this->tax_rate1 = 0;
$this->tax_name1 = '';
return $this;
}
/**
* Calculates the tax rate for a physical product
* @param mixed $item
*
* @return self
*/
public function taxPhysical($item): self
{
$this->default($item);
return $this;
}
/**
* Calculates the tax rate for an undefined product uses the default tax rate for the client county
*
* @return self
*/
public function default($item): self
{
if($this->tax_data?->stateSalesTax == 0) {
$this->tax_rate1 = 0;
$this->tax_name1 = '';
return $this;
}
$this->tax_rate1 = $this->tax_data->taxSales * 100;
$this->tax_name1 = "Sales Tax";
return $this;
}
public function zeroRated($item): self
{
$this->tax_rate1 = 0;
$this->tax_name1 = "{$this->tax_data->geoState} Zero Rated Tax";
return $this;
}
/**
* Calculates the tax rate for a reduced tax product
*
* @return self
*/
public function taxReduced($item): self
{
$this->default($item);
return $this;
}
/**
* Calculates the tax rate for a reverse tax product
*
* @return self
*/
public function reverseTax($item): self
{
$this->default($item);
return $this;
}
/**
* Calculates the tax rates to be applied
*
* @return self
*/
public function calculateRates(): self
{
return $this;
}
}

View File

@ -0,0 +1,116 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Tax\ZipTax;
class Response
{
public string $version = 'v40';
public int $rCode = 100;
/**
* [
* "geoPostalCode" => "92582",
* "geoCity" => "SAN JACINTO",
* "geoCounty" => "RIVERSIDE",
* "geoState" => "CA",
* "taxSales" => 0.0875,
* "taxUse" => 0.0875,
* "txbService" => "N",
* "txbFreight" => "N",
* "stateSalesTax" => 0.06,
* "stateUseTax" => 0.06,
* "citySalesTax" => 0.01,
* "cityUseTax" => 0.01,
* "cityTaxCode" => "874",
* "countySalesTax" => 0.0025,
* "countyUseTax" => 0.0025,
* "countyTaxCode" => "",
* "districtSalesTax" => 0.015,
* "districtUseTax" => 0.015,
* "district1Code" => "26",
* "district1SalesTax" => 0,
* "district1UseTax" => 0,
* "district2Code" => "26",
* "district2SalesTax" => 0.005,
* "district2UseTax" => 0.005,
* "district3Code" => "",
* "district3SalesTax" => 0,
* "district3UseTax" => 0,
* "district4Code" => "33",
* "district4SalesTax" => 0.01,
* "district4UseTax" => 0.01,
* "district5Code" => "",
* "district5SalesTax" => 0,
* "district5UseTax" => 0,
* "originDestination" => "D",
*
* ];
*
*/
public string $seller_subregion = "";
//US
public string $geoPostalCode = "";
public string $geoCity = "";
public string $geoCounty = "";
public string $geoState = "";
public float $taxSales = 0;
public string $taxName = "";
public float $taxUse = 0;
public string $txbService = "Y"; // N = No, Y = Yes
public string $txbFreight = "Y"; // N = No, Y = Yes
public float $stateSalesTax = 0;
public float $stateUseTax = 0;
public float $citySalesTax = 0;
public float $cityUseTax = 0;
public string $cityTaxCode = "";
/* US SPECIFIC TAX CODES */
public float $countySalesTax = 0;
public float $countyUseTax = 0;
public string $countyTaxCode = "";
public float $districtSalesTax = 0;
public float $districtUseTax = 0;
public string $district1Code = "";
public float $district1SalesTax = 0;
public float $district1UseTax = 0;
public string $district2Code = "";
public float $district2SalesTax = 0;
public float $district2UseTax = 0;
public string $district3Code = "";
public float $district3SalesTax = 0;
public float $district3UseTax = 0;
public string $district4Code = "";
public float $district4SalesTax = 0;
public float $district4UseTax = 0;
public string $district5Code = "";
public float $district5SalesTax = 0;
public float $district5UseTax = 0;
/* US SPECIFIC TAX CODES */
public string $originDestination = "D"; // defines if the client origin is the locale where the tax is remitted to
public function __construct($data = null)
{
if($data) {
foreach($data as $key => $value) {
$this->{$key} = $value;
}
}
}
}

View File

@ -0,0 +1,219 @@
region:
US:
tax_all_subregions: false
seller_subregion: CA
has_sales_above_threshold: false
subregions:
AL:
apply_tax: false
AK:
apply_tax: false
AZ:
apply_tax: false
AR:
apply_tax: false
CA:
apply_tax: false
CO:
apply_tax: false
CT:
apply_tax: false
DE:
apply_tax: false
FL:
apply_tax: false
GA:
apply_tax: false
HI:
apply_tax: false
ID:
apply_tax: false
IL:
apply_tax: false
IN:
apply_tax: false
IA:
apply_tax: false
KS:
apply_tax: false
KY:
apply_tax: false
LA:
apply_tax: false
ME:
apply_tax: false
MD:
apply_tax: false
MA:
apply_tax: false
MI:
apply_tax: false
MN:
apply_tax: false
MS:
apply_tax: false
MO:
apply_tax: false
MT:
apply_tax: false
NE:
apply_tax: false
NV:
apply_tax: false
NH:
apply_tax: false
NJ:
apply_tax: false
NM:
apply_tax: false
NY:
apply_tax: false
NC:
apply_tax: false
ND:
apply_tax: false
OH:
apply_tax: false
OK:
apply_tax: false
OR:
apply_tax: false
PA:
apply_tax: false
RI:
apply_tax: false
SC:
apply_tax: false
SD:
apply_tax: false
TN:
apply_tax: false
TX:
apply_tax: false
UT:
apply_tax: false
VT:
apply_tax: false
VA:
apply_tax: false
WA:
apply_tax: false
WV:
apply_tax: false
WI:
apply_tax: false
WY:
apply_tax: false
EU:
tax_all: false
tax_threshold: 10000
has_sales_above_threshold: false
subregions:
AT:
vat: 21
reduced_vat: 11
apply_tax: false
BE:
vat: 21
reduced_vat: 6
apply_tax: false
BG:
vat: 20
reduced_vat: 9
apply_tax: false
CY:
vat: 19
reduced_vat: 9
apply_tax: false
CZ:
vat: 21
reduced_vat: 15
apply_tax: false
DE:
vat: 19
reduced_vat: 7
apply_tax: false
DK:
vat: 25
reduced_vat: 0
apply_tax: false
EE:
vat: 20
reduced_vat: 9
apply_tax: false
ES:
vat: 21
reduced_vat: 10
apply_tax: false
FI:
vat: 24
reduced_vat: 14
apply_tax: false
FR:
vat: 20
reduced_vat: 5.5
apply_tax: false
GB:
vat: 20
reduced_vat: 0
apply_tax: false
GR:
vat: 24
reduced_vat: 13
apply_tax: false
HR:
vat: 25
reduced_vat: 5
apply_tax: false
HU:
vat: 27
reduced_vat: 5
apply_tax: false
IE:
vat: 23
reduced_vat: 0
apply_tax: false
IT:
vat: 22
reduced_vat: 10
apply_tax: false
LT:
vat: 21
reduced_vat: 9
apply_tax: false
LU:
vat: 17
reduced_vat: 3
apply_tax: false
LV:
vat: 21
reduced_vat: 12
apply_tax: false
MT:
vat: 18
reduced_vat: 5
apply_tax: false
NL:
vat: 21
reduced_vat: 9
apply_tax: false
PT:
vat: 23
reduced_vat: 6
apply_tax: false
RO:
vat: 19
reduced_vat: 5
apply_tax: false
SE:
vat: 25
reduced_vat: 12
apply_tax: false
SI:
vat: 22
reduced_vat: 9.5
apply_tax: false
SK:
vat: 20
reduced_vat: 10
apply_tax: false

View File

@ -11,10 +11,6 @@
namespace App\DataMapper\Transactions;
use App\Models\Client;
use App\Models\Credit;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\TransactionEvent;
/**

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