diff --git a/VERSION.txt b/VERSION.txt index 71c2c6e276..57ebcacf2c 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.7.33 \ No newline at end of file +5.7.34 \ No newline at end of file diff --git a/app/Casts/EncryptedCast.php b/app/Casts/EncryptedCast.php index 7be926072c..a8c11f3ad3 100644 --- a/app/Casts/EncryptedCast.php +++ b/app/Casts/EncryptedCast.php @@ -16,12 +16,12 @@ 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]; } } diff --git a/app/Console/Commands/BackupUpdate.php b/app/Console/Commands/BackupUpdate.php index 9a827b68d1..b6f1cb4f5b 100644 --- a/app/Console/Commands/BackupUpdate.php +++ b/app/Console/Commands/BackupUpdate.php @@ -11,13 +11,13 @@ namespace App\Console\Commands; -use App\Utils\Ninja; +use App\Libraries\MultiDB; use App\Models\Backup; use App\Models\Client; use App\Models\Company; use App\Models\Document; -use App\Libraries\MultiDB; use App\Models\GroupSetting; +use App\Utils\Ninja; use Illuminate\Console\Command; use Illuminate\Support\Facades\Storage; @@ -56,8 +56,9 @@ class BackupUpdate extends Command { //always return state to first DB - if(Ninja::isSelfHost()) + if(Ninja::isSelfHost()) { return; + } $current_db = config('database.default'); diff --git a/app/Console/Commands/CheckData.php b/app/Console/Commands/CheckData.php index 7c5a3f21bb..89492f8fe2 100644 --- a/app/Console/Commands/CheckData.php +++ b/app/Console/Commands/CheckData.php @@ -12,38 +12,37 @@ namespace App\Console\Commands; use App; -use Exception; -use App\Models\User; -use App\Utils\Ninja; -use App\Models\Quote; -use App\Models\Client; -use App\Models\Credit; -use App\Models\Vendor; -use App\Models\Account; -use App\Models\Company; -use App\Models\Contact; -use App\Models\Invoice; -use App\Models\Payment; -use App\Models\CompanyUser; -use Illuminate\Support\Str; -use App\Models\CompanyToken; -use App\Models\ClientContact; -use App\Models\CompanyLedger; -use App\Models\PurchaseOrder; -use App\Models\VendorContact; -use App\Models\BankTransaction; -use App\Models\QuoteInvitation; -use Illuminate\Console\Command; -use App\Models\CreditInvitation; -use App\Models\RecurringInvoice; -use App\Models\InvoiceInvitation; use App\DataMapper\ClientSettings; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Mail; use App\Factory\ClientContactFactory; use App\Factory\VendorContactFactory; use App\Jobs\Company\CreateCompanyToken; +use App\Models\Account; +use App\Models\BankTransaction; +use App\Models\Client; +use App\Models\ClientContact; +use App\Models\Company; +use App\Models\CompanyLedger; +use App\Models\CompanyToken; +use App\Models\CompanyUser; +use App\Models\Contact; +use App\Models\Credit; +use App\Models\CreditInvitation; +use App\Models\Invoice; +use App\Models\InvoiceInvitation; +use App\Models\Payment; +use App\Models\PurchaseOrder; +use App\Models\Quote; +use App\Models\QuoteInvitation; +use App\Models\RecurringInvoice; use App\Models\RecurringInvoiceInvitation; +use App\Models\User; +use App\Models\Vendor; +use App\Models\VendorContact; +use App\Utils\Ninja; +use Illuminate\Console\Command; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Str; use Symfony\Component\Console\Input\InputOption; /* @@ -185,8 +184,7 @@ class CheckData extends Command if ($cu->company && $cu->user) { (new CreateCompanyToken($cu->company, $cu->user, 'System'))->handle(); - } - else { + } else { // $cu->forceDelete(); } } @@ -445,7 +443,7 @@ class CheckData extends Command QuoteInvitation::where('deleted_at', "0000-00-00 00:00:00.000000")->withTrashed()->update(['deleted_at' => null]); CreditInvitation::where('deleted_at', "0000-00-00 00:00:00.000000")->withTrashed()->update(['deleted_at' => null]); - InvoiceInvitation::where('sent_date', '0000-00-00 00:00:00')->cursor()->each(function ($ii){ + InvoiceInvitation::where('sent_date', '0000-00-00 00:00:00')->cursor()->each(function ($ii) { $ii->sent_date = null; $ii->saveQuietly(); }); @@ -619,7 +617,7 @@ class CheckData extends Command } $this->logMessage("{$this->wrong_paid_to_dates} clients with incorrect paid to dates"); - } + } private function clientBalanceQuery() { @@ -1028,7 +1026,7 @@ class CheckData extends Command { $this->logMessage("checking bank transactions"); - BankTransaction::with('payment')->withTrashed()->where('invoice_ids', ',,,,,,,,')->cursor()->each(function ($bt){ + BankTransaction::with('payment')->withTrashed()->where('invoice_ids', ',,,,,,,,')->cursor()->each(function ($bt) { if($bt->payment->exists()) { @@ -1052,7 +1050,7 @@ class CheckData extends Command if ($this->option('fix') == 'true') { - $q->cursor()->each(function ($c){ + $q->cursor()->each(function ($c) { $c->send_email = false; $c->saveQuietly(); diff --git a/app/Console/Commands/CreateAccount.php b/app/Console/Commands/CreateAccount.php index d2ff960b20..d56433d42b 100644 --- a/app/Console/Commands/CreateAccount.php +++ b/app/Console/Commands/CreateAccount.php @@ -20,7 +20,6 @@ use App\Models\Account; use App\Models\Company; use App\Models\CompanyToken; use App\Models\User; -use App\Repositories\InvoiceRepository; use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\MakesHash; use Illuminate\Console\Command; diff --git a/app/Console/Commands/CreateSingleAccount.php b/app/Console/Commands/CreateSingleAccount.php index a28eb64e23..33196439f7 100644 --- a/app/Console/Commands/CreateSingleAccount.php +++ b/app/Console/Commands/CreateSingleAccount.php @@ -36,6 +36,7 @@ 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; @@ -303,6 +304,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) @@ -403,7 +460,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; @@ -885,11 +942,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; diff --git a/app/Console/Commands/CreateTestData.php b/app/Console/Commands/CreateTestData.php index ca8417573c..b882c9af6d 100644 --- a/app/Console/Commands/CreateTestData.php +++ b/app/Console/Commands/CreateTestData.php @@ -496,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; diff --git a/app/Console/Commands/ImportMigrations.php b/app/Console/Commands/ImportMigrations.php index 9e227c72bd..57d25844cb 100644 --- a/app/Console/Commands/ImportMigrations.php +++ b/app/Console/Commands/ImportMigrations.php @@ -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; diff --git a/app/Console/Commands/ReactBuilder.php b/app/Console/Commands/ReactBuilder.php index 6e8b3e10be..d40a250323 100644 --- a/app/Console/Commands/ReactBuilder.php +++ b/app/Console/Commands/ReactBuilder.php @@ -52,8 +52,7 @@ class ReactBuilder extends Command try { $directoryIterator = new \RecursiveDirectoryIterator(public_path('react/v'.config('ninja.app_version').'/'), \RecursiveDirectoryIterator::SKIP_DOTS); - } - catch (\Exception $e) { + } catch (\Exception $e) { $this->error('React files not found'); return; } @@ -61,14 +60,14 @@ class ReactBuilder extends Command foreach (new \RecursiveIteratorIterator($directoryIterator) as $file) { if ($file->getExtension() == 'js') { if (str_contains($file->getFileName(), 'index-')) { - $includes .= ''."\n"; + $includes .= ''."\n"; } else { - $includes .= ''."\n"; + $includes .= ''."\n"; } } if (str_contains($file->getFileName(), '.css')) { - $includes .= ''."\n"; + $includes .= ''."\n"; } } diff --git a/app/Console/Commands/SendRemindersCron.php b/app/Console/Commands/SendRemindersCron.php index 8328611b83..ab55404fec 100644 --- a/app/Console/Commands/SendRemindersCron.php +++ b/app/Console/Commands/SendRemindersCron.php @@ -174,7 +174,7 @@ class SendRemindersCron extends Command $invoice->calc()->getInvoice()->save(); $invoice->fresh(); // $invoice->service()->deletePdf()->save(); - if ($invoice->client->getSetting('enable_e_invoice')){ + if ($invoice->client->getSetting('enable_e_invoice')) { $invoice->service()->deleteEInvoice()->save(); } diff --git a/app/Console/Commands/SendTestEmails.php b/app/Console/Commands/SendTestEmails.php index 5a113e8f2f..9895355ac9 100644 --- a/app/Console/Commands/SendTestEmails.php +++ b/app/Console/Commands/SendTestEmails.php @@ -11,17 +11,10 @@ namespace App\Console\Commands; -use Faker\Factory; -use App\Models\User; -use App\Models\Account; -use App\Models\Company; -use App\Mail\TestMailServer; -use Illuminate\Console\Command; -use App\Jobs\Mail\NinjaMailerJob; -use App\DataMapper\CompanySettings; -use App\DataMapper\DefaultSettings; use App\Jobs\Mail\NinjaMailerObject; -use App\Mail\Migration\MaxCompanies; +use App\Mail\TestMailServer; +use App\Models\User; +use Illuminate\Console\Command; use Illuminate\Support\Facades\Mail; class SendTestEmails extends Command diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index c66cdc40e5..f67dc4af68 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -11,29 +11,29 @@ namespace App\Console; -use App\Utils\Ninja; -use App\Models\Account; -use App\Jobs\Ninja\QueueSize; -use App\Jobs\Util\DiskCleanup; -use App\Jobs\Util\ReminderJob; use App\Jobs\Cron\AutoBillCron; -use App\Jobs\Util\VersionCheck; -use App\Jobs\Ninja\TaskScheduler; -use App\Jobs\Util\SchedulerCheck; -use App\Jobs\Ninja\CheckACHStatus; -use App\Jobs\Cron\SubscriptionCron; -use App\Jobs\Ninja\AdjustEmailQuota; -use App\Jobs\Ninja\CompanySizeCheck; -use App\Jobs\Ninja\SystemMaintenance; -use App\Jobs\Quote\QuoteCheckExpired; -use App\Jobs\Util\UpdateExchangeRates; -use App\Jobs\Ninja\BankTransactionSync; use App\Jobs\Cron\RecurringExpensesCron; use App\Jobs\Cron\RecurringInvoicesCron; +use App\Jobs\Cron\SubscriptionCron; use App\Jobs\Cron\UpdateCalculatedFields; -use Illuminate\Console\Scheduling\Schedule; 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\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\UpdateExchangeRates; +use App\Jobs\Util\VersionCheck; +use App\Models\Account; +use App\Utils\Ninja; +use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; class Kernel extends ConsoleKernel diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index bebf00b65d..814dc40383 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -481,14 +481,27 @@ class CompanySettings extends BaseSettings public $enable_e_invoice = false; - public $classification = ''; // individual, company, partnership, trust, charity, government, other + 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 static $casts = [ - 'enable_e_invoice' => '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', + 'e_invoice_type' => 'string', + 'mailgun_endpoint' => 'string', 'client_initiated_payments' => 'bool', 'client_initiated_payments_minimum' => 'float', 'sync_invoice_quote_columns' => 'bool', @@ -768,6 +781,8 @@ class CompanySettings extends BaseSettings 'quote_design_id', 'credit_design_id', 'purchase_order_design_id', + 'statement_design_id', + 'delivery_note_design_id', ]; // /** @@ -999,6 +1014,15 @@ class CompanySettings extends BaseSettings '$total', '$credit.balance', ], + 'statement_details' => [ + '$statement_date', + '$balance' + ], + 'delivery_note_columns' => [ + '$product.item', + '$product.description', + '$product.quantity', + ], ]; return json_decode(json_encode($variables)); diff --git a/app/DataMapper/DefaultSettings.php b/app/DataMapper/DefaultSettings.php index 6451370cf6..e87fdf7993 100644 --- a/app/DataMapper/DefaultSettings.php +++ b/app/DataMapper/DefaultSettings.php @@ -34,4 +34,4 @@ class DefaultSettings extends BaseSettings ]; } -} \ No newline at end of file +} diff --git a/app/DataMapper/FreeCompanySettings.php b/app/DataMapper/FreeCompanySettings.php index 9866862a96..0ffe60f865 100644 --- a/app/DataMapper/FreeCompanySettings.php +++ b/app/DataMapper/FreeCompanySettings.php @@ -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; diff --git a/app/DataMapper/Settings/SettingsData.php b/app/DataMapper/Settings/SettingsData.php index b76379806f..2584a56540 100644 --- a/app/DataMapper/Settings/SettingsData.php +++ b/app/DataMapper/Settings/SettingsData.php @@ -10,7 +10,7 @@ */ namespace App\DataMapper\Settings; -class SettingsData +class SettingsData { public bool $auto_archive_invoice = false; // @implemented @@ -355,7 +355,7 @@ class SettingsData public string $email = ''; //@implemented - public string $country_id; //@implemented + public string $country_id = ''; //@implemented public string $vat_number = ''; //@implemented @@ -469,27 +469,28 @@ class SettingsData public function cast(mixed $object) { - if(is_array($object)) + if(is_array($object)) { $object = (object)$object; + } if (is_object($object)) { foreach ($object as $key => $value) { - try{ + try { settype($object->{$key}, gettype($this->{$key})); - } - catch(\Exception | \Error | \Throwable $e){ + } catch(\Exception | \Error | \Throwable $e) { - if(property_exists($this, $key)) + if(property_exists($this, $key)) { $object->{$key} = $this->{$key}; - else + } 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}; // } @@ -512,4 +513,4 @@ class SettingsData { return (array)$this->object; } -} \ No newline at end of file +} diff --git a/app/DataMapper/Tax/BaseRule.php b/app/DataMapper/Tax/BaseRule.php index ba81697345..1888662326 100644 --- a/app/DataMapper/Tax/BaseRule.php +++ b/app/DataMapper/Tax/BaseRule.php @@ -11,11 +11,11 @@ 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; -use App\DataProviders\USStates; -use App\DataMapper\Tax\ZipTax\Response; class BaseRule implements RuleInterface { @@ -66,7 +66,7 @@ class BaseRule implements RuleInterface 'SK', // Slovakia ]; - public array $region_codes = [ + public array $region_codes = [ 'AT' => 'EU', // Austria 'BE' => 'EU', // Belgium 'BG' => 'EU', // Bulgaria @@ -147,8 +147,9 @@ class BaseRule implements RuleInterface $this->resolveRegions(); - if(!$this->isTaxableRegion()) + if(!$this->isTaxableRegion()) { return $this; + } $this->configTaxData(); @@ -173,26 +174,26 @@ class BaseRule implements RuleInterface /** 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) + 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'){ + 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; + if(!$company->origin_tax_data) { + $this->should_calc_tax = false; return $this; } @@ -201,8 +202,7 @@ class BaseRule implements RuleInterface $tax_data = $company->origin_tax_data; - } - elseif($this->client->tax_data){ + } elseif($this->client->tax_data) { $tax_data = $this->client->tax_data; @@ -215,8 +215,9 @@ class BaseRule implements RuleInterface $this->invoice->tax_data = $tax_data; - if(\DB::transactionLevel() == 0) + if(\DB::transactionLevel() == 0) { $this->invoice->saveQuietly(); + } } return $this; @@ -234,7 +235,7 @@ class BaseRule implements RuleInterface $this->client_region = $this->region_codes[$this->client->country->iso_3166_2]; - match($this->client_region){ + 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', @@ -251,8 +252,9 @@ class BaseRule implements RuleInterface $states = USStates::$states; - if(isset($states[$this->client->state])) + 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); @@ -263,7 +265,7 @@ class BaseRule implements RuleInterface public function isTaxableRegion(): bool { - return $this->client->company->tax_data->regions->{$this->client_region}->tax_all_subregions || + 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); } @@ -277,8 +279,7 @@ class BaseRule implements RuleInterface return $this; - } - elseif($this->client_region == 'AU'){ //these are defaults and are only stubbed out for now, for AU we can actually remove these + } 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; diff --git a/app/DataMapper/Tax/DE/Rule.php b/app/DataMapper/Tax/DE/Rule.php index 8214188c36..5296674618 100644 --- a/app/DataMapper/Tax/DE/Rule.php +++ b/app/DataMapper/Tax/DE/Rule.php @@ -11,12 +11,12 @@ namespace App\DataMapper\Tax\DE; -use App\Models\Product; 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'; @@ -67,7 +67,7 @@ class Rule extends BaseRule implements RuleInterface return $this->taxExempt($item); } - match(intval($item->tax_id)){ + 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), @@ -220,39 +220,28 @@ class Rule extends BaseRule implements RuleInterface // nlog("tax exempt"); $this->tax_rate = 0; $this->reduced_tax_rate = 0; - } - elseif($this->client_subregion != $this->client->company->tax_data->seller_subregion && in_array($this->client_subregion, $this->eu_country_codes) && $this->client->vat_number && $this->eu_business_tax_exempt) - // elseif($this->client_subregion != $this->client->company->tax_data->seller_subregion && in_array($this->client_subregion, $this->eu_country_codes) && $this->client->has_valid_vat_number && $this->eu_business_tax_exempt) - { + } elseif($this->client_subregion != $this->client->company->tax_data->seller_subregion && in_array($this->client_subregion, $this->eu_country_codes) && $this->client->vat_number && $this->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 - { + } 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)) - { + } elseif(!in_array($this->client_subregion, $this->eu_country_codes)) { $this->defaultForeign(); - } - elseif(in_array($this->client_subregion, $this->eu_country_codes) && !$this->client->vat_number) //eu country / no valid vat - { - if(($this->client->company->tax_data->seller_subregion != $this->client_subregion) && $this->client->company->tax_data->regions->EU->has_sales_above_threshold) - { + } elseif(in_array($this->client_subregion, $this->eu_country_codes) && !$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 { + } 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 { + } 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; @@ -262,4 +251,4 @@ class Rule extends BaseRule implements RuleInterface } -} \ No newline at end of file +} diff --git a/app/DataMapper/Tax/RuleInterface.php b/app/DataMapper/Tax/RuleInterface.php index e4ff2c0e2d..73994de722 100644 --- a/app/DataMapper/Tax/RuleInterface.php +++ b/app/DataMapper/Tax/RuleInterface.php @@ -36,4 +36,4 @@ interface RuleInterface public function override($item); public function calculateRates(); -} \ No newline at end of file +} diff --git a/app/DataMapper/Tax/TaxData.php b/app/DataMapper/Tax/TaxData.php index c1696ef2a4..945a3d0a46 100644 --- a/app/DataMapper/Tax/TaxData.php +++ b/app/DataMapper/Tax/TaxData.php @@ -15,7 +15,7 @@ use App\DataMapper\Tax\ZipTax\Response; /** * InvoiceTaxData - * + * * Definition for the invoice tax data structure */ class TaxData diff --git a/app/DataMapper/Tax/TaxModel.php b/app/DataMapper/Tax/TaxModel.php index 8fd686e88f..ca6d41a3f4 100644 --- a/app/DataMapper/Tax/TaxModel.php +++ b/app/DataMapper/Tax/TaxModel.php @@ -11,7 +11,7 @@ namespace App\DataMapper\Tax; -class TaxModel +class TaxModel { /** @var string $seller_subregion */ @@ -32,10 +32,11 @@ class TaxModel public function __construct(public ?TaxModel $model = null) { - if(!$this->model) + if(!$this->model) { $this->regions = $this->init(); - else + } else { $this->regions = $model; + } } @@ -79,7 +80,7 @@ class TaxModel * * @return self */ - private function auSubRegions(): self + private function auSubRegions(): self { $this->regions->AU->subregions = new \stdClass(); @@ -387,7 +388,7 @@ class TaxModel $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->tax_name = 'KM'; $this->regions->EU->subregions->EE->reduced_tax_rate = 9; $this->regions->EU->subregions->EE->apply_tax = false; diff --git a/app/DataMapper/Tax/US/Rule.php b/app/DataMapper/Tax/US/Rule.php index e880027201..0a7179113b 100644 --- a/app/DataMapper/Tax/US/Rule.php +++ b/app/DataMapper/Tax/US/Rule.php @@ -74,7 +74,7 @@ class Rule extends BaseRule implements RuleInterface 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_OVERRIDE_TAX => $this->override($item), Product::PRODUCT_TYPE_ZERO_RATED => $this->zeroRated($item), default => $this->default($item), }; @@ -117,10 +117,9 @@ class Rule extends BaseRule implements RuleInterface */ public function taxService($item): self { - if(in_array($this->tax_data?->txbService,['Y','L'])) { + if(in_array($this->tax_data?->txbService, ['Y','L'])) { $this->default($item); - } - else { + } else { $this->taxExempt($item); } @@ -224,4 +223,4 @@ class Rule extends BaseRule implements RuleInterface return $this; } -} \ No newline at end of file +} diff --git a/app/DataMapper/Tax/ZipTax/Response.php b/app/DataMapper/Tax/ZipTax/Response.php index 4a8e036343..a64f275402 100644 --- a/app/DataMapper/Tax/ZipTax/Response.php +++ b/app/DataMapper/Tax/ZipTax/Response.php @@ -53,7 +53,7 @@ class Response * "district5SalesTax" => 0, * "district5UseTax" => 0, * "originDestination" => "D", - * + * * ]; * */ @@ -114,4 +114,3 @@ class Response } } - diff --git a/app/DataProviders/USStates.php b/app/DataProviders/USStates.php index 4809df8439..75c2059786 100644 --- a/app/DataProviders/USStates.php +++ b/app/DataProviders/USStates.php @@ -33868,18 +33868,21 @@ class USStates public static function getState(?string $zip = '90210'): string { - if(isset(self::$zip_code_map[$zip])) + if(isset(self::$zip_code_map[$zip])) { return self::$zip_code_map[$zip]; + } $prefix_state = self::getStateFromThreeDigitPrefix($zip); - if($prefix_state) + if($prefix_state) { return $prefix_state; + } $zippo_response = self::getStateFromZippo($zip); - if($zippo_response) + if($zippo_response) { return $zippo_response; + } throw new \Exception('Zip code not found'); } @@ -33905,8 +33908,9 @@ class USStates $response = Http::get("https://api.zippopotam.us/us/{$zip}"); - if($response->failed()) + if($response->failed()) { return false; + } $data = $response->object(); @@ -33921,7 +33925,7 @@ class USStates public static function getStateFromThreeDigitPrefix($zip): mixed { - /* 000 to 999 */ + /* 000 to 999 */ $zip_by_state = [ '--', '--', '--', '--', '--', 'NY', 'PR', 'PR', 'VI', 'PR', 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', @@ -34004,7 +34008,7 @@ class USStates $prefix = substr($zip, 0, 3); $index = intval($prefix); - /* converts prefix to integer */ + /* converts prefix to integer */ return $zip_by_state[$index] == "--" ? false : $zip_by_state[$index]; } diff --git a/app/Events/Account/AccountCreated.php b/app/Events/Account/AccountCreated.php index 0761cdf4b9..4cd6d46ee0 100644 --- a/app/Events/Account/AccountCreated.php +++ b/app/Events/Account/AccountCreated.php @@ -49,9 +49,9 @@ class AccountCreated // * // * @return Channel|array // */ - public function broadcastOn() - { + public function broadcastOn() + { return []; - // return new PrivateChannel('channel-name'); - } + // return new PrivateChannel('channel-name'); + } } diff --git a/app/Events/Account/StripeConnectFailure.php b/app/Events/Account/StripeConnectFailure.php index fd6f5a5d57..ebe501512b 100644 --- a/app/Events/Account/StripeConnectFailure.php +++ b/app/Events/Account/StripeConnectFailure.php @@ -12,11 +12,9 @@ namespace App\Events\Account; use App\Models\Company; -use Illuminate\Broadcasting\Channel; -use Illuminate\Queue\SerializesModels; -use Illuminate\Broadcasting\PrivateChannel; -use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Broadcasting\InteractsWithSockets; +use Illuminate\Foundation\Events\Dispatchable; +use Illuminate\Queue\SerializesModels; /** * Class StripeConnectFailure. diff --git a/app/Events/Client/ClientWasArchived.php b/app/Events/Client/ClientWasArchived.php index aca000b8e5..46aac9cba8 100644 --- a/app/Events/Client/ClientWasArchived.php +++ b/app/Events/Client/ClientWasArchived.php @@ -15,7 +15,6 @@ use App\Models\Client; use App\Models\Company; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; @@ -54,8 +53,8 @@ class ClientWasArchived // * // * @return Channel|array // */ - public function broadcastOn() - { + public function broadcastOn() + { return []; - } + } } diff --git a/app/Events/Company/CompanyDocumentsDeleted.php b/app/Events/Company/CompanyDocumentsDeleted.php index 2c8f11c4f2..831a4958d1 100644 --- a/app/Events/Company/CompanyDocumentsDeleted.php +++ b/app/Events/Company/CompanyDocumentsDeleted.php @@ -14,7 +14,6 @@ namespace App\Events\Company; use App\Models\Company; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; @@ -42,8 +41,8 @@ class CompanyDocumentsDeleted * * @return Channel|array */ - public function broadcastOn() - { -return []; - } + public function broadcastOn() + { + return []; + } } diff --git a/app/Events/Contact/ContactLoggedIn.php b/app/Events/Contact/ContactLoggedIn.php index 0e98a54449..e2fa77103f 100644 --- a/app/Events/Contact/ContactLoggedIn.php +++ b/app/Events/Contact/ContactLoggedIn.php @@ -14,7 +14,6 @@ namespace App\Events\Contact; use App\Models\ClientContact; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; @@ -50,8 +49,8 @@ class ContactLoggedIn * * @return Channel|array */ - public function broadcastOn() - { + public function broadcastOn() + { return []; - } + } } diff --git a/app/Events/Design/DesignWasArchived.php b/app/Events/Design/DesignWasArchived.php index 1dd9105c7c..a3745552b2 100644 --- a/app/Events/Design/DesignWasArchived.php +++ b/app/Events/Design/DesignWasArchived.php @@ -15,7 +15,6 @@ use App\Models\Company; use App\Models\Design; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; @@ -35,8 +34,8 @@ class DesignWasArchived * * @return Channel|array */ - public function broadcastOn() - { + public function broadcastOn() + { return []; - } + } } diff --git a/app/Events/Design/DesignWasCreated.php b/app/Events/Design/DesignWasCreated.php index 213eb98824..7cbf9aeec7 100644 --- a/app/Events/Design/DesignWasCreated.php +++ b/app/Events/Design/DesignWasCreated.php @@ -11,10 +11,10 @@ namespace App\Events\Design; -use App\Models\Design; use App\Models\Company; -use Illuminate\Queue\SerializesModels; +use App\Models\Design; use Illuminate\Broadcasting\PrivateChannel; +use Illuminate\Queue\SerializesModels; /** * Class DesignWasCreated. @@ -32,8 +32,8 @@ class DesignWasCreated * * @return PrivateChannel|array */ - public function broadcastOn() - { + public function broadcastOn() + { return []; - } + } } diff --git a/app/Events/Design/DesignWasDeleted.php b/app/Events/Design/DesignWasDeleted.php index d9142724a0..fe21e18646 100644 --- a/app/Events/Design/DesignWasDeleted.php +++ b/app/Events/Design/DesignWasDeleted.php @@ -11,10 +11,10 @@ namespace App\Events\Design; -use App\Models\Design; use App\Models\Company; -use Illuminate\Queue\SerializesModels; +use App\Models\Design; use Illuminate\Broadcasting\PrivateChannel; +use Illuminate\Queue\SerializesModels; /** * Class DesignWasDeleted. @@ -32,8 +32,8 @@ class DesignWasDeleted * * @return PrivateChannel|array */ - public function broadcastOn() - { + public function broadcastOn() + { return []; - } + } } diff --git a/app/Events/Design/DesignWasRestored.php b/app/Events/Design/DesignWasRestored.php index 6daa2c2545..c237d1f2c9 100644 --- a/app/Events/Design/DesignWasRestored.php +++ b/app/Events/Design/DesignWasRestored.php @@ -11,10 +11,10 @@ namespace App\Events\Design; -use App\Models\Design; use App\Models\Company; -use Illuminate\Queue\SerializesModels; +use App\Models\Design; use Illuminate\Broadcasting\PrivateChannel; +use Illuminate\Queue\SerializesModels; /** * Class DesignWasRestored. @@ -32,8 +32,8 @@ class DesignWasRestored * * @return PrivateChannel|array */ - public function broadcastOn() - { + public function broadcastOn() + { return []; - } + } } diff --git a/app/Events/Design/DesignWasUpdated.php b/app/Events/Design/DesignWasUpdated.php index 3b215fdff2..bf18bd88cf 100644 --- a/app/Events/Design/DesignWasUpdated.php +++ b/app/Events/Design/DesignWasUpdated.php @@ -11,10 +11,10 @@ namespace App\Events\Design; -use App\Models\Design; use App\Models\Company; -use Illuminate\Queue\SerializesModels; +use App\Models\Design; use Illuminate\Broadcasting\PrivateChannel; +use Illuminate\Queue\SerializesModels; /** * Class DesignWasUpdated. @@ -32,8 +32,8 @@ class DesignWasUpdated * * @return PrivateChannel|array */ - public function broadcastOn() - { + public function broadcastOn() + { return []; - } + } } diff --git a/app/Events/Document/DocumentWasArchived.php b/app/Events/Document/DocumentWasArchived.php index 229e7d6c16..edfa4bea85 100644 --- a/app/Events/Document/DocumentWasArchived.php +++ b/app/Events/Document/DocumentWasArchived.php @@ -15,7 +15,6 @@ use App\Models\Company; use App\Models\Document; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; diff --git a/app/Events/Payment/Methods/MethodDeleted.php b/app/Events/Payment/Methods/MethodDeleted.php index 5e80e20f8a..f205d872c8 100644 --- a/app/Events/Payment/Methods/MethodDeleted.php +++ b/app/Events/Payment/Methods/MethodDeleted.php @@ -15,7 +15,6 @@ use App\Models\ClientGatewayToken; use App\Models\Company; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; @@ -51,8 +50,8 @@ class MethodDeleted * * @return Channel|array */ - public function broadcastOn() - { + public function broadcastOn() + { return []; - } + } } diff --git a/app/Events/Payment/PaymentWasEmailed.php b/app/Events/Payment/PaymentWasEmailed.php index 284d52de48..767918ab03 100644 --- a/app/Events/Payment/PaymentWasEmailed.php +++ b/app/Events/Payment/PaymentWasEmailed.php @@ -11,12 +11,12 @@ namespace App\Events\Payment; +use App\Models\ClientContact; use App\Models\Company; use App\Models\Payment; -use App\Models\ClientContact; -use Illuminate\Queue\SerializesModels; -use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Broadcasting\InteractsWithSockets; +use Illuminate\Foundation\Events\Dispatchable; +use Illuminate\Queue\SerializesModels; /** * Class PaymentWasEmailed. diff --git a/app/Events/PurchaseOrder/PurchaseOrderWasViewed.php b/app/Events/PurchaseOrder/PurchaseOrderWasViewed.php index a144639a68..c9555bd90c 100644 --- a/app/Events/PurchaseOrder/PurchaseOrderWasViewed.php +++ b/app/Events/PurchaseOrder/PurchaseOrderWasViewed.php @@ -12,7 +12,6 @@ namespace App\Events\PurchaseOrder; use App\Models\Company; -use App\Models\PurchaseOrder; use App\Models\PurchaseOrderInvitation; use Illuminate\Queue\SerializesModels; diff --git a/app/Events/Quote/QuoteWasEmailed.php b/app/Events/Quote/QuoteWasEmailed.php index 5792167c42..ef8055edd4 100644 --- a/app/Events/Quote/QuoteWasEmailed.php +++ b/app/Events/Quote/QuoteWasEmailed.php @@ -12,7 +12,6 @@ namespace App\Events\Quote; use App\Models\Company; -use App\Models\Quote; use App\Models\QuoteInvitation; use Illuminate\Queue\SerializesModels; diff --git a/app/Events/Subscription/SubscriptionWasCreated.php b/app/Events/Subscription/SubscriptionWasCreated.php index 9d1616defb..fff93b1507 100644 --- a/app/Events/Subscription/SubscriptionWasCreated.php +++ b/app/Events/Subscription/SubscriptionWasCreated.php @@ -5,7 +5,6 @@ namespace App\Events\Subscription; use App\Models\Company; use App\Models\Subscription; use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; @@ -45,8 +44,8 @@ class SubscriptionWasCreated * * @return \Illuminate\Broadcasting\Channel|array */ - public function broadcastOn() - { + public function broadcastOn() + { return []; - } + } } diff --git a/app/Events/User/UserLoggedIn.php b/app/Events/User/UserLoggedIn.php index f3b55c2a1c..bddb7104ec 100644 --- a/app/Events/User/UserLoggedIn.php +++ b/app/Events/User/UserLoggedIn.php @@ -15,7 +15,6 @@ use App\Models\Company; use App\Models\User; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; @@ -35,8 +34,8 @@ class UserLoggedIn * * @return Channel|array */ - public function broadcastOn() - { + public function broadcastOn() + { return []; - } + } } diff --git a/app/Events/User/UserWasArchived.php b/app/Events/User/UserWasArchived.php index 51657baffb..cb7077668f 100644 --- a/app/Events/User/UserWasArchived.php +++ b/app/Events/User/UserWasArchived.php @@ -15,7 +15,6 @@ use App\Models\Company; use App\Models\User; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; @@ -35,8 +34,8 @@ class UserWasArchived * * @return Channel|array */ - public function broadcastOn() - { + public function broadcastOn() + { return []; - } + } } diff --git a/app/Events/User/UserWasCreated.php b/app/Events/User/UserWasCreated.php index c4bba87846..5d30fa5261 100644 --- a/app/Events/User/UserWasCreated.php +++ b/app/Events/User/UserWasCreated.php @@ -15,7 +15,6 @@ use App\Models\Company; use App\Models\User; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; @@ -35,8 +34,8 @@ class UserWasCreated * * @return Channel|array */ - public function broadcastOn() - { + public function broadcastOn() + { return []; - } + } } diff --git a/app/Events/User/UserWasDeleted.php b/app/Events/User/UserWasDeleted.php index cae639e208..d7323d78d5 100644 --- a/app/Events/User/UserWasDeleted.php +++ b/app/Events/User/UserWasDeleted.php @@ -15,7 +15,6 @@ use App\Models\Company; use App\Models\User; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; @@ -54,8 +53,8 @@ class UserWasDeleted * * @return Channel|array */ - public function broadcastOn() - { + public function broadcastOn() + { return []; - } + } } diff --git a/app/Events/User/UserWasRestored.php b/app/Events/User/UserWasRestored.php index 2d722fd189..196a73e451 100644 --- a/app/Events/User/UserWasRestored.php +++ b/app/Events/User/UserWasRestored.php @@ -15,7 +15,6 @@ use App\Models\Company; use App\Models\User; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; @@ -54,8 +53,8 @@ class UserWasRestored * * @return Channel|array */ - public function broadcastOn() - { + public function broadcastOn() + { return []; - } + } } diff --git a/app/Events/User/UserWasUpdated.php b/app/Events/User/UserWasUpdated.php index a4eda9752c..ef495814a7 100644 --- a/app/Events/User/UserWasUpdated.php +++ b/app/Events/User/UserWasUpdated.php @@ -15,7 +15,6 @@ use App\Models\Company; use App\Models\User; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; @@ -54,8 +53,8 @@ class UserWasUpdated * * @return Channel|array */ - public function broadcastOn() - { + public function broadcastOn() + { return []; - } + } } diff --git a/app/Events/Vendor/VendorContactLoggedIn.php b/app/Events/Vendor/VendorContactLoggedIn.php index 2422af1023..9a0ddad948 100644 --- a/app/Events/Vendor/VendorContactLoggedIn.php +++ b/app/Events/Vendor/VendorContactLoggedIn.php @@ -14,9 +14,9 @@ namespace App\Events\Vendor; use App\Models\Company; use App\Models\VendorContact; use Illuminate\Broadcasting\Channel; -use Illuminate\Queue\SerializesModels; -use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Broadcasting\InteractsWithSockets; +use Illuminate\Foundation\Events\Dispatchable; +use Illuminate\Queue\SerializesModels; /** * Class VendorContactLoggedIn. diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 4e8f339a39..e993581e5a 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -11,31 +11,31 @@ namespace App\Exceptions; -use Throwable; -use PDOException; use App\Utils\Ninja; -use Sentry\State\Scope; -use Illuminate\Support\Arr; -use Illuminate\Http\Request; -use InvalidArgumentException; -use Sentry\Laravel\Integration; -use Illuminate\Support\Facades\Schema; use Aws\Exception\CredentialsException; use GuzzleHttp\Exception\ConnectException; -use Illuminate\Auth\AuthenticationException; -use League\Flysystem\UnableToCreateDirectory; -use Illuminate\Session\TokenMismatchException; -use Illuminate\Validation\ValidationException; use Illuminate\Auth\Access\AuthorizationException; -use Illuminate\Queue\MaxAttemptsExceededException; -use Illuminate\Http\Exceptions\ThrottleRequestsException; -use Symfony\Component\Process\Exception\RuntimeException; +use Illuminate\Auth\AuthenticationException; +use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException; use Illuminate\Database\Eloquent\RelationNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; +use Illuminate\Http\Exceptions\ThrottleRequestsException; +use Illuminate\Http\Request; +use Illuminate\Queue\MaxAttemptsExceededException; +use Illuminate\Session\TokenMismatchException; +use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Schema; +use Illuminate\Validation\ValidationException; +use InvalidArgumentException; +use League\Flysystem\UnableToCreateDirectory; +use PDOException; +use Sentry\Laravel\Integration; +use Sentry\State\Scope; use Symfony\Component\Console\Exception\CommandNotFoundException; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; -use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\Process\Exception\RuntimeException; +use Throwable; class Handler extends ExceptionHandler { @@ -223,8 +223,8 @@ class Handler extends ExceptionHandler return response()->json(['message' => $exception->getMessage()], 500); } elseif ($exception instanceof ThrottleRequestsException && $request->expectsJson()) { return response()->json(['message'=>'Too many requests'], 429); - // } elseif ($exception instanceof FatalThrowableError && $request->expectsJson()) { - // return response()->json(['message'=>'Fatal error'], 500); //@deprecated + // } elseif ($exception instanceof FatalThrowableError && $request->expectsJson()) { + // return response()->json(['message'=>'Fatal error'], 500); //@deprecated } elseif ($exception instanceof AuthorizationException && $request->expectsJson()) { return response()->json(['message'=> $exception->getMessage()], 401); } elseif ($exception instanceof TokenMismatchException) { diff --git a/app/Exceptions/PaymentRefundFailed.php b/app/Exceptions/PaymentRefundFailed.php index 28e00858e8..9ba3a922fb 100644 --- a/app/Exceptions/PaymentRefundFailed.php +++ b/app/Exceptions/PaymentRefundFailed.php @@ -13,8 +13,8 @@ namespace App\Exceptions; use Exception; -use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; class PaymentRefundFailed extends Exception { diff --git a/app/Exceptions/QuoteConversion.php b/app/Exceptions/QuoteConversion.php index ceafdf8b49..a2e11e6374 100644 --- a/app/Exceptions/QuoteConversion.php +++ b/app/Exceptions/QuoteConversion.php @@ -13,8 +13,8 @@ namespace App\Exceptions; use Exception; -use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; class QuoteConversion extends Exception { diff --git a/app/Exceptions/YodleeApiException.php b/app/Exceptions/YodleeApiException.php index b56eedced3..c8150d18cb 100644 --- a/app/Exceptions/YodleeApiException.php +++ b/app/Exceptions/YodleeApiException.php @@ -13,8 +13,8 @@ namespace App\Exceptions; use Exception; -use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; class YodleeApiException extends Exception { diff --git a/app/Export/CSV/ActivityExport.php b/app/Export/CSV/ActivityExport.php index 3cd1c89632..e738f6371a 100644 --- a/app/Export/CSV/ActivityExport.php +++ b/app/Export/CSV/ActivityExport.php @@ -11,17 +11,17 @@ namespace App\Export\CSV; -use App\Models\Task; -use App\Utils\Ninja; -use League\Csv\Writer; -use App\Models\Company; -use App\Models\Activity; use App\Libraries\MultiDB; +use App\Models\Activity; +use App\Models\Company; use App\Models\DateFormat; +use App\Models\Task; +use App\Transformers\ActivityTransformer; +use App\Utils\Ninja; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\App; -use Illuminate\Database\Eloquent\Builder; -use App\Transformers\ActivityTransformer; +use League\Csv\Writer; class ActivityExport extends BaseExport { @@ -53,44 +53,44 @@ class ActivityExport extends BaseExport $headerdisplay = $this->buildHeader(); - $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ - return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; - })->toArray(); + $header = collect($this->input['report_keys'])->map(function ($key, $value) use ($headerdisplay) { + return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; + })->toArray(); - $report = $query->cursor() - ->map(function ($resource) { - $row = $this->buildActivityRow($resource); - return $this->processMetaData($row, $resource); - })->toArray(); + $report = $query->cursor() + ->map(function ($resource) { + $row = $this->buildActivityRow($resource); + return $this->processMetaData($row, $resource); + })->toArray(); return array_merge(['columns' => $header], $report); } private function buildActivityRow(Activity $activity): array { - return [ - Carbon::parse($activity->created_at)->format($this->date_format), - ctrans("texts.activity_{$activity->activity_type_id}",[ - 'payment_amount' => $activity->payment ? $activity->payment->amount : '', - 'adjustment' => $activity->payment ? $activity->payment->refunded : '', - 'client' => $activity->client ? $activity->client->present()->name() : '', - 'contact' => $activity->contact ? $activity->contact->present()->name() : '', - 'quote' => $activity->quote ? $activity->quote->number : '', - 'user' => $activity->user ? $activity->user->present()->name() : 'System', - 'expense' => $activity->expense ? $activity->expense->number : '', - 'invoice' => $activity->invoice ? $activity->invoice->number : '', - 'recurring_invoice' => $activity->recurring_invoice ? $activity->recurring_invoice->number : '', - 'payment' => $activity->payment ? $activity->payment->number : '', - 'credit' => $activity->credit ? $activity->credit->number : '', - 'task' => $activity->task ? $activity->task->number : '', - 'vendor' => $activity->vendor ? $activity->vendor->present()->name() : '', - 'purchase_order' => $activity->purchase_order ? $activity->purchase_order->number : '', - 'subscription' => $activity->subscription ? $activity->subscription->name : '', - 'vendor_contact' => $activity->vendor_contact ? $activity->vendor_contact->present()->name() : '', - 'recurring_expense' => $activity->recurring_expense ? $activity->recurring_expense->number : '', - ]), - $activity->ip, + return [ + Carbon::parse($activity->created_at)->format($this->date_format), + ctrans("texts.activity_{$activity->activity_type_id}", [ + 'payment_amount' => $activity->payment ? $activity->payment->amount : '', + 'adjustment' => $activity->payment ? $activity->payment->refunded : '', + 'client' => $activity->client ? $activity->client->present()->name() : '', + 'contact' => $activity->contact ? $activity->contact->present()->name() : '', + 'quote' => $activity->quote ? $activity->quote->number : '', + 'user' => $activity->user ? $activity->user->present()->name() : 'System', + 'expense' => $activity->expense ? $activity->expense->number : '', + 'invoice' => $activity->invoice ? $activity->invoice->number : '', + 'recurring_invoice' => $activity->recurring_invoice ? $activity->recurring_invoice->number : '', + 'payment' => $activity->payment ? $activity->payment->number : '', + 'credit' => $activity->credit ? $activity->credit->number : '', + 'task' => $activity->task ? $activity->task->number : '', + 'vendor' => $activity->vendor ? $activity->vendor->present()->name() : '', + 'purchase_order' => $activity->purchase_order ? $activity->purchase_order->number : '', + 'subscription' => $activity->subscription ? $activity->subscription->name : '', + 'vendor_contact' => $activity->vendor_contact ? $activity->vendor_contact->present()->name() : '', + 'recurring_expense' => $activity->recurring_expense ? $activity->recurring_expense->number : '', + ]), + $activity->ip, ]; } @@ -169,6 +169,6 @@ class ActivityExport extends BaseExport } return $clean_row; - } + } } diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index fa6ab80ef3..74474b98c7 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -11,30 +11,28 @@ namespace App\Export\CSV; -use App\Models\Activity; -use App\Models\Quote; -use App\Utils\Number; use App\Models\Client; -use App\Models\Credit; -use App\Utils\Helpers; +use App\Models\ClientContact; use App\Models\Company; +use App\Models\Credit; +use App\Models\Document; use App\Models\Expense; use App\Models\Invoice; use App\Models\Payment; -use App\Models\Document; -use League\Fractal\Manager; -use App\Models\ClientContact; -use App\Models\PurchaseOrder; -use App\Models\RecurringInvoice; -use Illuminate\Support\Carbon; -use App\Utils\Traits\MakesHash; -use App\Transformers\TaskTransformer; -use App\Transformers\PaymentTransformer; -use Illuminate\Database\Eloquent\Builder; -use League\Fractal\Serializer\ArraySerializer; use App\Models\Product; +use App\Models\PurchaseOrder; +use App\Models\Quote; +use App\Models\RecurringInvoice; use App\Models\Task; use App\Models\Vendor; +use App\Transformers\PaymentTransformer; +use App\Transformers\TaskTransformer; +use App\Utils\Helpers; +use App\Utils\Traits\MakesHash; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Carbon; +use League\Fractal\Manager; +use League\Fractal\Serializer\ArraySerializer; class BaseExport { @@ -172,7 +170,7 @@ class BaseExport 'recurring_invoice' => 'invoice.recurring_id', ]; - protected array $recurring_invoice_report_keys = [ + protected array $recurring_invoice_report_keys = [ "invoice_number" => "recurring_invoice.number", "amount" => "recurring_invoice.amount", "balance" => "recurring_invoice.balance", @@ -446,8 +444,7 @@ class BaseExport $client = Client::withTrashed()->find($this->input['client_id']); $this->client_description = $client->present()->name; return $query->where('client_id', $this->input['client_id']); - } - elseif(isset($this->input['clients']) && count($this->input['clients']) > 0) { + } elseif(isset($this->input['clients']) && count($this->input['clients']) > 0) { $this->client_description = 'Multiple Clients'; return $query->whereIn('client_id', $this->input['clients']); @@ -459,8 +456,9 @@ class BaseExport { $parts = explode(".", $key); - if(!is_array($parts) || count($parts) < 2) + if(!is_array($parts) || count($parts) < 2) { return ''; + } $value = ''; @@ -497,8 +495,9 @@ class BaseExport private function resolveVendorContactKey($column, $entity, $transformer) { - if(!$entity->vendor) + if(!$entity->vendor) { return ""; + } $primary_contact = $entity->vendor->primary_contact()->first() ?? $entity->vendor->contacts()->first(); @@ -510,18 +509,21 @@ class BaseExport private function resolveExpenseKey($column, $entity, $transformer) { - if($column == 'user' && $entity?->expense?->user) + if($column == 'user' && $entity?->expense?->user) { return $entity->expense->user->present()->name() ?? ' '; + } - if($column == 'assigned_user' && $entity?->expense?->assigned_user) + if($column == 'assigned_user' && $entity?->expense?->assigned_user) { return $entity->expense->assigned_user->present()->name() ?? ' '; + } if($column == 'category' && $entity->expense) { return $entity->expense->category?->name ?? ' '; } - if($entity instanceof Expense) + if($entity instanceof Expense) { return ''; + } $transformed_entity = $transformer->includeExpense($entity); @@ -529,11 +531,13 @@ class BaseExport $manager->setSerializer(new ArraySerializer()); $transformed_entity = $manager->createData($transformed_entity)->toArray(); - if(array_key_exists($column, $transformed_entity)) - return $transformed_entity[$column]; + if(array_key_exists($column, $transformed_entity)) { + return $transformed_entity[$column]; + } - if(property_exists($entity, $column)) + if(property_exists($entity, $column)) { return $entity?->{$column} ?? ''; + } nlog("export: Could not resolve expense key: {$column}"); @@ -560,8 +564,9 @@ class BaseExport private function resolveVendorKey($column, $entity, $transformer) { - if(!$entity->vendor) + if(!$entity->vendor) { return ''; + } $transformed_entity = $transformer->includeVendor($entity); @@ -569,24 +574,29 @@ class BaseExport $manager->setSerializer(new ArraySerializer()); $transformed_entity = $manager->createData($transformed_entity)->toArray(); - if($column == 'name') + if($column == 'name') { return $entity->vendor->present()->name() ?: ''; + } - if($column == 'user_id') + if($column == 'user_id') { return $entity->vendor->user->present()->name() ?: ''; + } - if($column == 'country_id') + if($column == 'country_id') { return $entity->vendor->country ? ctrans("texts.country_{$entity->vendor->country->name}") : ''; + } if ($column == 'currency_id') { return $entity->vendor->currency() ? $entity->vendor->currency()->code : $entity->company->currency()->code; } - if($column == 'status') + if($column == 'status') { return $entity->stringStatus($entity->status_id) ?: ''; + } - if(array_key_exists($column, $transformed_entity)) + if(array_key_exists($column, $transformed_entity)) { return $transformed_entity[$column]; + } // nlog("export: Could not resolve vendor key: {$column}"); @@ -598,8 +608,9 @@ class BaseExport private function resolveClientKey($column, $entity, $transformer) { - if(!$entity->client) + if(!$entity->client) { return ''; + } $transformed_client = $transformer->includeClient($entity); @@ -607,36 +618,46 @@ class BaseExport $manager->setSerializer(new ArraySerializer()); $transformed_client = $manager->createData($transformed_client)->toArray(); - if(in_array($column, ['client.name', 'name'])) + if(in_array($column, ['client.name', 'name'])) { return $transformed_client['display_name']; + } - if(in_array($column, ['client.user_id', 'user_id'])) + if(in_array($column, ['client.user_id', 'user_id'])) { return $entity->client->user->present()->name(); + } - if(in_array($column, ['client.assigned_user_id', 'assigned_user_id'])) + if(in_array($column, ['client.assigned_user_id', 'assigned_user_id'])) { return $entity->client->assigned_user->present()->name(); + } - if(in_array($column, ['client.country_id', 'country_id'])) + if(in_array($column, ['client.country_id', 'country_id'])) { return $entity->client->country ? ctrans("texts.country_{$entity->client->country->name}") : ''; + } - if(in_array($column, ['client.shipping_country_id', 'shipping_country_id'])) + if(in_array($column, ['client.shipping_country_id', 'shipping_country_id'])) { return $entity->client->shipping_country ? ctrans("texts.country_{$entity->client->shipping_country->name}") : ''; + } - if(in_array($column, ['client.size_id', 'size_id'])) + if(in_array($column, ['client.size_id', 'size_id'])) { return $entity->client->size?->name ?? ''; + } - if(in_array($column, ['client.industry_id', 'industry_id'])) + if(in_array($column, ['client.industry_id', 'industry_id'])) { return $entity->client->industry?->name ?? ''; + } - if (in_array($column, ['client.currency_id', 'currency_id'])) + if (in_array($column, ['client.currency_id', 'currency_id'])) { return $entity->client->currency() ? $entity->client->currency()->code : $entity->company->currency()->code; + } - if(in_array($column, ['payment_terms', 'client.payment_terms'])) + if(in_array($column, ['payment_terms', 'client.payment_terms'])) { return $entity->client->getSetting('payment_terms'); + } - if(array_key_exists($column, $transformed_client)) + if(array_key_exists($column, $transformed_client)) { return $transformed_client[$column]; + } // nlog("export: Could not resolve client key: {$column}"); @@ -650,8 +671,9 @@ class BaseExport $transformed_entity = $transformer->transform($entity); - if($column == 'status') + if($column == 'status') { return $entity->stringStatus($entity->status_id); + } return ''; } @@ -682,16 +704,19 @@ class BaseExport $manager->setSerializer(new ArraySerializer()); $transformed_invoices = $manager->createData($transformed_invoices)->toArray(); - if(!isset($transformed_invoices['App\\Models\\Invoice'])) + if(!isset($transformed_invoices['App\\Models\\Invoice'])) { return ''; + } $transformed_invoices = $transformed_invoices['App\\Models\\Invoice']; - if(count($transformed_invoices) == 1 && array_key_exists($column, $transformed_invoices[0])) + if(count($transformed_invoices) == 1 && array_key_exists($column, $transformed_invoices[0])) { return $transformed_invoices[0][$column]; + } - if(count($transformed_invoices) > 1 && array_key_exists($column, $transformed_invoices[0])) + if(count($transformed_invoices) > 1 && array_key_exists($column, $transformed_invoices[0])) { return implode(', ', array_column($transformed_invoices, $column)); + } return ""; @@ -700,8 +725,9 @@ class BaseExport if($transformer instanceof TaskTransformer && ($entity->invoice ?? false)) { $transformed_invoice = $transformer->includeInvoice($entity); - if(!$transformed_invoice) + if(!$transformed_invoice) { return ''; + } $manager = new Manager(); $manager->setSerializer(new ArraySerializer()); @@ -721,7 +747,7 @@ class BaseExport private function resolvePaymentKey($column, $entity, $transformer) { - if($entity instanceof Payment){ + if($entity instanceof Payment) { $transformed_payment = $transformer->transform($entity); @@ -737,8 +763,9 @@ class BaseExport } - if($column == 'amount') + if($column == 'amount') { return $entity->payments()->exists() ? $entity->payments()->withoutTrashed()->sum('paymentables.amount') : ctrans('texts.unpaid'); + } if($column == 'refunded') { return $entity->payments()->exists() ? $entity->payments()->withoutTrashed()->sum('paymentables.refunded') : ''; @@ -753,24 +780,28 @@ class BaseExport $payment = $entity->payments()->withoutTrashed()->first(); - if(!$payment) + if(!$payment) { return ''; + } - if($column == 'method') + if($column == 'method') { return $payment->translatedType(); + } - if($column == 'currency') + if($column == 'currency') { return $payment?->currency?->code ?? ''; + } $payment_transformer = new PaymentTransformer(); $transformed_payment = $payment_transformer->transform($payment); - if($column == 'status'){ + if($column == 'status') { return $payment->stringStatus($transformed_payment['status_id']); } - if(array_key_exists($column, $transformed_payment)) + if(array_key_exists($column, $transformed_payment)) { return $transformed_payment[$column]; + } return ''; @@ -782,8 +813,9 @@ class BaseExport $status_parameters = explode(',', $status); - if(in_array('all', $status_parameters)) + if(in_array('all', $status_parameters)) { return $query; + } $query->where(function ($nested) use ($status_parameters) { @@ -816,9 +848,9 @@ class BaseExport ->orWhere('partial_due_date', '<', Carbon::now()); } - if(in_array('viewed', $status_parameters)){ + if(in_array('viewed', $status_parameters)) { - $nested->whereHas('invitations', function ($q){ + $nested->whereHas('invitations', function ($q) { $q->whereNotNull('viewed_date')->whereNotNull('deleted_at'); }); @@ -884,8 +916,9 @@ class BaseExport $first_month_of_year = $this->company->getSetting('first_month_of_year') ?? 1; $fin_year_start = now()->createFromDate(now()->year, $first_month_of_year, 1); - if(now()->lt($fin_year_start)) + if(now()->lt($fin_year_start)) { $fin_year_start->subYearNoOverflow(); + } $this->start_date = $fin_year_start->format('Y-m-d'); $this->end_date = $fin_year_start->copy()->addYear()->subDay()->format('Y-m-d'); @@ -896,8 +929,9 @@ class BaseExport $fin_year_start = now()->createFromDate(now()->year, $first_month_of_year, 1); $fin_year_start->subYearNoOverflow(); - if(now()->subYear()->lt($fin_year_start)) + if(now()->subYear()->lt($fin_year_start)) { $fin_year_start->subYearNoOverflow(); + } $this->start_date = $fin_year_start->format('Y-m-d'); $this->end_date = $fin_year_start->copy()->addYear()->subDay()->format('Y-m-d'); @@ -914,7 +948,7 @@ class BaseExport } /** - * Returns the merged array of + * Returns the merged array of * the entity with the matching * item report keys * @@ -979,8 +1013,9 @@ class BaseExport $prefix = ctrans('texts.expense')." "; $key = array_search($value, $this->expense_report_keys); - if(!$key && $value == 'expense.category') - $key = 'category'; + if(!$key && $value == 'expense.category') { + $key = 'category'; + } } if(!$key) { @@ -1024,42 +1059,36 @@ class BaseExport $key = str_replace('product.', '', $key); $key = str_replace('task.', '', $key); - if(stripos($value, 'custom_value') !== false) - { + if(stripos($value, 'custom_value') !== false) { $parts = explode(".", $value); - if(count($parts) == 2 && in_array($parts[0], ['credit','quote','invoice','purchase_order','recurring_invoice'])){ + if(count($parts) == 2 && in_array($parts[0], ['credit','quote','invoice','purchase_order','recurring_invoice'])) { $entity = "invoice".substr($parts[1], -1); $prefix = ctrans("texts.".$parts[0]); $fallback = "custom_value".substr($parts[1], -1); $custom_field_label = $helper->makeCustomField($this->company->custom_fields, $entity); - if(strlen($custom_field_label) > 1) + if(strlen($custom_field_label) > 1) { $header[] = $custom_field_label; - else { + } else { $header[] = $prefix . " ". ctrans("texts.{$fallback}"); } - } - elseif(count($parts) == 2 && (stripos($parts[0], 'vendor_contact') !== false || stripos($parts[0], 'contact') !== false)) { + } elseif(count($parts) == 2 && (stripos($parts[0], 'vendor_contact') !== false || stripos($parts[0], 'contact') !== false)) { $parts[0] = str_replace('vendor_contact', 'contact', $parts[0]); $entity = "contact".substr($parts[1], -1); $custom_field_string = strlen($helper->makeCustomField($this->company->custom_fields, $entity)) > 1 ? $helper->makeCustomField($this->company->custom_fields, $entity) : ctrans("texts.{$parts[1]}"); $header[] = ctrans("texts.{$parts[0]}") . " " . $custom_field_string; - } - elseif(count($parts) == 2 && in_array(substr($original_key, 0, -1), ['credit','quote','invoice','purchase_order','recurring_invoice','task'])){ - $custom_field_string = strlen($helper->makeCustomField($this->company->custom_fields, "product".substr($original_key,-1))) > 1 ? $helper->makeCustomField($this->company->custom_fields, "product".substr($original_key,-1)) : ctrans("texts.{$parts[1]}"); + } elseif(count($parts) == 2 && in_array(substr($original_key, 0, -1), ['credit','quote','invoice','purchase_order','recurring_invoice','task'])) { + $custom_field_string = strlen($helper->makeCustomField($this->company->custom_fields, "product".substr($original_key, -1))) > 1 ? $helper->makeCustomField($this->company->custom_fields, "product".substr($original_key, -1)) : ctrans("texts.{$parts[1]}"); $header[] = ctrans("texts.{$parts[0]}") . " " . $custom_field_string; - } - else{ + } else { $header[] = "{$prefix}" . ctrans("texts.{$key}"); } - } - else - { + } else { $header[] = "{$prefix}" . ctrans("texts.{$key}"); } } @@ -1104,7 +1133,7 @@ class BaseExport $value = 'image'; } - if($value == 'tax_id') { + if($value == 'tax_id') { $column_key = 'tax_category'; $value = 'tax_category'; } @@ -1119,7 +1148,7 @@ class BaseExport } return $clean_row; - } + } public function processItemMetaData(array $row, $resource): array { @@ -1146,11 +1175,13 @@ class BaseExport $column_key = $value; - if($value == 'type_id' || $value == 'item.type_id') + if($value == 'type_id' || $value == 'item.type_id') { $column_key = 'type'; + } - if($value == 'tax_id' || $value == 'item.tax_id') + if($value == 'tax_id' || $value == 'item.tax_id') { $column_key = 'tax_category'; + } $clean_row[$key]['entity'] = $report_keys[0]; $clean_row[$key]['id'] = $report_keys[1] ?? $report_keys[0]; @@ -1162,6 +1193,6 @@ class BaseExport } return $clean_row; - } + } } diff --git a/app/Export/CSV/ClientExport.php b/app/Export/CSV/ClientExport.php index 16f9ca154d..29178f5236 100644 --- a/app/Export/CSV/ClientExport.php +++ b/app/Export/CSV/ClientExport.php @@ -11,16 +11,16 @@ namespace App\Export\CSV; +use App\Libraries\MultiDB; +use App\Models\Client; +use App\Models\Company; +use App\Transformers\ClientContactTransformer; +use App\Transformers\ClientTransformer; use App\Utils\Ninja; use App\Utils\Number; -use App\Models\Client; -use League\Csv\Writer; -use App\Models\Company; -use App\Libraries\MultiDB; -use Illuminate\Support\Facades\App; -use App\Transformers\ClientTransformer; use Illuminate\Database\Eloquent\Builder; -use App\Transformers\ClientContactTransformer; +use Illuminate\Support\Facades\App; +use League\Csv\Writer; class ClientExport extends BaseExport { @@ -92,9 +92,9 @@ class ClientExport extends BaseExport $headerdisplay = $this->buildHeader(); - $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ - return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; - })->toArray(); + $header = collect($this->input['report_keys'])->map(function ($key, $value) use ($headerdisplay) { + return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; + })->toArray(); $report = $query->cursor() ->map(function ($client) { @@ -126,7 +126,7 @@ class ClientExport extends BaseExport $query = $this->addDateRange($query); - return $query; + return $query; } @@ -191,10 +191,11 @@ class ClientExport extends BaseExport $clean_row[$key]['value'] = $row[$column_key]; $clean_row[$key]['identifier'] = $value; - if(in_array($clean_row[$key]['id'], ['paid_to_date', 'balance', 'credit_balance','payment_balance'])) + if(in_array($clean_row[$key]['id'], ['paid_to_date', 'balance', 'credit_balance','payment_balance'])) { $clean_row[$key]['display_value'] = Number::formatMoney($row[$column_key], $resource); - else + } else { $clean_row[$key]['display_value'] = $row[$column_key]; + } } diff --git a/app/Export/CSV/ContactExport.php b/app/Export/CSV/ContactExport.php index e07cf64d32..558c693319 100644 --- a/app/Export/CSV/ContactExport.php +++ b/app/Export/CSV/ContactExport.php @@ -88,9 +88,9 @@ class ContactExport extends BaseExport $headerdisplay = $this->buildHeader(); - $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ - return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; - })->toArray(); + $header = collect($this->input['report_keys'])->map(function ($key, $value) use ($headerdisplay) { + return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; + })->toArray(); $report = $query->cursor() ->map(function ($contact) { diff --git a/app/Export/CSV/CreditExport.php b/app/Export/CSV/CreditExport.php index c5c05f3c23..d0a29ea3c7 100644 --- a/app/Export/CSV/CreditExport.php +++ b/app/Export/CSV/CreditExport.php @@ -11,15 +11,15 @@ namespace App\Export\CSV; +use App\Libraries\MultiDB; +use App\Models\Company; +use App\Models\Credit; +use App\Transformers\CreditTransformer; use App\Utils\Ninja; use App\Utils\Number; -use App\Models\Credit; -use League\Csv\Writer; -use App\Models\Company; -use App\Libraries\MultiDB; -use Illuminate\Support\Facades\App; -use App\Transformers\CreditTransformer; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Facades\App; +use League\Csv\Writer; class CreditExport extends BaseExport { @@ -43,9 +43,9 @@ class CreditExport extends BaseExport $headerdisplay = $this->buildHeader(); - $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ - return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; - })->toArray(); + $header = collect($this->input['report_keys'])->map(function ($key, $value) use ($headerdisplay) { + return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; + })->toArray(); $report = $query->cursor() ->map(function ($credit) { @@ -70,10 +70,11 @@ class CreditExport extends BaseExport $clean_row[$key]['value'] = $row[$column_key]; $clean_row[$key]['identifier'] = $value; - if(in_array($clean_row[$key]['id'], ['paid_to_date','total_taxes','amount', 'balance', 'partial', 'refunded', 'applied','unit_cost','cost','price'])) + if(in_array($clean_row[$key]['id'], ['paid_to_date','total_taxes','amount', 'balance', 'partial', 'refunded', 'applied','unit_cost','cost','price'])) { $clean_row[$key]['display_value'] = Number::formatMoney($row[$column_key], $resource->client); - else + } else { $clean_row[$key]['display_value'] = $row[$column_key]; + } } @@ -139,10 +140,9 @@ class CreditExport extends BaseExport $entity[$keyval] = $transformed_credit[$credit_key]; } elseif (isset($transformed_credit[$keyval])) { $entity[$keyval] = $transformed_credit[$keyval]; - } elseif(isset($transformed_credit[$searched_credit_key])){ + } elseif(isset($transformed_credit[$searched_credit_key])) { $entity[$keyval] = $transformed_credit[$searched_credit_key]; - } - else { + } else { $entity[$keyval] = $this->resolveKey($keyval, $credit, $this->credit_transformer); } diff --git a/app/Export/CSV/DocumentExport.php b/app/Export/CSV/DocumentExport.php index cc4482c063..a5518da707 100644 --- a/app/Export/CSV/DocumentExport.php +++ b/app/Export/CSV/DocumentExport.php @@ -49,9 +49,9 @@ class DocumentExport extends BaseExport $headerdisplay = $this->buildHeader(); - $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ - return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; - })->toArray(); + $header = collect($this->input['report_keys'])->map(function ($key, $value) use ($headerdisplay) { + return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; + })->toArray(); $report = $query->cursor() ->map(function ($document) { diff --git a/app/Export/CSV/ExpenseExport.php b/app/Export/CSV/ExpenseExport.php index 7b51401629..ee8d5bf0c7 100644 --- a/app/Export/CSV/ExpenseExport.php +++ b/app/Export/CSV/ExpenseExport.php @@ -43,9 +43,9 @@ class ExpenseExport extends BaseExport $headerdisplay = $this->buildHeader(); - $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ - return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; - })->toArray(); + $header = collect($this->input['report_keys'])->map(function ($key, $value) use ($headerdisplay) { + return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; + })->toArray(); $report = $query->cursor() ->map(function ($resource) { diff --git a/app/Export/CSV/InvoiceExport.php b/app/Export/CSV/InvoiceExport.php index 4dc4ee3d8b..4773ec3aa2 100644 --- a/app/Export/CSV/InvoiceExport.php +++ b/app/Export/CSV/InvoiceExport.php @@ -11,16 +11,14 @@ namespace App\Export\CSV; -use App\Utils\Ninja; -use App\Utils\Number; -use League\Csv\Writer; +use App\Libraries\MultiDB; use App\Models\Company; use App\Models\Invoice; -use App\Libraries\MultiDB; -use App\Export\CSV\BaseExport; -use Illuminate\Support\Facades\App; use App\Transformers\InvoiceTransformer; +use App\Utils\Ninja; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Facades\App; +use League\Csv\Writer; class InvoiceExport extends BaseExport { @@ -74,9 +72,9 @@ class InvoiceExport extends BaseExport $headerdisplay = $this->buildHeader(); - $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ - return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; - })->toArray(); + $header = collect($this->input['report_keys'])->map(function ($key, $value) use ($headerdisplay) { + return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; + })->toArray(); $report = $query->cursor() ->map(function ($resource) { diff --git a/app/Export/CSV/InvoiceItemExport.php b/app/Export/CSV/InvoiceItemExport.php index ba2dc00da4..8ccc8a39ff 100644 --- a/app/Export/CSV/InvoiceItemExport.php +++ b/app/Export/CSV/InvoiceItemExport.php @@ -82,9 +82,9 @@ class InvoiceItemExport extends BaseExport $headerdisplay = $this->buildHeader(); - $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ - return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; - })->toArray(); + $header = collect($this->input['report_keys'])->map(function ($key, $value) use ($headerdisplay) { + return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; + })->toArray(); $query->cursor() @@ -131,7 +131,7 @@ class InvoiceItemExport extends BaseExport $transformed_items = []; foreach ($invoice->line_items as $item) { - $item_array = []; + $item_array = []; foreach (array_values(array_intersect($this->input['report_keys'], $this->item_report_keys)) as $key) { //items iterator produces item array @@ -139,16 +139,17 @@ class InvoiceItemExport extends BaseExport $tmp_key = str_replace("item.", "", $key); - if($tmp_key == 'type_id') + if($tmp_key == 'type_id') { $tmp_key = 'type'; + } - if($tmp_key == 'tax_id') + if($tmp_key == 'tax_id') { $tmp_key = 'tax_category'; + } if (property_exists($item, $tmp_key)) { $item_array[$key] = $item->{$tmp_key}; - } - else { + } else { $item_array[$key] = ''; } } @@ -174,15 +175,15 @@ class InvoiceItemExport extends BaseExport $parts = explode('.', $key); - if(is_array($parts) && $parts[0] == 'item') + if(is_array($parts) && $parts[0] == 'item') { continue; + } if (is_array($parts) && $parts[0] == 'invoice' && array_key_exists($parts[1], $transformed_invoice)) { $entity[$key] = $transformed_invoice[$parts[1]]; - }else if (array_key_exists($key, $transformed_invoice)) { + } elseif (array_key_exists($key, $transformed_invoice)) { $entity[$key] = $transformed_invoice[$key]; - } - else { + } else { $entity[$key] = $this->resolveKey($key, $invoice, $this->invoice_transformer); } } diff --git a/app/Export/CSV/PaymentExport.php b/app/Export/CSV/PaymentExport.php index 7f77563f9b..48f996fae7 100644 --- a/app/Export/CSV/PaymentExport.php +++ b/app/Export/CSV/PaymentExport.php @@ -71,8 +71,6 @@ class PaymentExport extends BaseExport return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; })->toArray(); - nlog($header); - $report = $query->cursor() ->map(function ($resource) { $row = $this->buildRow($resource); diff --git a/app/Export/CSV/ProductExport.php b/app/Export/CSV/ProductExport.php index d36c31b54e..7963cb76be 100644 --- a/app/Export/CSV/ProductExport.php +++ b/app/Export/CSV/ProductExport.php @@ -41,14 +41,14 @@ class ProductExport extends BaseExport $headerdisplay = $this->buildHeader(); - $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ - return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; - })->toArray(); + $header = collect($this->input['report_keys'])->map(function ($key, $value) use ($headerdisplay) { + return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; + })->toArray(); $report = $query->cursor() ->map(function ($resource) { $row = $this->buildRow($resource); - return $this->processMetaData($row, $resource); + return $this->processMetaData($row, $resource); })->toArray(); return array_merge(['columns' => $header], $report); diff --git a/app/Export/CSV/PurchaseOrderExport.php b/app/Export/CSV/PurchaseOrderExport.php index 45e8a6adb1..5f2ba4fbc1 100644 --- a/app/Export/CSV/PurchaseOrderExport.php +++ b/app/Export/CSV/PurchaseOrderExport.php @@ -11,15 +11,14 @@ namespace App\Export\CSV; -use App\Utils\Ninja; -use App\Utils\Number; -use League\Csv\Writer; -use App\Models\Company; use App\Libraries\MultiDB; +use App\Models\Company; use App\Models\PurchaseOrder; -use Illuminate\Support\Facades\App; use App\Transformers\PurchaseOrderTransformer; +use App\Utils\Ninja; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Facades\App; +use League\Csv\Writer; class PurchaseOrderExport extends BaseExport { @@ -116,9 +115,9 @@ class PurchaseOrderExport extends BaseExport $headerdisplay = $this->buildHeader(); - $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ - return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; - })->toArray(); + $header = collect($this->input['report_keys'])->map(function ($key, $value) use ($headerdisplay) { + return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; + })->toArray(); $report = $query->cursor() ->map(function ($resource) { diff --git a/app/Export/CSV/PurchaseOrderItemExport.php b/app/Export/CSV/PurchaseOrderItemExport.php index 3738ec5b6d..1351be3562 100644 --- a/app/Export/CSV/PurchaseOrderItemExport.php +++ b/app/Export/CSV/PurchaseOrderItemExport.php @@ -11,14 +11,14 @@ namespace App\Export\CSV; -use App\Utils\Ninja; -use League\Csv\Writer; -use App\Models\Company; use App\Libraries\MultiDB; +use App\Models\Company; use App\Models\PurchaseOrder; -use Illuminate\Support\Facades\App; -use Illuminate\Database\Eloquent\Builder; use App\Transformers\PurchaseOrderTransformer; +use App\Utils\Ninja; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Facades\App; +use League\Csv\Writer; class PurchaseOrderItemExport extends BaseExport { @@ -74,21 +74,21 @@ class PurchaseOrderItemExport extends BaseExport $headerdisplay = $this->buildHeader(); - $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ - return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; - })->toArray(); + $header = collect($this->input['report_keys'])->map(function ($key, $value) use ($headerdisplay) { + return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; + })->toArray(); $query->cursor() ->each(function ($resource) { - $this->iterateItems($resource); + $this->iterateItems($resource); - foreach($this->storage_array as $row) { - $this->storage_item_array[] = $this->processItemMetaData($row, $resource); - } + foreach($this->storage_array as $row) { + $this->storage_item_array[] = $this->processItemMetaData($row, $resource); + } - $this->storage_array = []; + $this->storage_array = []; - }); + }); return array_merge(['columns' => $header], $this->storage_item_array); } diff --git a/app/Export/CSV/QuoteItemExport.php b/app/Export/CSV/QuoteItemExport.php index 0db08629c6..fe8cbb593a 100644 --- a/app/Export/CSV/QuoteItemExport.php +++ b/app/Export/CSV/QuoteItemExport.php @@ -76,23 +76,23 @@ class QuoteItemExport extends BaseExport $headerdisplay = $this->buildHeader(); - $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ - return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; - })->toArray(); + $header = collect($this->input['report_keys'])->map(function ($key, $value) use ($headerdisplay) { + return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; + })->toArray(); - $query->cursor() - ->each(function ($resource) { - $this->iterateItems($resource); + $query->cursor() + ->each(function ($resource) { + $this->iterateItems($resource); - foreach($this->storage_array as $row) { - $this->storage_item_array[] = $this->processItemMetaData($row, $resource); - } + foreach($this->storage_array as $row) { + $this->storage_item_array[] = $this->processItemMetaData($row, $resource); + } - $this->storage_array = []; + $this->storage_array = []; - }); + }); - return array_merge(['columns' => $header], $this->storage_item_array); + return array_merge(['columns' => $header], $this->storage_item_array); } @@ -103,7 +103,7 @@ class QuoteItemExport extends BaseExport //load the CSV document from a string $this->csv = Writer::createFromString(); - $query = $this->init(); + $query = $this->init(); //insert the header $this->csv->insertOne($this->buildHeader()); @@ -127,7 +127,7 @@ class QuoteItemExport extends BaseExport $transformed_items = []; foreach ($quote->line_items as $item) { - $item_array = []; + $item_array = []; foreach (array_values(array_intersect($this->input['report_keys'], $this->item_report_keys)) as $key) { //items iterator produces item array @@ -135,16 +135,17 @@ class QuoteItemExport extends BaseExport $tmp_key = str_replace("item.", "", $key); - if($tmp_key == 'type_id') + if($tmp_key == 'type_id') { $tmp_key = 'type'; + } - if($tmp_key == 'tax_id') + if($tmp_key == 'tax_id') { $tmp_key = 'tax_category'; + } if (property_exists($item, $tmp_key)) { $item_array[$key] = $item->{$tmp_key}; - } - else { + } else { $item_array[$key] = ''; } } diff --git a/app/Export/CSV/RecurringInvoiceExport.php b/app/Export/CSV/RecurringInvoiceExport.php index 3234b06743..b88601ba99 100644 --- a/app/Export/CSV/RecurringInvoiceExport.php +++ b/app/Export/CSV/RecurringInvoiceExport.php @@ -88,9 +88,9 @@ class RecurringInvoiceExport extends BaseExport $headerdisplay = $this->buildHeader(); - $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ - return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; - })->toArray(); + $header = collect($this->input['report_keys'])->map(function ($key, $value) use ($headerdisplay) { + return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; + })->toArray(); $report = $query->cursor() ->map(function ($resource) { diff --git a/app/Export/CSV/TaskExport.php b/app/Export/CSV/TaskExport.php index b2eb6425c5..7d8271d860 100644 --- a/app/Export/CSV/TaskExport.php +++ b/app/Export/CSV/TaskExport.php @@ -101,17 +101,16 @@ class TaskExport extends BaseExport $headerdisplay = $this->buildHeader(); - $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ - return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; - })->toArray(); + $header = collect($this->input['report_keys'])->map(function ($key, $value) use ($headerdisplay) { + return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; + })->toArray(); $query->cursor() ->each(function ($resource) { $this->buildRow($resource); - foreach($this->storage_array as $row) - { + foreach($this->storage_array as $row) { $this->storage_item_array[] = $this->processMetaData($row, $resource); } diff --git a/app/Export/CSV/VendorExport.php b/app/Export/CSV/VendorExport.php index 062c09187d..259f53c59c 100644 --- a/app/Export/CSV/VendorExport.php +++ b/app/Export/CSV/VendorExport.php @@ -12,8 +12,8 @@ namespace App\Export\CSV; use App\Libraries\MultiDB; -use App\Models\Vendor; use App\Models\Company; +use App\Models\Vendor; use App\Transformers\VendorContactTransformer; use App\Transformers\VendorTransformer; use App\Utils\Ninja; @@ -73,9 +73,9 @@ class VendorExport extends BaseExport $headerdisplay = $this->buildHeader(); - $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ - return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; - })->toArray(); + $header = collect($this->input['report_keys'])->map(function ($key, $value) use ($headerdisplay) { + return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; + })->toArray(); $report = $query->cursor() ->map(function ($resource) { diff --git a/app/Factory/CompanyFactory.php b/app/Factory/CompanyFactory.php index 52324d698a..b48904b6d3 100644 --- a/app/Factory/CompanyFactory.php +++ b/app/Factory/CompanyFactory.php @@ -11,13 +11,13 @@ namespace App\Factory; -use App\Utils\Ninja; -use App\Models\Company; -use App\Libraries\MultiDB; -use App\Utils\Traits\MakesHash; -use App\DataMapper\Tax\TaxModel; -use App\DataMapper\CompanySettings; use App\DataMapper\ClientRegistrationFields; +use App\DataMapper\CompanySettings; +use App\DataMapper\Tax\TaxModel; +use App\Libraries\MultiDB; +use App\Models\Company; +use App\Utils\Ninja; +use App\Utils\Traits\MakesHash; class CompanyFactory { diff --git a/app/Factory/DesignFactory.php b/app/Factory/DesignFactory.php index 0013e37d7b..b8b8fe2092 100644 --- a/app/Factory/DesignFactory.php +++ b/app/Factory/DesignFactory.php @@ -25,6 +25,8 @@ class DesignFactory $design->is_active = true; $design->is_custom = true; $design->name = ''; + $design->is_template = false; + $design->entities = ''; $design->design = new DesignBlocks(); return $design; diff --git a/app/Factory/InvoiceItemFactory.php b/app/Factory/InvoiceItemFactory.php index 08c13f4784..f81872dc8c 100644 --- a/app/Factory/InvoiceItemFactory.php +++ b/app/Factory/InvoiceItemFactory.php @@ -63,7 +63,7 @@ class InvoiceItemFactory $item->line_total = $item->quantity * $item->cost; $item->is_amount_discount = true; $item->discount = $faker->numberBetween(1, 10); - $item->notes = $faker->realText(50); + $item->notes = str_replace(['"',"'"], ['',""], $faker->realText(20)); $item->product_key = $faker->word(); // $item->custom_value1 = $faker->realText(10); // $item->custom_value2 = $faker->realText(10); diff --git a/app/Factory/RecurringExpenseToExpenseFactory.php b/app/Factory/RecurringExpenseToExpenseFactory.php index 921130ac91..4d6bff4c4c 100644 --- a/app/Factory/RecurringExpenseToExpenseFactory.php +++ b/app/Factory/RecurringExpenseToExpenseFactory.php @@ -248,10 +248,10 @@ class RecurringExpenseToExpenseFactory $final_date = now()->addMonths($output-now()->month); $output = \sprintf( - '%s %s', - $final_date->translatedFormat('F'), - $final_date->year, - ); + '%s %s', + $final_date->translatedFormat('F'), + $final_date->year, + ); } $value = preg_replace( diff --git a/app/Factory/UserFactory.php b/app/Factory/UserFactory.php index 9b15037f62..c8fbc4f4d3 100644 --- a/app/Factory/UserFactory.php +++ b/app/Factory/UserFactory.php @@ -27,7 +27,7 @@ class UserFactory $user->last_login = now(); $user->failed_logins = 0; $user->signature = ''; - $user->theme_id = 0; + $user->theme_id = 0; $user->user_logged_in_notification = true; return $user; diff --git a/app/Filters/BankTransactionFilters.php b/app/Filters/BankTransactionFilters.php index 15c02a7b97..11d27df790 100644 --- a/app/Filters/BankTransactionFilters.php +++ b/app/Filters/BankTransactionFilters.php @@ -53,19 +53,19 @@ class BankTransactionFilters extends QueryFilters } -/** - * Filter based on client status. - * - * Statuses we need to handle - * - all - * - unmatched - * - matched - * - converted - * - deposits - * - withdrawals - * - * @return Builder - */ + /** + * Filter based on client status. + * + * Statuses we need to handle + * - all + * - unmatched + * - matched + * - converted + * - deposits + * - withdrawals + * + * @return Builder + */ public function client_status(string $value = ''): Builder { if (strlen($value) == 0) { diff --git a/app/Filters/CreditFilters.php b/app/Filters/CreditFilters.php index bd17e34819..a69daa60dd 100644 --- a/app/Filters/CreditFilters.php +++ b/app/Filters/CreditFilters.php @@ -144,7 +144,7 @@ class CreditFilters extends QueryFilters return $this->builder->company(); } -// return $this->builder->whereCompanyId(auth()->user()->company()->id); + // return $this->builder->whereCompanyId(auth()->user()->company()->id); } /** diff --git a/app/Filters/DesignFilters.php b/app/Filters/DesignFilters.php index 88593f2198..bd489b05c4 100644 --- a/app/Filters/DesignFilters.php +++ b/app/Filters/DesignFilters.php @@ -54,6 +54,19 @@ class DesignFilters extends QueryFilters return $this->builder->orderBy($sort_col[0], $sort_col[1]); } + public function entities(string $entities = ''): Builder + { + + if (strlen($entities) == 0 || str_contains($entities, ',')) { + return $this->builder; + } + + return $this->builder + ->where('is_template', true) + ->whereRaw('FIND_IN_SET( ? ,entities)', [trim($entities)]); + + } + /** * Filters the query by the users company ID. * @@ -64,11 +77,22 @@ class DesignFilters extends QueryFilters /** @var \App\Models\User $user */ $user = auth()->user(); - return $this->builder->where(function ($query) use($user){ + return $this->builder->where(function ($query) use ($user) { $query->where('company_id', $user->company()->id)->orWhere('company_id', null)->orderBy('id', 'asc'); }); } + public function template(string $template = 'false'): Builder + { + + if (strlen($template) == 0) { + return $this->builder; + } + + $bool_val = $template == 'true' ? true : false; + + return $this->builder->where('is_template', $bool_val); + } /** * Filter the designs by `is_custom` column. * diff --git a/app/Filters/ExpenseFilters.php b/app/Filters/ExpenseFilters.php index a2ddc98e3c..264be55d03 100644 --- a/app/Filters/ExpenseFilters.php +++ b/app/Filters/ExpenseFilters.php @@ -40,8 +40,8 @@ class ExpenseFilters extends QueryFilters ->orWhere('custom_value3', 'like', '%'.$filter.'%') ->orWhere('custom_value4', 'like', '%'.$filter.'%') ->orWhereHas('category', function ($q) use ($filter) { - $q->where('name', 'like', '%'.$filter.'%'); - }); + $q->where('name', 'like', '%'.$filter.'%'); + }); }); } @@ -125,9 +125,9 @@ class ExpenseFilters extends QueryFilters $search_key = $split[0] == 'client' ? 'client_id' : 'project_id'; - return $this->builder->whereHas('invoice', function ($query) use ($search_key, $split){ - $query->where($search_key, $this->decodePrimaryKey($split[1])) - ->whereIn('status_id', [\App\Models\Invoice::STATUS_DRAFT, \App\Models\Invoice::STATUS_SENT, \App\Models\Invoice::STATUS_PARTIAL]); + return $this->builder->whereHas('invoice', function ($query) use ($search_key, $split) { + $query->where($search_key, $this->decodePrimaryKey($split[1])) + ->whereIn('status_id', [\App\Models\Invoice::STATUS_DRAFT, \App\Models\Invoice::STATUS_SENT, \App\Models\Invoice::STATUS_PARTIAL]); }); } diff --git a/app/Filters/InvoiceFilters.php b/app/Filters/InvoiceFilters.php index 91e426192f..58af558b99 100644 --- a/app/Filters/InvoiceFilters.php +++ b/app/Filters/InvoiceFilters.php @@ -11,14 +11,13 @@ namespace App\Filters; -use RuntimeException; use App\Models\Client; use App\Models\Invoice; -use App\Filters\QueryFilters; -use InvalidArgumentException; -use Illuminate\Support\Carbon; use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Carbon; +use InvalidArgumentException; +use RuntimeException; /** * InvoiceFilters. @@ -241,15 +240,13 @@ class InvoiceFilters extends QueryFilters return $this->builder; } - try{ + try { $start_date = Carbon::parse($parts[1]); $end_date = Carbon::parse($parts[2]); return $this->builder->whereBetween($parts[0], [$start_date, $end_date]); - } - - catch(\Exception $e){ + } catch(\Exception $e) { return $this->builder; } diff --git a/app/Filters/PaymentFilters.php b/app/Filters/PaymentFilters.php index bc16744d4b..dbdb839bde 100644 --- a/app/Filters/PaymentFilters.php +++ b/app/Filters/PaymentFilters.php @@ -12,9 +12,8 @@ namespace App\Filters; use App\Models\Payment; -use Illuminate\Support\Carbon; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Contracts\Database\Eloquent\Builder as EloquentBuilder; +use Illuminate\Support\Carbon; /** * PaymentFilters. @@ -37,39 +36,39 @@ class PaymentFilters extends QueryFilters return $this->builder->where(function ($query) use ($filter) { $query->where('amount', 'like', '%'.$filter.'%') ->orWhere('date', 'like', '%'.$filter.'%') - ->orWhere('number','like', '%'.$filter.'%') + ->orWhere('number', 'like', '%'.$filter.'%') ->orWhere('transaction_reference', 'like', '%'.$filter.'%') ->orWhere('custom_value1', 'like', '%'.$filter.'%') ->orWhere('custom_value2', 'like', '%'.$filter.'%') ->orWhere('custom_value3', 'like', '%'.$filter.'%') ->orWhere('custom_value4', 'like', '%'.$filter.'%') ->orWhereHas('client', function ($q) use ($filter) { - $q->where('name', 'like', '%'.$filter.'%'); - }) + $q->where('name', 'like', '%'.$filter.'%'); + }) ->orWhereHas('client.contacts', function ($q) use ($filter) { - $q->where('first_name', 'like', '%'.$filter.'%') - ->orWhere('last_name', 'like', '%'.$filter.'%') - ->orWhere('email', 'like', '%'.$filter.'%'); - }); + $q->where('first_name', 'like', '%'.$filter.'%') + ->orWhere('last_name', 'like', '%'.$filter.'%') + ->orWhere('email', 'like', '%'.$filter.'%'); + }); }); } - /** - * Filter based on client status. - * - * Statuses we need to handle - * - all - * - pending - * - cancelled - * - failed - * - completed - * - partially refunded - * - refunded - * - * @param string $value The payment status as seen by the client - * @return Builder - */ + /** + * Filter based on client status. + * + * Statuses we need to handle + * - all + * - pending + * - cancelled + * - failed + * - completed + * - partially refunded + * - refunded + * + * @param string $value The payment status as seen by the client + * @return Builder + */ public function client_status(string $value = ''): Builder { if (strlen($value) == 0) { @@ -190,15 +189,13 @@ class PaymentFilters extends QueryFilters return $this->builder; } - try{ + try { $start_date = Carbon::parse($parts[1]); $end_date = Carbon::parse($parts[2]); return $this->builder->whereBetween($parts[0], [$start_date, $end_date]); - } - - catch(\Exception $e){ + } catch(\Exception $e) { return $this->builder; } diff --git a/app/Filters/PurchaseOrderFilters.php b/app/Filters/PurchaseOrderFilters.php index 8948a619f4..0d5c124498 100644 --- a/app/Filters/PurchaseOrderFilters.php +++ b/app/Filters/PurchaseOrderFilters.php @@ -145,7 +145,7 @@ class PurchaseOrderFilters extends QueryFilters return $this->builder->company(); } -// return $this->builder->whereCompanyId(auth()->user()->company()->id); + // return $this->builder->whereCompanyId(auth()->user()->company()->id); } /** diff --git a/app/Filters/QuoteFilters.php b/app/Filters/QuoteFilters.php index 220960a219..c28d0450cb 100644 --- a/app/Filters/QuoteFilters.php +++ b/app/Filters/QuoteFilters.php @@ -42,10 +42,10 @@ class QuoteFilters extends QueryFilters $q->where('name', 'like', '%'.$filter.'%'); }) ->orWhereHas('client.contacts', function ($q) use ($filter) { - $q->where('first_name', 'like', '%'.$filter.'%') - ->orWhere('last_name', 'like', '%'.$filter.'%') - ->orWhere('email', 'like', '%'.$filter.'%'); - }); + $q->where('first_name', 'like', '%'.$filter.'%') + ->orWhere('last_name', 'like', '%'.$filter.'%') + ->orWhere('email', 'like', '%'.$filter.'%'); + }); }); } @@ -146,7 +146,7 @@ class QuoteFilters extends QueryFilters return $this->builder; } - if($sort_col[0] == 'client_id'){ + if($sort_col[0] == 'client_id') { return $this->builder->orderBy(\App\Models\Client::select('name') ->whereColumn('clients.id', 'quotes.client_id'), $sort_col[1]); diff --git a/app/Filters/RecurringInvoiceFilters.php b/app/Filters/RecurringInvoiceFilters.php index 71fcca9d10..81060eed61 100644 --- a/app/Filters/RecurringInvoiceFilters.php +++ b/app/Filters/RecurringInvoiceFilters.php @@ -34,20 +34,20 @@ class RecurringInvoiceFilters extends QueryFilters } return $this->builder->where(function ($query) use ($filter) { - $query->where('date', 'like', '%'.$filter.'%') - ->orWhere('amount', 'like', '%'.$filter.'%') - ->orWhere('custom_value1', 'like', '%'.$filter.'%') - ->orWhere('custom_value2', 'like', '%'.$filter.'%') - ->orWhere('custom_value3', 'like', '%'.$filter.'%') - ->orWhere('custom_value4', 'like', '%'.$filter.'%') - ->orWhereHas('client', function ($q) use ($filter) { - $q->where('name', 'like', '%'.$filter.'%'); - }) - ->orWhereHas('client.contacts', function ($q) use ($filter) { - $q->where('first_name', 'like', '%'.$filter.'%') - ->orWhere('last_name', 'like', '%'.$filter.'%') - ->orWhere('email', 'like', '%'.$filter.'%'); - }); + $query->where('date', 'like', '%'.$filter.'%') + ->orWhere('amount', 'like', '%'.$filter.'%') + ->orWhere('custom_value1', 'like', '%'.$filter.'%') + ->orWhere('custom_value2', 'like', '%'.$filter.'%') + ->orWhere('custom_value3', 'like', '%'.$filter.'%') + ->orWhere('custom_value4', 'like', '%'.$filter.'%') + ->orWhereHas('client', function ($q) use ($filter) { + $q->where('name', 'like', '%'.$filter.'%'); + }) + ->orWhereHas('client.contacts', function ($q) use ($filter) { + $q->where('first_name', 'like', '%'.$filter.'%') + ->orWhere('last_name', 'like', '%'.$filter.'%') + ->orWhere('email', 'like', '%'.$filter.'%'); + }); }); } diff --git a/app/Filters/TaskFilters.php b/app/Filters/TaskFilters.php index 481fb44299..eb3ca35c41 100644 --- a/app/Filters/TaskFilters.php +++ b/app/Filters/TaskFilters.php @@ -41,16 +41,16 @@ class TaskFilters extends QueryFilters ->orWhere('custom_value3', 'like', '%'.$filter.'%') ->orWhere('custom_value4', 'like', '%'.$filter.'%') ->orWhereHas('project', function ($q) use ($filter) { - $q->where('name', 'like', '%'.$filter.'%'); - }) + $q->where('name', 'like', '%'.$filter.'%'); + }) ->orWhereHas('client', function ($q) use ($filter) { - $q->where('name', 'like', '%'.$filter.'%'); - }) + $q->where('name', 'like', '%'.$filter.'%'); + }) ->orWhereHas('client.contacts', function ($q) use ($filter) { - $q->where('first_name', 'like', '%'.$filter.'%') - ->orWhere('last_name', 'like', '%'.$filter.'%') - ->orWhere('email', 'like', '%'.$filter.'%'); - }); + $q->where('first_name', 'like', '%'.$filter.'%') + ->orWhere('last_name', 'like', '%'.$filter.'%') + ->orWhere('email', 'like', '%'.$filter.'%'); + }); }); } @@ -136,7 +136,7 @@ class TaskFilters extends QueryFilters $status_parameters = explode(',', $value); - if(count($status_parameters) >= 1){ + if(count($status_parameters) >= 1) { $this->builder->where(function ($query) use ($status_parameters) { $query->whereIn('status_id', $this->transformKeys($status_parameters))->whereNull('invoice_id'); diff --git a/app/Helpers/Bank/Yodlee/DTO/AccountSummary.php b/app/Helpers/Bank/Yodlee/DTO/AccountSummary.php index 346496411f..d7357c9fe3 100644 --- a/app/Helpers/Bank/Yodlee/DTO/AccountSummary.php +++ b/app/Helpers/Bank/Yodlee/DTO/AccountSummary.php @@ -11,10 +11,9 @@ namespace App\Helpers\Bank\Yodlee\DTO; -use Spatie\LaravelData\Data; -use Spatie\LaravelData\Attributes\MapInputName; -use Spatie\LaravelData\Attributes\MapOutputName; use Illuminate\Support\Collection; +use Spatie\LaravelData\Attributes\MapInputName; +use Spatie\LaravelData\Data; /** * [ @@ -72,40 +71,40 @@ use Illuminate\Support\Collection; */ class AccountSummary extends Data { - public ?int $id; + public ?int $id; - #[MapInputName('CONTAINER')] - public ?string $account_type = ''; + #[MapInputName('CONTAINER')] + public ?string $account_type = ''; - #[MapInputName('accountName')] - public ?string $account_name = ''; + #[MapInputName('accountName')] + public ?string $account_name = ''; - #[MapInputName('accountStatus')] - public ?string $account_status = ''; + #[MapInputName('accountStatus')] + public ?string $account_status = ''; - #[MapInputName('accountNumber')] - public ?string $account_number = ''; + #[MapInputName('accountNumber')] + public ?string $account_number = ''; - #[MapInputName('providerAccountId')] - public int $provider_account_id; + #[MapInputName('providerAccountId')] + public int $provider_account_id; - #[MapInputName('providerId')] - public ?string $provider_id = ''; + #[MapInputName('providerId')] + public ?string $provider_id = ''; - #[MapInputName('providerName')] - public ?string $provider_name = ''; + #[MapInputName('providerName')] + public ?string $provider_name = ''; - public ?string $nickname = ''; + public ?string $nickname = ''; - public ?float $current_balance = 0; - public ?string $account_currency = ''; + public ?float $current_balance = 0; + public ?string $account_currency = ''; - public static function prepareForPipeline(Collection $properties) : Collection - { + public static function prepareForPipeline(Collection $properties) : Collection + { - $properties->put('current_balance', $properties['currentBalance']['amount'] ?? 0); - $properties->put('account_currency', $properties['currentBalance']['currency'] ?? 0); + $properties->put('current_balance', $properties['currentBalance']['amount'] ?? 0); + $properties->put('account_currency', $properties['currentBalance']['currency'] ?? 0); - return $properties; - } -} \ No newline at end of file + return $properties; + } +} diff --git a/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php b/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php index ceb1417c75..9c44c3784b 100644 --- a/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php +++ b/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php @@ -88,20 +88,19 @@ class AccountTransformer implements AccountTransformerInterface if(property_exists($account, 'currentBalance')) { $current_balance = $account->currentBalance->amount ?? 0; $account_currency = $account->currentBalance->currency ?? ''; - } - elseif(property_exists($account, 'balance')){ + } elseif(property_exists($account, 'balance')) { $current_balance = $account->balance->amount ?? 0; $account_currency = $account->balance->currency ?? ''; } $account_status = $account->accountStatus; - if(property_exists($account, 'dataset')){ + if(property_exists($account, 'dataset')) { $dataset = $account->dataset[0]; $status = false; $update = false; - match($dataset->additionalStatus ?? ''){ + match($dataset->additionalStatus ?? '') { 'LOGIN_IN_PROGRESS' => $status = 'Data retrieval in progress.', 'USER_INPUT_REQUIRED' => $status = 'Please reconnect your account, authentication required.', 'LOGIN_SUCCESS' => $status = 'Data retrieval in progress', @@ -113,24 +112,23 @@ class AccountTransformer implements AccountTransformerInterface 'PARTIAL_DATA_RETRIEVED' => $status = 'Partial data update failed.', 'PARTIAL_DATA_RETRIEVED_REM_SCHED' => $status = 'Partial data update failed.', 'SUCCESS' => $status = 'All accounts added or updated successfully.', - default => $status = false + default => $status = false }; - if($status){ + if($status) { $account_status = $status; } - match($dataset->updateEligibility ?? ''){ + match($dataset->updateEligibility ?? '') { 'ALLOW_UPDATE' => $update = 'Account connection stable.', 'ALLOW_UPDATE_WITH_CREDENTIALS' => $update = 'Please reconnect your account with updated credentials.', 'DISALLOW_UPDATE' => $update = 'Update not available due to technical issues.', default => $update = false, }; - if($status && $update){ + if($status && $update) { $account_status = $status . ' - ' . $update; - } - elseif($update){ + } elseif($update) { $account_status = $update; } diff --git a/app/Helpers/Bank/Yodlee/Transformer/IncomeTransformer.php b/app/Helpers/Bank/Yodlee/Transformer/IncomeTransformer.php index 48966aa203..73f9e7fe31 100644 --- a/app/Helpers/Bank/Yodlee/Transformer/IncomeTransformer.php +++ b/app/Helpers/Bank/Yodlee/Transformer/IncomeTransformer.php @@ -127,12 +127,14 @@ class IncomeTransformer implements BankRevenueInterface foreach ($transaction->transaction as $transaction) { //do not store duplicate / pending transactions - if (property_exists($transaction, 'status') && $transaction->status == 'PENDING') + if (property_exists($transaction, 'status') && $transaction->status == 'PENDING') { continue; + } //some object do no store amounts ignore these - if(!property_exists($transaction, 'amount')) + if(!property_exists($transaction, 'amount')) { continue; + } $data[] = $this->transformTransaction($transaction); } diff --git a/app/Helpers/Bank/Yodlee/Yodlee.php b/app/Helpers/Bank/Yodlee/Yodlee.php index f8294becd9..6b15e1b2d5 100644 --- a/app/Helpers/Bank/Yodlee/Yodlee.php +++ b/app/Helpers/Bank/Yodlee/Yodlee.php @@ -300,7 +300,7 @@ class Yodlee /** * updateEligibility - * + * * ALLOW_UPDATE * ALLOW_UPDATE_WITH_CREDENTIALS * DISALLOW_UPDATE @@ -308,7 +308,7 @@ class Yodlee /** * additionalStatus - * + * * LOGIN_IN_PROGRESS * DATA_RETRIEVAL_IN_PROGRESS * ACCT_SUMMARY_RECEIVED @@ -339,7 +339,7 @@ class Yodlee * CONSENT_REVOKED * INCORRECT_OAUTH_TOKEN * MIGRATION_IN_PROGRESS - */ + */ /** * IN_PROGRESS LOGIN_IN_PROGRESS Provider login is in progress. @@ -356,12 +356,12 @@ class Yodlee * SUCCESS All accounts under the provider was added or updated successfully. */ - /** - * updateEligibility - * - * ALLOW_UPDATE The status indicates that the account is eligible for the next update and applies to both MFA and non-MFA accounts. For MFA-based accounts, the user may have to provide the MFA details during account refresh. - * ALLOW_UPDATE_WITH_CREDENTIALS The status indicates updating or refreshing the account by directing the user to edit the provided credentials. - * DISALLOW_UPDATE The status indicates the account is not eligible for the update or refresh process due to a site issue or a technical error. - */ + /** + * updateEligibility + * + * ALLOW_UPDATE The status indicates that the account is eligible for the next update and applies to both MFA and non-MFA accounts. For MFA-based accounts, the user may have to provide the MFA details during account refresh. + * ALLOW_UPDATE_WITH_CREDENTIALS The status indicates updating or refreshing the account by directing the user to edit the provided credentials. + * DISALLOW_UPDATE The status indicates the account is not eligible for the update or refresh process due to a site issue or a technical error. + */ } diff --git a/app/Helpers/Epc/EpcQrGenerator.php b/app/Helpers/Epc/EpcQrGenerator.php index 965e2dad1f..a8f4f1518e 100644 --- a/app/Helpers/Epc/EpcQrGenerator.php +++ b/app/Helpers/Epc/EpcQrGenerator.php @@ -64,7 +64,7 @@ class EpcQrGenerator } catch(\Exception $e) { nlog("EPC QR failure => ".$e->getMessage()); return ''; - } catch( InvalidArgumentException $e) { + } catch(InvalidArgumentException $e) { nlog("EPC QR failure => ".$e->getMessage()); return ''; } diff --git a/app/Helpers/Invoice/InvoiceItemSum.php b/app/Helpers/Invoice/InvoiceItemSum.php index b1f7fa9e64..c7d444b9c5 100644 --- a/app/Helpers/Invoice/InvoiceItemSum.php +++ b/app/Helpers/Invoice/InvoiceItemSum.php @@ -11,16 +11,16 @@ namespace App\Helpers\Invoice; -use App\Models\Quote; +use App\DataMapper\BaseSettings; +use App\DataMapper\InvoiceItem; +use App\DataMapper\Tax\RuleInterface; use App\Models\Client; use App\Models\Credit; use App\Models\Invoice; use App\Models\PurchaseOrder; -use App\Models\RecurringQuote; -use App\DataMapper\InvoiceItem; -use App\DataMapper\BaseSettings; +use App\Models\Quote; use App\Models\RecurringInvoice; -use App\DataMapper\Tax\RuleInterface; +use App\Models\RecurringQuote; use App\Utils\Traits\NumberFormatter; class InvoiceItemSum @@ -125,7 +125,7 @@ class InvoiceItemSum private RuleInterface $rule; - public function __construct( RecurringInvoice | Invoice | Quote | Credit | PurchaseOrder | RecurringQuote $invoice) + public function __construct(RecurringInvoice | Invoice | Quote | Credit | PurchaseOrder | RecurringQuote $invoice) { $this->tax_collection = collect([]); @@ -175,14 +175,15 @@ class InvoiceItemSum return $this; } - if (in_array($this->client->company->country()->iso_3166_2, $this->tax_jurisdictions) ) { //only calculate for supported tax jurisdictions + if (in_array($this->client->company->country()->iso_3166_2, $this->tax_jurisdictions)) { //only calculate for supported tax jurisdictions $class = "App\DataMapper\Tax\\".$this->client->company->country()->iso_3166_2."\\Rule"; $this->rule = new $class(); - if($this->rule->regionWithNoTaxCoverage($this->client->country->iso_3166_2)) - return $this; + if($this->rule->regionWithNoTaxCoverage($this->client->country->iso_3166_2)) { + return $this; + } $this->rule ->setEntity($this->invoice) @@ -399,7 +400,7 @@ class InvoiceItemSum $item_tax = 0; //$amount = $this->item->line_total - ($this->item->line_total * ($this->invoice->discount / $this->sub_total)); - $amount = ($this->sub_total > 0) ? $this->item->line_total - ($this->invoice->discount * ( $this->item->line_total / $this->sub_total)) : 0; + $amount = ($this->sub_total > 0) ? $this->item->line_total - ($this->invoice->discount * ($this->item->line_total / $this->sub_total)) : 0; $item_tax_rate1_total = $this->calcAmountLineTax($this->item->tax_rate1, $amount); diff --git a/app/Helpers/Invoice/InvoiceItemSumInclusive.php b/app/Helpers/Invoice/InvoiceItemSumInclusive.php index 7198abbfe8..7ca6fb5e27 100644 --- a/app/Helpers/Invoice/InvoiceItemSumInclusive.php +++ b/app/Helpers/Invoice/InvoiceItemSumInclusive.php @@ -11,14 +11,14 @@ namespace App\Helpers\Invoice; -use App\Models\Quote; +use App\DataMapper\Tax\RuleInterface; use App\Models\Client; use App\Models\Credit; use App\Models\Invoice; use App\Models\PurchaseOrder; -use App\Models\RecurringQuote; +use App\Models\Quote; use App\Models\RecurringInvoice; -use App\DataMapper\Tax\RuleInterface; +use App\Models\RecurringQuote; use App\Utils\Traits\NumberFormatter; class InvoiceItemSumInclusive @@ -404,14 +404,15 @@ class InvoiceItemSumInclusive return $this; } - if (in_array($this->client->company->country()->iso_3166_2, $this->tax_jurisdictions) ) { //only calculate for supported tax jurisdictions + if (in_array($this->client->company->country()->iso_3166_2, $this->tax_jurisdictions)) { //only calculate for supported tax jurisdictions $class = "App\DataMapper\Tax\\".$this->client->company->country()->iso_3166_2."\\Rule"; $this->rule = new $class(); - if($this->rule->regionWithNoTaxCoverage($this->client->country->iso_3166_2)) - return $this; + if($this->rule->regionWithNoTaxCoverage($this->client->country->iso_3166_2)) { + return $this; + } $this->rule ->setEntity($this->invoice) diff --git a/app/Helpers/Invoice/InvoiceSum.php b/app/Helpers/Invoice/InvoiceSum.php index 3f32ed486f..3767de76c4 100644 --- a/app/Helpers/Invoice/InvoiceSum.php +++ b/app/Helpers/Invoice/InvoiceSum.php @@ -11,15 +11,15 @@ namespace App\Helpers\Invoice; -use App\Models\Quote; -use App\Utils\Number; use App\Models\Credit; use App\Models\Invoice; use App\Models\PurchaseOrder; -use App\Models\RecurringQuote; +use App\Models\Quote; use App\Models\RecurringInvoice; -use Illuminate\Support\Collection; +use App\Models\RecurringQuote; +use App\Utils\Number; use App\Utils\Traits\NumberFormatter; +use Illuminate\Support\Collection; class InvoiceSum { diff --git a/app/Helpers/Invoice/InvoiceSumInclusive.php b/app/Helpers/Invoice/InvoiceSumInclusive.php index 69410f54db..3043607fdf 100644 --- a/app/Helpers/Invoice/InvoiceSumInclusive.php +++ b/app/Helpers/Invoice/InvoiceSumInclusive.php @@ -11,15 +11,14 @@ namespace App\Helpers\Invoice; -use App\Models\Quote; use App\Models\Credit; use App\Models\Invoice; use App\Models\PurchaseOrder; -use App\Models\RecurringQuote; +use App\Models\Quote; use App\Models\RecurringInvoice; -use Illuminate\Support\Collection; +use App\Models\RecurringQuote; use App\Utils\Traits\NumberFormatter; -use App\Helpers\Invoice\InvoiceItemSumInclusive; +use Illuminate\Support\Collection; class InvoiceSumInclusive { diff --git a/app/Helpers/SwissQr/SwissQrGenerator.php b/app/Helpers/SwissQr/SwissQrGenerator.php index d8c1ad7730..fa02a550be 100644 --- a/app/Helpers/SwissQr/SwissQrGenerator.php +++ b/app/Helpers/SwissQr/SwissQrGenerator.php @@ -57,7 +57,7 @@ class SwissQrGenerator // - with specified amount // - with human-readable additional information // - using your QR-IBAN - // + // // Likely the most common use-case in the business world. // Create a new instance of QrBill, containing default headers with fixed values @@ -83,7 +83,7 @@ class SwissQrGenerator // Add debtor information // Who has to pay the invoice? This part is optional. - // + // // Notice how you can use two different styles of addresses: CombinedAddress or StructuredAddress // They are interchangeable for creditor as well as debtor. $qrBill->setUltimateDebtor( @@ -121,7 +121,7 @@ class SwissQrGenerator $array = str_split($tempInvoiceNumber); foreach ($array as $char) { if (is_numeric($char)) { - // + // } else { if ($char) { $char = strtolower($char); diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 591041c31c..52a2d85329 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -11,17 +11,17 @@ namespace App\Http\Controllers; -use App\Models\Account; -use App\Libraries\MultiDB; -use App\Utils\TruthSource; -use App\Models\CompanyUser; -use Illuminate\Http\Response; -use App\Jobs\Account\CreateAccount; -use App\Transformers\AccountTransformer; -use App\Transformers\CompanyUserTransformer; -use Illuminate\Foundation\Bus\DispatchesJobs; use App\Http\Requests\Account\CreateAccountRequest; use App\Http\Requests\Account\UpdateAccountRequest; +use App\Jobs\Account\CreateAccount; +use App\Libraries\MultiDB; +use App\Models\Account; +use App\Models\CompanyUser; +use App\Transformers\AccountTransformer; +use App\Transformers\CompanyUserTransformer; +use App\Utils\TruthSource; +use Illuminate\Foundation\Bus\DispatchesJobs; +use Illuminate\Http\Response; class AccountController extends BaseController { diff --git a/app/Http/Controllers/ActivityController.php b/app/Http/Controllers/ActivityController.php index 2b377c7f5e..5e7e6068d6 100644 --- a/app/Http/Controllers/ActivityController.php +++ b/app/Http/Controllers/ActivityController.php @@ -11,19 +11,19 @@ namespace App\Http\Controllers; -use stdClass; -use App\Utils\Ninja; -use App\Models\Activity; -use Illuminate\Http\Request; -use App\Utils\Traits\MakesHash; -use App\Utils\PhantomJS\Phantom; -use App\Utils\HostedPDF\NinjaPdf; -use App\Utils\Traits\Pdf\PdfMaker; -use App\Utils\Traits\Pdf\PageNumbering; -use Illuminate\Support\Facades\Storage; -use App\Transformers\ActivityTransformer; -use App\Http\Requests\Activity\ShowActivityRequest; use App\Http\Requests\Activity\DownloadHistoricalEntityRequest; +use App\Http\Requests\Activity\ShowActivityRequest; +use App\Models\Activity; +use App\Transformers\ActivityTransformer; +use App\Utils\HostedPDF\NinjaPdf; +use App\Utils\Ninja; +use App\Utils\PhantomJS\Phantom; +use App\Utils\Traits\MakesHash; +use App\Utils\Traits\Pdf\PageNumbering; +use App\Utils\Traits\Pdf\PdfMaker; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Storage; +use stdClass; class ActivityController extends BaseController { diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index 4d2ad56329..afd98dcafb 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -11,13 +11,13 @@ namespace App\Http\Controllers\Auth; +use App\Http\Controllers\Controller; +use App\Libraries\MultiDB; use App\Models\Account; use App\Models\Company; -use App\Libraries\MultiDB; -use Illuminate\Http\Request; -use App\Http\Controllers\Controller; -use Illuminate\Support\Facades\Password; use Illuminate\Foundation\Auth\SendsPasswordResetEmails; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Password; class ForgotPasswordController extends Controller { @@ -90,7 +90,7 @@ class ForgotPasswordController extends Controller $account = Account::find($account_id); } - $is_react = request()->has('react') ? true : false; + $is_react = request()->has('react') ? true : false; return $this->render('auth.passwords.request', ['root' => 'themes', 'account' => $account, 'is_react' => $is_react]); } diff --git a/app/Http/Controllers/Auth/PasswordTimeoutController.php b/app/Http/Controllers/Auth/PasswordTimeoutController.php index 4ce83fa46b..614a48487e 100644 --- a/app/Http/Controllers/Auth/PasswordTimeoutController.php +++ b/app/Http/Controllers/Auth/PasswordTimeoutController.php @@ -25,4 +25,3 @@ class PasswordTimeoutController extends Controller return $cached ? response()->json(['message' => 'Password is valid'], 200) : response()->json(['message' => 'Invalid Password'], 412); } } - diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 0cf124ebd9..27039fcb90 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -111,8 +111,9 @@ class ResetPasswordController extends Controller { auth()->logout(); - if(request()->has('react') || request()->hasHeader('X-React')) + if(request()->has('react') || request()->hasHeader('X-React')) { return redirect(config('ninja.react_url').'/#/login'); + } return redirect('/'); } @@ -130,11 +131,11 @@ class ResetPasswordController extends Controller return new JsonResponse(['message' => trans($response)], 200); } - if($request->hasHeader('X-REACT') || $request->has('react')){ + if($request->hasHeader('X-REACT') || $request->has('react')) { return redirect(config('ninja.react_url').'/#/login'); + } else { + return redirect('/#/login'); } - else - return redirect('/#/login'); return redirect($this->redirectPath()) ->with('status', trans($response)); diff --git a/app/Http/Controllers/Bank/YodleeController.php b/app/Http/Controllers/Bank/YodleeController.php index bf4f1fc368..30e322a748 100644 --- a/app/Http/Controllers/Bank/YodleeController.php +++ b/app/Http/Controllers/Bank/YodleeController.php @@ -12,13 +12,13 @@ namespace App\Http\Controllers\Bank; use App\Helpers\Bank\Yodlee\DTO\AccountSummary; -use Illuminate\Http\Request; -use App\Models\BankIntegration; use App\Helpers\Bank\Yodlee\Yodlee; use App\Http\Controllers\BaseController; -use App\Jobs\Bank\ProcessBankTransactions; -use App\Http\Requests\Yodlee\YodleeAuthRequest; use App\Http\Requests\Yodlee\YodleeAdminRequest; +use App\Http\Requests\Yodlee\YodleeAuthRequest; +use App\Jobs\Bank\ProcessBankTransactions; +use App\Models\BankIntegration; +use Illuminate\Http\Request; class YodleeController extends BaseController { @@ -100,69 +100,69 @@ class YodleeController extends BaseController } - /** - * Process Yodlee Refresh Webhook. - * - * - * @OA\Post( - * path="/api/v1/yodlee/refresh", - * operationId="yodleeRefreshWebhook", - * tags={"yodlee"}, - * summary="Processing webhooks from Yodlee", - * description="Notifies the system when a data point can be refreshed", - * @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"), - * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), - * @OA\Parameter(ref="#/components/parameters/include"), - * @OA\Response( - * response=200, - * description="", - * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), - * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), - * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), - * @OA\JsonContent(ref="#/components/schemas/Credit"), - * ), - * @OA\Response( - * response=422, - * description="Validation error", - * @OA\JsonContent(ref="#/components/schemas/ValidationError"), - * - * ), - * @OA\Response( - * response="default", - * description="Unexpected Error", - * @OA\JsonContent(ref="#/components/schemas/Error"), - * ), - * ) - */ + /** + * Process Yodlee Refresh Webhook. + * + * + * @OA\Post( + * path="/api/v1/yodlee/refresh", + * operationId="yodleeRefreshWebhook", + * tags={"yodlee"}, + * summary="Processing webhooks from Yodlee", + * description="Notifies the system when a data point can be refreshed", + * @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Response( + * response=200, + * description="", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/Credit"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ - /* - { - "event":{ - "info":"REFRESH.PROCESS_COMPLETED", - "loginName":"fri21", - "data":{ - "providerAccount":[ - { - "id":10995860, - "providerId":16441, - "isManual":false, - "createdDate":"2017-12-22T05:47:35Z", - "aggregationSource":"USER", - "status":"SUCCESS", - "requestId":"NSyMGo+R4dktywIu3hBIkc3PgWA=", - "dataset":[ - { - "name":"BASIC_AGG_DATA", - "additionalStatus":"AVAILABLE_DATA_RETRIEVED", - "updateEligibility":"ALLOW_UPDATE", - "lastUpdated":"2017-12-22T05:48:16Z", - "lastUpdateAttempt":"2017-12-22T05:48:16Z" - } - ] - } - ] + /* + { + "event":{ + "info":"REFRESH.PROCESS_COMPLETED", + "loginName":"fri21", + "data":{ + "providerAccount":[ + { + "id":10995860, + "providerId":16441, + "isManual":false, + "createdDate":"2017-12-22T05:47:35Z", + "aggregationSource":"USER", + "status":"SUCCESS", + "requestId":"NSyMGo+R4dktywIu3hBIkc3PgWA=", + "dataset":[ + { + "name":"BASIC_AGG_DATA", + "additionalStatus":"AVAILABLE_DATA_RETRIEVED", + "updateEligibility":"ALLOW_UPDATE", + "lastUpdated":"2017-12-22T05:48:16Z", + "lastUpdateAttempt":"2017-12-22T05:48:16Z" + } + ] + } + ] + } } - } }*/ public function refreshWebhook(Request $request) { @@ -177,28 +177,28 @@ class YodleeController extends BaseController // return response()->json(['message' => 'Unauthorized'], 403); } -/* -{ - "event":{ - "notificationId":"63c73475-4db5-49ef-8553-8303337ca7c3", - "info":"LATEST_BALANCE_UPDATES", - "loginName":"user1", - "data":{ - "providerAccountId":658552, - "latestBalanceEvent":[ - { - "accountId":12345, - "status":"SUCCESS" - }, - { - "accountId":12346, - "status":"FAILED" - } - ] - } - } -} -*/ + /* + { + "event":{ + "notificationId":"63c73475-4db5-49ef-8553-8303337ca7c3", + "info":"LATEST_BALANCE_UPDATES", + "loginName":"user1", + "data":{ + "providerAccountId":658552, + "latestBalanceEvent":[ + { + "accountId":12345, + "status":"SUCCESS" + }, + { + "accountId":12346, + "status":"FAILED" + } + ] + } + } + } + */ public function balanceWebhook(Request $request) { nlog("yodlee refresh"); @@ -211,29 +211,29 @@ class YodleeController extends BaseController // return response()->json(['message' => 'Unauthorized'], 403); } -/* -{ - "event":{ - "data":[ - { - "autoRefresh":{ - "additionalStatus":"SCHEDULED", - "status":"ENABLED" - }, - "accountIds":[ - 1112645899, - 1112645898 - ], - "loginName":"YSL1555332811628", - "providerAccountId":11381459 - } - ], - "notificationTime":"2019-06-14T04:49:39Z", - "notificationId":"4e672150-156048777", - "info":"AUTO_REFRESH_UPDATES" - } -} -*/ + /* + { + "event":{ + "data":[ + { + "autoRefresh":{ + "additionalStatus":"SCHEDULED", + "status":"ENABLED" + }, + "accountIds":[ + 1112645899, + 1112645898 + ], + "loginName":"YSL1555332811628", + "providerAccountId":11381459 + } + ], + "notificationTime":"2019-06-14T04:49:39Z", + "notificationId":"4e672150-156048777", + "info":"AUTO_REFRESH_UPDATES" + } + } + */ public function refreshUpdatesWebhook(Request $request) { //notifies a user if there are problems with yodlee accessing the data @@ -248,27 +248,27 @@ class YodleeController extends BaseController } -/* -"event": { - "notificationId": "64b7ed1a-1530523285", - "info": "DATA_UPDATES.USER_DATA", - "data": { - "userCount": 1, - "fromDate": "2017-11-10T10:18:44Z", - "toDate": "2017-11-10T11:18:43Z", - "userData": [{ - "user": { - "loginName": "YSL1484052178554" - }, - "links": [{ - "methodType": "GET", - "rel": "getUserData", - "href": "dataExtracts/userData?fromDate=2017-11-10T10:18:44Z&toDate=2017-11-10T11:18:43Z&loginName=YSL1484052178554" + /* + "event": { + "notificationId": "64b7ed1a-1530523285", + "info": "DATA_UPDATES.USER_DATA", + "data": { + "userCount": 1, + "fromDate": "2017-11-10T10:18:44Z", + "toDate": "2017-11-10T11:18:43Z", + "userData": [{ + "user": { + "loginName": "YSL1484052178554" + }, + "links": [{ + "methodType": "GET", + "rel": "getUserData", + "href": "dataExtracts/userData?fromDate=2017-11-10T10:18:44Z&toDate=2017-11-10T11:18:43Z&loginName=YSL1484052178554" + }] }] - }] + } } -} -*/ + */ public function dataUpdatesWebhook(Request $request) { //this is the main hook we use for notifications @@ -292,8 +292,9 @@ class YodleeController extends BaseController ->where('account_id', $account_number) ->exists(); - if(!$bank_integration) + if(!$bank_integration) { return response()->json(['message' => 'Account does not exist.'], 400); + } $yodlee = new Yodlee($user->account->bank_integration_account_id); diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 3e404ff73f..b2d3a33a9a 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -11,26 +11,25 @@ namespace App\Http\Controllers; -use Illuminate\Http\Request; -use Illuminate\Http\Response; -use App\Models\BankIntegration; -use App\Utils\Traits\MakesHash; -use Illuminate\Http\JsonResponse; -use App\Helpers\Bank\Yodlee\Yodlee; -use Illuminate\Support\Facades\Cache; use App\Factory\BankIntegrationFactory; use App\Filters\BankIntegrationFilters; -use App\Jobs\Bank\ProcessBankTransactions; -use App\Repositories\BankIntegrationRepository; -use App\Transformers\BankIntegrationTransformer; +use App\Helpers\Bank\Yodlee\Yodlee; +use App\Http\Requests\BankIntegration\AdminBankIntegrationRequest; use App\Http\Requests\BankIntegration\BulkBankIntegrationRequest; +use App\Http\Requests\BankIntegration\CreateBankIntegrationRequest; +use App\Http\Requests\BankIntegration\DestroyBankIntegrationRequest; use App\Http\Requests\BankIntegration\EditBankIntegrationRequest; use App\Http\Requests\BankIntegration\ShowBankIntegrationRequest; -use App\Http\Requests\BankIntegration\AdminBankIntegrationRequest; use App\Http\Requests\BankIntegration\StoreBankIntegrationRequest; -use App\Http\Requests\BankIntegration\CreateBankIntegrationRequest; use App\Http\Requests\BankIntegration\UpdateBankIntegrationRequest; -use App\Http\Requests\BankIntegration\DestroyBankIntegrationRequest; +use App\Jobs\Bank\ProcessBankTransactions; +use App\Models\BankIntegration; +use App\Repositories\BankIntegrationRepository; +use App\Transformers\BankIntegrationTransformer; +use App\Utils\Traits\MakesHash; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Response; +use Illuminate\Support\Facades\Cache; class BankIntegrationController extends BaseController { @@ -109,7 +108,7 @@ class BankIntegrationController extends BaseController * @param CreateBankIntegrationRequest $request * @return Response * - * + * */ public function create(CreateBankIntegrationRequest $request) { @@ -210,12 +209,11 @@ class BankIntegrationController extends BaseController $accounts = $yodlee->getAccounts(); foreach ($accounts as $account) { - if ($bi = BankIntegration::withTrashed()->where('bank_account_id', $account['id'])->where('company_id', $user->company()->id)->first()){ - $bi->balance = $account['current_balance']; - $bi->currency = $account['account_currency']; - $bi->save(); - } - else { + if ($bi = BankIntegration::withTrashed()->where('bank_account_id', $account['id'])->where('company_id', $user->company()->id)->first()) { + $bi->balance = $account['current_balance']; + $bi->currency = $account['account_currency']; + $bi->save(); + } else { $bank_integration = new BankIntegration(); $bank_integration->company_id = $user->company()->id; $bank_integration->account_id = $user->account_id; @@ -293,7 +291,7 @@ class BankIntegrationController extends BaseController /** @var \App\Models\User $user */ $user = auth()->user(); - $user->account->bank_integrations->each(function ($bank_integration) use ($user){ + $user->account->bank_integrations->each(function ($bank_integration) use ($user) { (new ProcessBankTransactions($user->account->bank_integration_account_id, $bank_integration))->handle(); }); diff --git a/app/Http/Controllers/BankTransactionRuleController.php b/app/Http/Controllers/BankTransactionRuleController.php index a17cf4cfe7..b34043e48f 100644 --- a/app/Http/Controllers/BankTransactionRuleController.php +++ b/app/Http/Controllers/BankTransactionRuleController.php @@ -11,20 +11,20 @@ namespace App\Http\Controllers; -use App\Utils\Traits\MakesHash; -use App\Models\BankTransactionRule; use App\Factory\BankTransactionRuleFactory; use App\Filters\BankTransactionRuleFilters; -use App\Repositories\BankTransactionRuleRepository; -use App\Transformers\BankTransactionRuleTransformer; use App\Http\Requests\BankTransactionRule\BulkBankTransactionRuleRequest; +use App\Http\Requests\BankTransactionRule\CreateBankTransactionRuleRequest; +use App\Http\Requests\BankTransactionRule\DestroyBankTransactionRuleRequest; use App\Http\Requests\BankTransactionRule\EditBankTransactionRuleRequest; use App\Http\Requests\BankTransactionRule\ShowBankTransactionRuleRequest; use App\Http\Requests\BankTransactionRule\StoreBankTransactionRuleRequest; -use App\Http\Requests\BankTransactionRule\CreateBankTransactionRuleRequest; use App\Http\Requests\BankTransactionRule\UpdateBankTransactionRuleRequest; -use App\Http\Requests\BankTransactionRule\DestroyBankTransactionRuleRequest; +use App\Models\BankTransactionRule; +use App\Repositories\BankTransactionRuleRepository; use App\Services\Bank\BankMatchingService; +use App\Transformers\BankTransactionRuleTransformer; +use App\Utils\Traits\MakesHash; class BankTransactionRuleController extends BaseController { diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index a8f43eae85..2d5d41c24e 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -531,7 +531,7 @@ class BaseController extends Controller $paginator = $query->paginate($limit); /** @phpstan-ignore-next-line **/ - $query = $paginator->getCollection(); + $query = $paginator->getCollection(); $resource = new Collection($query, $transformer, $this->entity_type); @@ -889,7 +889,7 @@ class BaseController extends Controller $resource = new Collection($query, $transformer, $this->entity_type); $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); - } + } // else { // $resource = new Collection($query, $transformer, $this->entity_type); @@ -924,10 +924,9 @@ class BaseController extends Controller if ($this->entity_type == BankIntegration::class && !$user->isSuperUser() && $user->hasIntersectPermissions(['create_bank_transaction','edit_bank_transaction','view_bank_transaction'])) { $query->exclude(["balance"]); } //allows us to selective display bank integrations back to the user if they can view / create bank transactions but without the bank balance being present in the response - elseif($this->entity_type == TaxRate::class && $user->hasIntersectPermissions(['create_invoice','edit_invoice','create_quote','edit_quote','create_purchase_order','edit_purchase_order'])){ + elseif($this->entity_type == TaxRate::class && $user->hasIntersectPermissions(['create_invoice','edit_invoice','create_quote','edit_quote','create_purchase_order','edit_purchase_order'])) { // need to show tax rates if the user has the ability to create documents. - } - else { + } else { $query->where('user_id', '=', $user->id); } } elseif (in_array($this->entity_type, [Design::class, GroupSetting::class, PaymentTerm::class, TaskStatus::class])) { diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 1dfff875d3..158688ba55 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -11,36 +11,36 @@ namespace App\Http\Controllers; -use App\Utils\Ninja; -use App\Models\Client; -use App\Models\Account; -use App\Models\Company; -use App\Models\SystemLog; -use Postmark\PostmarkClient; -use Illuminate\Http\Response; -use App\Factory\ClientFactory; -use App\Filters\ClientFilters; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\Uploadable; -use App\Utils\Traits\BulkOptions; -use App\Jobs\Client\UpdateTaxData; -use App\Utils\Traits\SavesDocuments; -use App\Repositories\ClientRepository; use App\Events\Client\ClientWasCreated; use App\Events\Client\ClientWasUpdated; -use App\Transformers\ClientTransformer; -use Illuminate\Support\Facades\Storage; +use App\Factory\ClientFactory; +use App\Filters\ClientFilters; use App\Http\Requests\Client\BulkClientRequest; -use App\Http\Requests\Client\EditClientRequest; -use App\Http\Requests\Client\ShowClientRequest; -use App\Http\Requests\Client\PurgeClientRequest; -use App\Http\Requests\Client\StoreClientRequest; use App\Http\Requests\Client\CreateClientRequest; +use App\Http\Requests\Client\DestroyClientRequest; +use App\Http\Requests\Client\EditClientRequest; +use App\Http\Requests\Client\PurgeClientRequest; +use App\Http\Requests\Client\ReactivateClientEmailRequest; +use App\Http\Requests\Client\ShowClientRequest; +use App\Http\Requests\Client\StoreClientRequest; use App\Http\Requests\Client\UpdateClientRequest; use App\Http\Requests\Client\UploadClientRequest; -use App\Http\Requests\Client\DestroyClientRequest; -use App\Http\Requests\Client\ReactivateClientEmailRequest; +use App\Jobs\Client\UpdateTaxData; use App\Jobs\PostMark\ProcessPostmarkWebhook; +use App\Models\Account; +use App\Models\Client; +use App\Models\Company; +use App\Models\SystemLog; +use App\Repositories\ClientRepository; +use App\Transformers\ClientTransformer; +use App\Utils\Ninja; +use App\Utils\Traits\BulkOptions; +use App\Utils\Traits\MakesHash; +use App\Utils\Traits\SavesDocuments; +use App\Utils\Traits\Uploadable; +use Illuminate\Http\Response; +use Illuminate\Support\Facades\Storage; +use Postmark\PostmarkClient; /** * Class ClientController. @@ -74,10 +74,10 @@ class ClientController extends BaseController } /** - * + * * @param ClientFilters $filters * @return Response - * + * */ public function index(ClientFilters $filters) { @@ -275,15 +275,15 @@ class ClientController extends BaseController //todo add an event here using the client name as reference for purge event } -/** - * Update the specified resource in storage. - * - * @param PurgeClientRequest $request - * @param Client $client - * @param string $mergeable_client - * @return \Illuminate\Http\JsonResponse - * - */ + /** + * Update the specified resource in storage. + * + * @param PurgeClientRequest $request + * @param Client $client + * @param string $mergeable_client + * @return \Illuminate\Http\JsonResponse + * + */ public function merge(PurgeClientRequest $request, Client $client, string $mergeable_client) { @@ -313,8 +313,9 @@ class ClientController extends BaseController */ public function updateTaxData(PurgeClientRequest $request, Client $client) { - if($client->company->account->isPaid()) + if($client->company->account->isPaid()) { (new UpdateTaxData($client, $client->company))->handle(); + } return $this->itemResponse($client->fresh()); } @@ -331,8 +332,8 @@ class ClientController extends BaseController /** @var \App\Models\User $user */ $user = auth()->user(); - if(stripos($bounce_id, '-') !== false){ - $log = + if(stripos($bounce_id, '-') !== false) { + $log = SystemLog::query() ->where('company_id', $user->company()->id) ->where('type_id', SystemLog::TYPE_WEBHOOK_RESPONSE) @@ -343,16 +344,16 @@ class ClientController extends BaseController $resolved_bounce_id = false; - if($log && ($log?->log['ID'] ?? false)){ + if($log && ($log?->log['ID'] ?? false)) { $resolved_bounce_id = $log->log['ID'] ?? false; } - if(!$resolved_bounce_id){ + if(!$resolved_bounce_id) { $ppwebhook = new ProcessPostmarkWebhook([]); $resolved_bounce_id = $ppwebhook->getBounceId($bounce_id); } - if(!$resolved_bounce_id){ + if(!$resolved_bounce_id) { return response()->json(['message' => 'Bounce ID not found'], 400); } @@ -367,8 +368,7 @@ class ClientController extends BaseController return response()->json(['message' => 'Success'], 200); - } - catch(\Exception $e){ + } catch(\Exception $e) { return response()->json(['message' => $e->getMessage(), 400]); diff --git a/app/Http/Controllers/ClientPortal/ContactHashLoginController.php b/app/Http/Controllers/ClientPortal/ContactHashLoginController.php index 8501131833..abcef260ff 100644 --- a/app/Http/Controllers/ClientPortal/ContactHashLoginController.php +++ b/app/Http/Controllers/ClientPortal/ContactHashLoginController.php @@ -11,11 +11,10 @@ namespace App\Http\Controllers\ClientPortal; -use Auth; -use App\Models\RecurringInvoice; use App\Http\Controllers\Controller; -use Illuminate\Support\Facades\Redirect; use App\Http\ViewComposers\PortalComposer; +use App\Models\RecurringInvoice; +use Illuminate\Support\Facades\Redirect; class ContactHashLoginController extends Controller { diff --git a/app/Http/Controllers/ClientPortal/EntityViewController.php b/app/Http/Controllers/ClientPortal/EntityViewController.php deleted file mode 100644 index f5c40de3a3..0000000000 --- a/app/Http/Controllers/ClientPortal/EntityViewController.php +++ /dev/null @@ -1,184 +0,0 @@ -entity_types)) { - abort(404, 'Entity not found'); - } - - $invitation_entity = sprintf('App\\Models\\%sInvitation', ucfirst($entity_type)); - - $key = $entity_type.'_id'; - - $invitation = $invitation_entity::where('key', $invitation_key) - ->with('contact.client') - ->firstOrFail(); - - $contact = $invitation->contact; - $client = $contact->client; - $entity = $invitation->{$entity_type}; - - if (is_null($contact->password) || empty($contact->password)) { - return redirect("/client/password/reset?email={$contact->email}"); - } - - if ((bool) $invitation->contact->client->getSetting('enable_client_portal_password') !== false) { - session()->flash("{$entity_type}_VIEW_{$entity->hashed_id}", true); - } - - if (! session("{$entity_type}_VIEW_{$entity->hashed_id}")) { - return redirect()->route('client.entity_view.password', compact('entity_type', 'invitation_key')); - } - - return $this->render('view_entity.index', [ - 'root' => 'themes', - 'entity' => $entity, - ]); - } - - /** - * Show the form for entering password. - * - * @param string $entity_type - * @param string $invitation_key - * - * @return Factory|View - */ - public function password(string $entity_type, string $invitation_key) - { - return $this->render('view_entity.password', [ - 'root' => 'themes', - 'entity_type' => $entity_type, - ]); - } - - /**` - * Handle the password check. - * - * @param string $entity_type - * @param string $invitation_key - * - * @return Redirector|RedirectResponse|mixed - */ - public function handlePassword(string $entity_type, string $invitation_key) - { - if (! in_array($entity_type, $this->entity_types)) { - abort(404, 'Entity not found'); - } - - $invitation_entity = sprintf('App\\Models\\%sInvitation', ucfirst($entity_type)); - - $key = $entity_type.'_id'; - - $invitation = $invitation_entity::where('key', $invitation_key)->firstOrFail(); - - $contact = $invitation->contact; - - $check = Hash::check(request()->password, $contact->password); - - $entity_class = sprintf('App\\Models\\%s', ucfirst($entity_type)); - - $entity = $entity_class::findOrFail($invitation->{$key}); - - if ($check) { - session()->flash("{$entity_type}_VIEW_{$entity->hashed_id}", true); - - return redirect()->route('client.entity_view', compact('entity_type', 'invitation_key')); - } - - session()->flash('PASSWORD_FAILED', true); - - return back(); - } - - public function handlePasswordSet(Request $request) - { - $entity_obj = 'App\Models\\'.ucfirst(Str::camel($request->entity_type)).'Invitation'; - $key = $request->entity_type.'_id'; - - $invitation = $entity_obj::where('key', $request->invitation_key) - ->whereHas($request->entity_type, function ($query) { - $query->where('is_deleted', 0); - }) - ->with('contact.client') - ->firstOrFail(); - - $contact = $invitation->contact; - $contact->password = Hash::make($request->password); - $contact->save(); - - $request->session()->invalidate(); - auth()->guard('contact')->loginUsingId($contact->id, true); - - if (! $invitation->viewed_date) { - $invitation->markViewed(); - - if (! session()->get('is_silent')) { - event(new InvitationWasViewed($invitation->{$request->entity_type}, $invitation, $invitation->{$request->entity_type}->company, Ninja::eventVars())); - } - - if (! session()->get('is_silent')) { - $this->fireEntityViewedEvent($invitation, $request->entity_type); - } - } - - return redirect()->route('client.'.$request->entity_type.'.show', [$request->entity_type => $this->encodePrimaryKey($invitation->{$key})]); - } - - private function fireEntityViewedEvent($invitation, $entity_string) - { - switch ($entity_string) { - case 'invoice': - event(new InvoiceWasViewed($invitation, $invitation->company, Ninja::eventVars())); - break; - case 'quote': - event(new QuoteWasViewed($invitation, $invitation->company, Ninja::eventVars())); - break; - case 'credit': - event(new CreditWasViewed($invitation, $invitation->company, Ninja::eventVars())); - break; - default: - // code... - break; - } - } -} diff --git a/app/Http/Controllers/ClientPortal/InvitationController.php b/app/Http/Controllers/ClientPortal/InvitationController.php index e5007cca9a..8f62169b0e 100644 --- a/app/Http/Controllers/ClientPortal/InvitationController.php +++ b/app/Http/Controllers/ClientPortal/InvitationController.php @@ -11,27 +11,27 @@ namespace App\Http\Controllers\ClientPortal; -use App\Utils\Ninja; -use App\Models\Client; -use App\Models\Payment; -use Illuminate\Support\Str; -use Illuminate\Http\Request; -use App\Models\ClientContact; -use App\Models\QuoteInvitation; -use App\Utils\Traits\MakesHash; -use App\Models\CreditInvitation; -use App\Utils\Traits\MakesDates; -use App\Jobs\Entity\CreateRawPdf; -use App\Models\InvoiceInvitation; -use App\Events\Quote\QuoteWasViewed; -use App\Http\Controllers\Controller; -use Illuminate\Support\Facades\Auth; -use App\Events\Credit\CreditWasViewed; use App\Events\Contact\ContactLoggedIn; -use App\Models\PurchaseOrderInvitation; +use App\Events\Credit\CreditWasViewed; use App\Events\Invoice\InvoiceWasViewed; use App\Events\Misc\InvitationWasViewed; +use App\Events\Quote\QuoteWasViewed; +use App\Http\Controllers\Controller; +use App\Jobs\Entity\CreateRawPdf; +use App\Models\ClientContact; +use App\Models\CreditInvitation; +use App\Models\InvoiceInvitation; +use App\Models\Payment; +use App\Models\PurchaseOrderInvitation; +use App\Models\QuoteInvitation; use App\Services\ClientPortal\InstantPayment; +use App\Utils\Ninja; +use App\Utils\Traits\MakesDates; +use App\Utils\Traits\MakesHash; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Str; /** * Class InvitationController. @@ -100,7 +100,7 @@ class InvitationController extends Controller if (empty($client_contact->email)) { $client_contact->email = Str::random(15) . "@example.com"; $client_contact->save(); - } + } if (request()->has('client_hash') && request()->input('client_hash') == $invitation->contact->client->client_hash) { request()->session()->invalidate(); @@ -195,7 +195,7 @@ class InvitationController extends Controller $file_name = $invitation->{$entity}->numberFormatter().'.pdf'; - $file = (new CreateRawPdf($invitation, $invitation->company->db))->handle(); + $file = (new CreateRawPdf($invitation))->handle(); $headers = ['Content-Type' => 'application/pdf']; @@ -212,6 +212,41 @@ class InvitationController extends Controller { } + + public function handlePasswordSet(Request $request) + { + $entity_obj = 'App\Models\\'.ucfirst(Str::camel($request->entity_type)).'Invitation'; + $key = $request->entity_type.'_id'; + + $invitation = $entity_obj::where('key', $request->invitation_key) + ->whereHas($request->entity_type, function ($query) { + $query->where('is_deleted', 0); + }) + ->with('contact.client') + ->firstOrFail(); + + $contact = $invitation->contact; + $contact->password = Hash::make($request->password); + $contact->save(); + + $request->session()->invalidate(); + auth()->guard('contact')->loginUsingId($contact->id, true); + + if (! $invitation->viewed_date) { + $invitation->markViewed(); + + if (! session()->get('is_silent')) { + event(new InvitationWasViewed($invitation->{$request->entity_type}, $invitation, $invitation->{$request->entity_type}->company, Ninja::eventVars())); + } + + if (! session()->get('is_silent')) { + $this->fireEntityViewedEvent($invitation, $request->entity_type); + } + } + + return redirect()->route('client.'.$request->entity_type.'.show', [$request->entity_type => $this->encodePrimaryKey($invitation->{$key})]); + } + public function paymentRouter(string $contact_key, string $payment_id) { /** @var \App\Models\ClientContact $contact **/ diff --git a/app/Http/Controllers/ClientPortal/InvoiceController.php b/app/Http/Controllers/ClientPortal/InvoiceController.php index bbed2ee5bb..e293eacbbd 100644 --- a/app/Http/Controllers/ClientPortal/InvoiceController.php +++ b/app/Http/Controllers/ClientPortal/InvoiceController.php @@ -11,29 +11,27 @@ namespace App\Http\Controllers\ClientPortal; -use App\Utils\Ninja; -use App\Utils\Number; -use App\Models\Invoice; -use Illuminate\View\View; -use Illuminate\Http\Request; -use App\Models\QuoteInvitation; -use App\Utils\Traits\MakesHash; -use App\Models\CreditInvitation; -use App\Utils\Traits\MakesDates; -use App\Models\InvoiceInvitation; -use App\Http\Controllers\Controller; -use Illuminate\Http\RedirectResponse; -use Illuminate\Support\Facades\Cache; -use Illuminate\Contracts\View\Factory; -use App\Models\PurchaseOrderInvitation; -use Illuminate\Support\Facades\Storage; use App\Events\Invoice\InvoiceWasViewed; use App\Events\Misc\InvitationWasViewed; -use App\Models\RecurringInvoiceInvitation; -use App\Jobs\Vendor\CreatePurchaseOrderPdf; +use App\Http\Controllers\Controller; +use App\Http\Requests\ClientPortal\Invoices\ProcessInvoicesInBulkRequest; use App\Http\Requests\ClientPortal\Invoices\ShowInvoiceRequest; use App\Http\Requests\ClientPortal\Invoices\ShowInvoicesRequest; -use App\Http\Requests\ClientPortal\Invoices\ProcessInvoicesInBulkRequest; +use App\Models\CreditInvitation; +use App\Models\Invoice; +use App\Models\InvoiceInvitation; +use App\Models\QuoteInvitation; +use App\Models\RecurringInvoiceInvitation; +use App\Utils\Ninja; +use App\Utils\Number; +use App\Utils\Traits\MakesDates; +use App\Utils\Traits\MakesHash; +use Illuminate\Contracts\View\Factory; +use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Storage; +use Illuminate\View\View; class InvoiceController extends Controller { @@ -88,14 +86,14 @@ class InvoiceController extends Controller { $data = Cache::get($hash); - if(!$data){ + if(!$data) { usleep(200000); $data = Cache::get($hash); } $invitation = false; - match($data['entity_type'] ?? false){ + match($data['entity_type'] ?? false) { 'invoice' => $invitation = InvoiceInvitation::withTrashed()->find($data['invitation_id']), 'quote' => $invitation = QuoteInvitation::withTrashed()->find($data['invitation_id']), 'credit' => $invitation = CreditInvitation::withTrashed()->find($data['invitation_id']), @@ -107,7 +105,7 @@ class InvoiceController extends Controller return redirect('/'); } - $file = (new \App\Jobs\Entity\CreateRawPdf($invitation, $invitation->company->db))->handle(); + $file = (new \App\Jobs\Entity\CreateRawPdf($invitation))->handle(); $headers = ['Content-Type' => 'application/pdf']; return response()->make($file, 200, $headers); diff --git a/app/Http/Controllers/ClientPortal/PaymentController.php b/app/Http/Controllers/ClientPortal/PaymentController.php index 841a61a105..81b560ab19 100644 --- a/app/Http/Controllers/ClientPortal/PaymentController.php +++ b/app/Http/Controllers/ClientPortal/PaymentController.php @@ -27,7 +27,6 @@ use App\Services\Subscription\SubscriptionService; use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesHash; use Illuminate\Contracts\View\Factory; -use Illuminate\Database\Eloquent\Collection; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\View\View; diff --git a/app/Http/Controllers/ClientPortal/QuoteController.php b/app/Http/Controllers/ClientPortal/QuoteController.php index 39d48aab29..98cb3cd871 100644 --- a/app/Http/Controllers/ClientPortal/QuoteController.php +++ b/app/Http/Controllers/ClientPortal/QuoteController.php @@ -12,22 +12,21 @@ namespace App\Http\Controllers\ClientPortal; -use App\Utils\Ninja; -use App\Models\Quote; -use Illuminate\View\View; -use Illuminate\Http\Request; -use App\Models\QuoteInvitation; -use App\Utils\Traits\MakesHash; +use App\Events\Misc\InvitationWasViewed; use App\Events\Quote\QuoteWasViewed; use App\Http\Controllers\Controller; -use App\Jobs\Invoice\InjectSignature; -use Illuminate\Contracts\View\Factory; -use Illuminate\Support\Facades\Storage; -use App\Events\Misc\InvitationWasViewed; -use Symfony\Component\HttpFoundation\BinaryFileResponse; +use App\Http\Requests\ClientPortal\Quotes\ProcessQuotesInBulkRequest; use App\Http\Requests\ClientPortal\Quotes\ShowQuoteRequest; use App\Http\Requests\ClientPortal\Quotes\ShowQuotesRequest; -use App\Http\Requests\ClientPortal\Quotes\ProcessQuotesInBulkRequest; +use App\Jobs\Invoice\InjectSignature; +use App\Models\Quote; +use App\Models\QuoteInvitation; +use App\Utils\Ninja; +use App\Utils\Traits\MakesHash; +use Illuminate\Contracts\View\Factory; +use Illuminate\Http\Request; +use Illuminate\View\View; +use Symfony\Component\HttpFoundation\BinaryFileResponse; class QuoteController extends Controller { @@ -123,7 +122,7 @@ class QuoteController extends Controller $client_contact = auth()->user(); $quote_invitations = QuoteInvitation::query() - ->with('quote','company') + ->with('quote', 'company') ->whereIn('quote_id', $ids) ->where('client_contact_id', $client_contact->id) ->withTrashed() @@ -137,7 +136,7 @@ class QuoteController extends Controller if ($quote_invitations->count() == 1) { $invitation = $quote_invitations->first(); - $file = (new \App\Jobs\Entity\CreateRawPdf($invitation, $invitation->company->db))->handle(); + $file = (new \App\Jobs\Entity\CreateRawPdf($invitation))->handle(); return response()->streamDownload(function () use ($file) { echo $file; }, $invitation->quote->numberFormatter().".pdf", ['Content-Type' => 'application/pdf']); @@ -152,7 +151,7 @@ class QuoteController extends Controller $zipFile = new \PhpZip\ZipFile(); try { foreach ($quote_invitations as $invitation) { - $file = (new \App\Jobs\Entity\CreateRawPdf($invitation, $invitation->company->db))->handle(); + $file = (new \App\Jobs\Entity\CreateRawPdf($invitation))->handle(); $zipFile->addFromString($invitation->quote->numberFormatter() . '.pdf', $file); } diff --git a/app/Http/Controllers/ClientPortal/StatementController.php b/app/Http/Controllers/ClientPortal/StatementController.php index 7ed81d48fe..d5bbafb024 100644 --- a/app/Http/Controllers/ClientPortal/StatementController.php +++ b/app/Http/Controllers/ClientPortal/StatementController.php @@ -23,7 +23,7 @@ class StatementController extends Controller /** * Show the statement in the client portal. * - * @return \Illuminate\View\View + * @return \Illuminate\View\View */ public function index(): View { diff --git a/app/Http/Controllers/ClientPortal/TempRouteController.php b/app/Http/Controllers/ClientPortal/TempRouteController.php index b1f71a8370..4efe5b9b63 100644 --- a/app/Http/Controllers/ClientPortal/TempRouteController.php +++ b/app/Http/Controllers/ClientPortal/TempRouteController.php @@ -11,7 +11,6 @@ namespace App\Http\Controllers\ClientPortal; -use Auth; use App\Http\Controllers\Controller; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Redirect; diff --git a/app/Http/Controllers/ClientStatementController.php b/app/Http/Controllers/ClientStatementController.php index 13d4a81ff0..fe10335b81 100644 --- a/app/Http/Controllers/ClientStatementController.php +++ b/app/Http/Controllers/ClientStatementController.php @@ -11,10 +11,10 @@ namespace App\Http\Controllers; +use App\Http\Requests\Statements\CreateStatementRequest; use App\Utils\Traits\MakesHash; use App\Utils\Traits\Pdf\PdfMaker; use Illuminate\Support\Facades\Response; -use App\Http\Requests\Statements\CreateStatementRequest; class ClientStatementController extends BaseController { @@ -43,7 +43,7 @@ class ClientStatementController extends BaseController } $pdf = $request->client()->service()->statement( - $request->only(['start_date', 'end_date', 'show_payments_table', 'show_aging_table', 'status', 'show_credits_table']), + $request->only(['start_date', 'end_date', 'show_payments_table', 'show_aging_table', 'status', 'show_credits_table', 'template']), $send_email ); diff --git a/app/Http/Controllers/CompanyController.php b/app/Http/Controllers/CompanyController.php index ede5c1b92d..6c476ab979 100644 --- a/app/Http/Controllers/CompanyController.php +++ b/app/Http/Controllers/CompanyController.php @@ -11,39 +11,38 @@ namespace App\Http\Controllers; -use Str; -use App\Utils\Ninja; -use App\Models\Account; -use App\Models\Company; -use App\Models\CompanyUser; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\Uploadable; -use App\Jobs\Mail\NinjaMailerJob; -use App\DataMapper\CompanySettings; -use App\Jobs\Company\CreateCompany; -use App\Jobs\Company\CompanyTaxRate; -use App\Jobs\Mail\NinjaMailerObject; -use App\Mail\Company\CompanyDeleted; -use App\Utils\Traits\SavesDocuments; -use Turbo124\Beacon\Facades\LightLogs; -use App\Repositories\CompanyRepository; -use Illuminate\Support\Facades\Storage; -use App\Jobs\Company\CreateCompanyToken; -use App\Transformers\CompanyTransformer; use App\DataMapper\Analytics\AccountDeleted; -use App\Transformers\CompanyUserTransformer; -use Illuminate\Foundation\Bus\DispatchesJobs; -use App\Jobs\Company\CreateCompanyPaymentTerms; -use App\Jobs\Company\CreateCompanyTaskStatuses; +use App\DataMapper\CompanySettings; +use App\Http\Requests\Company\CreateCompanyRequest; +use App\Http\Requests\Company\DefaultCompanyRequest; +use App\Http\Requests\Company\DestroyCompanyRequest; use App\Http\Requests\Company\EditCompanyRequest; use App\Http\Requests\Company\ShowCompanyRequest; use App\Http\Requests\Company\StoreCompanyRequest; -use App\Http\Requests\Company\CreateCompanyRequest; use App\Http\Requests\Company\UpdateCompanyRequest; use App\Http\Requests\Company\UploadCompanyRequest; -use App\Http\Requests\Company\DefaultCompanyRequest; -use App\Http\Requests\Company\DestroyCompanyRequest; +use App\Jobs\Company\CompanyTaxRate; +use App\Jobs\Company\CreateCompany; +use App\Jobs\Company\CreateCompanyPaymentTerms; +use App\Jobs\Company\CreateCompanyTaskStatuses; +use App\Jobs\Company\CreateCompanyToken; +use App\Jobs\Mail\NinjaMailerJob; +use App\Jobs\Mail\NinjaMailerObject; +use App\Mail\Company\CompanyDeleted; +use App\Models\Account; +use App\Models\Company; +use App\Models\CompanyUser; +use App\Repositories\CompanyRepository; +use App\Transformers\CompanyTransformer; +use App\Transformers\CompanyUserTransformer; +use App\Utils\Ninja; +use App\Utils\Traits\MakesHash; +use App\Utils\Traits\SavesDocuments; +use App\Utils\Traits\Uploadable; +use Illuminate\Foundation\Bus\DispatchesJobs; +use Illuminate\Http\Response; +use Illuminate\Support\Facades\Storage; +use Turbo124\Beacon\Facades\LightLogs; /** * Class CompanyController. @@ -427,7 +426,7 @@ class CompanyController extends BaseController $this->saveDocuments($request->input('documents'), $company, $request->input('is_public', true)); } - if($request->has('e_invoice_certificate') && !is_null($request->file("e_invoice_certificate"))){ + if($request->has('e_invoice_certificate') && !is_null($request->file("e_invoice_certificate"))) { $company->e_invoice_certificate = base64_encode($request->file("e_invoice_certificate")->get()); @@ -684,16 +683,15 @@ class CompanyController extends BaseController public function updateOriginTaxData(DefaultCompanyRequest $request, Company $company) { - if($company->settings->country_id == "840" && !$company?->account->isFreeHostedClient()) - { + if($company->settings->country_id == "840" && !$company?->account->isFreeHostedClient()) { try { (new CompanyTaxRate($company))->handle(); } catch(\Exception $e) { return response()->json(['message' => 'There was a problem updating the tax rates. Please try again.'], 400); } - } - else + } else { return response()->json(['message' => 'Tax configuration not available due to settings / plan restriction.'], 400); + } return $this->itemResponse($company->fresh()); } @@ -707,7 +705,7 @@ class CompanyController extends BaseController $logo = strlen($company->settings->company_logo) > 5 ? $company->settings->company_logo : 'https://pdf.invoicing.co/favicon-v2.png'; $headers = ['Content-Disposition' => 'inline']; - return response()->streamDownload(function () use ($logo){ + return response()->streamDownload(function () use ($logo) { echo @file_get_contents($logo); }, 'logo.png', $headers); diff --git a/app/Http/Controllers/CompanyGatewayController.php b/app/Http/Controllers/CompanyGatewayController.php index 5e9fed6bfd..9914327f52 100644 --- a/app/Http/Controllers/CompanyGatewayController.php +++ b/app/Http/Controllers/CompanyGatewayController.php @@ -213,8 +213,7 @@ class CompanyGatewayController extends BaseController if (in_array($company_gateway->gateway_key, $this->stripe_keys)) { StripeWebhook::dispatch($company_gateway->company->company_key, $company_gateway->id); - } - elseif($company_gateway->gateway_key == $this->checkout_key) { + } elseif($company_gateway->gateway_key == $this->checkout_key) { CheckoutSetupWebhook::dispatch($company_gateway->company->company_key, $company_gateway->id); } diff --git a/app/Http/Controllers/CompanyUserController.php b/app/Http/Controllers/CompanyUserController.php index 69de122d5f..e020807da7 100644 --- a/app/Http/Controllers/CompanyUserController.php +++ b/app/Http/Controllers/CompanyUserController.php @@ -11,14 +11,14 @@ namespace App\Http\Controllers; -use App\Models\User; -use App\Models\CompanyUser; -use Illuminate\Http\Response; -use App\Transformers\UserTransformer; -use App\Transformers\CompanyUserTransformer; -use Illuminate\Database\Eloquent\ModelNotFoundException; -use App\Http\Requests\CompanyUser\UpdateCompanyUserRequest; use App\Http\Requests\CompanyUser\UpdateCompanyUserPreferencesRequest; +use App\Http\Requests\CompanyUser\UpdateCompanyUserRequest; +use App\Models\CompanyUser; +use App\Models\User; +use App\Transformers\CompanyUserTransformer; +use App\Transformers\UserTransformer; +use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Http\Response; class CompanyUserController extends BaseController { @@ -115,7 +115,7 @@ class CompanyUserController extends BaseController $auth_user = auth()->user(); $company = $auth_user->company(); - $company_user = CompanyUser::query()->where('user_id', $user->id)->where('company_id',$company->id)->first(); + $company_user = CompanyUser::query()->where('user_id', $user->id)->where('company_id', $company->id)->first(); if (! $company_user) { throw new ModelNotFoundException(ctrans('texts.company_user_not_found')); diff --git a/app/Http/Controllers/CreditController.php b/app/Http/Controllers/CreditController.php index b2be03691e..24db27876e 100644 --- a/app/Http/Controllers/CreditController.php +++ b/app/Http/Controllers/CreditController.php @@ -11,35 +11,35 @@ namespace App\Http\Controllers; -use App\Utils\Ninja; -use App\Models\Client; -use App\Models\Credit; -use App\Models\Account; -use App\Models\Invoice; -use Illuminate\Http\Response; -use App\Factory\CreditFactory; -use App\Filters\CreditFilters; -use App\Jobs\Credit\ZipCredits; -use App\Utils\Traits\MakesHash; -use App\Jobs\Entity\EmailEntity; -use App\Factory\CloneCreditFactory; -use App\Services\PdfMaker\PdfMerge; -use Illuminate\Support\Facades\App; -use App\Utils\Traits\SavesDocuments; -use App\Repositories\CreditRepository; use App\Events\Credit\CreditWasCreated; use App\Events\Credit\CreditWasUpdated; -use App\Transformers\CreditTransformer; -use Illuminate\Support\Facades\Storage; +use App\Factory\CloneCreditFactory; +use App\Factory\CreditFactory; +use App\Filters\CreditFilters; +use App\Http\Requests\Credit\ActionCreditRequest; use App\Http\Requests\Credit\BulkCreditRequest; +use App\Http\Requests\Credit\CreateCreditRequest; +use App\Http\Requests\Credit\DestroyCreditRequest; use App\Http\Requests\Credit\EditCreditRequest; use App\Http\Requests\Credit\ShowCreditRequest; use App\Http\Requests\Credit\StoreCreditRequest; -use App\Http\Requests\Credit\ActionCreditRequest; -use App\Http\Requests\Credit\CreateCreditRequest; use App\Http\Requests\Credit\UpdateCreditRequest; use App\Http\Requests\Credit\UploadCreditRequest; -use App\Http\Requests\Credit\DestroyCreditRequest; +use App\Jobs\Credit\ZipCredits; +use App\Jobs\Entity\EmailEntity; +use App\Models\Account; +use App\Models\Client; +use App\Models\Credit; +use App\Models\Invoice; +use App\Repositories\CreditRepository; +use App\Services\PdfMaker\PdfMerge; +use App\Transformers\CreditTransformer; +use App\Utils\Ninja; +use App\Utils\Traits\MakesHash; +use App\Utils\Traits\SavesDocuments; +use Illuminate\Http\Response; +use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Storage; /** * Class CreditController. @@ -386,7 +386,7 @@ class CreditController extends BaseController $credit->service() ->triggeredActions($request); - // ->deletePdf(); + // ->deletePdf(); /** @var \App\Models\User $user**/ $user = auth()->user(); @@ -527,20 +527,20 @@ class CreditController extends BaseController */ if ($action == 'bulk_download' && $credits->count() > 1) { - $credits->each(function ($credit) use($user){ + $credits->each(function ($credit) use ($user) { if ($user->cannot('view', $credit)) { return response()->json(['message' => ctrans('text.access_denied')]); } }); - ZipCredits::dispatch($credits->pluck('id')->toArray(), $credits->first()->company, $user); + ZipCredits::dispatch($credits->pluck('id')->toArray(), $user->company(), $user); return response()->json(['message' => ctrans('texts.sent_message')], 200); } if ($action == 'bulk_print' && $user->can('view', $credits->first())) { $paths = $credits->map(function ($credit) { - return (new \App\Jobs\Entity\CreateRawPdf($credit->invitations->first(), $credit->company->db))->handle(); + return (new \App\Jobs\Entity\CreateRawPdf($credit->invitations->first()))->handle(); }); $merge = (new PdfMerge($paths->toArray()))->run(); @@ -593,8 +593,8 @@ class CreditController extends BaseController $file = $credit->service()->getCreditPdf($credit->invitations->first()); return response()->streamDownload(function () use ($file) { - echo Storage::get($file); - }, basename($file), ['Content-Type' => 'application/pdf']); + echo $file; + }, $credit->numberFormatter().'.pdf', ['Content-Type' => 'application/pdf']); break; case 'archive': $this->credit_repository->archive($credit); @@ -710,8 +710,9 @@ class CreditController extends BaseController } return response()->streamDownload(function () use ($file) { - echo Storage::get($file); - }, basename($file), $headers); + echo $file; + }, $credit->numberFormatter().'.pdf', $headers); + } /** diff --git a/app/Http/Controllers/DesignController.php b/app/Http/Controllers/DesignController.php index 3e97a8c609..9f537f6e44 100644 --- a/app/Http/Controllers/DesignController.php +++ b/app/Http/Controllers/DesignController.php @@ -301,7 +301,10 @@ class DesignController extends BaseController */ public function create(CreateDesignRequest $request) { - $design = DesignFactory::create(auth()->user()->company()->id, auth()->user()->id); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $design = DesignFactory::create($user->company()->id, $user->id); return $this->itemResponse($design); } @@ -346,7 +349,11 @@ class DesignController extends BaseController */ public function store(StoreDesignRequest $request) { - $design = DesignFactory::create(auth()->user()->company()->id, auth()->user()->id); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $design = DesignFactory::create($user->company()->id, $user->id); + $design->fill($request->all()); $design->save(); diff --git a/app/Http/Controllers/DocumentController.php b/app/Http/Controllers/DocumentController.php index 70d4b1e8a1..9d822aee13 100644 --- a/app/Http/Controllers/DocumentController.php +++ b/app/Http/Controllers/DocumentController.php @@ -145,7 +145,7 @@ class DocumentController extends BaseController * @return Response */ public function update(UpdateDocumentRequest $request, Document $document) - { + { $document->fill($request->all()); $document->save(); diff --git a/app/Http/Controllers/EmailHistoryController.php b/app/Http/Controllers/EmailHistoryController.php index f29f1a79bd..493becc50d 100644 --- a/app/Http/Controllers/EmailHistoryController.php +++ b/app/Http/Controllers/EmailHistoryController.php @@ -33,7 +33,7 @@ class EmailHistoryController extends BaseController ->cursor() ->filter(function ($system_log) { return (isset($system_log->log['history']) && isset($system_log->log['history']['events']) && count($system_log->log['history']['events']) >=1) !== false; - })->map(function ($system_log) { + })->map(function ($system_log) { return $system_log->log['history']; })->values()->all(); @@ -59,7 +59,7 @@ class EmailHistoryController extends BaseController ->cursor() ->filter(function ($system_log) { return ($system_log->log['history'] && isset($system_log->log['history']['events']) && count($system_log->log['history']['events']) >=1) !== false; - })->map(function ($system_log) { + })->map(function ($system_log) { return $system_log->log['history']; })->values()->all(); diff --git a/app/Http/Controllers/ExpenseCategoryController.php b/app/Http/Controllers/ExpenseCategoryController.php index 36c47b9ba1..0c43f649aa 100644 --- a/app/Http/Controllers/ExpenseCategoryController.php +++ b/app/Http/Controllers/ExpenseCategoryController.php @@ -24,7 +24,6 @@ use App\Models\ExpenseCategory; use App\Repositories\BaseRepository; use App\Transformers\ExpenseCategoryTransformer; use App\Utils\Traits\MakesHash; -use Illuminate\Http\Request; use Illuminate\Http\Response; /** @@ -138,7 +137,7 @@ class ExpenseCategoryController extends BaseController /** * Store a newly created resource in storage. * - * @param StoreExpenseCategoryRequest $request + * @param StoreExpenseCategoryRequest $request * @return Response * * diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php index f459a27333..55038c9895 100644 --- a/app/Http/Controllers/ExportController.php +++ b/app/Http/Controllers/ExportController.php @@ -11,12 +11,12 @@ namespace App\Http\Controllers; -use Illuminate\Support\Str; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; -use App\Jobs\Company\CompanyExport; -use Illuminate\Support\Facades\Cache; use App\Http\Requests\Export\StoreExportRequest; +use App\Jobs\Company\CompanyExport; +use App\Utils\Traits\MakesHash; +use Illuminate\Http\Response; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Str; class ExportController extends BaseController { diff --git a/app/Http/Controllers/GroupSettingController.php b/app/Http/Controllers/GroupSettingController.php index ff22b55242..cdd3d94c50 100644 --- a/app/Http/Controllers/GroupSettingController.php +++ b/app/Http/Controllers/GroupSettingController.php @@ -140,8 +140,9 @@ class GroupSettingController extends BaseController public function update(UpdateGroupSettingRequest $request, GroupSetting $group_setting) { /** Need this to prevent settings from being overwritten */ - if(!$request->file('company_logo')) + if(!$request->file('company_logo')) { $group_setting = $this->group_setting_repo->save($request->all(), $group_setting); + } $this->uploadLogo($request->file('company_logo'), $group_setting->company, $group_setting); diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php index 2871bc41e7..e8b82a4859 100644 --- a/app/Http/Controllers/ImportController.php +++ b/app/Http/Controllers/ImportController.php @@ -108,7 +108,7 @@ class ImportController extends Controller { $hints = []; - $translated_keys = collect($available_keys)->map(function ($value,$key){ + $translated_keys = collect($available_keys)->map(function ($value, $key) { $parts = explode(".", $value); $index = $parts[0]; @@ -121,16 +121,14 @@ class ImportController extends Controller foreach($headers as $key => $value) { - foreach($translated_keys as $tkey => $tvalue) - { + foreach($translated_keys as $tkey => $tvalue) { if($this->testMatch($value, $tvalue['label'])) { $hit = $tvalue['key']; $hints[$key] = $hit; unset($translated_keys[$tkey]); break; - } - else { + } else { $hints[$key] = null; } @@ -140,14 +138,12 @@ class ImportController extends Controller } //second pass using the index of the translation here - foreach($headers as $key => $value) - { + foreach($headers as $key => $value) { if(isset($hints[$key])) { continue; } - foreach($translated_keys as $tkey => $tvalue) - { + foreach($translated_keys as $tkey => $tvalue) { if($this->testMatch($value, $tvalue['index'])) { $hit = $tvalue['key']; $hints[$key] = $hit; @@ -164,7 +160,7 @@ class ImportController extends Controller } private function testMatch($haystack, $needle): bool - { + { return stripos($haystack, $needle) !== false; } @@ -256,7 +252,7 @@ class ImportController extends Controller if (substr_count(strstr($csvfile, "\n", true), $delimiter) >= $count) { $count = substr_count(strstr($csvfile, "\n", true), $delimiter); - $bestDelimiter = $delimiter; + $bestDelimiter = $delimiter; } } diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 2eca29cedd..22a1fdcb37 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -12,39 +12,40 @@ namespace App\Http\Controllers; -use App\Utils\Ninja; -use App\Models\Quote; -use App\Models\Account; -use App\Models\Invoice; -use App\Jobs\Cron\AutoBill; -use Illuminate\Http\Response; -use App\Factory\InvoiceFactory; -use App\Filters\InvoiceFilters; -use App\Utils\Traits\MakesHash; -use App\Jobs\Invoice\ZipInvoices; -use App\Services\PdfMaker\PdfMerge; -use Illuminate\Support\Facades\App; -use App\Factory\CloneInvoiceFactory; -use App\Jobs\Invoice\BulkInvoiceJob; -use App\Utils\Traits\SavesDocuments; -use App\Jobs\Invoice\UpdateReminders; -use App\Transformers\QuoteTransformer; -use App\Repositories\InvoiceRepository; -use Illuminate\Support\Facades\Storage; -use App\Transformers\InvoiceTransformer; use App\Events\Invoice\InvoiceWasCreated; use App\Events\Invoice\InvoiceWasUpdated; +use App\Factory\CloneInvoiceFactory; use App\Factory\CloneInvoiceToQuoteFactory; +use App\Factory\InvoiceFactory; +use App\Filters\InvoiceFilters; +use App\Http\Requests\Invoice\ActionInvoiceRequest; use App\Http\Requests\Invoice\BulkInvoiceRequest; +use App\Http\Requests\Invoice\CreateInvoiceRequest; +use App\Http\Requests\Invoice\DestroyInvoiceRequest; use App\Http\Requests\Invoice\EditInvoiceRequest; use App\Http\Requests\Invoice\ShowInvoiceRequest; use App\Http\Requests\Invoice\StoreInvoiceRequest; -use App\Http\Requests\Invoice\ActionInvoiceRequest; -use App\Http\Requests\Invoice\CreateInvoiceRequest; use App\Http\Requests\Invoice\UpdateInvoiceRequest; -use App\Http\Requests\Invoice\UploadInvoiceRequest; -use App\Http\Requests\Invoice\DestroyInvoiceRequest; use App\Http\Requests\Invoice\UpdateReminderRequest; +use App\Http\Requests\Invoice\UploadInvoiceRequest; +use App\Jobs\Cron\AutoBill; +use App\Jobs\Invoice\BulkInvoiceJob; +use App\Jobs\Invoice\UpdateReminders; +use App\Jobs\Invoice\ZipInvoices; +use App\Models\Account; +use App\Models\Invoice; +use App\Models\Quote; +use App\Repositories\InvoiceRepository; +use App\Services\PdfMaker\PdfMerge; +use App\Services\Template\TemplateAction; +use App\Transformers\InvoiceTransformer; +use App\Transformers\QuoteTransformer; +use App\Utils\Ninja; +use App\Utils\Traits\MakesHash; +use App\Utils\Traits\SavesDocuments; +use Illuminate\Http\Response; +use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Storage; /** * Class InvoiceController. @@ -504,7 +505,7 @@ class InvoiceController extends BaseController */ if ($action == 'bulk_download' && $invoices->count() > 1) { - $invoices->each(function ($invoice) use($user) { + $invoices->each(function ($invoice) use ($user) { if ($user->cannot('view', $invoice)) { nlog('access denied'); @@ -527,7 +528,7 @@ class InvoiceController extends BaseController if ($action == 'bulk_print' && $user->can('view', $invoices->first())) { $paths = $invoices->map(function ($invoice) { - return (new \App\Jobs\Entity\CreateRawPdf($invoice->invitations->first(), $invoice->company->db))->handle(); + return (new \App\Jobs\Entity\CreateRawPdf($invoice->invitations->first()))->handle(); }); $merge = (new PdfMerge($paths->toArray()))->run(); @@ -537,6 +538,24 @@ class InvoiceController extends BaseController }, 'print.pdf', ['Content-Type' => 'application/pdf']); } + if($action == 'template' && $user->can('view', $invoices->first())) { + + $hash_or_response = $request->boolean('send_email') ? 'email sent' : \Illuminate\Support\Str::uuid(); + + TemplateAction::dispatch( + $ids, + $request->template_id, + Invoice::class, + $user->id, + $user->company(), + $user->company()->db, + $hash_or_response, + $request->boolean('send_email') + ); + + return response()->json(['message' => $hash_or_response], 200); + } + /* * Send the other actions to the switch */ @@ -718,8 +737,7 @@ class InvoiceController extends BaseController return response()->json(['message' => 'email sent'], 200); } break; - - + default: return response()->json(['message' => ctrans('texts.action_unavailable', ['action' => $action])], 400); } @@ -782,7 +800,7 @@ class InvoiceController extends BaseController $file_name = $invoice->numberFormatter().'.pdf'; - $file = (new \App\Jobs\Entity\CreateRawPdf($invitation, $invitation->company->db))->handle(); + $file = (new \App\Jobs\Entity\CreateRawPdf($invitation))->handle(); $headers = ['Content-Type' => 'application/pdf']; @@ -911,8 +929,12 @@ class InvoiceController extends BaseController $file = $invoice->service()->getInvoiceDeliveryNote($invoice, $invoice->invitations->first()->contact); return response()->streamDownload(function () use ($file) { - echo Storage::get($file); + echo $file; }, basename($file), ['Content-Type' => 'application/pdf']); + + // return response()->streamDownload(function () use ($file) { + // echo Storage::get($file); + // }, basename($file), ['Content-Type' => 'application/pdf']); } /** diff --git a/app/Http/Controllers/LicenseController.php b/app/Http/Controllers/LicenseController.php index ec130115ba..659aa77d9a 100644 --- a/app/Http/Controllers/LicenseController.php +++ b/app/Http/Controllers/LicenseController.php @@ -90,7 +90,7 @@ class LicenseController extends BaseController if(substr($license_key, 0, 3) == 'v5_') { return $this->v5ClaimLicense($license_key, $product_id); - } + } $url = config('ninja.license_url')."/claim_license?license_key={$license_key}&product_id={$product_id}&get_date=true"; $data = trim(CurlUtils::get($url)); diff --git a/app/Http/Controllers/MigrationController.php b/app/Http/Controllers/MigrationController.php index 217d29ed5e..c0bce2fe34 100644 --- a/app/Http/Controllers/MigrationController.php +++ b/app/Http/Controllers/MigrationController.php @@ -261,8 +261,9 @@ class MigrationController extends BaseController { nlog('Starting Migration'); - if($request->has('silent_migration')) + if($request->has('silent_migration')) { $this->silent_migration = true; + } if ($request->companies) { //handle Laravel 5.5 UniHTTP @@ -318,8 +319,9 @@ class MigrationController extends BaseController $nmo->settings = $user->account->companies()->first()->settings; $nmo->to_user = $user; - if(!$this->silent_migration) + if(!$this->silent_migration) { NinjaMailerJob::dispatch($nmo, true); + } return; } elseif ($existing_company && $company_count > 10) { @@ -329,8 +331,9 @@ class MigrationController extends BaseController $nmo->settings = $user->account->companies()->first()->settings; $nmo->to_user = $user; - if(!$this->silent_migration) + if(!$this->silent_migration) { NinjaMailerJob::dispatch($nmo, true); + } return; } @@ -350,8 +353,9 @@ class MigrationController extends BaseController $nmo->settings = $user->account->companies()->first(); $nmo->to_user = $user; - if(!$this->silent_migration) + if(!$this->silent_migration) { NinjaMailerJob::dispatch($nmo, true); + } return response()->json([ '_id' => Str::uuid(), diff --git a/app/Http/Controllers/OneTimeTokenController.php b/app/Http/Controllers/OneTimeTokenController.php index 70d32d42cd..6c37479b7e 100644 --- a/app/Http/Controllers/OneTimeTokenController.php +++ b/app/Http/Controllers/OneTimeTokenController.php @@ -11,15 +11,14 @@ namespace App\Http\Controllers; -use App\Models\User; -use App\Models\Company; -use App\Libraries\MultiDB; -use Illuminate\Support\Str; -use Illuminate\Http\Response; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Cache; -use App\Http\Requests\OneTimeToken\OneTimeTokenRequest; use App\Http\Requests\OneTimeToken\OneTimeRouterRequest; +use App\Http\Requests\OneTimeToken\OneTimeTokenRequest; +use App\Libraries\MultiDB; +use App\Models\Company; +use App\Models\User; +use Illuminate\Http\Response; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Str; class OneTimeTokenController extends BaseController { diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 413f8213ef..adc3500aca 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -14,7 +14,6 @@ namespace App\Http\Controllers; use App\Events\Payment\PaymentWasUpdated; use App\Factory\PaymentFactory; use App\Filters\PaymentFilters; -use App\Http\Requests\Payment\ActionPaymentRequest; use App\Http\Requests\Payment\CreatePaymentRequest; use App\Http\Requests\Payment\DestroyPaymentRequest; use App\Http\Requests\Payment\EditPaymentRequest; @@ -24,14 +23,12 @@ use App\Http\Requests\Payment\StorePaymentRequest; use App\Http\Requests\Payment\UpdatePaymentRequest; use App\Http\Requests\Payment\UploadPaymentRequest; use App\Models\Account; -use App\Models\Invoice; use App\Models\Payment; use App\Repositories\PaymentRepository; use App\Transformers\PaymentTransformer; use App\Utils\Ninja; use App\Utils\Traits\MakesHash; use App\Utils\Traits\SavesDocuments; -use Illuminate\Http\Request; use Illuminate\Http\Response; /** diff --git a/app/Http/Controllers/PaymentTermController.php b/app/Http/Controllers/PaymentTermController.php index a4992ca040..5c8b5ad90a 100644 --- a/app/Http/Controllers/PaymentTermController.php +++ b/app/Http/Controllers/PaymentTermController.php @@ -11,19 +11,19 @@ namespace App\Http\Controllers; -use App\Models\PaymentTerm; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; use App\Factory\PaymentTermFactory; use App\Filters\PaymentTermFilters; -use App\Repositories\PaymentTermRepository; -use App\Transformers\PaymentTermTransformer; +use App\Http\Requests\PaymentTerm\CreatePaymentTermRequest; +use App\Http\Requests\PaymentTerm\DestroyPaymentTermRequest; use App\Http\Requests\PaymentTerm\EditPaymentTermRequest; use App\Http\Requests\PaymentTerm\ShowPaymentTermRequest; use App\Http\Requests\PaymentTerm\StorePaymentTermRequest; -use App\Http\Requests\PaymentTerm\CreatePaymentTermRequest; use App\Http\Requests\PaymentTerm\UpdatePaymentTermRequest; -use App\Http\Requests\PaymentTerm\DestroyPaymentTermRequest; +use App\Models\PaymentTerm; +use App\Repositories\PaymentTermRepository; +use App\Transformers\PaymentTermTransformer; +use App\Utils\Traits\MakesHash; +use Illuminate\Http\Response; class PaymentTermController extends BaseController { diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 6c2d217fbe..d680935af1 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -12,20 +12,15 @@ namespace App\Http\Controllers; use App\Utils\Ninja; -use App\Models\Quote; use App\Models\Client; -use App\Models\Credit; use App\Models\Invoice; use App\Utils\HtmlEngine; -use App\Libraries\MultiDB; -use App\Factory\QuoteFactory; +use Twig\Error\SyntaxError; use App\Jobs\Util\PreviewPdf; use App\Models\ClientContact; use App\Services\Pdf\PdfMock; -use App\Factory\CreditFactory; -use App\Factory\InvoiceFactory; use App\Utils\Traits\MakesHash; -use App\Models\RecurringInvoice; +use App\Services\Pdf\PdfService; use App\Utils\PhantomJS\Phantom; use App\Models\InvoiceInvitation; use App\Services\PdfMaker\Design; @@ -33,17 +28,13 @@ use App\Utils\HostedPDF\NinjaPdf; use Illuminate\Support\Facades\DB; use App\Services\PdfMaker\PdfMaker; use Illuminate\Support\Facades\App; -use App\Repositories\QuoteRepository; use Illuminate\Support\Facades\Cache; -use App\Repositories\CreditRepository; use App\Utils\Traits\MakesInvoiceHtml; use Turbo124\Beacon\Facades\LightLogs; -use App\Repositories\InvoiceRepository; use App\Utils\Traits\Pdf\PageNumbering; -use App\Factory\RecurringInvoiceFactory; use Illuminate\Support\Facades\Response; use App\DataMapper\Analytics\LivePreview; -use App\Repositories\RecurringInvoiceRepository; +use App\Services\Template\TemplateService; use App\Http\Requests\Preview\DesignPreviewRequest; use App\Services\PdfMaker\Design as PdfDesignModel; use App\Services\PdfMaker\Design as PdfMakerDesign; @@ -57,24 +48,71 @@ class PreviewController extends BaseController public function __construct() { - parent::__construct(); - } - - private function purgeCache() - { - Cache::pull("preview_".auth()->user()->id); + parent::__construct(); } + public function live(PreviewInvoiceRequest $request): mixed + { + + if (Ninja::isHosted() && !in_array($request->getHost(), ['preview.invoicing.co','staging.invoicing.co'])) { + return response()->json(['message' => 'This server cannot handle this request.'], 400); + } + + $start = microtime(true); + + /** Build models */ + $invitation = $request->resolveInvitation(); + $client = $request->getClient(); + $settings = $client->getMergedSettings(); + $entity_prop = str_replace("recurring_", "", $request->entity); + $entity_obj = $invitation->{$request->entity}; + $entity_obj->fill($request->all()); + + if(!$entity_obj->id) { + $entity_obj->design_id = intval($this->decodePrimaryKey($settings->{$entity_prop."_design_id"})); + $entity_obj->footer = empty($entity_obj->footer) ? $settings->{$entity_prop."_footer"} : $entity_obj->footer; + $entity_obj->terms = empty($entity_obj->terms) ? $settings->{$entity_prop."_terms"} : $entity_obj->terms; + $entity_obj->public_notes = empty($entity_obj->public_notes) ? $request->getClient()->public_notes : $entity_obj->public_notes; + $invitation->setRelation($request->entity, $entity_obj); + } + + $ps = new PdfService($invitation, 'product', [ + 'client' => $client ?? false, + // 'vendor' => $vendor ?? false, + "{$entity_prop}s" => [$entity_obj], + ]); + + $pdf = $ps->boot()->getPdf(); + + + if (Ninja::isHosted()) { + LightLogs::create(new LivePreview()) + ->increment() + ->batch(); + } + + /** Return PDF */ + return response()->streamDownload(function () use ($pdf) { + echo $pdf; + }, 'preview.pdf', [ + 'Content-Disposition' => 'inline', + 'Content-Type' => 'application/pdf', + 'Cache-Control:' => 'no-cache', + 'Server-Timing' => microtime(true)-$start + ]); + + } + /** * Refactor - 2023-10-19 - * + * * New method does not require Transactions. * * @param PreviewInvoiceRequest $request * @return mixed */ - public function live(PreviewInvoiceRequest $request): mixed - { + public function livexx(PreviewInvoiceRequest $request): mixed + { if (Ninja::isHosted() && !in_array($request->getHost(), ['preview.invoicing.co','staging.invoicing.co'])) { return response()->json(['message' => 'This server cannot handle this request.'], 400); @@ -101,13 +139,14 @@ class PreviewController extends BaseController if(!$entity_obj->id) { $entity_obj->design_id = intval($this->decodePrimaryKey($settings->{$entity_prop."_design_id"})); $entity_obj->footer = empty($entity_obj->footer) ? $settings->{$entity_prop."_footer"} : $entity_obj->footer; - $entity_obj->terms = empty($entity_obj->term) ? $settings->{$entity_prop."_terms"} : $entity_obj->terms; + $entity_obj->terms = empty($entity_obj->terms) ? $settings->{$entity_prop."_terms"} : $entity_obj->terms; $entity_obj->public_notes = empty($entity_obj->public_notes) ? $request->getClient()->public_notes : $entity_obj->public_notes; $invitation->{$request->entity} = $entity_obj; } - if(empty($entity_obj->design_id)) + if(empty($entity_obj->design_id)) { $entity_obj->design_id = intval($this->decodePrimaryKey($settings->{$entity_prop."_design_id"})); + } /** Generate variables */ $html = new HtmlEngine($invitation); @@ -116,11 +155,6 @@ class PreviewController extends BaseController $design = \App\Models\Design::query()->withTrashed()->find($entity_obj->design_id ?? 2); - /* Catch all in case migration doesn't pass back a valid design */ - if (! $design) { - $design = \App\Models\Design::query()->find(2); - } - if ($design->is_custom) { $options = [ 'custom_partials' => json_decode(json_encode($design->design), true), @@ -142,6 +176,9 @@ class PreviewController extends BaseController 'options' => [ 'all_pages_header' => $client->getSetting('all_pages_header'), 'all_pages_footer' => $client->getSetting('all_pages_footer'), + 'client' => $entity_obj->client ?? [], + 'vendor' => $entity_obj->vendor ?? [], + $request->input('entity')."s" => [$entity_obj], ], 'process_markdown' => $client->company->markdown_enabled, ]; @@ -155,8 +192,9 @@ class PreviewController extends BaseController /** Generate HTML */ $html = $maker->getCompiledHTML(true); - if (request()->query('html') == 'true') + if (request()->query('html') == 'true') { return $html; + } //if phantom js...... inject here.. if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { @@ -172,8 +210,9 @@ class PreviewController extends BaseController $pdf = (new NinjaPdf())->build($html); $numbered_pdf = $this->pageNumbering($pdf, $company); - if ($numbered_pdf) + if ($numbered_pdf) { $pdf = $numbered_pdf; + } return $pdf; } @@ -190,9 +229,9 @@ class PreviewController extends BaseController return response()->streamDownload(function () use ($pdf) { echo $pdf; }, 'preview.pdf', [ - 'Content-Disposition' => 'inline', - 'Content-Type' => 'application/pdf', - 'Cache-Control:' => 'no-cache', + 'Content-Disposition' => 'inline', + 'Content-Type' => 'application/pdf', + 'Cache-Control:' => 'no-cache', 'Server-Timing' => microtime(true)-$start ]); @@ -203,7 +242,7 @@ class PreviewController extends BaseController * Returns the mocked PDF for the invoice design preview. * * Only used in Settings > Invoice Design as a general overview - * + * * @param DesignPreviewRequest $request * @return mixed */ @@ -228,12 +267,16 @@ class PreviewController extends BaseController /** * Returns a template filled with entity variables. - * + * * Used in the Custom Designer to preview design changes * @return mixed */ public function show() { + if(request()->has('template')) { + return $this->template(); + } + if (request()->has('entity') && request()->has('entity_id') && ! empty(request()->input('entity')) && @@ -278,6 +321,11 @@ class PreviewController extends BaseController ]), 'variables' => $html->generateLabelsAndValues(), 'process_markdown' => $entity_obj->client->company->markdown_enabled, + 'options' => [ + 'client' => $entity_obj->client ?? [], + 'vendor' => $entity_obj->vendor ?? [], + request()->input('entity_type', 'invoice')."s" => [$entity_obj], + ] ]; $design = new Design(request()->design['name']); @@ -304,8 +352,9 @@ class PreviewController extends BaseController $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); $numbered_pdf = $this->pageNumbering($pdf, $company); - if ($numbered_pdf) + if ($numbered_pdf) { $pdf = $numbered_pdf; + } return $pdf; @@ -327,191 +376,62 @@ class PreviewController extends BaseController return $this->blankEntity(); } - - - /** - * @deprecated due to usage of transactions - * - * @param PreviewInvoiceRequest $request - * @return mixed - */ - public function livex(PreviewInvoiceRequest $request) + private function template() { - if(Cache::has("preview_".auth()->user()->id)) - return response()->json(['message' => 'Please wait a few seconds before trying again, this many requests are not good.'], 400); - - if (Ninja::isHosted() && !in_array($request->getHost(), ['preview.invoicing.co','staging.invoicing.co'])) { - return response()->json(['message' => 'This server cannot handle this request.'], 400); - } - - Cache::put("preview_".auth()->user()->id, 60); - - $start = microtime(true); - /** @var \App\Models\User $user */ $user = auth()->user(); + /** @var \App\Models\Company $company */ $company = $user->company(); - MultiDB::setDb($company->db); - - if ($request->input('entity') == 'quote') { - $repo = new QuoteRepository(); - $entity_obj = QuoteFactory::create($company->id, $user->id); - $class = Quote::class; - } elseif ($request->input('entity') == 'credit') { - $repo = new CreditRepository(); - $entity_obj = CreditFactory::create($company->id, $user->id); - $class = Credit::class; - } elseif ($request->input('entity') == 'recurring_invoice') { - $repo = new RecurringInvoiceRepository(); - $entity_obj = RecurringInvoiceFactory::create($company->id, $user->id); - $class = RecurringInvoice::class; - } else { //assume it is either an invoice or a null object - $repo = new InvoiceRepository(); - $entity_obj = InvoiceFactory::create($company->id, $user->id); - $class = Invoice::class; - } + $design_object = json_decode(json_encode(request()->input('design')), 1); + $ts = (new TemplateService()); try { - DB::connection(config('database.default'))->beginTransaction(); + $ts->setCompany($company) + ->setTemplate($design_object) + ->mock(); + } catch(SyntaxError $e) { - if ($request->has('entity_id')) { + // return response()->json(['message' => 'Twig syntax is invalid.', 'errors' => new \stdClass], 422); - /** @var \App\Models\Quote | \App\Models\Invoice | \App\Models\RecurringInvoice | \App\Models\Credit $class */ - $temp_obj = $class::on(config('database.default')) - ->with('client.company') - ->where('id', $this->decodePrimaryKey($request->input('entity_id'))) - ->where('company_id', $company->id) - ->withTrashed() - ->first(); - - /** Prevents null values from being passed into entity_obj */ - if($temp_obj) - $entity_obj = $temp_obj; - } - - if ($request->has('footer') && !$request->filled('footer') && $request->input('entity') == 'recurring_invoice') { - $request->merge(['footer' => $company->settings->invoice_footer]); - } - - if ($request->has('terms') && !$request->filled('terms') && $request->input('entity') == 'recurring_invoice') { - $request->merge(['terms' => $company->settings->invoice_terms]); - } - - $entity_obj = $repo->save($request->all(), $entity_obj); - - if (! $request->has('entity_id')) { - $entity_obj->service()->fillDefaults()->save(); - } - - App::forgetInstance('translator'); - $t = app('translator'); - App::setLocale($entity_obj->client->locale()); - $t->replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings())); - - $html = new HtmlEngine($entity_obj->invitations()->first()); - - /** @var \App\Models\Design $design */ - $design = \App\Models\Design::withTrashed()->find($entity_obj->design_id); - - /* Catch all in case migration doesn't pass back a valid design */ - if (! $design) { - $design = \App\Models\Design::find(2); - } - - if ($design->is_custom) { - $options = [ - 'custom_partials' => json_decode(json_encode($design->design), true), - ]; - $template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options); - } else { - $template = new PdfMakerDesign(strtolower($design->name)); - } - - $variables = $html->generateLabelsAndValues(); - - $state = [ - 'template' => $template->elements([ - 'client' => $entity_obj->client, - 'entity' => $entity_obj, - 'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables, - '$product' => $design->design->product, - 'variables' => $variables, - ]), - 'variables' => $variables, - 'options' => [ - 'all_pages_header' => $entity_obj->client->getSetting('all_pages_header'), - 'all_pages_footer' => $entity_obj->client->getSetting('all_pages_footer'), - ], - 'process_markdown' => $entity_obj->client->company->markdown_enabled, - ]; - - $maker = new PdfMaker($state); - - $maker - ->design($template) - ->build(); - - DB::connection(config('database.default'))->rollBack(); - - if (request()->query('html') == 'true') { - $this->purgeCache(); - return $maker->getCompiledHTML(); - } - - } catch(\Exception $e) { - - $this->purgeCache(); - - DB::connection(config('database.default'))->rollBack(); - - if (DB::connection(config('database.default'))->transactionLevel() > 0) { - DB::connection(config('database.default'))->rollBack(); - } - - return response()->json(['message' => 'Error generating preview. Please retry again shortly.'], 400); } - //if phantom js...... inject here.. - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - $this->purgeCache(); - return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true)); - } - - /** @var \App\Models\User $user */ - $user = auth()->user(); + $html = $ts->getHtml(); - /** @var \App\Models\Company $company */ - $company = $user->company(); - - if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - - $numbered_pdf = $this->pageNumbering($pdf, $company); - - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } - $this->purgeCache(); - return $pdf; - } - - $file_path = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); - - if (Ninja::isHosted()) { - LightLogs::create(new LivePreview()) - ->increment() - ->batch(); + if (request()->query('html') == 'true') { + return $html; } + if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { + return (new Phantom)->convertHtmlToPdf($html); + } + + if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { + $pdf = (new NinjaPdf())->build($html); + + $numbered_pdf = $this->pageNumbering($pdf, $company); + + if ($numbered_pdf) { + $pdf = $numbered_pdf; + } + + return $pdf; + } + + $file_path = (new PreviewPdf($html, $company))->handle(); + $response = Response::make($file_path, 200); $response->header('Content-Type', 'application/pdf'); - $response->header('Server-Timing', microtime(true)-$start); - $this->purgeCache(); return $response; + + } + + private function stubTemplateData() + { + } private function blankEntity() @@ -554,6 +474,10 @@ class PreviewController extends BaseController ]), 'variables' => $html->generateLabelsAndValues(), 'process_markdown' => $invitation->invoice->client->company->markdown_enabled, + 'options' => [ + 'client' => $invitation->invoice->client, + 'invoices' => [$invitation->invoice], + ] ]; $maker = new PdfMaker($state); @@ -604,76 +528,84 @@ class PreviewController extends BaseController /** @var \App\Models\Company $company */ $company = $user->company(); + try { + DB::connection($company->db)->beginTransaction(); - DB::connection($company->db)->beginTransaction(); + /** @var \App\Models\Client $client */ + $client = Client::factory()->create([ + 'user_id' => auth()->user()->id, + 'company_id' => $company->id, + ]); - /** @var \App\Models\Client $client */ - $client = Client::factory()->create([ - 'user_id' => auth()->user()->id, - 'company_id' => $company->id, - ]); + /** @var \App\Models\ClientContact $contact */ + $contact = ClientContact::factory()->create([ + 'user_id' => auth()->user()->id, + 'company_id' => $company->id, + 'client_id' => $client->id, + 'is_primary' => 1, + 'send_email' => true, + ]); - /** @var \App\Models\ClientContact $contact */ - $contact = ClientContact::factory()->create([ - 'user_id' => auth()->user()->id, - 'company_id' => $company->id, - 'client_id' => $client->id, - 'is_primary' => 1, - 'send_email' => true, - ]); + /** @var \App\Models\Invoice $invoice */ - /** @var \App\Models\Invoice $invoice */ + $invoice = Invoice::factory()->create([ + 'user_id' => auth()->user()->id, + 'company_id' => $company->id, + 'client_id' => $client->id, + 'terms' => $company->settings->invoice_terms, + 'footer' => $company->settings->invoice_footer, + 'public_notes' => 'Sample Public Notes', + ]); - $invoice = Invoice::factory()->create([ - 'user_id' => auth()->user()->id, - 'company_id' => $company->id, - 'client_id' => $client->id, - 'terms' => $company->settings->invoice_terms, - 'footer' => $company->settings->invoice_footer, - 'public_notes' => 'Sample Public Notes', - ]); + $invitation = InvoiceInvitation::factory()->create([ + 'user_id' => auth()->user()->id, + 'company_id' => $company->id, + 'invoice_id' => $invoice->id, + 'client_contact_id' => $contact->id, + ]); - $invitation = InvoiceInvitation::factory()->create([ - 'user_id' => auth()->user()->id, - 'company_id' => $company->id, - 'invoice_id' => $invoice->id, - 'client_contact_id' => $contact->id, - ]); + $invoice->setRelation('invitations', $invitation); + $invoice->setRelation('client', $client); + $invoice->setRelation('company', $company); + $invoice->load('client.company'); - $invoice->setRelation('invitations', $invitation); - $invoice->setRelation('client', $client); - $invoice->setRelation('company', $company); - $invoice->load('client.company'); + $design_object = json_decode(json_encode(request()->input('design'))); - $design_object = json_decode(json_encode(request()->input('design'))); + if (! is_object($design_object)) { + return response()->json(['message' => 'Invalid custom design object'], 400); + } - if (! is_object($design_object)) { - return response()->json(['message' => 'Invalid custom design object'], 400); + $html = new HtmlEngine($invoice->invitations()->first()); + + $design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]); + + $state = [ + 'template' => $design->elements([ + 'client' => $invoice->client, + 'entity' => $invoice, + 'pdf_variables' => (array) $invoice->company->settings->pdf_variables, + 'products' => request()->design['design']['product'], + ]), + 'variables' => $html->generateLabelsAndValues(), + 'process_markdown' => $invoice->client->company->markdown_enabled, + 'options' => [ + 'client' => $invoice->client, + 'invoices' => [$invoice], + ] + ]; + + $maker = new PdfMaker($state); + + $maker + ->design($design) + ->build(); + + DB::connection($company->db)->rollBack(); + } catch(\Exception $e) { + DB::connection($company->db)->rollBack(); + return response()->json(['message' => $e->getMessage()], 400); } - $html = new HtmlEngine($invoice->invitations()->first()); - - $design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]); - - $state = [ - 'template' => $design->elements([ - 'client' => $invoice->client, - 'entity' => $invoice, - 'pdf_variables' => (array) $invoice->company->settings->pdf_variables, - 'products' => request()->design['design']['product'], - ]), - 'variables' => $html->generateLabelsAndValues(), - 'process_markdown' => $invoice->client->company->markdown_enabled, - ]; - - $maker = new PdfMaker($state); - - $maker - ->design($design) - ->build(); - - DB::connection($company->db)->rollBack(); - if (request()->query('html') == 'true') { return $maker->getCompiledHTML(); } diff --git a/app/Http/Controllers/PreviewPurchaseOrderController.php b/app/Http/Controllers/PreviewPurchaseOrderController.php index 81d823cf25..5884e09f56 100644 --- a/app/Http/Controllers/PreviewPurchaseOrderController.php +++ b/app/Http/Controllers/PreviewPurchaseOrderController.php @@ -11,32 +11,33 @@ namespace App\Http\Controllers; -use App\DataMapper\Analytics\LivePreview; -use App\Factory\PurchaseOrderFactory; -use App\Http\Requests\Preview\PreviewPurchaseOrderRequest; -use App\Jobs\Util\PreviewPdf; -use App\Libraries\MultiDB; +use App\Utils\Ninja; use App\Models\Client; -use App\Models\PurchaseOrder; -use App\Models\PurchaseOrderInvitation; use App\Models\Vendor; +use App\Libraries\MultiDB; +use App\Jobs\Util\PreviewPdf; +use App\Models\PurchaseOrder; use App\Models\VendorContact; -use App\Repositories\PurchaseOrderRepository; +use App\Utils\Traits\MakesHash; +use App\Utils\VendorHtmlEngine; +use App\Services\Pdf\PdfService; +use App\Utils\PhantomJS\Phantom; use App\Services\PdfMaker\Design; +use App\Utils\HostedPDF\NinjaPdf; +use Illuminate\Support\Facades\DB; +use App\Services\PdfMaker\PdfMaker; +use Illuminate\Support\Facades\App; +use App\Factory\PurchaseOrderFactory; +use App\Utils\Traits\MakesInvoiceHtml; +use Turbo124\Beacon\Facades\LightLogs; +use App\Models\PurchaseOrderInvitation; +use App\Utils\Traits\Pdf\PageNumbering; +use Illuminate\Support\Facades\Response; +use App\DataMapper\Analytics\LivePreview; +use App\Repositories\PurchaseOrderRepository; use App\Services\PdfMaker\Design as PdfDesignModel; use App\Services\PdfMaker\Design as PdfMakerDesign; -use App\Services\PdfMaker\PdfMaker; -use App\Utils\HostedPDF\NinjaPdf; -use App\Utils\Ninja; -use App\Utils\PhantomJS\Phantom; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\MakesInvoiceHtml; -use App\Utils\Traits\Pdf\PageNumbering; -use App\Utils\VendorHtmlEngine; -use Illuminate\Support\Facades\App; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Response; -use Turbo124\Beacon\Facades\LightLogs; +use App\Http\Requests\Preview\PreviewPurchaseOrderRequest; class PreviewPurchaseOrderController extends BaseController { @@ -122,6 +123,10 @@ class PreviewPurchaseOrderController extends BaseController ]), 'variables' => $html->generateLabelsAndValues(), 'process_markdown' => $entity_obj->company->markdown_enabled, + 'options' => [ + 'vendor' => $entity_obj->vendor ?? [], + request()->input('entity')."s" => [$entity_obj], + ] ]; $design = new Design(request()->design['name']); @@ -165,6 +170,56 @@ class PreviewPurchaseOrderController extends BaseController } public function live(PreviewPurchaseOrderRequest $request) + { + + $start = microtime(true); + + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $invitation = $request->resolveInvitation(); + $vendor = $request->getVendor(); + $settings = $user->company()->settings; + $entity_obj = $invitation->purchase_order; + $entity_obj->fill($request->all()); + + if(!$entity_obj->id) { + $entity_obj->design_id = intval($this->decodePrimaryKey($settings->{"purchase_order_design_id"})); + $entity_obj->footer = empty($entity_obj->footer) ? $settings->{"purchase_order_footer"} : $entity_obj->footer; + $entity_obj->terms = empty($entity_obj->terms) ? $settings->{"purchase_order_terms"} : $entity_obj->terms; + $entity_obj->public_notes = empty($entity_obj->public_notes) ? $request->getVendor()->public_notes : $entity_obj->public_notes; + $invitation->setRelation($request->entity, $entity_obj); + + } + + $ps = new PdfService($invitation, 'purchase_order', [ + 'client' => $entity_obj->client ?? false, + 'vendor' => $vendor ?? false, + "purchase_orders" => [$entity_obj], + ]); + + $pdf = $ps->boot()->getPdf(); + + if (Ninja::isHosted()) { + LightLogs::create(new LivePreview()) + ->increment() + ->batch(); + } + + /** Return PDF */ + return response()->streamDownload(function () use ($pdf) { + echo $pdf; + }, 'preview.pdf', [ + 'Content-Disposition' => 'inline', + 'Content-Type' => 'application/pdf', + 'Cache-Control:' => 'no-cache', + 'Server-Timing' => microtime(true)-$start + ]); + + + } + + public function livex(PreviewPurchaseOrderRequest $request) { /** @var \App\Models\User $user */ $user = auth()->user(); @@ -232,6 +287,12 @@ class PreviewPurchaseOrderController extends BaseController '$product' => $design->design->product, ]), 'variables' => $html->generateLabelsAndValues(), + 'options' => [ + 'client' => null, + 'vendor' => $entity_obj->vendor, + 'purchase_orders' => [$entity_obj], + 'variables' => $html->generateLabelsAndValues(), + ], 'process_markdown' => $entity_obj->company->markdown_enabled, ]; @@ -254,24 +315,24 @@ class PreviewPurchaseOrderController extends BaseController /** @var \App\Models\User $user */ $user = auth()->user(); - //if phantom js...... inject here.. - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true)); - } + //if phantom js...... inject here.. + if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { + return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true)); + } - if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); + if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { + $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - $numbered_pdf = $this->pageNumbering($pdf, $user->company()); + $numbered_pdf = $this->pageNumbering($pdf, $user->company()); - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } - - return $pdf; + if ($numbered_pdf) { + $pdf = $numbered_pdf; } - $file_path = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); + return $pdf; + } + + $file_path = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); if (Ninja::isHosted()) { @@ -324,6 +385,10 @@ class PreviewPurchaseOrderController extends BaseController ]), 'variables' => $html->generateLabelsAndValues(), 'process_markdown' => $invitation->company->markdown_enabled, + 'options' => [ + 'vendor' => $invitation->purchase_order->vendor, + 'purchase_orders' => [$invitation->purchase_order], + ], ]; @@ -429,6 +494,10 @@ class PreviewPurchaseOrderController extends BaseController ]), 'variables' => $html->generateLabelsAndValues(), 'process_markdown' => $purchase_order->company->markdown_enabled, + 'options' => [ + 'vendor' => $invitation->purchase_order->vendor, + 'purchase_orders' => [$invitation->purchase_order], + ], ]; $maker = new PdfMaker($state); diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index cc2784f5c0..c6bf1acc36 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -467,7 +467,7 @@ class ProductController extends BaseController $products = Product::withTrashed()->whereIn('id', $ids); - if($action == 'set_tax_id'){ + if($action == 'set_tax_id') { $tax_id = $request->input('tax_id'); diff --git a/app/Http/Controllers/ProtectedDownloadController.php b/app/Http/Controllers/ProtectedDownloadController.php index 014527bace..51c9077419 100644 --- a/app/Http/Controllers/ProtectedDownloadController.php +++ b/app/Http/Controllers/ProtectedDownloadController.php @@ -11,10 +11,8 @@ namespace App\Http\Controllers; -use App\Utils\Ninja; -use Illuminate\Http\Request; -use App\Jobs\Util\UnlinkFile; use App\Exceptions\SystemError; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Storage; diff --git a/app/Http/Controllers/PurchaseOrderController.php b/app/Http/Controllers/PurchaseOrderController.php index 4ed383f308..4b0ad2e006 100644 --- a/app/Http/Controllers/PurchaseOrderController.php +++ b/app/Http/Controllers/PurchaseOrderController.php @@ -11,33 +11,33 @@ namespace App\Http\Controllers; -use App\Utils\Ninja; -use App\Models\Client; -use App\Models\Account; -use App\Models\PurchaseOrder; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; -use App\Services\PdfMaker\PdfMerge; -use Illuminate\Support\Facades\App; -use App\Utils\Traits\SavesDocuments; -use App\Factory\PurchaseOrderFactory; -use App\Filters\PurchaseOrderFilters; -use Illuminate\Support\Facades\Storage; -use App\Jobs\PurchaseOrder\ZipPurchaseOrders; -use App\Repositories\PurchaseOrderRepository; -use App\Jobs\PurchaseOrder\PurchaseOrderEmail; -use App\Transformers\PurchaseOrderTransformer; use App\Events\PurchaseOrder\PurchaseOrderWasCreated; use App\Events\PurchaseOrder\PurchaseOrderWasUpdated; +use App\Factory\PurchaseOrderFactory; +use App\Filters\PurchaseOrderFilters; +use App\Http\Requests\PurchaseOrder\ActionPurchaseOrderRequest; use App\Http\Requests\PurchaseOrder\BulkPurchaseOrderRequest; +use App\Http\Requests\PurchaseOrder\CreatePurchaseOrderRequest; +use App\Http\Requests\PurchaseOrder\DestroyPurchaseOrderRequest; use App\Http\Requests\PurchaseOrder\EditPurchaseOrderRequest; use App\Http\Requests\PurchaseOrder\ShowPurchaseOrderRequest; use App\Http\Requests\PurchaseOrder\StorePurchaseOrderRequest; -use App\Http\Requests\PurchaseOrder\ActionPurchaseOrderRequest; -use App\Http\Requests\PurchaseOrder\CreatePurchaseOrderRequest; use App\Http\Requests\PurchaseOrder\UpdatePurchaseOrderRequest; use App\Http\Requests\PurchaseOrder\UploadPurchaseOrderRequest; -use App\Http\Requests\PurchaseOrder\DestroyPurchaseOrderRequest; +use App\Jobs\Entity\CreateRawPdf; +use App\Jobs\PurchaseOrder\PurchaseOrderEmail; +use App\Jobs\PurchaseOrder\ZipPurchaseOrders; +use App\Models\Account; +use App\Models\Client; +use App\Models\PurchaseOrder; +use App\Repositories\PurchaseOrderRepository; +use App\Services\PdfMaker\PdfMerge; +use App\Transformers\PurchaseOrderTransformer; +use App\Utils\Ninja; +use App\Utils\Traits\MakesHash; +use App\Utils\Traits\SavesDocuments; +use Illuminate\Http\Response; +use Illuminate\Support\Facades\Storage; class PurchaseOrderController extends BaseController { @@ -367,7 +367,6 @@ class PurchaseOrderController extends BaseController $purchase_order = $purchase_order->service() ->triggeredActions($request) - // ->touchPdf() ->save(); event(new PurchaseOrderWasUpdated($purchase_order, $purchase_order->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); @@ -502,7 +501,7 @@ class PurchaseOrderController extends BaseController * Download Purchase Order/s */ if ($action == 'bulk_download' && $purchase_orders->count() >= 1) { - $purchase_orders->each(function ($purchase_order) use ($user){ + $purchase_orders->each(function ($purchase_order) use ($user) { if ($user->cannot('view', $purchase_order)) { return response()->json(['message' => ctrans('text.access_denied')]); } @@ -515,7 +514,7 @@ class PurchaseOrderController extends BaseController if ($action == 'bulk_print' && $user->can('view', $purchase_orders->first())) { $paths = $purchase_orders->map(function ($purchase_order) { - return (new \App\Jobs\Vendor\CreatePurchaseOrderPdf($purchase_order->invitations->first()))->rawPdf(); + return (new CreateRawPdf($purchase_order->invitations->first()))->handle(); }); $merge = (new PdfMerge($paths->toArray()))->run(); @@ -624,8 +623,8 @@ class PurchaseOrderController extends BaseController $file = $purchase_order->service()->getPurchaseOrderPdf(); return response()->streamDownload(function () use ($file) { - echo Storage::get($file); - }, basename($file), ['Content-Type' => 'application/pdf']); + echo $file; + }, $purchase_order->numberFormatter().".pdf", ['Content-Type' => 'application/pdf']); break; case 'restore': @@ -829,7 +828,7 @@ class PurchaseOrderController extends BaseController } return response()->streamDownload(function () use ($file) { - echo Storage::get($file); - }, basename($file), $headers); + echo $file; + }, $purchase_order->numberFormatter().".pdf", $headers); } } diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index 45cfc2293f..94f3218d73 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -11,41 +11,40 @@ namespace App\Http\Controllers; -use App\Utils\Ninja; -use App\Models\Quote; -use App\Models\Client; -use App\Models\Account; -use App\Models\Invoice; -use App\Models\Project; -use Illuminate\Http\Request; -use App\Factory\QuoteFactory; -use App\Filters\QuoteFilters; -use App\Jobs\Quote\ZipQuotes; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; -use App\Factory\CloneQuoteFactory; -use App\Services\PdfMaker\PdfMerge; -use Illuminate\Support\Facades\App; -use App\Utils\Traits\SavesDocuments; use App\Events\Quote\QuoteWasCreated; use App\Events\Quote\QuoteWasUpdated; -use App\Repositories\QuoteRepository; -use App\Transformers\QuoteTransformer; -use App\Utils\Traits\GeneratesCounter; -use Illuminate\Support\Facades\Storage; -use App\Transformers\InvoiceTransformer; -use App\Transformers\ProjectTransformer; +use App\Factory\CloneQuoteFactory; use App\Factory\CloneQuoteToInvoiceFactory; -use App\Factory\CloneQuoteToProjectFactory; +use App\Factory\QuoteFactory; +use App\Filters\QuoteFilters; +use App\Http\Requests\Quote\ActionQuoteRequest; +use App\Http\Requests\Quote\BulkActionQuoteRequest; +use App\Http\Requests\Quote\CreateQuoteRequest; +use App\Http\Requests\Quote\DestroyQuoteRequest; use App\Http\Requests\Quote\EditQuoteRequest; use App\Http\Requests\Quote\ShowQuoteRequest; use App\Http\Requests\Quote\StoreQuoteRequest; -use App\Http\Requests\Quote\ActionQuoteRequest; -use App\Http\Requests\Quote\CreateQuoteRequest; use App\Http\Requests\Quote\UpdateQuoteRequest; use App\Http\Requests\Quote\UploadQuoteRequest; -use App\Http\Requests\Quote\DestroyQuoteRequest; -use App\Http\Requests\Quote\BulkActionQuoteRequest; +use App\Jobs\Quote\ZipQuotes; +use App\Models\Account; +use App\Models\Client; +use App\Models\Invoice; +use App\Models\Project; +use App\Models\Quote; +use App\Repositories\QuoteRepository; +use App\Services\PdfMaker\PdfMerge; +use App\Transformers\InvoiceTransformer; +use App\Transformers\ProjectTransformer; +use App\Transformers\QuoteTransformer; +use App\Utils\Ninja; +use App\Utils\Traits\GeneratesCounter; +use App\Utils\Traits\MakesHash; +use App\Utils\Traits\SavesDocuments; +use Illuminate\Http\Request; +use Illuminate\Http\Response; +use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Storage; /** * Class QuoteController. @@ -398,7 +397,7 @@ class QuoteController extends BaseController $quote->service() ->triggeredActions($request); - // ->deletePdf(); + // ->deletePdf(); event(new QuoteWasUpdated($quote, $quote->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); @@ -536,7 +535,7 @@ class QuoteController extends BaseController * Download Quote/s */ if ($action == 'bulk_download' && $quotes->count() >= 1) { - $quotes->each(function ($quote) use($user){ + $quotes->each(function ($quote) use ($user) { if ($user->cannot('view', $quote)) { return response()->json(['message'=> ctrans('texts.access_denied')]); } @@ -562,7 +561,7 @@ class QuoteController extends BaseController if ($action == 'bulk_print' && $user->can('view', $quotes->first())) { $paths = $quotes->map(function ($quote) { - return (new \App\Jobs\Entity\CreateRawPdf($quote->invitations->first(), $quote->company->db))->handle(); + return (new \App\Jobs\Entity\CreateRawPdf($quote->invitations->first()))->handle(); }); $merge = (new PdfMerge($paths->toArray()))->run(); @@ -687,7 +686,7 @@ class QuoteController extends BaseController return $this->itemResponse($quote->service()->convertToProject()); - case 'convert': + case 'convert': case 'convert_to_invoice': $this->entity_type = Invoice::class; @@ -724,8 +723,8 @@ class QuoteController extends BaseController $file = $quote->service()->getQuotePdf(); return response()->streamDownload(function () use ($file) { - echo Storage::get($file); - }, basename($file), ['Content-Type' => 'application/pdf']); + echo $file; + }, $quote->numberFormatter().".pdf", ['Content-Type' => 'application/pdf']); case 'restore': $this->quote_repo->restore($quote); @@ -828,17 +827,18 @@ class QuoteController extends BaseController App::setLocale($invitation->contact->preferredLocale()); - $file = $quote->service()->getQuotePdf($contact); - $headers = ['Content-Type' => 'application/pdf']; if (request()->input('inline') == 'true') { $headers = array_merge($headers, ['Content-Disposition' => 'inline']); } + $file = $quote->service()->getQuotePdf($contact); + return response()->streamDownload(function () use ($file) { - echo Storage::get($file); - }, basename($file), $headers); + echo $file; + }, $quote->numberFormatter().".pdf", $headers); + } /** diff --git a/app/Http/Controllers/RecurringInvoiceController.php b/app/Http/Controllers/RecurringInvoiceController.php index 94c2192d17..aae571d71d 100644 --- a/app/Http/Controllers/RecurringInvoiceController.php +++ b/app/Http/Controllers/RecurringInvoiceController.php @@ -11,28 +11,28 @@ namespace App\Http\Controllers; -use App\Utils\Ninja; -use App\Models\Account; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; -use App\Models\RecurringInvoice; -use App\Utils\Traits\SavesDocuments; -use App\Factory\RecurringInvoiceFactory; -use App\Filters\RecurringInvoiceFilters; -use App\Jobs\RecurringInvoice\UpdateRecurring; -use App\Repositories\RecurringInvoiceRepository; -use App\Transformers\RecurringInvoiceTransformer; use App\Events\RecurringInvoice\RecurringInvoiceWasCreated; use App\Events\RecurringInvoice\RecurringInvoiceWasUpdated; +use App\Factory\RecurringInvoiceFactory; +use App\Filters\RecurringInvoiceFilters; +use App\Http\Requests\RecurringInvoice\ActionRecurringInvoiceRequest; use App\Http\Requests\RecurringInvoice\BulkRecurringInvoiceRequest; +use App\Http\Requests\RecurringInvoice\CreateRecurringInvoiceRequest; +use App\Http\Requests\RecurringInvoice\DestroyRecurringInvoiceRequest; use App\Http\Requests\RecurringInvoice\EditRecurringInvoiceRequest; use App\Http\Requests\RecurringInvoice\ShowRecurringInvoiceRequest; use App\Http\Requests\RecurringInvoice\StoreRecurringInvoiceRequest; -use App\Http\Requests\RecurringInvoice\ActionRecurringInvoiceRequest; -use App\Http\Requests\RecurringInvoice\CreateRecurringInvoiceRequest; use App\Http\Requests\RecurringInvoice\UpdateRecurringInvoiceRequest; use App\Http\Requests\RecurringInvoice\UploadRecurringInvoiceRequest; -use App\Http\Requests\RecurringInvoice\DestroyRecurringInvoiceRequest; +use App\Jobs\RecurringInvoice\UpdateRecurring; +use App\Models\Account; +use App\Models\RecurringInvoice; +use App\Repositories\RecurringInvoiceRepository; +use App\Transformers\RecurringInvoiceTransformer; +use App\Utils\Ninja; +use App\Utils\Traits\MakesHash; +use App\Utils\Traits\SavesDocuments; +use Illuminate\Http\Response; /** * Class RecurringInvoiceController. @@ -580,7 +580,7 @@ class RecurringInvoiceController extends BaseController $file_name = $invoice->numberFormatter().'.pdf'; - $file = (new \App\Jobs\Entity\CreateRawPdf($invitation, $invitation->company->db))->handle(); + $file = (new \App\Jobs\Entity\CreateRawPdf($invitation))->handle(); $headers = ['Content-Type' => 'application/pdf']; diff --git a/app/Http/Controllers/RecurringQuoteController.php b/app/Http/Controllers/RecurringQuoteController.php index 348440c234..725980110a 100644 --- a/app/Http/Controllers/RecurringQuoteController.php +++ b/app/Http/Controllers/RecurringQuoteController.php @@ -578,8 +578,8 @@ class RecurringQuoteController extends BaseController { switch ($action) { case 'clone_to_recurring_quote': - // $recurring_invoice = CloneRecurringQuoteFactory::create($recurring_invoice, auth()->user()->id); - // return $this->itemResponse($recurring_invoice); + // $recurring_invoice = CloneRecurringQuoteFactory::create($recurring_invoice, auth()->user()->id); + // return $this->itemResponse($recurring_invoice); break; case 'clone_to_quote': // $quote = CloneRecurringQuoteToQuoteFactory::create($recurring_invoice, auth()->user()->id); diff --git a/app/Http/Controllers/Reports/ARDetailReportController.php b/app/Http/Controllers/Reports/ARDetailReportController.php index 4f6c460e7a..1d37848222 100644 --- a/app/Http/Controllers/Reports/ARDetailReportController.php +++ b/app/Http/Controllers/Reports/ARDetailReportController.php @@ -11,11 +11,11 @@ namespace App\Http\Controllers\Reports; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; -use App\Services\Report\ARDetailReport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\SendToAdmin; +use App\Services\Report\ARDetailReport; +use App\Utils\Traits\MakesHash; class ARDetailReportController extends BaseController { diff --git a/app/Http/Controllers/Reports/ARSummaryReportController.php b/app/Http/Controllers/Reports/ARSummaryReportController.php index a9c9f6e689..cde4779451 100644 --- a/app/Http/Controllers/Reports/ARSummaryReportController.php +++ b/app/Http/Controllers/Reports/ARSummaryReportController.php @@ -61,14 +61,17 @@ class ARSummaryReportController extends BaseController */ public function __invoke(GenericReportRequest $request) { + /** @var \App\Models\User $user */ + $user = auth()->user(); + if ($request->has('send_email') && $request->get('send_email')) { - SendToAdmin::dispatch(auth()->user()->company(), $request->all(), ARSummaryReport::class, $this->filename); + SendToAdmin::dispatch($user->company(), $request->all(), ARSummaryReport::class, $this->filename); return response()->json(['message' => 'working...'], 200); } // expect a list of visible fields, or use the default - $export = new ARSummaryReport(auth()->user()->company(), $request->all()); + $export = new ARSummaryReport($user->company(), $request->all()); $csv = $export->run(); diff --git a/app/Http/Controllers/Reports/ActivityReportController.php b/app/Http/Controllers/Reports/ActivityReportController.php index ed241a9e38..f9af9fa3af 100644 --- a/app/Http/Controllers/Reports/ActivityReportController.php +++ b/app/Http/Controllers/Reports/ActivityReportController.php @@ -11,12 +11,12 @@ namespace App\Http\Controllers\Reports; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; use App\Export\CSV\ActivityExport; -use App\Jobs\Report\PreviewReport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\PreviewReport; +use App\Jobs\Report\SendToAdmin; +use App\Utils\Traits\MakesHash; class ActivityReportController extends BaseController { @@ -41,28 +41,12 @@ class ActivityReportController extends BaseController return response()->json(['message' => 'working...'], 200); } - // expect a list of visible fields, or use the default - if($request->has('output') && $request->input('output') == 'json') { + $hash = \Illuminate\Support\Str::uuid(); - $hash = \Illuminate\Support\Str::uuid(); + PreviewReport::dispatch($user->company(), $request->all(), ActivityExport::class, $hash); - PreviewReport::dispatch($user->company(), $request->all(), ActivityExport::class, $hash); + return response()->json(['message' => $hash], 200); - return response()->json(['message' => $hash], 200); - } - - $export = new ActivityExport($user->company(), $request->all()); - - $csv = $export->run(); - - $headers = [ - 'Content-Disposition' => 'attachment', - 'Content-Type' => 'text/csv', - ]; - - return response()->streamDownload(function () use ($csv) { - echo $csv; - }, $this->filename, $headers); } } diff --git a/app/Http/Controllers/Reports/ClientBalanceReportController.php b/app/Http/Controllers/Reports/ClientBalanceReportController.php index 0202af3d2f..c29b0a213a 100644 --- a/app/Http/Controllers/Reports/ClientBalanceReportController.php +++ b/app/Http/Controllers/Reports/ClientBalanceReportController.php @@ -11,12 +11,11 @@ namespace App\Http\Controllers\Reports; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; use App\Http\Controllers\BaseController; -use App\Services\Report\ARSummaryReport; -use App\Services\Report\ClientBalanceReport; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\SendToAdmin; +use App\Services\Report\ClientBalanceReport; +use App\Utils\Traits\MakesHash; class ClientBalanceReportController extends BaseController { diff --git a/app/Http/Controllers/Reports/ClientContactReportController.php b/app/Http/Controllers/Reports/ClientContactReportController.php index e29eefe195..0062eeffa9 100644 --- a/app/Http/Controllers/Reports/ClientContactReportController.php +++ b/app/Http/Controllers/Reports/ClientContactReportController.php @@ -11,13 +11,13 @@ namespace App\Http\Controllers\Reports; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; use App\Export\CSV\ContactExport; -use App\Jobs\Report\PreviewReport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\PreviewReport; +use App\Jobs\Report\SendToAdmin; +use App\Utils\Traits\MakesHash; +use Illuminate\Http\Response; class ClientContactReportController extends BaseController { @@ -72,30 +72,11 @@ class ClientContactReportController extends BaseController return response()->json(['message' => 'working...'], 200); } + $hash = \Illuminate\Support\Str::uuid(); - // expect a list of visible fields, or use the default - if($request->has('output') && $request->input('output') == 'json') { + PreviewReport::dispatch($user->company(), $request->all(), ContactExport::class, $hash); - $hash = \Illuminate\Support\Str::uuid(); + return response()->json(['message' => $hash], 200); - PreviewReport::dispatch($user->company(), $request->all(), ContactExport::class, $hash); - - return response()->json(['message' => $hash], 200); - } - - - // expect a list of visible fields, or use the default - $export = new ContactExport($user->company(), $request->all()); - - $csv = $export->run(); - - $headers = [ - 'Content-Disposition' => 'attachment', - 'Content-Type' => 'text/csv', - ]; - - return response()->streamDownload(function () use ($csv) { - echo $csv; - }, $this->filename, $headers); } } diff --git a/app/Http/Controllers/Reports/ClientReportController.php b/app/Http/Controllers/Reports/ClientReportController.php index bcbc2c02d9..a0ff126cc6 100644 --- a/app/Http/Controllers/Reports/ClientReportController.php +++ b/app/Http/Controllers/Reports/ClientReportController.php @@ -11,14 +11,14 @@ namespace App\Http\Controllers\Reports; -use App\Models\Client; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; use App\Export\CSV\ClientExport; -use App\Jobs\Report\SendToAdmin; -use App\Jobs\Report\PreviewReport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\PreviewReport; +use App\Jobs\Report\SendToAdmin; +use App\Models\Client; +use App\Utils\Traits\MakesHash; +use Illuminate\Http\Response; class ClientReportController extends BaseController { @@ -73,27 +73,11 @@ class ClientReportController extends BaseController return response()->json(['message' => 'working...'], 200); } - // expect a list of visible fields, or use the default - if($request->has('output') && $request->input('output') == 'json') { + $hash = \Illuminate\Support\Str::uuid(); - $hash = \Illuminate\Support\Str::uuid(); + PreviewReport::dispatch($user->company(), $request->all(), ClientExport::class, $hash); - PreviewReport::dispatch($user->company(), $request->all(), ClientExport::class, $hash); + return response()->json(['message' => $hash], 200); - return response()->json(['message' => $hash], 200); - } - - $export = new ClientExport($user->company(), $request->all()); - - $csv = $export->run(); - - $headers = [ - 'Content-Disposition' => 'attachment', - 'Content-Type' => 'text/csv', - ]; - - return response()->streamDownload(function () use ($csv) { - echo $csv; - }, $this->filename, $headers); } } diff --git a/app/Http/Controllers/Reports/ClientSalesReportController.php b/app/Http/Controllers/Reports/ClientSalesReportController.php index 056b157775..63c0ac69fe 100644 --- a/app/Http/Controllers/Reports/ClientSalesReportController.php +++ b/app/Http/Controllers/Reports/ClientSalesReportController.php @@ -11,12 +11,11 @@ namespace App\Http\Controllers\Reports; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; use App\Http\Controllers\BaseController; -use App\Services\Report\ClientSalesReport; -use App\Services\Report\ClientBalanceReport; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\SendToAdmin; +use App\Services\Report\ClientSalesReport; +use App\Utils\Traits\MakesHash; class ClientSalesReportController extends BaseController { diff --git a/app/Http/Controllers/Reports/CreditReportController.php b/app/Http/Controllers/Reports/CreditReportController.php index 5a1a952d63..533a45e046 100644 --- a/app/Http/Controllers/Reports/CreditReportController.php +++ b/app/Http/Controllers/Reports/CreditReportController.php @@ -71,28 +71,12 @@ class CreditReportController extends BaseController return response()->json(['message' => 'working...'], 200); } - // expect a list of visible fields, or use the default - if($request->has('output') && $request->input('output') == 'json') { + $hash = \Illuminate\Support\Str::uuid(); - $hash = \Illuminate\Support\Str::uuid(); + PreviewReport::dispatch($user->company(), $request->all(), CreditExport::class, $hash); - PreviewReport::dispatch($user->company(), $request->all(), CreditExport::class, $hash); + return response()->json(['message' => $hash], 200); - return response()->json(['message' => $hash], 200); - } - - $export = new CreditExport($user->company(), $request->all()); - - $csv = $export->run(); - - $headers = [ - 'Content-Disposition' => 'attachment', - 'Content-Type' => 'text/csv', - ]; - - return response()->streamDownload(function () use ($csv) { - echo $csv; - }, $this->filename, $headers); } } diff --git a/app/Http/Controllers/Reports/DocumentReportController.php b/app/Http/Controllers/Reports/DocumentReportController.php index d0cd939c86..fb592f9909 100644 --- a/app/Http/Controllers/Reports/DocumentReportController.php +++ b/app/Http/Controllers/Reports/DocumentReportController.php @@ -11,13 +11,13 @@ namespace App\Http\Controllers\Reports; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; use App\Export\CSV\DocumentExport; -use App\Jobs\Report\PreviewReport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\PreviewReport; +use App\Jobs\Report\SendToAdmin; +use App\Utils\Traits\MakesHash; +use Illuminate\Http\Response; class DocumentReportController extends BaseController { @@ -72,28 +72,12 @@ class DocumentReportController extends BaseController return response()->json(['message' => 'working...'], 200); } - // expect a list of visible fields, or use the default - if($request->has('output') && $request->input('output') == 'json') { + $hash = \Illuminate\Support\Str::uuid(); - $hash = \Illuminate\Support\Str::uuid(); + PreviewReport::dispatch($user->company(), $request->all(), DocumentExport::class, $hash); - PreviewReport::dispatch($user->company(), $request->all(), DocumentExport::class, $hash); + return response()->json(['message' => $hash], 200); - return response()->json(['message' => $hash], 200); - } - - $export = new DocumentExport($user->company(), $request->all()); - - $csv = $export->run(); - - $headers = [ - 'Content-Disposition' => 'attachment', - 'Content-Type' => 'text/csv', - ]; - - return response()->streamDownload(function () use ($csv) { - echo $csv; - }, $this->filename, $headers); } } diff --git a/app/Http/Controllers/Reports/ExpenseReportController.php b/app/Http/Controllers/Reports/ExpenseReportController.php index f1276cdaff..12e3c7cf81 100644 --- a/app/Http/Controllers/Reports/ExpenseReportController.php +++ b/app/Http/Controllers/Reports/ExpenseReportController.php @@ -11,14 +11,14 @@ namespace App\Http\Controllers\Reports; -use App\Models\Client; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; use App\Export\CSV\ExpenseExport; -use App\Jobs\Report\PreviewReport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\PreviewReport; +use App\Jobs\Report\SendToAdmin; +use App\Models\Client; +use App\Utils\Traits\MakesHash; +use Illuminate\Http\Response; class ExpenseReportController extends BaseController { @@ -73,28 +73,12 @@ class ExpenseReportController extends BaseController return response()->json(['message' => 'working...'], 200); } - // expect a list of visible fields, or use the default - if($request->has('output') && $request->input('output') == 'json') { + $hash = \Illuminate\Support\Str::uuid(); - $hash = \Illuminate\Support\Str::uuid(); + PreviewReport::dispatch($user->company(), $request->all(), ExpenseExport::class, $hash); - PreviewReport::dispatch($user->company(), $request->all(), ExpenseExport::class, $hash); + return response()->json(['message' => $hash], 200); - return response()->json(['message' => $hash], 200); - } - - $export = new ExpenseExport($user->company(), $request->all()); - - $csv = $export->run(); - - $headers = [ - 'Content-Disposition' => 'attachment', - 'Content-Type' => 'text/csv', - ]; - - return response()->streamDownload(function () use ($csv) { - echo $csv; - }, $this->filename, $headers); } } diff --git a/app/Http/Controllers/Reports/InvoiceItemReportController.php b/app/Http/Controllers/Reports/InvoiceItemReportController.php index a124171cc6..847db7a339 100644 --- a/app/Http/Controllers/Reports/InvoiceItemReportController.php +++ b/app/Http/Controllers/Reports/InvoiceItemReportController.php @@ -11,13 +11,13 @@ namespace App\Http\Controllers\Reports; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; -use App\Jobs\Report\PreviewReport; use App\Export\CSV\InvoiceItemExport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\PreviewReport; +use App\Jobs\Report\SendToAdmin; +use App\Utils\Traits\MakesHash; +use Illuminate\Http\Response; class InvoiceItemReportController extends BaseController { @@ -72,26 +72,11 @@ class InvoiceItemReportController extends BaseController return response()->json(['message' => 'working...'], 200); } - if($request->has('output') && $request->input('output') == 'json') { + $hash = \Illuminate\Support\Str::uuid(); - $hash = \Illuminate\Support\Str::uuid(); + PreviewReport::dispatch($user->company(), $request->all(), InvoiceItemExport::class, $hash); - PreviewReport::dispatch($user->company(), $request->all(), InvoiceItemExport::class, $hash); + return response()->json(['message' => $hash], 200); - return response()->json(['message' => $hash], 200); - } - - $export = new InvoiceItemExport($user->company(), $request->all()); - - $csv = $export->run(); - - $headers = [ - 'Content-Disposition' => 'attachment', - 'Content-Type' => 'text/csv', - ]; - - return response()->streamDownload(function () use ($csv) { - echo $csv; - }, $this->filename, $headers); } } diff --git a/app/Http/Controllers/Reports/InvoiceReportController.php b/app/Http/Controllers/Reports/InvoiceReportController.php index c00687fb68..d6b8a0dc08 100644 --- a/app/Http/Controllers/Reports/InvoiceReportController.php +++ b/app/Http/Controllers/Reports/InvoiceReportController.php @@ -11,13 +11,13 @@ namespace App\Http\Controllers\Reports; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; use App\Export\CSV\InvoiceExport; -use App\Jobs\Report\PreviewReport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\PreviewReport; +use App\Jobs\Report\SendToAdmin; +use App\Utils\Traits\MakesHash; +use Illuminate\Http\Response; class InvoiceReportController extends BaseController { @@ -72,27 +72,11 @@ class InvoiceReportController extends BaseController return response()->json(['message' => 'working...'], 200); } - if($request->has('output') && $request->input('output') == 'json') { + $hash = \Illuminate\Support\Str::uuid(); - $hash = \Illuminate\Support\Str::uuid(); + PreviewReport::dispatch($user->company(), $request->all(), InvoiceExport::class, $hash); - PreviewReport::dispatch($user->company(), $request->all(), InvoiceExport::class, $hash); + return response()->json(['message' => $hash], 200); - return response()->json(['message' => $hash], 200); - } - - // expect a list of visible fields, or use the default - $export = new InvoiceExport($user->company(), $request->all()); - - $csv = $export->run(); - - $headers = [ - 'Content-Disposition' => 'attachment', - 'Content-Type' => 'text/csv', - ]; - - return response()->streamDownload(function () use ($csv) { - echo $csv; - }, $this->filename, $headers); } } diff --git a/app/Http/Controllers/Reports/PaymentReportController.php b/app/Http/Controllers/Reports/PaymentReportController.php index cca6925ef9..27db61d231 100644 --- a/app/Http/Controllers/Reports/PaymentReportController.php +++ b/app/Http/Controllers/Reports/PaymentReportController.php @@ -11,14 +11,14 @@ namespace App\Http\Controllers\Reports; -use App\Models\Client; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; use App\Export\CSV\PaymentExport; -use App\Jobs\Report\PreviewReport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\PreviewReport; +use App\Jobs\Report\SendToAdmin; +use App\Models\Client; +use App\Utils\Traits\MakesHash; +use Illuminate\Http\Response; class PaymentReportController extends BaseController { @@ -72,29 +72,13 @@ class PaymentReportController extends BaseController return response()->json(['message' => 'working...'], 200); } - // expect a list of visible fields, or use the default - if($request->has('output') && $request->input('output') == 'json') { + $hash = \Illuminate\Support\Str::uuid(); - $hash = \Illuminate\Support\Str::uuid(); + PreviewReport::dispatch($user->company(), $request->all(), PaymentExport::class, $hash); - PreviewReport::dispatch($user->company(), $request->all(), PaymentExport::class, $hash); + return response()->json(['message' => $hash], 200); - return response()->json(['message' => $hash], 200); - } - - $export = new PaymentExport($user->company(), $request->all()); - - $csv = $export->run(); - - $headers = [ - 'Content-Disposition' => 'attachment', - 'Content-Type' => 'text/csv', - ]; - - return response()->streamDownload(function () use ($csv) { - echo $csv; - }, $this->filename, $headers); } } diff --git a/app/Http/Controllers/Reports/ProductReportController.php b/app/Http/Controllers/Reports/ProductReportController.php index 664bd51a69..fb48f5c523 100644 --- a/app/Http/Controllers/Reports/ProductReportController.php +++ b/app/Http/Controllers/Reports/ProductReportController.php @@ -11,14 +11,14 @@ namespace App\Http\Controllers\Reports; -use App\Models\Client; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; use App\Export\CSV\ProductExport; -use App\Jobs\Report\PreviewReport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\PreviewReport; +use App\Jobs\Report\SendToAdmin; +use App\Models\Client; +use App\Utils\Traits\MakesHash; +use Illuminate\Http\Response; class ProductReportController extends BaseController { @@ -73,28 +73,12 @@ class ProductReportController extends BaseController return response()->json(['message' => 'working...'], 200); } - // expect a list of visible fields, or use the default - if($request->has('output') && $request->input('output') == 'json') { + $hash = \Illuminate\Support\Str::uuid(); - $hash = \Illuminate\Support\Str::uuid(); + PreviewReport::dispatch($user->company(), $request->all(), ProductExport::class, $hash); - PreviewReport::dispatch($user->company(), $request->all(), ProductExport::class, $hash); + return response()->json(['message' => $hash], 200); - return response()->json(['message' => $hash], 200); - } - - $export = new ProductExport($user->company(), $request->all()); - - $csv = $export->run(); - - $headers = [ - 'Content-Disposition' => 'attachment', - 'Content-Type' => 'text/csv', - ]; - - return response()->streamDownload(function () use ($csv) { - echo $csv; - }, $this->filename, $headers); } } diff --git a/app/Http/Controllers/Reports/ProfitAndLossController.php b/app/Http/Controllers/Reports/ProfitAndLossController.php index 3d1f075106..26e860f16b 100644 --- a/app/Http/Controllers/Reports/ProfitAndLossController.php +++ b/app/Http/Controllers/Reports/ProfitAndLossController.php @@ -63,14 +63,17 @@ class ProfitAndLossController extends BaseController */ public function __invoke(ProfitLossRequest $request) { + /** @var \App\Models\User $user */ + $user = auth()->user(); + if ($request->has('send_email') && $request->get('send_email')) { - SendToAdmin::dispatch(auth()->user()->company(), $request->all(), ProfitLoss::class, $this->filename); + SendToAdmin::dispatch($user->company(), $request->all(), ProfitLoss::class, $this->filename); return response()->json(['message' => 'working...'], 200); } // expect a list of visible fields, or use the default - $pnl = new ProfitLoss(auth()->user()->company(), $request->all()); + $pnl = new ProfitLoss($user->company(), $request->all()); $csv = $pnl->run(); $headers = [ diff --git a/app/Http/Controllers/Reports/PurchaseOrderItemReportController.php b/app/Http/Controllers/Reports/PurchaseOrderItemReportController.php index 71509576f5..b6ede66f16 100644 --- a/app/Http/Controllers/Reports/PurchaseOrderItemReportController.php +++ b/app/Http/Controllers/Reports/PurchaseOrderItemReportController.php @@ -11,12 +11,12 @@ namespace App\Http\Controllers\Reports; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; -use App\Jobs\Report\PreviewReport; -use App\Http\Controllers\BaseController; use App\Export\CSV\PurchaseOrderItemExport; +use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\PreviewReport; +use App\Jobs\Report\SendToAdmin; +use App\Utils\Traits\MakesHash; class PurchaseOrderItemReportController extends BaseController { @@ -39,28 +39,12 @@ class PurchaseOrderItemReportController extends BaseController return response()->json(['message' => 'working...'], 200); } - // expect a list of visible fields, or use the default - if($request->has('output') && $request->input('output') == 'json') { + $hash = \Illuminate\Support\Str::uuid(); - $hash = \Illuminate\Support\Str::uuid(); + PreviewReport::dispatch($user->company(), $request->all(), PurchaseOrderItemExport::class, $hash); - PreviewReport::dispatch($user->company(), $request->all(), PurchaseOrderItemExport::class, $hash); + return response()->json(['message' => $hash], 200); - return response()->json(['message' => $hash], 200); - } - - $export = new PurchaseOrderItemExport($user->company(), $request->all()); - - $csv = $export->run(); - - $headers = [ - 'Content-Disposition' => 'attachment', - 'Content-Type' => 'text/csv', - ]; - - return response()->streamDownload(function () use ($csv) { - echo $csv; - }, $this->filename, $headers); } } diff --git a/app/Http/Controllers/Reports/PurchaseOrderReportController.php b/app/Http/Controllers/Reports/PurchaseOrderReportController.php index 5899b20cfc..c8d14be824 100644 --- a/app/Http/Controllers/Reports/PurchaseOrderReportController.php +++ b/app/Http/Controllers/Reports/PurchaseOrderReportController.php @@ -11,12 +11,12 @@ namespace App\Http\Controllers\Reports; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; -use App\Jobs\Report\PreviewReport; use App\Export\CSV\PurchaseOrderExport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\PreviewReport; +use App\Jobs\Report\SendToAdmin; +use App\Utils\Traits\MakesHash; class PurchaseOrderReportController extends BaseController { @@ -41,29 +41,12 @@ class PurchaseOrderReportController extends BaseController return response()->json(['message' => 'working...'], 200); } - // expect a list of visible fields, or use the default - if($request->has('output') && $request->input('output') == 'json') { + $hash = \Illuminate\Support\Str::uuid(); - $hash = \Illuminate\Support\Str::uuid(); + PreviewReport::dispatch($user->company(), $request->all(), PurchaseOrderExport::class, $hash); - PreviewReport::dispatch($user->company(), $request->all(), PurchaseOrderExport::class, $hash); + return response()->json(['message' => $hash], 200); - return response()->json(['message' => $hash], 200); - } - - - $export = new PurchaseOrderExport($user->company(), $request->all()); - - $csv = $export->run(); - - $headers = [ - 'Content-Disposition' => 'attachment', - 'Content-Type' => 'text/csv', - ]; - - return response()->streamDownload(function () use ($csv) { - echo $csv; - }, $this->filename, $headers); } } diff --git a/app/Http/Controllers/Reports/QuoteItemReportController.php b/app/Http/Controllers/Reports/QuoteItemReportController.php index 78295f1ddc..2fad0670c8 100644 --- a/app/Http/Controllers/Reports/QuoteItemReportController.php +++ b/app/Http/Controllers/Reports/QuoteItemReportController.php @@ -11,13 +11,13 @@ namespace App\Http\Controllers\Reports; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; -use App\Jobs\Report\PreviewReport; use App\Export\CSV\QuoteItemExport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\PreviewReport; +use App\Jobs\Report\SendToAdmin; +use App\Utils\Traits\MakesHash; +use Illuminate\Http\Response; class QuoteItemReportController extends BaseController { @@ -71,28 +71,12 @@ class QuoteItemReportController extends BaseController return response()->json(['message' => 'working...'], 200); } - // expect a list of visible fields, or use the default - if($request->has('output') && $request->input('output') == 'json') { + $hash = \Illuminate\Support\Str::uuid(); - $hash = \Illuminate\Support\Str::uuid(); + PreviewReport::dispatch($user->company(), $request->all(), QuoteItemExport::class, $hash); - PreviewReport::dispatch($user->company(), $request->all(), QuoteItemExport::class, $hash); + return response()->json(['message' => $hash], 200); - return response()->json(['message' => $hash], 200); - } - - $export = new QuoteItemExport($user->company(), $request->all()); - - $csv = $export->run(); - - $headers = [ - 'Content-Disposition' => 'attachment', - 'Content-Type' => 'text/csv', - ]; - - return response()->streamDownload(function () use ($csv) { - echo $csv; - }, $this->filename, $headers); } } diff --git a/app/Http/Controllers/Reports/QuoteReportController.php b/app/Http/Controllers/Reports/QuoteReportController.php index 9034b1a939..a215846aaa 100644 --- a/app/Http/Controllers/Reports/QuoteReportController.php +++ b/app/Http/Controllers/Reports/QuoteReportController.php @@ -11,13 +11,13 @@ namespace App\Http\Controllers\Reports; -use Illuminate\Http\Response; use App\Export\CSV\QuoteExport; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; -use App\Jobs\Report\PreviewReport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\PreviewReport; +use App\Jobs\Report\SendToAdmin; +use App\Utils\Traits\MakesHash; +use Illuminate\Http\Response; class QuoteReportController extends BaseController { @@ -71,28 +71,12 @@ class QuoteReportController extends BaseController return response()->json(['message' => 'working...'], 200); } - // expect a list of visible fields, or use the default - if($request->has('output') && $request->input('output') == 'json') { + $hash = \Illuminate\Support\Str::uuid(); - $hash = \Illuminate\Support\Str::uuid(); + PreviewReport::dispatch($user->company(), $request->all(), QuoteExport::class, $hash); - PreviewReport::dispatch($user->company(), $request->all(), QuoteExport::class, $hash); + return response()->json(['message' => $hash], 200); - return response()->json(['message' => $hash], 200); - } - - $export = new QuoteExport($user->company(), $request->all()); - - $csv = $export->run(); - - $headers = [ - 'Content-Disposition' => 'attachment', - 'Content-Type' => 'text/csv', - ]; - - return response()->streamDownload(function () use ($csv) { - echo $csv; - }, $this->filename, $headers); } } diff --git a/app/Http/Controllers/Reports/RecurringInvoiceReportController.php b/app/Http/Controllers/Reports/RecurringInvoiceReportController.php index 26eb279106..7536b25611 100644 --- a/app/Http/Controllers/Reports/RecurringInvoiceReportController.php +++ b/app/Http/Controllers/Reports/RecurringInvoiceReportController.php @@ -11,13 +11,12 @@ namespace App\Http\Controllers\Reports; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; -use App\Jobs\Report\PreviewReport; -use App\Http\Controllers\BaseController; use App\Export\CSV\RecurringInvoiceExport; +use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\PreviewReport; +use App\Jobs\Report\SendToAdmin; +use App\Utils\Traits\MakesHash; class RecurringInvoiceReportController extends BaseController { @@ -40,28 +39,12 @@ class RecurringInvoiceReportController extends BaseController return response()->json(['message' => 'working...'], 200); } - // expect a list of visible fields, or use the default - if($request->has('output') && $request->input('output') == 'json') { + $hash = \Illuminate\Support\Str::uuid(); - $hash = \Illuminate\Support\Str::uuid(); + PreviewReport::dispatch($user->company(), $request->all(), RecurringInvoiceExport::class, $hash); - PreviewReport::dispatch($user->company(), $request->all(), RecurringInvoiceExport::class, $hash); + return response()->json(['message' => $hash], 200); - return response()->json(['message' => $hash], 200); - } - - $export = new RecurringInvoiceExport($user->company(), $request->all()); - - $csv = $export->run(); - - $headers = [ - 'Content-Disposition' => 'attachment', - 'Content-Type' => 'text/csv', - ]; - - return response()->streamDownload(function () use ($csv) { - echo $csv; - }, $this->filename, $headers); } } diff --git a/app/Http/Controllers/Reports/ReportExportController.php b/app/Http/Controllers/Reports/ReportExportController.php new file mode 100644 index 0000000000..bb936668bc --- /dev/null +++ b/app/Http/Controllers/Reports/ReportExportController.php @@ -0,0 +1,56 @@ +json(['message' => 'Still working.....'], 409); + } + + if($report) { + + Cache::forget($hash); + + $headers = [ + 'Content-Disposition' => 'attachment', + 'Content-Type' => 'text/csv', + ]; + + return response()->streamDownload(function () use ($report) { + echo $report; + }, $this->filename, $headers); + + } + + + } +} diff --git a/app/Http/Controllers/Reports/ReportPreviewController.php b/app/Http/Controllers/Reports/ReportPreviewController.php index 82f55bc2f4..1bb559552c 100644 --- a/app/Http/Controllers/Reports/ReportPreviewController.php +++ b/app/Http/Controllers/Reports/ReportPreviewController.php @@ -11,10 +11,10 @@ namespace App\Http\Controllers\Reports; -use App\Utils\Traits\MakesHash; -use Illuminate\Support\Facades\Cache; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\ReportPreviewRequest; +use App\Utils\Traits\MakesHash; +use Illuminate\Support\Facades\Cache; class ReportPreviewController extends BaseController { @@ -30,10 +30,11 @@ class ReportPreviewController extends BaseController $report = Cache::get($hash); - if(!$report) + if(!$report) { return response()->json(['message' => 'Still working.....'], 409); + } - if($report){ + if($report) { Cache::forget($hash); diff --git a/app/Http/Controllers/Reports/TaskReportController.php b/app/Http/Controllers/Reports/TaskReportController.php index 8339a909bc..8293f863c5 100644 --- a/app/Http/Controllers/Reports/TaskReportController.php +++ b/app/Http/Controllers/Reports/TaskReportController.php @@ -11,13 +11,13 @@ namespace App\Http\Controllers\Reports; -use Illuminate\Http\Response; use App\Export\CSV\TaskExport; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; -use App\Jobs\Report\PreviewReport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\PreviewReport; +use App\Jobs\Report\SendToAdmin; +use App\Utils\Traits\MakesHash; +use Illuminate\Http\Response; class TaskReportController extends BaseController { @@ -71,28 +71,12 @@ class TaskReportController extends BaseController return response()->json(['message' => 'working...'], 200); } - // expect a list of visible fields, or use the default - if($request->has('output') && $request->input('output') == 'json') { + $hash = \Illuminate\Support\Str::uuid(); - $hash = \Illuminate\Support\Str::uuid(); + PreviewReport::dispatch($user->company(), $request->all(), TaskExport::class, $hash); - PreviewReport::dispatch($user->company(), $request->all(), TaskExport::class, $hash); + return response()->json(['message' => $hash], 200); - return response()->json(['message' => $hash], 200); - } - - $export = new TaskExport($user->company(), $request->all()); - - $csv = $export->run(); - - $headers = [ - 'Content-Disposition' => 'attachment', - 'Content-Type' => 'text/csv', - ]; - - return response()->streamDownload(function () use ($csv) { - echo $csv; - }, $this->filename, $headers); } } diff --git a/app/Http/Controllers/Reports/TaxSummaryReportController.php b/app/Http/Controllers/Reports/TaxSummaryReportController.php index 776fa988d6..b199eadcc1 100644 --- a/app/Http/Controllers/Reports/TaxSummaryReportController.php +++ b/app/Http/Controllers/Reports/TaxSummaryReportController.php @@ -11,12 +11,11 @@ namespace App\Http\Controllers\Reports; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; use App\Http\Controllers\BaseController; -use App\Services\Report\TaxSummaryReport; -use App\Services\Report\ClientSalesReport; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\SendToAdmin; +use App\Services\Report\TaxSummaryReport; +use App\Utils\Traits\MakesHash; class TaxSummaryReportController extends BaseController { diff --git a/app/Http/Controllers/Reports/UserSalesReportController.php b/app/Http/Controllers/Reports/UserSalesReportController.php index 582eb4e717..2d80995791 100644 --- a/app/Http/Controllers/Reports/UserSalesReportController.php +++ b/app/Http/Controllers/Reports/UserSalesReportController.php @@ -11,12 +11,11 @@ namespace App\Http\Controllers\Reports; -use App\Utils\Traits\MakesHash; -use App\Jobs\Report\SendToAdmin; use App\Http\Controllers\BaseController; -use App\Services\Report\UserSalesReport; -use App\Services\Report\TaxSummaryReport; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\SendToAdmin; +use App\Services\Report\UserSalesReport; +use App\Utils\Traits\MakesHash; class UserSalesReportController extends BaseController { diff --git a/app/Http/Controllers/Reports/VendorReportController.php b/app/Http/Controllers/Reports/VendorReportController.php index bfd0f3b229..d86f031df7 100644 --- a/app/Http/Controllers/Reports/VendorReportController.php +++ b/app/Http/Controllers/Reports/VendorReportController.php @@ -11,12 +11,12 @@ namespace App\Http\Controllers\Reports; -use App\Utils\Traits\MakesHash; use App\Export\CSV\VendorExport; -use App\Jobs\Report\SendToAdmin; -use App\Jobs\Report\PreviewReport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; +use App\Jobs\Report\PreviewReport; +use App\Jobs\Report\SendToAdmin; +use App\Utils\Traits\MakesHash; class VendorReportController extends BaseController { @@ -39,28 +39,12 @@ class VendorReportController extends BaseController return response()->json(['message' => 'working...'], 200); } - // expect a list of visible fields, or use the default - if($request->has('output') && $request->input('output') == 'json') { + $hash = \Illuminate\Support\Str::uuid(); - $hash = \Illuminate\Support\Str::uuid(); + PreviewReport::dispatch($user->company(), $request->all(), VendorExport::class, $hash); - PreviewReport::dispatch($user->company(), $request->all(), VendorExport::class, $hash); + return response()->json(['message' => $hash], 200); - return response()->json(['message' => $hash], 200); - } - - $export = new VendorExport($user->company(), $request->all()); - - $csv = $export->run(); - - $headers = [ - 'Content-Disposition' => 'attachment', - 'Content-Type' => 'text/csv', - ]; - - return response()->streamDownload(function () use ($csv) { - echo $csv; - }, $this->filename, $headers); } } diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index cd985b44c7..7699e8c3d6 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -54,25 +54,25 @@ class SearchController extends Controller ->take(1000) ->get(); - foreach($clients as $client) { - $this->clients[] = [ - 'name' => $client->present()->name(), - 'type' => '/client', - 'id' => $client->hashed_id, - 'path' => "/clients/{$client->hashed_id}/edit" - ]; + foreach($clients as $client) { + $this->clients[] = [ + 'name' => $client->present()->name(), + 'type' => '/client', + 'id' => $client->hashed_id, + 'path' => "/clients/{$client->hashed_id}/edit" + ]; - $client->contacts->each(function ($contact) { - $this->client_contacts[] = [ - 'name' => $contact->present()->search_display(), - 'type' => '/client_contact', - 'id' => $contact->hashed_id, - 'path' => "/clients/{$contact->hashed_id}" - ]; + $client->contacts->each(function ($contact) { + $this->client_contacts[] = [ + 'name' => $contact->present()->search_display(), + 'type' => '/client_contact', + 'id' => $contact->hashed_id, + 'path' => "/clients/{$contact->hashed_id}" + ]; - }); - } + }); + } } @@ -92,16 +92,16 @@ class SearchController extends Controller }) ->orderBy('id', 'desc') ->take(3000) - ->get(); + ->get(); - foreach($invoices as $invoice) { - $this->invoices[] = [ - 'name' => $invoice->client->present()->name() . ' - ' . $invoice->number, - 'type' => '/invoice', - 'id' => $invoice->hashed_id, - 'path' => "/invoices/{$invoice->hashed_id}/edit" - ]; - } + foreach($invoices as $invoice) { + $this->invoices[] = [ + 'name' => $invoice->client->present()->name() . ' - ' . $invoice->number, + 'type' => '/invoice', + 'id' => $invoice->hashed_id, + 'path' => "/invoices/{$invoice->hashed_id}/edit" + ]; + } } diff --git a/app/Http/Controllers/SelfUpdateController.php b/app/Http/Controllers/SelfUpdateController.php index 75d2442fcd..651d884ac3 100644 --- a/app/Http/Controllers/SelfUpdateController.php +++ b/app/Http/Controllers/SelfUpdateController.php @@ -11,14 +11,14 @@ namespace App\Http\Controllers; -use App\Utils\Ninja; +use App\Exceptions\FilePermissionsFailure; use App\Models\Company; +use App\Utils\Ninja; use App\Utils\Traits\AppSetup; +use App\Utils\Traits\ClientGroupSettingsSaver; +use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Storage; -use App\Exceptions\FilePermissionsFailure; -use Illuminate\Foundation\Bus\DispatchesJobs; -use App\Utils\Traits\ClientGroupSettingsSaver; class SelfUpdateController extends BaseController { @@ -72,8 +72,7 @@ class SelfUpdateController extends BaseController if (copy($this->getDownloadUrl(), storage_path("app/{$this->filename}"))) { nlog('Copied file from URL'); } - } - catch(\Exception $e) { + } catch(\Exception $e) { nlog($e->getMessage()); return response()->json(['message' => 'File exists on the server, however there was a problem downloading and copying to the local filesystem'], 500); } @@ -121,18 +120,19 @@ class SelfUpdateController extends BaseController { Company::query() ->cursor() - ->each(function ($company){ + ->each(function ($company) { - $settings = $company->settings; + $settings = $company->settings; - if(property_exists($settings->pdf_variables, 'purchase_order_details')) - return; + if(property_exists($settings->pdf_variables, 'purchase_order_details')) { + return; + } - $pdf_variables = $settings->pdf_variables; - $pdf_variables->purchase_order_details = []; - $settings->pdf_variables = $pdf_variables; - $company->settings = $settings; - $company->save(); + $pdf_variables = $settings->pdf_variables; + $pdf_variables->purchase_order_details = []; + $settings->pdf_variables = $pdf_variables; + $company->settings = $settings; + $company->save(); }); } diff --git a/app/Http/Controllers/SetupController.php b/app/Http/Controllers/SetupController.php index c849e73480..e7ac3a6107 100644 --- a/app/Http/Controllers/SetupController.php +++ b/app/Http/Controllers/SetupController.php @@ -121,8 +121,7 @@ class SetupController extends Controller unset($env_values['DB_DATABASE']); unset($env_values['DB_USERNAME']); unset($env_values['DB_PASSWORD']); - } - else { + } else { config(['database.connections.mysql.host' => $request->input('db_host')]); config(['database.connections.mysql.port' => $request->input('db_port')]); diff --git a/app/Http/Controllers/Shop/ProfileController.php b/app/Http/Controllers/Shop/ProfileController.php index 975ca5cf34..4239baeaa2 100644 --- a/app/Http/Controllers/Shop/ProfileController.php +++ b/app/Http/Controllers/Shop/ProfileController.php @@ -28,7 +28,7 @@ class ProfileController extends BaseController public function show(Request $request) { - /** @var \App\Models\Company $company */ + /** @var \App\Models\Company $company */ $company = Company::where('company_key', $request->header('X-API-COMPANY-KEY'))->first(); if (! $company->enable_shop_api) { diff --git a/app/Http/Controllers/StripeConnectController.php b/app/Http/Controllers/StripeConnectController.php index edb97e486d..3a5f9ddd8e 100644 --- a/app/Http/Controllers/StripeConnectController.php +++ b/app/Http/Controllers/StripeConnectController.php @@ -123,7 +123,7 @@ class StripeConnectController extends BaseController $company_gateway->setConfig($payload); $company_gateway->save(); - try{ + try { $stripe = $company_gateway->driver()->init(); $a = \Stripe\Account::retrieve($response->stripe_user_id, $stripe->stripe_connect_auth); @@ -131,8 +131,7 @@ class StripeConnectController extends BaseController $company_gateway->label = substr("Stripe - {$a->business_name}", 0, 250); $company_gateway->save(); } - } - catch(\Exception $e){ + } catch(\Exception $e) { nlog("could not harvest stripe company name"); } diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php index 1d722b86a2..a66c64424f 100644 --- a/app/Http/Controllers/TaskController.php +++ b/app/Http/Controllers/TaskController.php @@ -327,7 +327,7 @@ class TaskController extends BaseController * ) */ public function create(CreateTaskRequest $request) - { + { /** @var \App\Models\User $user */ $user = auth()->user(); @@ -506,7 +506,7 @@ class TaskController extends BaseController $tasks->each(function ($task, $key) use ($action) { /** @var \App\Models\User $user */ - $user = auth()->user(); + $user = auth()->user(); if ($user->can('edit', $task)) { $this->task_repo->{$action}($task); } @@ -633,7 +633,7 @@ class TaskController extends BaseController /** @var \App\Models\User $user */ $user = auth()->user(); - collect($task_statuses)->each(function ($task_status_hashed_id, $key) use($user){ + collect($task_statuses)->each(function ($task_status_hashed_id, $key) use ($user) { $task_status = TaskStatus::query()->where('id', $this->decodePrimaryKey($task_status_hashed_id)) ->where('company_id', $user->company()->id) ->withTrashed() diff --git a/app/Http/Controllers/TaskSchedulerController.php b/app/Http/Controllers/TaskSchedulerController.php index 24e19e9559..e206ca8e4e 100644 --- a/app/Http/Controllers/TaskSchedulerController.php +++ b/app/Http/Controllers/TaskSchedulerController.php @@ -22,7 +22,6 @@ use App\Models\Scheduler; use App\Repositories\SchedulerRepository; use App\Transformers\SchedulerTransformer; use App\Utils\Traits\MakesHash; -use Symfony\Component\HttpFoundation\Request; class TaskSchedulerController extends BaseController { diff --git a/app/Http/Controllers/TaskStatusController.php b/app/Http/Controllers/TaskStatusController.php index 836dc9f88a..db3d79f406 100644 --- a/app/Http/Controllers/TaskStatusController.php +++ b/app/Http/Controllers/TaskStatusController.php @@ -11,20 +11,20 @@ namespace App\Http\Controllers; -use App\Models\TaskStatus; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; use App\Factory\TaskStatusFactory; use App\Filters\TaskStatusFilters; -use App\Repositories\TaskStatusRepository; -use App\Transformers\TaskStatusTransformer; +use App\Http\Requests\TaskStatus\ActionTaskStatusRequest; +use App\Http\Requests\TaskStatus\CreateTaskStatusRequest; +use App\Http\Requests\TaskStatus\DestroyTaskStatusRequest; use App\Http\Requests\TaskStatus\EditTaskStatusRequest; use App\Http\Requests\TaskStatus\ShowTaskStatusRequest; use App\Http\Requests\TaskStatus\StoreTaskStatusRequest; -use App\Http\Requests\TaskStatus\ActionTaskStatusRequest; -use App\Http\Requests\TaskStatus\CreateTaskStatusRequest; use App\Http\Requests\TaskStatus\UpdateTaskStatusRequest; -use App\Http\Requests\TaskStatus\DestroyTaskStatusRequest; +use App\Models\TaskStatus; +use App\Repositories\TaskStatusRepository; +use App\Transformers\TaskStatusTransformer; +use App\Utils\Traits\MakesHash; +use Illuminate\Http\Response; class TaskStatusController extends BaseController { @@ -135,8 +135,9 @@ class TaskStatusController extends BaseController $reorder = $task_status->isDirty('status_order'); $task_status->save(); - if ($reorder) + if ($reorder) { $this->task_status_repo->reorder($task_status); + } return $this->itemResponse($task_status->fresh()); @@ -148,7 +149,7 @@ class TaskStatusController extends BaseController * @param DestroyTaskStatusRequest $request * @param TaskStatus $task_status * @return Response - * + * * @throws \Exception */ public function destroy(DestroyTaskStatusRequest $request, TaskStatus $task_status) diff --git a/app/Http/Controllers/TemplatePreviewController.php b/app/Http/Controllers/TemplatePreviewController.php new file mode 100644 index 0000000000..2cfd05ed8a --- /dev/null +++ b/app/Http/Controllers/TemplatePreviewController.php @@ -0,0 +1,51 @@ +exists($this->path_prefix.$hash.$this->path_suffix); + + if(!$report) { + return response()->json(['message' => 'Still working.....'], 409); + } + + Cache::forget($hash); + + return response()->streamDownload(function () use ($hash) { + + echo Storage::get($this->path_prefix.$hash.$this->path_suffix); + Storage::delete($this->path_prefix.$hash.$this->path_suffix); + + }, 'template.pdf', ['Content-Type' => 'application/pdf']); + + } +} diff --git a/app/Http/Controllers/Traits/VerifiesUserEmail.php b/app/Http/Controllers/Traits/VerifiesUserEmail.php index 7b26c9de39..f5796c51e9 100644 --- a/app/Http/Controllers/Traits/VerifiesUserEmail.php +++ b/app/Http/Controllers/Traits/VerifiesUserEmail.php @@ -36,7 +36,7 @@ trait VerifiesUserEmail if (! $user) { return $this->render('auth.confirmed', [ - 'root' => 'themes', + 'root' => 'themes', 'message' => ctrans('texts.wrong_confirmation'), 'redirect_url' => request()->has('react') ? config('ninja.react_url')."/#/" : url('/')]); } diff --git a/app/Http/Controllers/TwilioController.php b/app/Http/Controllers/TwilioController.php index d6bf7488d9..39b7f72637 100644 --- a/app/Http/Controllers/TwilioController.php +++ b/app/Http/Controllers/TwilioController.php @@ -11,13 +11,13 @@ namespace App\Http\Controllers; -use App\Models\User; -use Twilio\Rest\Client; -use App\Libraries\MultiDB; use App\Http\Requests\Twilio\Confirm2faRequest; use App\Http\Requests\Twilio\ConfirmSmsRequest; use App\Http\Requests\Twilio\Generate2faRequest; use App\Http\Requests\Twilio\GenerateSmsRequest; +use App\Libraries\MultiDB; +use App\Models\User; +use Twilio\Rest\Client; class TwilioController extends BaseController { diff --git a/app/Http/Controllers/TwoFactorController.php b/app/Http/Controllers/TwoFactorController.php index 9ba490a6f1..2d86469f9d 100644 --- a/app/Http/Controllers/TwoFactorController.php +++ b/app/Http/Controllers/TwoFactorController.php @@ -11,11 +11,11 @@ namespace App\Http\Controllers; +use App\Http\Requests\TwoFactor\EnableTwoFactorRequest; use App\Models\User; +use App\Transformers\UserTransformer; use App\Utils\Ninja; use PragmaRX\Google2FA\Google2FA; -use App\Transformers\UserTransformer; -use App\Http\Requests\TwoFactor\EnableTwoFactorRequest; class TwoFactorController extends BaseController { @@ -30,10 +30,9 @@ class TwoFactorController extends BaseController if ($user->google_2fa_secret) { return response()->json(['message' => '2FA already enabled'], 400); - } elseif(Ninja::isSelfHost()){ + } elseif(Ninja::isSelfHost()) { - } - elseif (! $user->phone) { + } elseif (! $user->phone) { return response()->json(['message' => ctrans('texts.set_phone_for_two_factor')], 400); } elseif (! $user->isVerified()) { return response()->json(['message' => 'Please confirm your account first'], 400); diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 2c785dbc84..03f9f0584d 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -11,31 +11,31 @@ namespace App\Http\Controllers; -use App\Models\User; -use App\Utils\Ninja; -use App\Models\CompanyUser; -use App\Factory\UserFactory; -use App\Filters\UserFilters; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; use App\Events\User\UserWasCreated; use App\Events\User\UserWasDeleted; use App\Events\User\UserWasUpdated; -use App\Jobs\User\UserEmailChanged; -use App\Repositories\UserRepository; -use App\Transformers\UserTransformer; -use App\Jobs\Company\CreateCompanyToken; -use App\Http\Requests\User\BulkUserRequest; -use App\Http\Requests\User\EditUserRequest; -use App\Http\Requests\User\ShowUserRequest; -use App\Http\Requests\User\StoreUserRequest; -use App\Http\Requests\User\CreateUserRequest; -use App\Http\Requests\User\UpdateUserRequest; -use App\Http\Requests\User\DestroyUserRequest; -use App\Http\Requests\User\ReconfirmUserRequest; +use App\Factory\UserFactory; +use App\Filters\UserFilters; use App\Http\Controllers\Traits\VerifiesUserEmail; +use App\Http\Requests\User\BulkUserRequest; +use App\Http\Requests\User\CreateUserRequest; +use App\Http\Requests\User\DestroyUserRequest; use App\Http\Requests\User\DetachCompanyUserRequest; use App\Http\Requests\User\DisconnectUserMailerRequest; +use App\Http\Requests\User\EditUserRequest; +use App\Http\Requests\User\ReconfirmUserRequest; +use App\Http\Requests\User\ShowUserRequest; +use App\Http\Requests\User\StoreUserRequest; +use App\Http\Requests\User\UpdateUserRequest; +use App\Jobs\Company\CreateCompanyToken; +use App\Jobs\User\UserEmailChanged; +use App\Models\CompanyUser; +use App\Models\User; +use App\Repositories\UserRepository; +use App\Transformers\UserTransformer; +use App\Utils\Ninja; +use App\Utils\Traits\MakesHash; +use Illuminate\Http\Response; /** * Class UserController. @@ -235,7 +235,7 @@ class UserController extends BaseController $return_user_collection = collect(); /** @var \App\Models\User $logged_in_user */ - $logged_in_user = auth()->user(); + $logged_in_user = auth()->user(); $users->each(function ($user, $key) use ($logged_in_user, $action, $return_user_collection) { if ($logged_in_user->can('edit', $user)) { diff --git a/app/Http/Controllers/VendorPortal/InvitationController.php b/app/Http/Controllers/VendorPortal/InvitationController.php index 7ce6ae394d..81bffff02d 100644 --- a/app/Http/Controllers/VendorPortal/InvitationController.php +++ b/app/Http/Controllers/VendorPortal/InvitationController.php @@ -11,17 +11,16 @@ namespace App\Http\Controllers\VendorPortal; -use App\Utils\Ninja; -use Illuminate\Support\Str; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\MakesDates; -use Illuminate\Support\Facades\App; -use App\Http\Controllers\Controller; -use Illuminate\Support\Facades\Auth; -use App\Models\PurchaseOrderInvitation; use App\Events\Misc\InvitationWasViewed; -use App\Jobs\Vendor\CreatePurchaseOrderPdf; use App\Events\PurchaseOrder\PurchaseOrderWasViewed; +use App\Http\Controllers\Controller; +use App\Models\PurchaseOrderInvitation; +use App\Utils\Ninja; +use App\Utils\Traits\MakesDates; +use App\Utils\Traits\MakesHash; +use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Str; /** * Class InvitationController. @@ -94,7 +93,7 @@ class InvitationController extends Controller $file_name = $invitation->purchase_order->numberFormatter().'.pdf'; - $file = (new CreatePurchaseOrderPdf($invitation))->rawPdf(); + $file = $invitation->purchase_order->service()->getPurchaseOrderPdf(); $headers = ['Content-Type' => 'application/pdf']; diff --git a/app/Http/Controllers/VendorPortal/PurchaseOrderController.php b/app/Http/Controllers/VendorPortal/PurchaseOrderController.php index 74d66f0542..fb70c248b7 100644 --- a/app/Http/Controllers/VendorPortal/PurchaseOrderController.php +++ b/app/Http/Controllers/VendorPortal/PurchaseOrderController.php @@ -16,13 +16,13 @@ use Illuminate\View\View; use App\Models\PurchaseOrder; use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesDates; +use App\Jobs\Entity\CreateRawPdf; use App\Http\Controllers\Controller; use App\Jobs\Invoice\InjectSignature; use Illuminate\Support\Facades\Cache; use Illuminate\Contracts\View\Factory; use App\Models\PurchaseOrderInvitation; use App\Events\Misc\InvitationWasViewed; -use App\Jobs\Vendor\CreatePurchaseOrderPdf; use App\Events\PurchaseOrder\PurchaseOrderWasViewed; use App\Events\PurchaseOrder\PurchaseOrderWasAccepted; use App\Http\Requests\VendorPortal\PurchaseOrders\ShowPurchaseOrderRequest; @@ -116,7 +116,7 @@ class PurchaseOrderController extends Controller $invitation = PurchaseOrderInvitation::withTrashed()->find($data['invitation_id']); - $file = (new CreatePurchaseOrderPdf($invitation, $invitation->company->db))->rawPdf(); + $file = $invitation->purchase_order->service()->getPurchaseOrderPdf(); $headers = ['Content-Type' => 'application/pdf']; @@ -184,7 +184,7 @@ class PurchaseOrderController extends Controller } event(new PurchaseOrderWasAccepted($purchase_order, auth()->guard('vendor')->user(), $purchase_order->company, Ninja::eventVars())); - }); + }); if ($purchase_count_query->count() == 1) { $purchase_order = $purchase_count_query->first(); @@ -211,7 +211,8 @@ class PurchaseOrderController extends Controller if (count($purchase_order_invitations) == 1) { $invitation = $purchase_order_invitations->first(); - $file = (new CreatePurchaseOrderPdf($invitation, $invitation->company->db))->rawPdf(); + $file = (new CreateRawPdf($invitation))->handle(); + return response()->streamDownload(function () use ($file) { echo $file; }, $invitation->purchase_order->numberFormatter().".pdf", ['Content-Type' => 'application/pdf']); @@ -226,7 +227,8 @@ class PurchaseOrderController extends Controller $zipFile = new \PhpZip\ZipFile(); try { foreach ($invitations as $invitation) { - $file = (new CreatePurchaseOrderPdf($invitation, $invitation->company->db))->rawPdf(); + + $file = (new CreateRawPdf($invitation))->handle(); $zipFile->addFromString($invitation->purchase_order->numberFormatter().".pdf", $file); } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 1716268271..6a53796f3b 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -11,53 +11,52 @@ namespace App\Http; -use App\Utils\Ninja; -use App\Http\Middleware\Cors; -use App\Http\Middleware\SetDb; -use App\Http\Middleware\Locale; -use App\Http\Middleware\SetWebDb; -use App\Http\Middleware\UrlSetDb; -use App\Http\Middleware\TokenAuth; -use App\Http\Middleware\SetEmailDb; -use App\Http\Middleware\VerifyHash; -use App\Http\Middleware\SetInviteDb; -use App\Http\Middleware\TrimStrings; -use App\Http\Middleware\Authenticate; -use App\Http\Middleware\ContactSetDb; -use App\Http\Middleware\QueryLogging; -use App\Http\Middleware\TrustProxies; -use App\Http\Middleware\UserVerified; -use App\Http\Middleware\VendorLocale; -use App\Http\Middleware\PhantomSecret; -use App\Http\Middleware\SetDocumentDb; use App\Http\Middleware\ApiSecretCheck; +use App\Http\Middleware\Authenticate; +use App\Http\Middleware\CheckClientExistence; +use App\Http\Middleware\CheckForMaintenanceMode; +use App\Http\Middleware\ClientPortalEnabled; use App\Http\Middleware\ContactAccount; -use App\Http\Middleware\EncryptCookies; -use App\Http\Middleware\SessionDomains; use App\Http\Middleware\ContactKeyLogin; use App\Http\Middleware\ContactRegister; -use App\Http\Middleware\SetDomainNameDb; -use App\Http\Middleware\VerifyCsrfToken; +use App\Http\Middleware\ContactSetDb; use App\Http\Middleware\ContactTokenAuth; -use Illuminate\Auth\Middleware\Authorize; -use App\Http\Middleware\SetDbByCompanyKey; -use App\Http\Middleware\ValidateSignature; +use App\Http\Middleware\Cors; +use App\Http\Middleware\EncryptCookies; +use App\Http\Middleware\Locale; use App\Http\Middleware\PasswordProtection; -use App\Http\Middleware\ClientPortalEnabled; -use App\Http\Middleware\CheckClientExistence; -use App\Http\Middleware\VendorContactKeyLogin; -use Illuminate\Http\Middleware\SetCacheHeaders; -use Illuminate\Session\Middleware\StartSession; -use App\Http\Middleware\CheckForMaintenanceMode; +use App\Http\Middleware\PhantomSecret; +use App\Http\Middleware\QueryLogging; use App\Http\Middleware\RedirectIfAuthenticated; -use Illuminate\Foundation\Http\Kernel as HttpKernel; -use Illuminate\Auth\Middleware\EnsureEmailIsVerified; -use Illuminate\Routing\Middleware\SubstituteBindings; -use Illuminate\View\Middleware\ShareErrorsFromSession; +use App\Http\Middleware\SessionDomains; +use App\Http\Middleware\SetDb; +use App\Http\Middleware\SetDbByCompanyKey; +use App\Http\Middleware\SetDocumentDb; +use App\Http\Middleware\SetDomainNameDb; +use App\Http\Middleware\SetEmailDb; +use App\Http\Middleware\SetInviteDb; +use App\Http\Middleware\SetWebDb; +use App\Http\Middleware\TokenAuth; +use App\Http\Middleware\TrimStrings; +use App\Http\Middleware\TrustProxies; +use App\Http\Middleware\UrlSetDb; +use App\Http\Middleware\UserVerified; +use App\Http\Middleware\ValidateSignature; +use App\Http\Middleware\VendorContactKeyLogin; +use App\Http\Middleware\VendorLocale; +use App\Http\Middleware\VerifyCsrfToken; +use App\Http\Middleware\VerifyHash; use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth; -use Illuminate\Foundation\Http\Middleware\ValidatePostSize; +use Illuminate\Auth\Middleware\Authorize; +use Illuminate\Auth\Middleware\EnsureEmailIsVerified; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; +use Illuminate\Foundation\Http\Kernel as HttpKernel; use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; +use Illuminate\Foundation\Http\Middleware\ValidatePostSize; +use Illuminate\Http\Middleware\SetCacheHeaders; +use Illuminate\Routing\Middleware\SubstituteBindings; +use Illuminate\Session\Middleware\StartSession; +use Illuminate\View\Middleware\ShareErrorsFromSession; class Kernel extends HttpKernel { diff --git a/app/Http/Livewire/BillingPortalPurchase.php b/app/Http/Livewire/BillingPortalPurchase.php index f8e625e03c..caa9b4b195 100644 --- a/app/Http/Livewire/BillingPortalPurchase.php +++ b/app/Http/Livewire/BillingPortalPurchase.php @@ -23,7 +23,6 @@ use App\Models\Invoice; use App\Models\Subscription; use App\Repositories\ClientContactRepository; use App\Repositories\ClientRepository; -use App\Services\Subscription\SubscriptionService; use App\Utils\Ninja; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Cache; diff --git a/app/Http/Livewire/PdfSlot.php b/app/Http/Livewire/PdfSlot.php index 3ca80bd22e..91a51e588a 100644 --- a/app/Http/Livewire/PdfSlot.php +++ b/app/Http/Livewire/PdfSlot.php @@ -12,21 +12,20 @@ namespace App\Http\Livewire; -use App\Utils\Number; -use Livewire\Component; -use App\Utils\HtmlEngine; +use App\Jobs\Invoice\CreateEInvoice; use App\Libraries\MultiDB; -use Illuminate\Support\Str; -use App\Models\QuoteInvitation; -use App\Utils\VendorHtmlEngine; use App\Models\CreditInvitation; use App\Models\InvoiceInvitation; -use App\Jobs\Invoice\CreateEInvoice; -use Illuminate\Support\Facades\Cache; use App\Models\PurchaseOrderInvitation; +use App\Models\QuoteInvitation; use App\Models\RecurringInvoiceInvitation; -use App\Jobs\Vendor\CreatePurchaseOrderPdf; use App\Services\PdfMaker\Designs\Utilities\DesignHelpers; +use App\Utils\HtmlEngine; +use App\Utils\Number; +use App\Utils\VendorHtmlEngine; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Str; +use Livewire\Component; class PdfSlot extends Component { @@ -70,6 +69,11 @@ class PdfSlot extends Component public function getPdf() { + if(!$this->invitation) { + $this->entity->service()->createInvitations(); + $this->invitation = $this->entity->invitations()->first(); + } + $blob = [ 'entity_type' => $this->resolveEntityType(), 'entity_id' => $this->entity->id, @@ -90,10 +94,7 @@ class PdfSlot extends Component $file_name = $this->entity->numberFormatter().'.pdf'; - if($this->entity instanceof \App\Models\PurchaseOrder) - $file = (new CreatePurchaseOrderPdf($this->invitation, $this->invitation->company->db))->rawPdf(); - else - $file = (new \App\Jobs\Entity\CreateRawPdf($this->invitation, $this->invitation->company->db))->handle(); + $file = (new \App\Jobs\Entity\CreateRawPdf($this->invitation))->handle(); $headers = ['Content-Type' => 'application/pdf']; @@ -102,6 +103,7 @@ class PdfSlot extends Component }, $file_name, $headers); } + public function downloadEInvoice() { @@ -128,7 +130,7 @@ class PdfSlot extends Component $this->show_line_total = in_array('$product.line_total', $this->settings->pdf_variables->product_columns); $this->show_quantity = in_array('$product.quantity', $this->settings->pdf_variables->product_columns); - if($this->entity_type == 'quote' && !$this->settings->sync_invoice_quote_columns ){ + if($this->entity_type == 'quote' && !$this->settings->sync_invoice_quote_columns) { $this->show_cost = in_array('$product.unit_cost', $this->settings->pdf_variables->product_quote_columns); $this->show_quantity = in_array('$product.quantity', $this->settings->pdf_variables->product_quote_columns); $this->show_line_total = in_array('$product.line_total', $this->settings->pdf_variables->product_quote_columns); @@ -195,21 +197,22 @@ class PdfSlot extends Component $entity_details = ""; if($this->entity_type == 'invoice' || $this->entity_type == 'recurring_invoice') { - foreach($this->settings->pdf_variables->invoice_details as $variable) + foreach($this->settings->pdf_variables->invoice_details as $variable) { $entity_details .= "

{$variable}_label

{$variable}

"; + } - } - elseif($this->entity_type == 'quote'){ - foreach($this->settings->pdf_variables->quote_details ?? [] as $variable) + } elseif($this->entity_type == 'quote') { + foreach($this->settings->pdf_variables->quote_details ?? [] as $variable) { $entity_details .= "

{$variable}_label

{$variable}

"; - } - elseif($this->entity_type == 'credit') { - foreach($this->settings->pdf_variables->credit_details ?? [] as $variable) + } + } elseif($this->entity_type == 'credit') { + foreach($this->settings->pdf_variables->credit_details ?? [] as $variable) { $entity_details .= "

{$variable}_label

{$variable}

"; - } - elseif($this->entity_type == 'purchase_order'){ - foreach($this->settings->pdf_variables->purchase_order_details ?? [] as $variable) + } + } elseif($this->entity_type == 'purchase_order') { + foreach($this->settings->pdf_variables->purchase_order_details ?? [] as $variable) { $entity_details .= "

{$variable}_label

{$variable}

"; + } } return $this->convertVariables($entity_details); @@ -237,12 +240,11 @@ class PdfSlot extends Component $user_details = ""; if($this->entity_type == 'purchase_order') { - foreach(array_slice($this->settings->pdf_variables->vendor_details,1) as $variable) { + foreach(array_slice($this->settings->pdf_variables->vendor_details, 1) as $variable) { $user_details .= "

{$variable}

"; } - } - else{ - foreach(array_slice($this->settings->pdf_variables->client_details,1) as $variable) { + } else { + foreach(array_slice($this->settings->pdf_variables->client_details, 1) as $variable) { $user_details .= "

{$variable}

"; } } @@ -255,7 +257,7 @@ class PdfSlot extends Component $product_items = collect($this->entity->line_items)->filter(function ($item) { return $item->type_id == 1 || $item->type_id == 6 || $item->type_id == 5; - })->map(function ($item){ + })->map(function ($item) { $notes = strlen($item->notes) > 4 ? $item->notes : $item->product_key; @@ -274,7 +276,7 @@ class PdfSlot extends Component { $task_items = collect($this->entity->line_items)->filter(function ($item) { return $item->type_id == 2; - })->map(function ($item){ + })->map(function ($item) { return [ 'quantity' => $item->quantity, 'cost' => Number::formatMoney($item->cost, $this->entity->client ?: $this->entity->vendor), diff --git a/app/Http/Livewire/TasksTable.php b/app/Http/Livewire/TasksTable.php index f229db889f..fa3f45df6e 100644 --- a/app/Http/Livewire/TasksTable.php +++ b/app/Http/Livewire/TasksTable.php @@ -39,11 +39,11 @@ class TasksTable extends Component ->where('is_deleted', false) ->where('client_id', auth()->guard('contact')->user()->client_id); - if ( auth()->guard('contact')->user()->client->getSetting('show_all_tasks_client_portal') === 'invoiced') { + if (auth()->guard('contact')->user()->client->getSetting('show_all_tasks_client_portal') === 'invoiced') { $query = $query->whereNotNull('invoice_id'); } - if ( auth()->guard('contact')->user()->client->getSetting('show_all_tasks_client_portal') === 'uninvoiced') { + if (auth()->guard('contact')->user()->client->getSetting('show_all_tasks_client_portal') === 'uninvoiced') { $query = $query->whereNull('invoice_id'); } diff --git a/app/Http/Middleware/PasswordProtection.php b/app/Http/Middleware/PasswordProtection.php index 4450ec3210..d34c817f2a 100644 --- a/app/Http/Middleware/PasswordProtection.php +++ b/app/Http/Middleware/PasswordProtection.php @@ -38,7 +38,7 @@ class PasswordProtection ]; /** @var \App\Models\User auth()->user() */ - $user = auth()->user(); + $user = auth()->user(); $timeout = $user->company()->default_password_timeout; if ($timeout == 0) { @@ -59,11 +59,9 @@ class PasswordProtection Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout); return $next($request); - } - elseif(strlen(auth()->user()->oauth_provider_id) > 2 && !auth()->user()->company()->oauth_password_required){ + } elseif(strlen(auth()->user()->oauth_provider_id) > 2 && !auth()->user()->company()->oauth_password_required) { return $next($request); - } - elseif ($request->header('X-API-OAUTH-PASSWORD') && strlen($request->header('X-API-OAUTH-PASSWORD')) >=1) { + } elseif ($request->header('X-API-OAUTH-PASSWORD') && strlen($request->header('X-API-OAUTH-PASSWORD')) >=1) { //user is attempting to reauth with OAuth - check the token value //todo expand this to include all OAuth providers if (auth()->user()->oauth_provider_id == 'google') { diff --git a/app/Http/Middleware/QueryLogging.php b/app/Http/Middleware/QueryLogging.php index e6aa71a7b8..39fac1a5a6 100644 --- a/app/Http/Middleware/QueryLogging.php +++ b/app/Http/Middleware/QueryLogging.php @@ -47,8 +47,9 @@ class QueryLogging public function terminate($request, $response) { - if (! Ninja::isHosted() || ! config('beacon.enabled')) + if (! Ninja::isHosted() || ! config('beacon.enabled')) { return; + } // hide requests made by debugbar if (strstr($request->url(), '_debugbar') === false) { diff --git a/app/Http/Middleware/TokenAuth.php b/app/Http/Middleware/TokenAuth.php index e73ccb00fc..227d47a380 100644 --- a/app/Http/Middleware/TokenAuth.php +++ b/app/Http/Middleware/TokenAuth.php @@ -31,7 +31,7 @@ class TokenAuth public function handle($request, Closure $next) { if ($request->header('X-API-TOKEN') && ($company_token = CompanyToken::with([ - 'user' => [ + 'user' => [ 'account', ], 'company'])->where('token', $request->header('X-API-TOKEN'))->first())) { $user = $company_token->user; diff --git a/app/Http/Middleware/UserVerified.php b/app/Http/Middleware/UserVerified.php index c951c13f3a..e45acb74c7 100644 --- a/app/Http/Middleware/UserVerified.php +++ b/app/Http/Middleware/UserVerified.php @@ -21,10 +21,9 @@ use Illuminate\Http\Request; */ class UserVerified { - public $user; - - public function __construct(?User $user) + public function __construct(public ?User $user) { + $this->user = property_exists($user, 'id') ? $user : auth()->user(); } diff --git a/app/Http/Middleware/ValidateSignature.php b/app/Http/Middleware/ValidateSignature.php index 6308b30862..deb5f45679 100644 --- a/app/Http/Middleware/ValidateSignature.php +++ b/app/Http/Middleware/ValidateSignature.php @@ -11,7 +11,6 @@ namespace App\Http\Middleware; - use Closure; use Illuminate\Routing\Exceptions\InvalidSignatureException; diff --git a/app/Http/Middleware/VendorContactKeyLogin.php b/app/Http/Middleware/VendorContactKeyLogin.php index e73a325546..407665e59f 100644 --- a/app/Http/Middleware/VendorContactKeyLogin.php +++ b/app/Http/Middleware/VendorContactKeyLogin.php @@ -11,14 +11,14 @@ namespace App\Http\Middleware; +use App\Libraries\MultiDB; +use App\Models\Vendor; +use App\Models\VendorContact; use Auth; use Closure; -use App\Models\Vendor; -use App\Libraries\MultiDB; -use Illuminate\Support\Str; use Illuminate\Http\Request; -use App\Models\VendorContact; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Str; class VendorContactKeyLogin { diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 115de351c7..cbbd75224b 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -12,7 +12,6 @@ namespace App\Http\Middleware; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware; -use Illuminate\Session\TokenMismatchException; class VerifyCsrfToken extends Middleware { diff --git a/app/Http/Requests/Activity/ShowActivityRequest.php b/app/Http/Requests/Activity/ShowActivityRequest.php index 219844f35d..a2a13eae6f 100644 --- a/app/Http/Requests/Activity/ShowActivityRequest.php +++ b/app/Http/Requests/Activity/ShowActivityRequest.php @@ -40,8 +40,9 @@ class ShowActivityRequest extends Request { $input = $this->all(); - if(isset($input['entity_id'])) + if(isset($input['entity_id'])) { $input['entity_id'] = $this->decodePrimaryKey($input['entity_id']); + } $this->replace($input); diff --git a/app/Http/Requests/ClientPortal/Invoices/ProcessInvoicesInBulkRequest.php b/app/Http/Requests/ClientPortal/Invoices/ProcessInvoicesInBulkRequest.php index 73724da000..7f855ef8de 100644 --- a/app/Http/Requests/ClientPortal/Invoices/ProcessInvoicesInBulkRequest.php +++ b/app/Http/Requests/ClientPortal/Invoices/ProcessInvoicesInBulkRequest.php @@ -33,7 +33,7 @@ class ProcessInvoicesInBulkRequest extends FormRequest { $input = $this->all(); - if(isset($input['invoices'])){ + if(isset($input['invoices'])) { $input['invoices'] = array_unique($input['invoices']); } diff --git a/app/Http/Requests/Company/UpdateCompanyRequest.php b/app/Http/Requests/Company/UpdateCompanyRequest.php index 9f64e4a284..4ffa0e3309 100644 --- a/app/Http/Requests/Company/UpdateCompanyRequest.php +++ b/app/Http/Requests/Company/UpdateCompanyRequest.php @@ -63,8 +63,9 @@ class UpdateCompanyRequest extends Request $rules['portal_domain'] = 'sometimes|url'; } - if (Ninja::isHosted()) + if (Ninja::isHosted()) { $rules['subdomain'] = ['nullable', 'regex:/^[a-zA-Z0-9.-]+[a-zA-Z0-9]$/', new ValidSubdomain()]; + } return $rules; } diff --git a/app/Http/Requests/CompanyGateway/BulkCompanyGatewayRequest.php b/app/Http/Requests/CompanyGateway/BulkCompanyGatewayRequest.php index 444bf89291..397734aed5 100644 --- a/app/Http/Requests/CompanyGateway/BulkCompanyGatewayRequest.php +++ b/app/Http/Requests/CompanyGateway/BulkCompanyGatewayRequest.php @@ -26,13 +26,20 @@ class BulkCompanyGatewayRequest extends Request */ public function authorize() : bool { - return auth()->user()->isAdmin(); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->isAdmin(); } public function rules() { + + /** @var \App\Models\User $user */ + $user = auth()->user(); + return [ - 'ids' => ['required','bail','array',Rule::exists('company_gateways', 'id')->where('company_id', auth()->user()->company()->id)], + 'ids' => ['required','bail','array',Rule::exists('company_gateways', 'id')->where('company_id', $user->company()->id)], 'action' => 'required|bail|in:archive,restore,delete' ]; } diff --git a/app/Http/Requests/Design/StoreDesignRequest.php b/app/Http/Requests/Design/StoreDesignRequest.php index 2af551664c..63d2b17911 100644 --- a/app/Http/Requests/Design/StoreDesignRequest.php +++ b/app/Http/Requests/Design/StoreDesignRequest.php @@ -16,6 +16,11 @@ use App\Models\Account; class StoreDesignRequest extends Request { + + private array $valid_entities = [ + 'invoice', + ]; + /** * Determine if the user is authorized to make this request. * @@ -23,20 +28,29 @@ class StoreDesignRequest extends Request */ public function authorize() : bool { - return auth()->user()->isAdmin() && auth()->user()->account->hasFeature(Account::FEATURE_API); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->isAdmin() && $user->account->hasFeature(Account::FEATURE_API); ; } public function rules() { + + /** @var \App\Models\User $user */ + $user = auth()->user(); + return [ //'name' => 'required', - 'name' => 'required|unique:designs,name,null,null,company_id,'.auth()->user()->companyId(), + 'name' => 'required|unique:designs,name,null,null,company_id,'.$user->companyId(), 'design' => 'required|array', - 'design.header' => 'required|min:1', - 'design.body' => 'required|min:1', - 'design.footer' => 'required|min:1', - 'design.includes' => 'required|min:1', + 'design.header' => 'sometimes|string', + 'design.body' => 'sometimes|string', + 'design.footer' => 'sometimes|string', + 'design.includes' => 'sometimes|string', + 'is_template' => 'sometimes|boolean', + 'entities' => 'sometimes|string|nullable' ]; } @@ -69,6 +83,20 @@ class StoreDesignRequest extends Request $input['design']['body'] = ''; } + if(array_key_exists('entities', $input)) { + $user_entities = explode(",", $input['entities']); + + $e = []; + + foreach ($user_entities as $entity) { + if (in_array($entity, $this->valid_entities)) { + $e[] = $entity; + } + } + + $input['entities'] = implode(",", $e); + } + $this->replace($input); } } diff --git a/app/Http/Requests/Design/UpdateDesignRequest.php b/app/Http/Requests/Design/UpdateDesignRequest.php index bae0d93d92..07fb4e68ea 100644 --- a/app/Http/Requests/Design/UpdateDesignRequest.php +++ b/app/Http/Requests/Design/UpdateDesignRequest.php @@ -18,6 +18,10 @@ class UpdateDesignRequest extends Request { use ChecksEntityStatus; + private array $valid_entities = [ + 'invoice', + ]; + /** * Determine if the user is authorized to make this request. * @@ -25,12 +29,18 @@ class UpdateDesignRequest extends Request */ public function authorize() : bool { - return auth()->user()->isAdmin(); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->isAdmin(); } public function rules() { - return []; + return [ + 'is_template' => 'sometimes|boolean', + 'entities' => 'sometimes|string|nullable' + ]; } public function prepareForValidation() @@ -61,6 +71,21 @@ class UpdateDesignRequest extends Request $input['design']['body'] = ''; } + if(array_key_exists('entities', $input)) { + $user_entities = explode(",", $input['entities']); + + $e = []; + + foreach ($user_entities as $entity) { + if (in_array($entity, $this->valid_entities)) { + $e[] = $entity; + } + } + + $input['entities'] = implode(",", $e); + } + + $this->replace($input); } } diff --git a/app/Http/Requests/Document/StoreDocumentRequest.php b/app/Http/Requests/Document/StoreDocumentRequest.php index f31504615d..7c3d4356f6 100644 --- a/app/Http/Requests/Document/StoreDocumentRequest.php +++ b/app/Http/Requests/Document/StoreDocumentRequest.php @@ -40,8 +40,9 @@ class StoreDocumentRequest extends Request { $input = $this->all(); - if(isset($input['is_public'])) + if(isset($input['is_public'])) { $input['is_public'] = $this->toBoolean($input['is_public']); + } $this->replace($input); } diff --git a/app/Http/Requests/Document/UpdateDocumentRequest.php b/app/Http/Requests/Document/UpdateDocumentRequest.php index bfa876eca2..3dcf677a6a 100644 --- a/app/Http/Requests/Document/UpdateDocumentRequest.php +++ b/app/Http/Requests/Document/UpdateDocumentRequest.php @@ -44,8 +44,9 @@ class UpdateDocumentRequest extends Request { $input = $this->all(); - if(isset($input['is_public'])) + if(isset($input['is_public'])) { $input['is_public'] = $this->toBoolean($input['is_public']); + } $this->replace($input); } diff --git a/app/Http/Requests/Email/ClientEmailHistoryRequest.php b/app/Http/Requests/Email/ClientEmailHistoryRequest.php index 45d60b458b..24d96a6e0b 100644 --- a/app/Http/Requests/Email/ClientEmailHistoryRequest.php +++ b/app/Http/Requests/Email/ClientEmailHistoryRequest.php @@ -13,7 +13,6 @@ namespace App\Http\Requests\Email; use App\Http\Requests\Request; use App\Utils\Traits\MakesHash; -use Illuminate\Support\Str; class ClientEmailHistoryRequest extends Request { diff --git a/app/Http/Requests/Email/EntityEmailHistoryRequest.php b/app/Http/Requests/Email/EntityEmailHistoryRequest.php index 2c506e653d..61bdf94033 100644 --- a/app/Http/Requests/Email/EntityEmailHistoryRequest.php +++ b/app/Http/Requests/Email/EntityEmailHistoryRequest.php @@ -11,9 +11,9 @@ namespace App\Http\Requests\Email; -use Illuminate\Support\Str; use App\Http\Requests\Request; use App\Utils\Traits\MakesHash; +use Illuminate\Support\Str; use Illuminate\Validation\Rule; class EntityEmailHistoryRequest extends Request diff --git a/app/Http/Requests/Email/SendEmailRequest.php b/app/Http/Requests/Email/SendEmailRequest.php index d917742dfa..53a48cc528 100644 --- a/app/Http/Requests/Email/SendEmailRequest.php +++ b/app/Http/Requests/Email/SendEmailRequest.php @@ -108,8 +108,7 @@ class SendEmailRequest extends Request if ($entity_obj && ($company->id == $entity_obj->company_id) && $user->can('edit', $entity_obj)) { return true; } - } - else { + } else { $this->error_message = "Invalid entity or entity_id"; } diff --git a/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php b/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php index d35e7ac2bc..b39f11533a 100644 --- a/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php +++ b/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php @@ -11,13 +11,13 @@ namespace App\Http\Requests\GroupSetting; -use App\Models\Account; -use App\Models\GroupSetting; -use App\Http\Requests\Request; use App\DataMapper\ClientSettings; use App\DataMapper\CompanySettings; use App\DataMapper\Settings\SettingsData; +use App\Http\Requests\Request; use App\Http\ValidationRules\ValidClientGroupSettingsRule; +use App\Models\Account; +use App\Models\GroupSetting; class StoreGroupSettingRequest extends Request { @@ -52,8 +52,7 @@ class StoreGroupSettingRequest extends Request if (array_key_exists('settings', $input)) { $input['settings'] = $this->filterSaveableSettings($input['settings']); - } - else { + } else { $input['settings'] = (array)ClientSettings::defaults(); } diff --git a/app/Http/Requests/GroupSetting/UpdateGroupSettingRequest.php b/app/Http/Requests/GroupSetting/UpdateGroupSettingRequest.php index a264d74c01..ebb6f550fb 100644 --- a/app/Http/Requests/GroupSetting/UpdateGroupSettingRequest.php +++ b/app/Http/Requests/GroupSetting/UpdateGroupSettingRequest.php @@ -11,9 +11,9 @@ namespace App\Http\Requests\GroupSetting; -use App\Http\Requests\Request; use App\DataMapper\CompanySettings; use App\DataMapper\Settings\SettingsData; +use App\Http\Requests\Request; use App\Http\ValidationRules\ValidClientGroupSettingsRule; class UpdateGroupSettingRequest extends Request diff --git a/app/Http/Requests/Invoice/BulkInvoiceRequest.php b/app/Http/Requests/Invoice/BulkInvoiceRequest.php index 68554d1ee7..c46fe6db80 100644 --- a/app/Http/Requests/Invoice/BulkInvoiceRequest.php +++ b/app/Http/Requests/Invoice/BulkInvoiceRequest.php @@ -25,7 +25,10 @@ class BulkInvoiceRequest extends Request return [ 'action' => 'required|string', 'ids' => 'required|array', - 'email_type' => 'sometimes|in:reminder1,reminder2,reminder3,reminder_endless,custom1,custom2,custom3,invoice,quote,credit,payment,payment_partial,statement,purchase_order' + 'email_type' => 'sometimes|in:reminder1,reminder2,reminder3,reminder_endless,custom1,custom2,custom3,invoice,quote,credit,payment,payment_partial,statement,purchase_order', + 'template' => 'sometimes|string', + 'template_id' => 'sometimes|string', + 'send_email' => 'sometimes|bool' ]; } } diff --git a/app/Http/Requests/Invoice/StoreInvoiceRequest.php b/app/Http/Requests/Invoice/StoreInvoiceRequest.php index 10ab3a1765..0cf37f68e6 100644 --- a/app/Http/Requests/Invoice/StoreInvoiceRequest.php +++ b/app/Http/Requests/Invoice/StoreInvoiceRequest.php @@ -54,7 +54,7 @@ class StoreInvoiceRequest extends Request } elseif ($this->file('file')) { $rules['file'] = $this->file_validation; } - + $rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.$user->company()->id.',is_deleted,0'; $rules['invitations.*.client_contact_id'] = 'distinct'; @@ -73,6 +73,8 @@ class StoreInvoiceRequest extends Request $rules['tax_name2'] = 'bail|sometimes|string|nullable'; $rules['tax_name3'] = 'bail|sometimes|string|nullable'; $rules['exchange_rate'] = 'bail|sometimes|numeric'; + $rules['partial'] = 'bail|sometimes|nullable|numeric'; + $rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date']; return $rules; } diff --git a/app/Http/Requests/Invoice/UpdateInvoiceRequest.php b/app/Http/Requests/Invoice/UpdateInvoiceRequest.php index 859924029a..25c7c2fb2f 100644 --- a/app/Http/Requests/Invoice/UpdateInvoiceRequest.php +++ b/app/Http/Requests/Invoice/UpdateInvoiceRequest.php @@ -31,7 +31,7 @@ class UpdateInvoiceRequest extends Request * @return bool */ public function authorize() : bool - { + { /** @var \App\Models\User $user */ $user = auth()->user(); @@ -76,6 +76,8 @@ class UpdateInvoiceRequest extends Request $rules['tax_name3'] = 'bail|sometimes|string|nullable'; $rules['status_id'] = 'bail|sometimes|not_in:5'; //do not allow cancelled invoices to be modfified. $rules['exchange_rate'] = 'bail|sometimes|numeric'; + $rules['partial'] = 'bail|sometimes|nullable|numeric'; + $rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date']; return $rules; } diff --git a/app/Http/Requests/Invoice/UploadInvoiceRequest.php b/app/Http/Requests/Invoice/UploadInvoiceRequest.php index 45d97b0799..1da7f3e829 100644 --- a/app/Http/Requests/Invoice/UploadInvoiceRequest.php +++ b/app/Http/Requests/Invoice/UploadInvoiceRequest.php @@ -12,7 +12,6 @@ namespace App\Http\Requests\Invoice; use App\Http\Requests\Request; -use Illuminate\Http\UploadedFile; class UploadInvoiceRequest extends Request { diff --git a/app/Http/Requests/Payment/RefundPaymentRequest.php b/app/Http/Requests/Payment/RefundPaymentRequest.php index 5f0a8381d5..5a6f71d4cb 100644 --- a/app/Http/Requests/Payment/RefundPaymentRequest.php +++ b/app/Http/Requests/Payment/RefundPaymentRequest.php @@ -58,7 +58,7 @@ class RefundPaymentRequest extends Request if (isset($input['credits'])) { unset($input['credits']); // foreach($input['credits'] as $key => $credit) - // $input['credits'][$key]['credit_id'] = $this->decodePrimaryKey($credit['credit_id']); + // $input['credits'][$key]['credit_id'] = $this->decodePrimaryKey($credit['credit_id']); } $this->replace($input); diff --git a/app/Http/Requests/Payment/UpdatePaymentRequest.php b/app/Http/Requests/Payment/UpdatePaymentRequest.php index 02b465638e..113bf94932 100644 --- a/app/Http/Requests/Payment/UpdatePaymentRequest.php +++ b/app/Http/Requests/Payment/UpdatePaymentRequest.php @@ -82,8 +82,8 @@ class UpdatePaymentRequest extends Request if (isset($input['invoices']) && is_array($input['invoices']) !== false) { foreach ($input['invoices'] as $key => $value) { - if(isset($input['invoices'][$key]['invoice_id'])){ - // if (array_key_exists('invoice_id', $input['invoices'][$key])) { + if(isset($input['invoices'][$key]['invoice_id'])) { + // if (array_key_exists('invoice_id', $input['invoices'][$key])) { $input['invoices'][$key]['invoice_id'] = $this->decodePrimaryKey($value['invoice_id']); } } diff --git a/app/Http/Requests/Preview/PreviewInvoiceRequest.php b/app/Http/Requests/Preview/PreviewInvoiceRequest.php index 57bd0cf22e..7bbdaebdbe 100644 --- a/app/Http/Requests/Preview/PreviewInvoiceRequest.php +++ b/app/Http/Requests/Preview/PreviewInvoiceRequest.php @@ -11,19 +11,19 @@ namespace App\Http\Requests\Preview; -use App\Models\Quote; +use App\Http\Requests\Request; use App\Models\Client; use App\Models\Credit; +use App\Models\CreditInvitation; use App\Models\Invoice; -use App\Http\Requests\Request; +use App\Models\InvoiceInvitation; +use App\Models\Quote; use App\Models\QuoteInvitation; +use App\Models\RecurringInvoice; +use App\Models\RecurringInvoiceInvitation; +use App\Utils\Traits\CleanLineItems; use App\Utils\Traits\MakesHash; use Illuminate\Validation\Rule; -use App\Models\CreditInvitation; -use App\Models\RecurringInvoice; -use App\Models\InvoiceInvitation; -use App\Utils\Traits\CleanLineItems; -use App\Models\RecurringInvoiceInvitation; class PreviewInvoiceRequest extends Request { @@ -55,7 +55,7 @@ class PreviewInvoiceRequest extends Request return [ 'number' => 'nullable', 'entity' => 'bail|sometimes|in:invoice,quote,credit,recurring_invoice', - 'entity_id' => ['bail','sometimes','integer',Rule::exists($this->entity_plural, 'id')->where('is_deleted',0)->where('company_id', $user->company()->id)], + 'entity_id' => ['bail','sometimes','integer',Rule::exists($this->entity_plural, 'id')->where('is_deleted', 0)->where('company_id', $user->company()->id)], 'client_id' => ['required', Rule::exists(Client::class, 'id')->where('is_deleted', 0)->where('company_id', $user->company()->id)], ]; @@ -76,8 +76,9 @@ class PreviewInvoiceRequest extends Request $input['balance'] = 0; $input['number'] = isset($input['number']) ? $input['number'] : ctrans('texts.live_preview').' #'.rand(0, 1000); - if($input['entity_id'] ?? false) + if($input['entity_id'] ?? false) { $input['entity_id'] = $this->decodePrimaryKey($input['entity_id'], true); + } $this->convertEntityPlural($input['entity'] ?? 'invoice'); @@ -88,26 +89,29 @@ class PreviewInvoiceRequest extends Request { $invitation = false; - if(! $this->entity_id ?? false) + if(! $this->entity_id ?? false) { return $this->stubInvitation(); + } - match($this->entity){ - 'invoice' => $invitation = InvoiceInvitation::withTrashed()->where('invoice_id', $this->entity_id)->first(), - 'quote' => $invitation = QuoteInvitation::withTrashed()->where('quote_id', $this->entity_id)->first(), - 'credit' => $invitation = CreditInvitation::withTrashed()->where('credit_id', $this->entity_id)->first(), - 'recurring_invoice' => $invitation = RecurringInvoiceInvitation::withTrashed()->where('recurring_invoice_id', $this->entity_id)->first(), + match($this->entity) { + 'invoice' => $invitation = InvoiceInvitation::withTrashed()->where('invoice_id', $this->entity_id)->first(), + 'quote' => $invitation = QuoteInvitation::withTrashed()->where('quote_id', $this->entity_id)->first(), + 'credit' => $invitation = CreditInvitation::withTrashed()->where('credit_id', $this->entity_id)->first(), + 'recurring_invoice' => $invitation = RecurringInvoiceInvitation::withTrashed()->where('recurring_invoice_id', $this->entity_id)->first(), }; - if($invitation) + if($invitation) { return $invitation; + } - $invitation = $this->stubInvitation(); + return $this->stubInvitation(); } public function getClient(): ?Client { - if(!$this->client) + if(!$this->client) { $this->client = Client::query()->with('contacts', 'company', 'user')->withTrashed()->find($this->client_id); + } return $this->client; } @@ -147,7 +151,7 @@ class PreviewInvoiceRequest extends Request { $entity = false; - match($this->entity){ + match($this->entity) { 'invoice' => $entity = Invoice::factory()->make(['client_id' => $client->id,'user_id' => $client->user_id, 'company_id' => $client->company_id]), 'quote' => $entity = Quote::factory()->make(['client_id' => $client->id,'user_id' => $client->user_id, 'company_id' => $client->company_id]), 'credit' => $entity = Credit::factory()->make(['client_id' => $client->id,'user_id' => $client->user_id, 'company_id' => $client->company_id]), diff --git a/app/Http/Requests/Preview/PreviewPurchaseOrderRequest.php b/app/Http/Requests/Preview/PreviewPurchaseOrderRequest.php index 927937bf25..e7075e1a0b 100644 --- a/app/Http/Requests/Preview/PreviewPurchaseOrderRequest.php +++ b/app/Http/Requests/Preview/PreviewPurchaseOrderRequest.php @@ -11,15 +11,21 @@ namespace App\Http\Requests\Preview; +use App\Models\Vendor; +use App\Models\PurchaseOrder; use App\Http\Requests\Request; -use App\Utils\Traits\CleanLineItems; use App\Utils\Traits\MakesHash; +use App\Utils\Traits\CleanLineItems; +use App\Models\PurchaseOrderInvitation; class PreviewPurchaseOrderRequest extends Request { use MakesHash; use CleanLineItems; + private ?Vendor $vendor = null; + private string $entity_plural = ''; + /** * Determine if the user is authorized to make this request. * @@ -27,7 +33,10 @@ class PreviewPurchaseOrderRequest extends Request */ public function authorize() : bool { - return auth()->user()->hasIntersectPermissionsOrAdmin(['create_purchase_order', 'edit_purchase_order', 'view_purchase_order']); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->hasIntersectPermissionsOrAdmin(['create_purchase_order', 'edit_purchase_order', 'view_purchase_order']); } public function rules() @@ -52,4 +61,77 @@ class PreviewPurchaseOrderRequest extends Request $this->replace($input); } + + + + public function resolveInvitation() + { + $invitation = false; + + if(! $this->entity_id ?? false) { + return $this->stubInvitation(); + } + + $invitation = PurchaseOrderInvitation::withTrashed()->where('purchase_order_id', $this->entity_id)->first(); + + if($invitation) { + return $invitation; + } + + return $this->stubInvitation(); + + + } + + public function getVendor(): ?Vendor + { + if(!$this->vendor) { + $this->vendor = Vendor::query()->with('contacts', 'company', 'user')->withTrashed()->find($this->vendor_id); + } + + return $this->vendor; + } + + public function setVendor(Vendor $vendor): self + { + $this->vendor = $vendor; + + return $this; + } + + public function stubInvitation() + { + $vendor = Vendor::query()->with('contacts', 'company', 'user')->withTrashed()->find($this->vendor_id); + $this->setVendor($vendor); + $invitation = false; + + $entity = $this->stubEntity($vendor); + $invitation = PurchaseOrderInvitation::factory()->make(); + $invitation->setRelation('purchase_order', $entity); + $invitation->setRelation('contact', $vendor->contacts->first()->load('vendor.company')); + $invitation->setRelation('company', $vendor->company); + + return $invitation; + } + + private function stubEntity(Vendor $vendor) + { + $entity = PurchaseOrder::factory()->make(['vendor_id' => $vendor->id,'user_id' => $vendor->user_id, 'company_id' => $vendor->company_id]); + + $entity->setRelation('vendor', $vendor); + $entity->setRelation('company', $vendor->company); + $entity->setRelation('user', $vendor->user); + $entity->fill($this->all()); + + return $entity; + } + + private function convertEntityPlural(string $entity) :self + { + + $this->entity_plural = 'purchase_orders'; + + return $this; + } + } diff --git a/app/Http/Requests/Project/StoreProjectRequest.php b/app/Http/Requests/Project/StoreProjectRequest.php index 98664cd84c..a8684c0d24 100644 --- a/app/Http/Requests/Project/StoreProjectRequest.php +++ b/app/Http/Requests/Project/StoreProjectRequest.php @@ -28,18 +28,25 @@ class StoreProjectRequest extends Request */ public function authorize() : bool { - return auth()->user()->can('create', Project::class); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->can('create', Project::class); } public function rules() { + + /** @var \App\Models\User $user */ + $user = auth()->user(); + $rules = []; $rules['name'] = 'required'; - $rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id; + $rules['client_id'] = 'required|exists:clients,id,company_id,'.$user->company()->id; if (isset($this->number)) { - $rules['number'] = Rule::unique('projects')->where('company_id', auth()->user()->company()->id); + $rules['number'] = Rule::unique('projects')->where('company_id', $user->company()->id); } if ($this->file('documents') && is_array($this->file('documents'))) { diff --git a/app/Http/Requests/Project/UpdateProjectRequest.php b/app/Http/Requests/Project/UpdateProjectRequest.php index 484f18f036..dc5484f8a2 100644 --- a/app/Http/Requests/Project/UpdateProjectRequest.php +++ b/app/Http/Requests/Project/UpdateProjectRequest.php @@ -26,15 +26,23 @@ class UpdateProjectRequest extends Request */ public function authorize() : bool { - return auth()->user()->can('edit', $this->project); + + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->can('edit', $this->project); } public function rules() { + + /** @var \App\Models\User $user */ + $user = auth()->user(); + $rules = []; if (isset($this->number)) { - $rules['number'] = Rule::unique('projects')->where('company_id', auth()->user()->company()->id)->ignore($this->project->id); + $rules['number'] = Rule::unique('projects')->where('company_id', $user->company()->id)->ignore($this->project->id); } if ($this->file('documents') && is_array($this->file('documents'))) { diff --git a/app/Http/Requests/PurchaseOrder/UploadPurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/UploadPurchaseOrderRequest.php index 746e17abf1..8d290a696a 100644 --- a/app/Http/Requests/PurchaseOrder/UploadPurchaseOrderRequest.php +++ b/app/Http/Requests/PurchaseOrder/UploadPurchaseOrderRequest.php @@ -44,7 +44,7 @@ class UploadPurchaseOrderRequest extends Request $rules['file'] = $this->file_validation; } - $rules['is_public'] = 'sometimes|boolean'; + $rules['is_public'] = 'sometimes|boolean'; return $rules; } diff --git a/app/Http/Requests/Quote/BulkActionQuoteRequest.php b/app/Http/Requests/Quote/BulkActionQuoteRequest.php index c9f41313a5..cb77becc06 100644 --- a/app/Http/Requests/Quote/BulkActionQuoteRequest.php +++ b/app/Http/Requests/Quote/BulkActionQuoteRequest.php @@ -34,7 +34,7 @@ class BulkActionQuoteRequest extends Request 'action' => 'sometimes|in:convert_to_invoice,convert_to_project,email,bulk_download,bulk_print,clone_to_invoice,approve,download,restore,archive,delete,send_email,mark_sent', ]; - if (in_array($input['action'], ['convert,convert_to_invoice']) ) { + if (in_array($input['action'], ['convert,convert_to_invoice'])) { $rules['action'] = [new ConvertableQuoteRule()]; } diff --git a/app/Http/Requests/Statements/CreateStatementRequest.php b/app/Http/Requests/Statements/CreateStatementRequest.php index 3406be9468..2cf38a3a58 100644 --- a/app/Http/Requests/Statements/CreateStatementRequest.php +++ b/app/Http/Requests/Statements/CreateStatementRequest.php @@ -17,9 +17,10 @@ class CreateStatementRequest extends Request */ public function authorize(): bool { - // return auth()->user()->isAdmin(); + /** @var \App\Models\User $user */ + $user = auth()->user(); - return auth()->user()->can('view', $this->client()); + return $user->can('view', $this->client()); } /** @@ -29,14 +30,18 @@ class CreateStatementRequest extends Request */ public function rules() { + /** @var \App\Models\User $user */ + $user = auth()->user(); + return [ 'start_date' => 'required|date_format:Y-m-d', 'end_date' => 'required|date_format:Y-m-d', - 'client_id' => 'bail|required|exists:clients,id,company_id,'.auth()->user()->company()->id, + 'client_id' => 'bail|required|exists:clients,id,company_id,'.$user->company()->id, 'show_payments_table' => 'boolean', 'show_aging_table' => 'boolean', 'show_credits_table' => 'boolean', 'status' => 'string', + 'template' => 'sometimes|string|nullable', ]; } diff --git a/app/Http/Requests/Task/StoreTaskRequest.php b/app/Http/Requests/Task/StoreTaskRequest.php index 52d331d3ab..5ae5e13dd4 100644 --- a/app/Http/Requests/Task/StoreTaskRequest.php +++ b/app/Http/Requests/Task/StoreTaskRequest.php @@ -54,15 +54,24 @@ class StoreTaskRequest extends Request $rules['project_id'] = 'bail|required|exists:projects,id,company_id,'.$user->company()->id.',is_deleted,0'; } - $rules['timelog'] = ['bail','array',function ($attribute, $values, $fail) { + $rules['time_log'] = ['bail', function ($attribute, $values, $fail) { + + if(is_string($values)) { + $values = json_decode($values, 1); + } + + if(!is_array($values)) { + return $fail('The '.$attribute.' is invalid. Must be an array.'); + } + foreach ($values as $k) { if (!is_int($k[0]) || !is_int($k[1])) { - $fail('The '.$attribute.' - '.print_r($k, 1).' is invalid. Unix timestamps only.'); + return $fail('The '.$attribute.' - '.print_r($k, 1).' is invalid. Unix timestamps only.'); } } if (!$this->checkTimeLog($values)) { - $fail('Please correct overlapping values'); + return $fail('Please correct overlapping values'); } }]; diff --git a/app/Http/Requests/Task/UpdateTaskRequest.php b/app/Http/Requests/Task/UpdateTaskRequest.php index 7107df9fe6..7779dc0340 100644 --- a/app/Http/Requests/Task/UpdateTaskRequest.php +++ b/app/Http/Requests/Task/UpdateTaskRequest.php @@ -34,35 +34,51 @@ class UpdateTaskRequest extends Request if ($this->task->invoice_id && $this->task->company->invoice_task_lock) { return false; } + + /** @var \App\Models\User $user */ + $user = auth()->user(); - return auth()->user()->can('edit', $this->task); + return $user->can('edit', $this->task); } public function rules() { + + /** @var \App\Models\User $user */ + $user = auth()->user(); + $rules = []; if (isset($this->number)) { - $rules['number'] = Rule::unique('tasks')->where('company_id', auth()->user()->company()->id)->ignore($this->task->id); + $rules['number'] = Rule::unique('tasks')->where('company_id', $user->company()->id)->ignore($this->task->id); } if (isset($this->client_id)) { - $rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; + $rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.$user->company()->id.',is_deleted,0'; } if (isset($this->project_id)) { - $rules['project_id'] = 'bail|required|exists:projects,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; + $rules['project_id'] = 'bail|required|exists:projects,id,company_id,'.$user->company()->id.',is_deleted,0'; } - $rules['timelog'] = ['bail','array',function ($attribute, $values, $fail) { + $rules['time_log'] = ['bail',function ($attribute, $values, $fail) { + + if(is_string($values)) { + $values = json_decode($values, 1); + } + + if(!is_array($values)) { + return $fail('The '.$attribute.' is invalid. Must be an array.'); + } + foreach ($values as $k) { if (!is_int($k[0]) || !is_int($k[1])) { - $fail('The '.$attribute.' - '.print_r($k, 1).' is invalid. Unix timestamps only.'); + return $fail('The '.$attribute.' - '.print_r($k, 1).' is invalid. Unix timestamps only.'); } } if (!$this->checkTimeLog($values)) { - $fail('Please correct overlapping values'); + return $fail('Please correct overlapping values'); } }]; @@ -104,10 +120,10 @@ class UpdateTaskRequest extends Request $input['color'] = ''; } - if(isset($input['project_id']) && isset($input['client_id'])){ + if(isset($input['project_id']) && isset($input['client_id'])) { $search_project_with_client = Project::withTrashed()->where('id', $input['project_id'])->where('client_id', $input['client_id'])->company()->doesntExist(); - if($search_project_with_client){ + if($search_project_with_client) { unset($input['project_id']); } diff --git a/app/Http/Requests/Task/UploadTaskRequest.php b/app/Http/Requests/Task/UploadTaskRequest.php index 6b7d96ef04..088ec573c3 100644 --- a/app/Http/Requests/Task/UploadTaskRequest.php +++ b/app/Http/Requests/Task/UploadTaskRequest.php @@ -53,8 +53,9 @@ class UploadTaskRequest extends Request { $input = $this->all(); - if(isset($input['is_public'])) + if(isset($input['is_public'])) { $input['is_public'] = $this->toBoolean($input['is_public']); + } $this->replace($input); } diff --git a/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php b/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php index c0fc0c9d60..312e073099 100644 --- a/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php +++ b/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php @@ -14,7 +14,6 @@ namespace App\Http\Requests\TaskScheduler; use App\Http\Requests\Request; use App\Http\ValidationRules\Scheduler\ValidClientIds; use App\Utils\Traits\MakesHash; -use Illuminate\Validation\Rule; class StoreSchedulerRequest extends Request { @@ -60,7 +59,7 @@ class StoreSchedulerRequest extends Request $input['next_run_client'] = $input['next_run']; } - if($input['template'] == 'email_record'){ + if($input['template'] == 'email_record') { $input['frequency_id'] = 0; } diff --git a/app/Http/Requests/TaskScheduler/UpdateSchedulerRequest.php b/app/Http/Requests/TaskScheduler/UpdateSchedulerRequest.php index 24dea23f0a..6a7750320f 100644 --- a/app/Http/Requests/TaskScheduler/UpdateSchedulerRequest.php +++ b/app/Http/Requests/TaskScheduler/UpdateSchedulerRequest.php @@ -12,7 +12,6 @@ namespace App\Http\Requests\TaskScheduler; use App\Http\Requests\Request; use App\Http\ValidationRules\Scheduler\ValidClientIds; -use Illuminate\Validation\Rule; class UpdateSchedulerRequest extends Request { diff --git a/app/Http/Requests/TaskStatus/UpdateTaskStatusRequest.php b/app/Http/Requests/TaskStatus/UpdateTaskStatusRequest.php index e7a0b757c9..c1f2bb31ae 100644 --- a/app/Http/Requests/TaskStatus/UpdateTaskStatusRequest.php +++ b/app/Http/Requests/TaskStatus/UpdateTaskStatusRequest.php @@ -13,7 +13,6 @@ namespace App\Http\Requests\TaskStatus; use App\Http\Requests\Request; use App\Utils\Traits\MakesHash; -use Illuminate\Validation\Rule; class UpdateTaskStatusRequest extends Request { diff --git a/app/Http/Requests/User/BulkUserRequest.php b/app/Http/Requests/User/BulkUserRequest.php index c07032f4f2..f0893654e5 100644 --- a/app/Http/Requests/User/BulkUserRequest.php +++ b/app/Http/Requests/User/BulkUserRequest.php @@ -11,10 +11,10 @@ namespace App\Http\Requests\User; -use App\Utils\Ninja; use App\Http\Requests\Request; -use Illuminate\Auth\Access\AuthorizationException; use App\Http\ValidationRules\Ninja\CanRestoreUserRule; +use App\Utils\Ninja; +use Illuminate\Auth\Access\AuthorizationException; class BulkUserRequest extends Request { @@ -25,8 +25,9 @@ class BulkUserRequest extends Request */ public function authorize() : bool { - if($this->action == 'delete' && in_array(auth()->user()->hashed_id, $this->ids)) + if($this->action == 'delete' && in_array(auth()->user()->hashed_id, $this->ids)) { return false; + } return auth()->user()->isAdmin(); } diff --git a/app/Http/Requests/User/DisconnectUserMailerRequest.php b/app/Http/Requests/User/DisconnectUserMailerRequest.php index 881276e4dc..ffe07871e8 100644 --- a/app/Http/Requests/User/DisconnectUserMailerRequest.php +++ b/app/Http/Requests/User/DisconnectUserMailerRequest.php @@ -23,7 +23,7 @@ class DisconnectUserMailerRequest extends Request * @return bool */ public function authorize() : bool - { + { return auth()->user()->id == $this->user->id || auth()->user()->isAdmin(); } diff --git a/app/Http/Requests/Vendor/StoreVendorRequest.php b/app/Http/Requests/Vendor/StoreVendorRequest.php index 3ce8ba4721..f60c7997b7 100644 --- a/app/Http/Requests/Vendor/StoreVendorRequest.php +++ b/app/Http/Requests/Vendor/StoreVendorRequest.php @@ -38,8 +38,21 @@ class StoreVendorRequest extends Request $user = auth()->user(); $rules = []; - + + $rules['contacts'] = 'bail|array'; $rules['contacts.*.email'] = 'bail|nullable|distinct|sometimes|email'; + $rules['contacts.*.password'] = [ + 'bail', + 'nullable', + 'sometimes', + 'string', + 'min:7', // must be at least 10 characters in length + 'regex:/[a-z]/', // must contain at least one lowercase letter + 'regex:/[A-Z]/', // must contain at least one uppercase letter + 'regex:/[0-9]/', // must contain at least one digit + //'regex:/[@$!%*#?&.]/', // must contain a special character + ]; + if (isset($this->number)) { $rules['number'] = Rule::unique('vendors')->where('company_id', $user->company()->id); diff --git a/app/Http/Requests/Vendor/UpdateVendorRequest.php b/app/Http/Requests/Vendor/UpdateVendorRequest.php index b50e8cf792..e8b6811fd1 100644 --- a/app/Http/Requests/Vendor/UpdateVendorRequest.php +++ b/app/Http/Requests/Vendor/UpdateVendorRequest.php @@ -45,7 +45,20 @@ class UpdateVendorRequest extends Request $rules['number'] = Rule::unique('vendors')->where('company_id', $user->company()->id)->ignore($this->vendor->id); } - $rules['contacts.*.email'] = 'nullable|distinct'; + $rules['contacts'] = 'bail|array'; + $rules['contacts.*.email'] = 'bail|nullable|distinct|sometimes|email'; + $rules['contacts.*.password'] = [ + 'bail', + 'nullable', + 'sometimes', + 'string', + 'min:7', // must be at least 10 characters in length + 'regex:/[a-z]/', // must contain at least one lowercase letter + 'regex:/[A-Z]/', // must contain at least one uppercase letter + 'regex:/[0-9]/', // must contain at least one digit + //'regex:/[@$!%*#?&.]/', // must contain a special character + ]; + $rules['currency_id'] = 'bail|sometimes|exists:currencies,id'; if ($this->file('documents') && is_array($this->file('documents'))) { diff --git a/app/Http/Requests/Vendor/UploadVendorRequest.php b/app/Http/Requests/Vendor/UploadVendorRequest.php index a9263fb979..f1219bd6d6 100644 --- a/app/Http/Requests/Vendor/UploadVendorRequest.php +++ b/app/Http/Requests/Vendor/UploadVendorRequest.php @@ -50,8 +50,9 @@ class UploadVendorRequest extends Request { $input = $this->all(); - if(isset($input['is_public'])) + if(isset($input['is_public'])) { $input['is_public'] = $this->toBoolean($input['is_public']); + } $this->replace($input); } diff --git a/app/Http/Requests/Webhook/UpdateWebhookRequest.php b/app/Http/Requests/Webhook/UpdateWebhookRequest.php index d8fd2ed783..a02890d55d 100644 --- a/app/Http/Requests/Webhook/UpdateWebhookRequest.php +++ b/app/Http/Requests/Webhook/UpdateWebhookRequest.php @@ -49,7 +49,7 @@ class UpdateWebhookRequest extends Request } // if(isset($input['headers']) && count($input['headers']) == 0) - // $input['headers'] = null; + // $input['headers'] = null; $this->replace($input); } diff --git a/app/Http/ValidationRules/Credit/ValidCreditsRules.php b/app/Http/ValidationRules/Credit/ValidCreditsRules.php index d02097df21..8c43fed216 100644 --- a/app/Http/ValidationRules/Credit/ValidCreditsRules.php +++ b/app/Http/ValidationRules/Credit/ValidCreditsRules.php @@ -76,7 +76,7 @@ class ValidCreditsRules implements Rule return false; } - if($cred->status_id == Credit::STATUS_DRAFT){ + if($cred->status_id == Credit::STATUS_DRAFT) { $cred->service()->markSent()->save(); $cred = $cred->fresh(); } diff --git a/app/Http/ValidationRules/Payment/ValidRefundableRequest.php b/app/Http/ValidationRules/Payment/ValidRefundableRequest.php index 6de59ca0b9..49530bca72 100644 --- a/app/Http/ValidationRules/Payment/ValidRefundableRequest.php +++ b/app/Http/ValidationRules/Payment/ValidRefundableRequest.php @@ -11,7 +11,6 @@ namespace App\Http\ValidationRules\Payment; -use App\Models\Credit; use App\Models\Invoice; use App\Models\Payment; use App\Utils\Traits\MakesHash; diff --git a/app/Http/ValidationRules/PaymentAppliedValidAmount.php b/app/Http/ValidationRules/PaymentAppliedValidAmount.php index 2958f04229..f64f72d2b1 100644 --- a/app/Http/ValidationRules/PaymentAppliedValidAmount.php +++ b/app/Http/ValidationRules/PaymentAppliedValidAmount.php @@ -91,7 +91,7 @@ class PaymentAppliedValidAmount implements Rule } } - if(count($this->input['invoices']) >=1 && $payment->status_id == Payment::STATUS_PENDING){ + if(count($this->input['invoices']) >=1 && $payment->status_id == Payment::STATUS_PENDING) { $this->message = 'Cannot apply a payment until the status is completed.'; return false; } diff --git a/app/Http/ValidationRules/Project/ValidProjectForClient.php b/app/Http/ValidationRules/Project/ValidProjectForClient.php index 46f3b1a8c8..188d94c939 100644 --- a/app/Http/ValidationRules/Project/ValidProjectForClient.php +++ b/app/Http/ValidationRules/Project/ValidProjectForClient.php @@ -53,7 +53,7 @@ class ValidProjectForClient implements Rule return; } - if(!isset($this->input['client_id'])){ + if(!isset($this->input['client_id'])) { $this->message = 'No Client ID provided.'; return false; } diff --git a/app/Http/ValidationRules/ValidAmount.php b/app/Http/ValidationRules/ValidAmount.php index 4438b935ad..2f219f17b8 100644 --- a/app/Http/ValidationRules/ValidAmount.php +++ b/app/Http/ValidationRules/ValidAmount.php @@ -27,7 +27,7 @@ class ValidAmount implements Rule { return is_numeric((string) $value); //return filter_var((string)$value, FILTER_VALIDATE_FLOAT); -// return preg_match('^(?=.)([+-]?([0-9]*)(\.([0-9]+))?)$^', (string)$value); + // return preg_match('^(?=.)([+-]?([0-9]*)(\.([0-9]+))?)$^', (string)$value); // return trim($value, '-1234567890.,') === ''; } diff --git a/app/Import/Definitions/VendorMap.php b/app/Import/Definitions/VendorMap.php index 4e5cbd3752..1c23f5e340 100644 --- a/app/Import/Definitions/VendorMap.php +++ b/app/Import/Definitions/VendorMap.php @@ -33,14 +33,18 @@ class VendorMap 14 => 'vendor.state', 15 => 'vendor.postal_code', 16 => 'vendor.country_id', - 17 => 'contact.first_name', - 18 => 'contact.last_name', - 19 => 'contact.email', - 20 => 'contact.phone', - 21 => 'contact.custom_value1', - 22 => 'contact.custom_value2', - 23 => 'contact.custom_value3', - 24 => 'contact.custom_value4', + 17 => 'vendor.custom_value1', + 18 => 'vendor.custom_value2', + 19 => 'vendor.custom_value3', + 20 => 'vendor.custom_value4', + 21 => 'contact.first_name', + 22 => 'contact.last_name', + 23 => 'contact.email', + 24 => 'contact.phone', + 25 => 'contact.custom_value1', + 26 => 'contact.custom_value2', + 27 => 'contact.custom_value3', + 28 => 'contact.custom_value4', ]; } @@ -65,14 +69,18 @@ class VendorMap 14 => 'texts.state', 15 => 'texts.postal_code', 16 => 'texts.country', - 17 => 'texts.first_name', - 18 => 'texts.last_name', - 19 => 'texts.email', - 20 => 'texts.phone', - 21 => 'texts.custom_value', - 22 => 'texts.custom_value', - 23 => 'texts.custom_value', - 24 => 'texts.custom_value', + 17 => 'texts.custom_value', + 18 => 'texts.custom_value', + 19 => 'texts.custom_value', + 20 => 'texts.custom_value', + 21 => 'texts.first_name', + 22 => 'texts.last_name', + 23 => 'texts.email', + 24 => 'texts.phone', + 25 => 'texts.custom_value', + 26 => 'texts.custom_value', + 27 => 'texts.custom_value', + 28 => 'texts.custom_value', ]; } } diff --git a/app/Import/Providers/BaseImport.php b/app/Import/Providers/BaseImport.php index e401e85aa0..f5396e7efd 100644 --- a/app/Import/Providers/BaseImport.php +++ b/app/Import/Providers/BaseImport.php @@ -11,31 +11,31 @@ namespace App\Import\Providers; -use App\Models\User; -use App\Models\Quote; -use League\Csv\Reader; -use App\Models\Company; -use App\Models\Invoice; -use League\Csv\Statement; -use App\Factory\QuoteFactory; use App\Factory\ClientFactory; -use Illuminate\Support\Carbon; use App\Factory\InvoiceFactory; use App\Factory\PaymentFactory; +use App\Factory\QuoteFactory; +use App\Factory\RecurringInvoiceFactory; +use App\Http\Requests\Quote\StoreQuoteRequest; use App\Import\ImportException; use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; -use App\Utils\Traits\CleanLineItems; -use App\Repositories\QuoteRepository; -use Illuminate\Support\Facades\Cache; -use App\Repositories\ClientRepository; use App\Mail\Import\CsvImportCompleted; +use App\Models\Company; +use App\Models\Invoice; +use App\Models\Quote; +use App\Models\User; +use App\Repositories\ClientRepository; use App\Repositories\InvoiceRepository; use App\Repositories\PaymentRepository; -use App\Factory\RecurringInvoiceFactory; -use Illuminate\Support\Facades\Validator; -use App\Http\Requests\Quote\StoreQuoteRequest; +use App\Repositories\QuoteRepository; use App\Repositories\RecurringInvoiceRepository; +use App\Utils\Traits\CleanLineItems; +use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Validator; +use League\Csv\Reader; +use League\Csv\Statement; class BaseImport { @@ -482,8 +482,9 @@ class BaseImport nlog($invoice_data); $saveable_invoice_data = $invoice_data; - if(array_key_exists('payments', $saveable_invoice_data)) + if(array_key_exists('payments', $saveable_invoice_data)) { unset($saveable_invoice_data['payments']); + } $invoice_repository->save($saveable_invoice_data, $invoice); @@ -524,8 +525,7 @@ class BaseImport $payment_date = Carbon::parse($payment->date); - if(!$payment_date->isToday()) - { + if(!$payment_date->isToday()) { $payment->paymentables()->update(['created_at' => $payment_date]); @@ -743,8 +743,9 @@ class BaseImport } - if($user) + if($user) { return $user->id; + } $user = User::whereRaw("account_id = ? AND CONCAT_WS(' ', first_name, last_name) like ?", [$this->company->account_id, '%'.$user_hash.'%']) ->first(); diff --git a/app/Import/Providers/Csv.php b/app/Import/Providers/Csv.php index 8615328203..9e29d5af9f 100644 --- a/app/Import/Providers/Csv.php +++ b/app/Import/Providers/Csv.php @@ -11,44 +11,44 @@ namespace App\Import\Providers; -use App\Factory\QuoteFactory; +use App\Factory\BankTransactionFactory; use App\Factory\ClientFactory; -use App\Factory\VendorFactory; use App\Factory\ExpenseFactory; use App\Factory\InvoiceFactory; use App\Factory\PaymentFactory; use App\Factory\ProductFactory; -use App\Utils\Traits\MakesHash; -use App\Repositories\QuoteRepository; -use App\Repositories\ClientRepository; -use App\Repositories\VendorRepository; -use App\Factory\BankTransactionFactory; -use App\Repositories\ExpenseRepository; -use App\Repositories\InvoiceRepository; -use App\Repositories\PaymentRepository; -use App\Repositories\ProductRepository; +use App\Factory\QuoteFactory; use App\Factory\RecurringInvoiceFactory; -use App\Services\Bank\BankMatchingService; -use App\Http\Requests\Quote\StoreQuoteRequest; -use App\Repositories\BankTransactionRepository; +use App\Factory\VendorFactory; +use App\Http\Requests\BankTransaction\StoreBankTransactionRequest; use App\Http\Requests\Client\StoreClientRequest; -use App\Http\Requests\Vendor\StoreVendorRequest; -use App\Import\Transformer\Bank\BankTransformer; -use App\Import\Transformer\Csv\QuoteTransformer; -use App\Repositories\RecurringInvoiceRepository; -use App\Import\Transformer\Csv\ClientTransformer; -use App\Import\Transformer\Csv\VendorTransformer; use App\Http\Requests\Expense\StoreExpenseRequest; use App\Http\Requests\Invoice\StoreInvoiceRequest; use App\Http\Requests\Payment\StorePaymentRequest; use App\Http\Requests\Product\StoreProductRequest; +use App\Http\Requests\Quote\StoreQuoteRequest; +use App\Http\Requests\RecurringInvoice\StoreRecurringInvoiceRequest; +use App\Http\Requests\Vendor\StoreVendorRequest; +use App\Import\Transformer\Bank\BankTransformer; +use App\Import\Transformer\Csv\ClientTransformer; use App\Import\Transformer\Csv\ExpenseTransformer; use App\Import\Transformer\Csv\InvoiceTransformer; use App\Import\Transformer\Csv\PaymentTransformer; use App\Import\Transformer\Csv\ProductTransformer; +use App\Import\Transformer\Csv\QuoteTransformer; use App\Import\Transformer\Csv\RecurringInvoiceTransformer; -use App\Http\Requests\BankTransaction\StoreBankTransactionRequest; -use App\Http\Requests\RecurringInvoice\StoreRecurringInvoiceRequest; +use App\Import\Transformer\Csv\VendorTransformer; +use App\Repositories\BankTransactionRepository; +use App\Repositories\ClientRepository; +use App\Repositories\ExpenseRepository; +use App\Repositories\InvoiceRepository; +use App\Repositories\PaymentRepository; +use App\Repositories\ProductRepository; +use App\Repositories\QuoteRepository; +use App\Repositories\RecurringInvoiceRepository; +use App\Repositories\VendorRepository; +use App\Services\Bank\BankMatchingService; +use App\Utils\Traits\MakesHash; class Csv extends BaseImport implements ImportInterface { diff --git a/app/Import/Transformer/BaseTransformer.php b/app/Import/Transformer/BaseTransformer.php index f651e164aa..31c05ceb16 100644 --- a/app/Import/Transformer/BaseTransformer.php +++ b/app/Import/Transformer/BaseTransformer.php @@ -11,27 +11,27 @@ namespace App\Import\Transformer; -use App\Models\Quote; -use App\Utils\Number; +use App\Factory\ClientFactory; +use App\Factory\ExpenseCategoryFactory; +use App\Factory\ProjectFactory; +use App\Factory\VendorFactory; use App\Models\Client; -use App\Models\Vendor; +use App\Models\ClientContact; use App\Models\Country; use App\Models\Expense; +use App\Models\ExpenseCategory; use App\Models\Invoice; +use App\Models\PaymentType; use App\Models\Product; use App\Models\Project; -use App\Models\TaxRate; -use App\Models\PaymentType; -use App\Models\ClientContact; -use App\Factory\ClientFactory; -use App\Factory\VendorFactory; -use Illuminate\Support\Carbon; -use App\Factory\ProjectFactory; -use App\Models\ExpenseCategory; +use App\Models\Quote; use App\Models\RecurringInvoice; -use Illuminate\Support\Facades\Cache; +use App\Models\TaxRate; +use App\Models\Vendor; use App\Repositories\ClientRepository; -use App\Factory\ExpenseCategoryFactory; +use App\Utils\Number; +use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Cache; /** * Class BaseTransformer. @@ -47,8 +47,9 @@ class BaseTransformer public function parseDate($date) { - if(stripos($date,"/") !== false && $this->company->settings->country_id != 840) + if(stripos($date, "/") !== false && $this->company->settings->country_id != 840) { $date = str_replace('/', '-', $date); + } try { $parsed_date = Carbon::parse($date); @@ -331,10 +332,11 @@ class BaseTransformer */ public function getFloatOrOne($data, $field) { - if (array_key_exists($field, $data)) + if (array_key_exists($field, $data)) { return Number::parseStringFloat($data[$field]) > 0 ? Number::parseStringFloat($data[$field]) : 1; + } - return 1; + return 1; } @@ -637,8 +639,9 @@ class BaseTransformer ]) ->first(); - if($ec) + if($ec) { return $ec->id; + } $ec = \App\Factory\ExpenseCategoryFactory::create($this->company->id, $this->company->owner()->id); $ec->name = $name; diff --git a/app/Import/Transformer/Csv/InvoiceTransformer.php b/app/Import/Transformer/Csv/InvoiceTransformer.php index 1399990a2a..38980311ea 100644 --- a/app/Import/Transformer/Csv/InvoiceTransformer.php +++ b/app/Import/Transformer/Csv/InvoiceTransformer.php @@ -167,7 +167,7 @@ class InvoiceTransformer extends BaseTransformer ), ], ]; - } + } // elseif ( // isset($transformed['amount']) && // isset($transformed['balance']) && diff --git a/app/Import/Transformer/Csv/RecurringInvoiceTransformer.php b/app/Import/Transformer/Csv/RecurringInvoiceTransformer.php index d4c2a17bdf..2f924da831 100644 --- a/app/Import/Transformer/Csv/RecurringInvoiceTransformer.php +++ b/app/Import/Transformer/Csv/RecurringInvoiceTransformer.php @@ -11,10 +11,10 @@ namespace App\Import\Transformer\Csv; -use App\Models\Invoice; use App\Import\ImportException; -use App\Models\RecurringInvoice; use App\Import\Transformer\BaseTransformer; +use App\Models\Invoice; +use App\Models\RecurringInvoice; /** * Class RecurringInvoiceTransformer. @@ -134,10 +134,12 @@ class RecurringInvoiceTransformer extends BaseTransformer // ] ?? Invoice::STATUS_SENT, 'auto_bill' => $this->getAutoBillFlag( $this->getString($invoice_data, 'invoice.auto_bill') - ), - 'frequency_id' => $this->getFrequency(isset($invoice_data['invoice.frequency_id']) ? $invoice_data['invoice.frequency_id'] : 'monthly' ), - 'remaining_cycles' => $this->getRemainingCycles(isset($invoice_data['invoice.remaining_cycles']) ? $invoice_data['invoice.remaining_cycles'] : -1 + 'frequency_id' => $this->getFrequency( + isset($invoice_data['invoice.frequency_id']) ? $invoice_data['invoice.frequency_id'] : 'monthly' + ), + 'remaining_cycles' => $this->getRemainingCycles( + isset($invoice_data['invoice.remaining_cycles']) ? $invoice_data['invoice.remaining_cycles'] : -1 ), // 'archived' => $status === 'archived', ]; diff --git a/app/Import/Transformer/Invoice2Go/InvoiceTransformer.php b/app/Import/Transformer/Invoice2Go/InvoiceTransformer.php index ce790aa254..e6ad42416a 100644 --- a/app/Import/Transformer/Invoice2Go/InvoiceTransformer.php +++ b/app/Import/Transformer/Invoice2Go/InvoiceTransformer.php @@ -63,8 +63,7 @@ class InvoiceTransformer extends BaseTransformer $client_id = null; - if($this->hasClient($this->getString($invoice_data, 'Name') || $this->getContact($this->getString($invoice_data, 'EmailRecipient')))) - { + if($this->hasClient($this->getString($invoice_data, 'Name') || $this->getContact($this->getString($invoice_data, 'EmailRecipient')))) { $client_id = $this->getClient($this->getString($invoice_data, 'Name'), $this->getString($invoice_data, 'EmailRecipient')); diff --git a/app/Import/Transformer/Zoho/ClientTransformer.php b/app/Import/Transformer/Zoho/ClientTransformer.php index 0849641498..b63891b5b3 100644 --- a/app/Import/Transformer/Zoho/ClientTransformer.php +++ b/app/Import/Transformer/Zoho/ClientTransformer.php @@ -31,8 +31,7 @@ class ClientTransformer extends BaseTransformer if(isset($data[$client_id_proxy]) && $this->hasClientIdNumber($data[$client_id_proxy])) { throw new ImportException('Client ID already exists => '. $data[$client_id_proxy]); - } - elseif (isset($data['Company Name']) && $this->hasClient($data['Company Name'])) { + } elseif (isset($data['Company Name']) && $this->hasClient($data['Company Name'])) { throw new ImportException('Client already exists => '. $data['Company Name']); } diff --git a/app/Import/Transformer/Zoho/InvoiceTransformer.php b/app/Import/Transformer/Zoho/InvoiceTransformer.php index faae4fffa2..704c82008e 100644 --- a/app/Import/Transformer/Zoho/InvoiceTransformer.php +++ b/app/Import/Transformer/Zoho/InvoiceTransformer.php @@ -141,7 +141,6 @@ class InvoiceTransformer extends BaseTransformer 'postal_code' => $this->getString($invoice_data, 'Billing Code'), 'country_id' => $this->getCountryId($this->getString($invoice_data, 'Billing Country')), ], - \App\Factory\ClientFactory::create( $this->company->id, $this->company->owner()->id diff --git a/app/Jobs/Bank/MatchBankTransactions.php b/app/Jobs/Bank/MatchBankTransactions.php index 518926734c..b0453fb4c7 100644 --- a/app/Jobs/Bank/MatchBankTransactions.php +++ b/app/Jobs/Bank/MatchBankTransactions.php @@ -182,7 +182,7 @@ class MatchBankTransactions implements ShouldQueue return $this; } - private function coalesceExpenses($expense): string + private function coalesceExpenses($expense): string { if (!$this->bt->expense_id || strlen($this->bt->expense_id) < 1) { diff --git a/app/Jobs/Bank/ProcessBankTransactions.php b/app/Jobs/Bank/ProcessBankTransactions.php index d6b6d8582a..2d621799de 100644 --- a/app/Jobs/Bank/ProcessBankTransactions.php +++ b/app/Jobs/Bank/ProcessBankTransactions.php @@ -11,20 +11,20 @@ namespace App\Jobs\Bank; -use App\Models\Company; +use App\Helpers\Bank\Yodlee\Transformer\AccountTransformer; +use App\Helpers\Bank\Yodlee\Yodlee; use App\Libraries\MultiDB; -use Illuminate\Bus\Queueable; use App\Models\BankIntegration; use App\Models\BankTransaction; -use App\Helpers\Bank\Yodlee\Yodlee; -use Illuminate\Queue\SerializesModels; -use Illuminate\Queue\InteractsWithQueue; +use App\Models\Company; +use App\Notifications\Ninja\GenericNinjaAdminNotification; use App\Services\Bank\BankMatchingService; +use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Events\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; -use App\Notifications\Ninja\GenericNinjaAdminNotification; -use App\Helpers\Bank\Yodlee\Transformer\AccountTransformer; +use Illuminate\Queue\SerializesModels; class ProcessBankTransactions implements ShouldQueue { @@ -116,8 +116,7 @@ class ProcessBankTransactions implements ShouldQueue } } - } - catch(\Exception $e) { + } catch(\Exception $e) { nlog("YODLEE: unable to update account summary for {$this->bank_integration->bank_account_id} => ". $e->getMessage()); } diff --git a/app/Jobs/Client/CheckVat.php b/app/Jobs/Client/CheckVat.php index 49d5f17d2a..a51a6ce1c0 100644 --- a/app/Jobs/Client/CheckVat.php +++ b/app/Jobs/Client/CheckVat.php @@ -11,18 +11,17 @@ namespace App\Jobs\Client; +use App\Libraries\MultiDB; use App\Models\Client; use App\Models\Company; -use App\Libraries\MultiDB; -use Illuminate\Bus\Queueable; -use App\DataProviders\USStates; -use App\Utils\Traits\MakesHash; use App\Services\Tax\TaxService; -use Illuminate\Queue\SerializesModels; -use Illuminate\Queue\InteractsWithQueue; +use App\Utils\Traits\MakesHash; +use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; +use Illuminate\Queue\SerializesModels; class CheckVat implements ShouldQueue { diff --git a/app/Jobs/Client/UpdateTaxData.php b/app/Jobs/Client/UpdateTaxData.php index 89a25bdd86..c27733c066 100644 --- a/app/Jobs/Client/UpdateTaxData.php +++ b/app/Jobs/Client/UpdateTaxData.php @@ -11,18 +11,17 @@ namespace App\Jobs\Client; -use App\DataMapper\Tax\ZipTax\Response; +use App\DataProviders\USStates; +use App\Libraries\MultiDB; use App\Models\Client; use App\Models\Company; -use App\Libraries\MultiDB; -use Illuminate\Bus\Queueable; -use App\DataProviders\USStates; use App\Utils\Traits\MakesHash; -use Illuminate\Queue\SerializesModels; -use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; +use Illuminate\Queue\SerializesModels; class UpdateTaxData implements ShouldQueue { @@ -52,8 +51,9 @@ class UpdateTaxData implements ShouldQueue { MultiDB::setDb($this->company->db); - if($this->company->account->isFreeHostedClient() || $this->client->country_id != 840) + if($this->company->account->isFreeHostedClient() || $this->client->country_id != 840) { return; + } $tax_provider = new \App\Services\Tax\Providers\TaxProvider($this->company, $this->client); @@ -69,7 +69,7 @@ class UpdateTaxData implements ShouldQueue } - }catch(\Exception $e){ + } catch(\Exception $e) { nlog("problem getting tax data => ".$e->getMessage()); } @@ -87,4 +87,4 @@ class UpdateTaxData implements ShouldQueue } -} \ No newline at end of file +} diff --git a/app/Jobs/Company/CompanyExport.php b/app/Jobs/Company/CompanyExport.php index 1a3bfc43b2..b58e20f26a 100644 --- a/app/Jobs/Company/CompanyExport.php +++ b/app/Jobs/Company/CompanyExport.php @@ -11,30 +11,29 @@ namespace App\Jobs\Company; -use App\Models\User; -use App\Utils\Ninja; -use App\Models\Company; -use App\Libraries\MultiDB; -use Illuminate\Support\Str; -use App\Mail\DownloadBackup; -use App\Jobs\Util\UnlinkFile; -use App\Models\VendorContact; -use Illuminate\Bus\Queueable; -use App\Models\QuoteInvitation; -use App\Utils\Traits\MakesHash; -use App\Models\CreditInvitation; use App\Jobs\Mail\NinjaMailerJob; -use App\Models\InvoiceInvitation; -use Illuminate\Support\Facades\App; use App\Jobs\Mail\NinjaMailerObject; -use Illuminate\Support\Facades\Cache; -use Illuminate\Queue\SerializesModels; +use App\Jobs\Util\UnlinkFile; +use App\Libraries\MultiDB; +use App\Mail\DownloadBackup; +use App\Models\Company; +use App\Models\CreditInvitation; +use App\Models\InvoiceInvitation; use App\Models\PurchaseOrderInvitation; -use Illuminate\Support\Facades\Storage; -use Illuminate\Queue\InteractsWithQueue; +use App\Models\QuoteInvitation; use App\Models\RecurringInvoiceInvitation; +use App\Models\User; +use App\Models\VendorContact; +use App\Utils\Ninja; +use App\Utils\Traits\MakesHash; +use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Storage; class CompanyExport implements ShouldQueue { @@ -461,13 +460,15 @@ class CompanyExport implements ShouldQueue Storage::disk(config('filesystems.default'))->put('backups/'.$file_name, file_get_contents($zip_path)); - if(file_exists($zip_path)) - unlink($zip_path); + if(file_exists($zip_path)) { + unlink($zip_path); + } - if(Ninja::isSelfHost()) + if(Ninja::isSelfHost()) { $storage_path = 'backups/'.$file_name; - else + } else { $storage_path = Storage::disk(config('filesystems.default'))->path('backups/'.$file_name); + } $url = Cache::get($this->hash); @@ -492,8 +493,9 @@ class CompanyExport implements ShouldQueue if (Ninja::isHosted()) { sleep(3); - if(file_exists($zip_path)) + if(file_exists($zip_path)) { unlink($zip_path); + } } } } diff --git a/app/Jobs/Company/CompanyTaxRate.php b/app/Jobs/Company/CompanyTaxRate.php index 886704fa35..9bb1eb470b 100644 --- a/app/Jobs/Company/CompanyTaxRate.php +++ b/app/Jobs/Company/CompanyTaxRate.php @@ -11,17 +11,17 @@ namespace App\Jobs\Company; -use App\Models\Company; -use App\Libraries\MultiDB; -use Illuminate\Bus\Queueable; -use App\DataProviders\USStates; -use Illuminate\Queue\SerializesModels; use App\DataMapper\Tax\ZipTax\Response; -use Illuminate\Queue\InteractsWithQueue; +use App\DataProviders\USStates; +use App\Libraries\MultiDB; +use App\Models\Company; use App\Services\Tax\Providers\TaxProvider; +use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; +use Illuminate\Queue\SerializesModels; class CompanyTaxRate implements ShouldQueue { @@ -53,21 +53,21 @@ class CompanyTaxRate implements ShouldQueue /** State must be calculated else default to the company state for taxes */ if(array_key_exists($this->company->settings->state, USStates::get())) { $calculated_state = $this->company->settings->state; - } - else { + } else { - try{ + try { $calculated_state = USStates::getState($this->company->settings->postal_code); - } - catch(\Exception $e){ + } catch(\Exception $e) { nlog("could not calculate state from postal code => {$this->company->settings->postal_code} or from state {$this->company->settings->state}"); } - if(!$calculated_state && $this->company->tax_data?->seller_subregion) + if(!$calculated_state && $this->company->tax_data?->seller_subregion) { $calculated_state = $this->company->tax_data?->seller_subregion; + } - if(!$calculated_state) + if(!$calculated_state) { return; + } } @@ -93,7 +93,8 @@ class CompanyTaxRate implements ShouldQueue return [new WithoutOverlapping($this->company->id)]; } - public function failed($e){ + public function failed($e) + { nlog($e->getMessage()); } -} \ No newline at end of file +} diff --git a/app/Jobs/Company/CreateCompany.php b/app/Jobs/Company/CreateCompany.php index 90bddda492..a9377fa478 100644 --- a/app/Jobs/Company/CreateCompany.php +++ b/app/Jobs/Company/CreateCompany.php @@ -11,16 +11,16 @@ namespace App\Jobs\Company; -use App\Utils\Ninja; +use App\DataMapper\ClientRegistrationFields; +use App\DataMapper\CompanySettings; +use App\DataMapper\Tax\TaxModel; +use App\Factory\TaxRateFactory; +use App\Libraries\MultiDB; use App\Models\Company; use App\Models\Country; -use App\Libraries\MultiDB; +use App\Utils\Ninja; use App\Utils\Traits\MakesHash; -use App\DataMapper\Tax\TaxModel; -use App\DataMapper\CompanySettings; use Illuminate\Foundation\Bus\Dispatchable; -use App\DataMapper\ClientRegistrationFields; -use App\Factory\TaxRateFactory; class CreateCompany { @@ -55,7 +55,7 @@ class CreateCompany $settings->name = isset($this->request['name']) ? $this->request['name'] : ''; - if($country_id = $this->resolveCountry()){ + if($country_id = $this->resolveCountry()) { $settings->country_id = $country_id; } @@ -95,33 +95,34 @@ class CreateCompany * * @return string */ - private function resolveCountry(): string + private function resolveCountry(): string { - try{ + try { $ip = request()->ip(); - if(request()->hasHeader('cf-ipcountry')){ + if(request()->hasHeader('cf-ipcountry')) { $c = Country::where('iso_3166_2', request()->header('cf-ipcountry'))->first(); - if($c) + if($c) { return (string)$c->id; + } } $details = json_decode(file_get_contents("http://ip-api.com/json/{$ip}")); - if($details && property_exists($details, 'countryCode')){ + if($details && property_exists($details, 'countryCode')) { $c = Country::where('iso_3166_2', $details->countryCode)->first(); - if($c) + if($c) { return (string)$c->id; + } } - } - catch(\Exception $e){ + } catch(\Exception $e) { nlog("Could not resolve country => {$e->getMessage()}"); } @@ -163,8 +164,7 @@ class CreateCompany return $company; - } - catch(\Exception $e){ + } catch(\Exception $e) { nlog("SETUP: could not complete setup for Spanish Locale"); } @@ -206,8 +206,7 @@ class CreateCompany return $company; - } - catch(\Exception $e){ + } catch(\Exception $e) { nlog($e->getMessage()); nlog("SETUP: could not complete setup for Australian Locale"); } diff --git a/app/Jobs/Credit/ZipCredits.php b/app/Jobs/Credit/ZipCredits.php index 2239fad4f8..88a2d66d51 100644 --- a/app/Jobs/Credit/ZipCredits.php +++ b/app/Jobs/Credit/ZipCredits.php @@ -11,7 +11,6 @@ namespace App\Jobs\Credit; -use App\Jobs\Entity\CreateEntityPdf; use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; use App\Jobs\Util\UnlinkFile; @@ -31,8 +30,6 @@ class ZipCredits implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public $settings; - public $tries = 1; public function __construct(protected array $credit_ids, protected Company $company, protected User $user) @@ -48,7 +45,7 @@ class ZipCredits implements ShouldQueue { MultiDB::setDb($this->company->db); - $this->settings = $this->company->settings; + $settings = $this->company->settings; $zipFile = new \PhpZip\ZipFile(); $file_name = now()->addSeconds($this->company->timezone_offset())->format('Y-m-d-h-m-s').'_'.str_replace(' ', '_', trans('texts.credits')).'.zip'; @@ -58,7 +55,7 @@ class ZipCredits implements ShouldQueue try { foreach ($invitations as $invitation) { - $file = (new \App\Jobs\Entity\CreateRawPdf($invitation, $this->company->db))->handle(); + $file = (new \App\Jobs\Entity\CreateRawPdf($invitation))->handle(); $zipFile->addFromString($invitation->credit->numberFormatter() . '.pdf', $file); } @@ -67,7 +64,7 @@ class ZipCredits implements ShouldQueue $nmo = new NinjaMailerObject; $nmo->mailable = new DownloadCredits(Storage::url($path.$file_name), $this->company); $nmo->to_user = $this->user; - $nmo->settings = $this->settings; + $nmo->settings = $settings; $nmo->company = $this->company; NinjaMailerJob::dispatch($nmo); diff --git a/app/Jobs/Cron/AutoBill.php b/app/Jobs/Cron/AutoBill.php index 5376571d7c..cc48b91423 100644 --- a/app/Jobs/Cron/AutoBill.php +++ b/app/Jobs/Cron/AutoBill.php @@ -11,14 +11,14 @@ namespace App\Jobs\Cron; -use App\Models\Invoice; -use App\Libraries\MultiDB; -use Illuminate\Bus\Queueable; use App\Jobs\Entity\EmailEntity; -use Illuminate\Queue\SerializesModels; -use Illuminate\Queue\InteractsWithQueue; +use App\Libraries\MultiDB; +use App\Models\Invoice; +use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\SerializesModels; class AutoBill implements ShouldQueue { @@ -60,8 +60,7 @@ class AutoBill implements ShouldQueue } catch (\Exception $e) { nlog("Failed to capture payment for {$this->invoice_id} ->".$e->getMessage()); - if($this->send_email_on_failure && $invoice) - { + if($this->send_email_on_failure && $invoice) { $invoice->invitations->each(function ($invitation) use ($invoice) { if ($invitation->contact && ! $invitation->contact->trashed() && strlen($invitation->contact->email) >= 1 && $invoice->client->getSetting('auto_email_invoice')) { diff --git a/app/Jobs/Cron/AutoBillCron.php b/app/Jobs/Cron/AutoBillCron.php index cf2c5f987f..ff3c2c2955 100644 --- a/app/Jobs/Cron/AutoBillCron.php +++ b/app/Jobs/Cron/AutoBillCron.php @@ -11,11 +11,11 @@ namespace App\Jobs\Cron; -use App\Models\Invoice; use App\Libraries\MultiDB; +use App\Models\Invoice; +use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Auth; -use Illuminate\Foundation\Bus\Dispatchable; class AutoBillCron { diff --git a/app/Jobs/Cron/RecurringExpensesCron.php b/app/Jobs/Cron/RecurringExpensesCron.php index 55eee07e5b..3b38aafb0c 100644 --- a/app/Jobs/Cron/RecurringExpensesCron.php +++ b/app/Jobs/Cron/RecurringExpensesCron.php @@ -11,16 +11,16 @@ namespace App\Jobs\Cron; -use App\Utils\Ninja; +use App\Events\Expense\ExpenseWasCreated; +use App\Factory\RecurringExpenseToExpenseFactory; use App\Libraries\MultiDB; -use Illuminate\Support\Carbon; use App\Models\RecurringExpense; use App\Models\RecurringInvoice; -use Illuminate\Support\Facades\Auth; +use App\Utils\Ninja; use App\Utils\Traits\GeneratesCounter; -use App\Events\Expense\ExpenseWasCreated; use Illuminate\Foundation\Bus\Dispatchable; -use App\Factory\RecurringExpenseToExpenseFactory; +use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Auth; class RecurringExpensesCron { @@ -105,8 +105,9 @@ class RecurringExpensesCron $expense = RecurringExpenseToExpenseFactory::create($recurring_expense); $expense->saveQuietly(); - if($expense->company->mark_expenses_paid) + if($expense->company->mark_expenses_paid) { $expense->payment_date = now()->format('Y-m-d'); + } $expense->number = $this->getNextExpenseNumber($expense); $expense->saveQuietly(); diff --git a/app/Jobs/Cron/RecurringInvoicesCron.php b/app/Jobs/Cron/RecurringInvoicesCron.php index edd57acf26..895a172a79 100644 --- a/app/Jobs/Cron/RecurringInvoicesCron.php +++ b/app/Jobs/Cron/RecurringInvoicesCron.php @@ -11,13 +11,13 @@ namespace App\Jobs\Cron; -use App\Models\Invoice; -use App\Libraries\MultiDB; -use Illuminate\Support\Carbon; -use App\Models\RecurringInvoice; -use Illuminate\Support\Facades\Auth; -use Illuminate\Foundation\Bus\Dispatchable; use App\Jobs\RecurringInvoice\SendRecurring; +use App\Libraries\MultiDB; +use App\Models\Invoice; +use App\Models\RecurringInvoice; +use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Auth; class RecurringInvoicesCron { diff --git a/app/Jobs/Cron/SubscriptionCron.php b/app/Jobs/Cron/SubscriptionCron.php index c77a627695..efa1da9c24 100644 --- a/app/Jobs/Cron/SubscriptionCron.php +++ b/app/Jobs/Cron/SubscriptionCron.php @@ -11,11 +11,11 @@ namespace App\Jobs\Cron; -use App\Models\Invoice; use App\Libraries\MultiDB; -use Illuminate\Support\Facades\Auth; +use App\Models\Invoice; use App\Utils\Traits\SubscriptionHooker; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Support\Facades\Auth; class SubscriptionCron { diff --git a/app/Jobs/Cron/UpdateCalculatedFields.php b/app/Jobs/Cron/UpdateCalculatedFields.php index 865d3ed123..77a629fce3 100644 --- a/app/Jobs/Cron/UpdateCalculatedFields.php +++ b/app/Jobs/Cron/UpdateCalculatedFields.php @@ -11,11 +11,10 @@ namespace App\Jobs\Cron; -use App\Models\Payment; -use App\Models\Project; use App\Libraries\MultiDB; -use Illuminate\Support\Facades\Auth; +use App\Models\Project; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Support\Facades\Auth; class UpdateCalculatedFields { @@ -43,15 +42,15 @@ class UpdateCalculatedFields if (! config('ninja.db.multi_db_enabled')) { - Project::query()->with('tasks')->whereHas('tasks', function ($query){ - $query->where('updated_at', '>', now()->subHours(2)); + Project::query()->with('tasks')->whereHas('tasks', function ($query) { + $query->where('updated_at', '>', now()->subHours(2)); }) ->cursor() ->each(function ($project) { $project->current_hours = $this->calculateDuration($project); $project->save(); - }); + }); @@ -61,14 +60,14 @@ class UpdateCalculatedFields MultiDB::setDB($db); - Project::query()->with('tasks')->whereHas('tasks', function ($query){ + Project::query()->with('tasks')->whereHas('tasks', function ($query) { $query->where('updated_at', '>', now()->subHours(2)); - }) - ->cursor() - ->each(function ($project) { - $project->current_hours = $this->calculateDuration($project); - $project->save(); - }); + }) + ->cursor() + ->each(function ($project) { + $project->current_hours = $this->calculateDuration($project); + $project->save(); + }); } } @@ -80,16 +79,17 @@ class UpdateCalculatedFields $project->tasks->each(function ($task) use (&$duration) { - if(is_iterable($task->time_log)) { + if(is_iterable(json_decode($task->time_log) )) { + foreach(json_decode($task->time_log) as $log) { - $start_time = $log[0]; - $end_time = $log[1] == 0 ? time() : $log[1]; + $start_time = $log[0]; + $end_time = $log[1] == 0 ? time() : $log[1]; - $duration += $end_time - $start_time; + $duration += $end_time - $start_time; + } } - } }); @@ -97,7 +97,3 @@ class UpdateCalculatedFields } } - - - - diff --git a/app/Jobs/Entity/CreateEntityPdf.php b/app/Jobs/Entity/CreateEntityPdf.php deleted file mode 100644 index 7ef89d2c3e..0000000000 --- a/app/Jobs/Entity/CreateEntityPdf.php +++ /dev/null @@ -1,294 +0,0 @@ -invitation = $invitation; - - if ($invitation instanceof InvoiceInvitation) { - $this->entity = $invitation->invoice; - $this->entity_string = 'invoice'; - } elseif ($invitation instanceof QuoteInvitation) { - $this->entity = $invitation->quote; - $this->entity_string = 'quote'; - } elseif ($invitation instanceof CreditInvitation) { - $this->entity = $invitation->credit; - $this->entity_string = 'credit'; - } elseif ($invitation instanceof RecurringInvoiceInvitation) { - $this->entity = $invitation->recurring_invoice; - $this->entity_string = 'recurring_invoice'; - } - - $this->company = $invitation->company; - - $this->contact = $invitation->contact; - - $this->client = $invitation->contact->client; - $this->client->load('company'); - - $this->disk = $disk ?? config('filesystems.default'); - } - - public function handle() - { - MultiDB::setDb($this->company->db); - - /* Forget the singleton*/ - App::forgetInstance('translator'); - - /* Init a new copy of the translator*/ - $t = app('translator'); - /* Set the locale*/ - App::setLocale($this->client->locale()); - - /* Set customized translations _NOW_ */ - $t->replace(Ninja::transformTranslations($this->client->getMergedSettings())); - - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - return (new Phantom)->generate($this->invitation); - } - - $entity_design_id = ''; - $path = ''; - - if ($this->entity instanceof Invoice) { - $path = $this->client->invoice_filepath($this->invitation); - $entity_design_id = 'invoice_design_id'; - } elseif ($this->entity instanceof Quote) { - $path = $this->client->quote_filepath($this->invitation); - $entity_design_id = 'quote_design_id'; - } elseif ($this->entity instanceof Credit) { - $path = $this->client->credit_filepath($this->invitation); - $entity_design_id = 'credit_design_id'; - } elseif ($this->entity instanceof RecurringInvoice) { - $path = $this->client->recurring_invoice_filepath($this->invitation); - $entity_design_id = 'invoice_design_id'; - } - - $file_path = $path.$this->entity->numberFormatter().'.pdf'; - - $entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey($this->client->getSetting($entity_design_id)); - - /** @var \App\Models\Design $design */ - $design = Design::withTrashed()->find($entity_design_id); - - /* Catch all in case migration doesn't pass back a valid design */ - if (! $design) { - $design = Design::find(2); - } - - $html = new HtmlEngine($this->invitation); - - if ($design->is_custom) { - $options = [ - 'custom_partials' => json_decode(json_encode($design->design), true), - ]; - $template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options); - } else { - $template = new PdfMakerDesign(strtolower($design->name)); - } - - $variables = $html->generateLabelsAndValues(); - - $state = [ - 'template' => $template->elements([ - 'client' => $this->client, - 'entity' => $this->entity, - 'pdf_variables' => (array) $this->company->settings->pdf_variables, - '$product' => $design->design->product, - 'variables' => $variables, - ]), - 'variables' => $variables, - 'options' => [ - 'all_pages_header' => $this->entity->client->getSetting('all_pages_header'), - 'all_pages_footer' => $this->entity->client->getSetting('all_pages_footer'), - ], - 'process_markdown' => $this->entity->client->company->markdown_enabled, - ]; - - $maker = new PdfMakerService($state); - - $maker - ->design($template) - ->build(); - - $pdf = null; - - try { - if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - - $numbered_pdf = $this->pageNumbering($pdf, $this->company); - - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } - } else { - $pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true)); - - $numbered_pdf = $this->pageNumbering($pdf, $this->company); - - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } - } - } catch (\Exception $e) { - nlog(print_r($e->getMessage(), 1)); - } - - if (config('ninja.log_pdf_html')) { - info($maker->getCompiledHTML()); - } - - if($this->entity_string == "invoice" && $this->client->getSetting('enable_e_invoice')) - { - $pdf = $this->checkEInvoice($pdf); - } - - if ($pdf) { - try { - Storage::disk($this->disk)->put($file_path, $pdf); - } catch (\Exception $e) { - throw new FilePermissionsFailure($e->getMessage()); - } - } - - $this->invitation = null; - // $this->entity = null; - $this->company = null; - $this->client = null; - $this->contact = null; - $maker = null; - $state = null; - - return $file_path; - } - - /** - * Switch to determine if we need to embed the xml into the PDF itself - * - * @param string $pdf - * @return string - */ - private function checkEInvoice(string $pdf): string - { - if(!$this->entity instanceof Invoice) - return $pdf; - - $e_invoice_type = $this->entity->client->getSetting('e_invoice_type'); - - switch ($e_invoice_type) { - case "EN16931": - case "XInvoice_2_2": - case "XInvoice_2_1": - case "XInvoice_2_0": - case "XInvoice_1_0": - case "XInvoice-Extended": - case "XInvoice-BasicWL": - case "XInvoice-Basic": - return $this->embedEInvoiceZuGFerD($pdf) ?? $pdf; - //case "Facturae_3.2": - //case "Facturae_3.2.1": - //case "Facturae_3.2.2": - // - default: - return $pdf; - } - - } - - /** - * Embed the .xml file into the PDF - * - * @param string $pdf - * @return string - */ - private function embedEInvoiceZuGFerD(string $pdf): string - { - try { - - $e_rechnung = (new CreateEInvoice($this->entity, true))->handle(); - $pdfBuilder = new ZugferdDocumentPdfBuilder($e_rechnung, $pdf); - $pdfBuilder->generateDocument(); - return $pdfBuilder->downloadString(basename($this->entity->getFileName())); - - } catch (\Exception $e) { - nlog("E_Invoice Merge failed - " . $e->getMessage()); - } - - return $pdf; - } - - - public function failed($e) - { - } -} diff --git a/app/Jobs/Entity/CreateRawPdf.php b/app/Jobs/Entity/CreateRawPdf.php index 593566577b..70c5754f92 100644 --- a/app/Jobs/Entity/CreateRawPdf.php +++ b/app/Jobs/Entity/CreateRawPdf.php @@ -17,12 +17,12 @@ use App\Models\Credit; use App\Models\Design; use App\Models\Invoice; use App\Utils\HtmlEngine; -use App\Libraries\MultiDB; -use Illuminate\Bus\Queueable; +use App\Models\PurchaseOrder; use App\Models\QuoteInvitation; use App\Utils\Traits\MakesHash; use App\Models\CreditInvitation; use App\Models\RecurringInvoice; +use App\Services\Pdf\PdfService; use App\Utils\PhantomJS\Phantom; use App\Models\InvoiceInvitation; use App\Utils\HostedPDF\NinjaPdf; @@ -31,23 +31,20 @@ use Illuminate\Support\Facades\App; use App\Jobs\Invoice\CreateEInvoice; use App\Utils\Traits\NumberFormatter; use App\Utils\Traits\MakesInvoiceHtml; -use Illuminate\Queue\SerializesModels; +use App\Models\PurchaseOrderInvitation; use App\Utils\Traits\Pdf\PageNumbering; -use Illuminate\Queue\InteractsWithQueue; use App\Exceptions\FilePermissionsFailure; use App\Models\RecurringInvoiceInvitation; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Foundation\Bus\Dispatchable; use horstoeko\zugferd\ZugferdDocumentPdfBuilder; use App\Services\PdfMaker\Design as PdfDesignModel; use App\Services\PdfMaker\Design as PdfMakerDesign; use App\Services\PdfMaker\PdfMaker as PdfMakerService; -class CreateRawPdf implements ShouldQueue +class CreateRawPdf { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash, PageNumbering; + use NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash, PageNumbering; - public Invoice | Credit | Quote | RecurringInvoice $entity; + public Invoice | Credit | Quote | RecurringInvoice | PurchaseOrder $entity; public $company; @@ -58,13 +55,10 @@ class CreateRawPdf implements ShouldQueue public $entity_string = ''; /** - * Create a new job instance. - * * @param $invitation */ - public function __construct($invitation, $db) + public function __construct($invitation, private ?string $type = null) { - MultiDB::setDb($db); $this->invitation = $invitation; @@ -80,15 +74,47 @@ class CreateRawPdf implements ShouldQueue } elseif ($invitation instanceof RecurringInvoiceInvitation) { $this->entity = $invitation->recurring_invoice; $this->entity_string = 'recurring_invoice'; + } elseif ($invitation instanceof PurchaseOrderInvitation){ + $this->entity = $invitation->purchase_order; + $this->entity_string = 'purchase_order'; } $this->company = $invitation->company; - $this->contact = $invitation->contact; } + private function resolveType(): string + { + if($this->type) + return $this->type; + + $type = 'product'; + + match($this->entity_string) { + 'purchase_order' => $type = 'purchase_order', + 'invoice' => $type = 'product', + 'quote' => $type = 'product', + 'credit' => $type = 'product', + 'recurring_invoice' => $type = 'product', + }; + + return $type; + + } + public function handle() { + /** Testing this override to improve PDF generation performance */ + $ps = new PdfService($this->invitation, $this->resolveType(), [ + 'client' => $this->entity->client ?? false, + 'vendor' => $this->entity->vendor ?? false, + "{$this->entity_string}s" => [$this->entity], + ]); + + $pdf = $ps->boot()->getPdf(); + return $pdf; + nlog("pdf timer = ". $ps->execution_time); + /* Forget the singleton*/ App::forgetInstance('translator'); @@ -157,6 +183,9 @@ class CreateRawPdf implements ShouldQueue 'options' => [ 'all_pages_header' => $this->entity->client->getSetting('all_pages_header'), 'all_pages_footer' => $this->entity->client->getSetting('all_pages_footer'), + 'client' => $this->entity->client, + 'entity' => $this->entity, + 'variables' => $variables, ], 'process_markdown' => $this->entity->client->company->markdown_enabled, ]; @@ -220,8 +249,9 @@ class CreateRawPdf implements ShouldQueue */ private function checkEInvoice(string $pdf): string { - if(!$this->entity instanceof Invoice || !$this->company->getSetting('enable_e_invoice')) + if(!$this->entity instanceof Invoice || !$this->company->getSetting('enable_e_invoice')) { return $pdf; + } $e_invoice_type = $this->entity->client->getSetting('e_invoice_type'); diff --git a/app/Jobs/Entity/EmailEntity.php b/app/Jobs/Entity/EmailEntity.php index 41473f1b8f..f25d101e16 100644 --- a/app/Jobs/Entity/EmailEntity.php +++ b/app/Jobs/Entity/EmailEntity.php @@ -11,7 +11,6 @@ namespace App\Jobs\Entity; -use App\Events\Invoice\InvoiceWasEmailedAndFailed; use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; use App\Libraries\MultiDB; diff --git a/app/Jobs/Invoice/CheckGatewayFee.php b/app/Jobs/Invoice/CheckGatewayFee.php index e7a9784270..3f79e41bcf 100644 --- a/app/Jobs/Invoice/CheckGatewayFee.php +++ b/app/Jobs/Invoice/CheckGatewayFee.php @@ -11,14 +11,14 @@ namespace App\Jobs\Invoice; -use App\Models\Invoice; use App\Libraries\MultiDB; +use App\Models\Invoice; use Illuminate\Bus\Queueable; -use Illuminate\Queue\SerializesModels; -use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; +use Illuminate\Queue\SerializesModels; class CheckGatewayFee implements ShouldQueue { diff --git a/app/Jobs/Invoice/CreateEInvoice.php b/app/Jobs/Invoice/CreateEInvoice.php index c68662c4b1..b3f64776ea 100644 --- a/app/Jobs/Invoice/CreateEInvoice.php +++ b/app/Jobs/Invoice/CreateEInvoice.php @@ -12,17 +12,17 @@ namespace App\Jobs\Invoice; -use App\Utils\Ninja; use App\Models\Invoice; -use horstoeko\zugferd\ZugferdDocumentBuilder; -use Illuminate\Bus\Queueable; -use Illuminate\Support\Facades\App; -use Illuminate\Queue\SerializesModels; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Foundation\Bus\Dispatchable; use App\Services\Invoice\EInvoice\FacturaEInvoice; use App\Services\Invoice\EInvoice\ZugferdEInvoice; +use App\Utils\Ninja; +use horstoeko\zugferd\ZugferdDocumentBuilder; +use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\App; class CreateEInvoice implements ShouldQueue { diff --git a/app/Jobs/Invoice/InjectSignature.php b/app/Jobs/Invoice/InjectSignature.php index b296b43e6f..7f161b6531 100644 --- a/app/Jobs/Invoice/InjectSignature.php +++ b/app/Jobs/Invoice/InjectSignature.php @@ -4,10 +4,10 @@ namespace App\Jobs\Invoice; use App\Models\PurchaseOrder; use Illuminate\Bus\Queueable; -use Illuminate\Queue\SerializesModels; -use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\SerializesModels; class InjectSignature implements ShouldQueue { @@ -34,19 +34,20 @@ class InjectSignature implements ShouldQueue { $invitation = false; - if($this->entity instanceof PurchaseOrder){ + if($this->entity instanceof PurchaseOrder) { $invitation = $this->entity->invitations()->where('vendor_contact_id', $this->contact_id)->first(); - if(!$invitation) + if(!$invitation) { $invitation = $this->entity->invitations->first(); + } - } - else { + } else { $invitation = $this->entity->invitations()->where('client_contact_id', $this->contact_id)->first(); - if(!$invitation) + if(!$invitation) { $invitation = $this->entity->invitations->first(); + } } if (! $invitation) { diff --git a/app/Jobs/Invoice/InvoiceWorkflowSettings.php b/app/Jobs/Invoice/InvoiceWorkflowSettings.php index 615b43ad92..9994971089 100644 --- a/app/Jobs/Invoice/InvoiceWorkflowSettings.php +++ b/app/Jobs/Invoice/InvoiceWorkflowSettings.php @@ -12,7 +12,6 @@ namespace App\Jobs\Invoice; -use App\Models\Client; use App\Models\Invoice; use App\Repositories\BaseRepository; use Illuminate\Bus\Queueable; diff --git a/app/Jobs/Invoice/ZipInvoices.php b/app/Jobs/Invoice/ZipInvoices.php index f7292988b0..82ddade105 100644 --- a/app/Jobs/Invoice/ZipInvoices.php +++ b/app/Jobs/Invoice/ZipInvoices.php @@ -11,7 +11,6 @@ namespace App\Jobs\Invoice; -use App\Jobs\Entity\CreateEntityPdf; use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; use App\Jobs\Util\UnlinkFile; @@ -30,14 +29,6 @@ class ZipInvoices implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public $invoices; - - private $company; - - private $user; - - public $settings; - public $tries = 1; /** @@ -47,15 +38,8 @@ class ZipInvoices implements ShouldQueue * @deprecated confirm to be deleted * Create a new job instance. */ - public function __construct($invoices, Company $company, User $user) + public function __construct(public mixed $invoices, public Company $company, public User $user) { - $this->invoices = $invoices; - - $this->company = $company; - - $this->user = $user; - - $this->settings = $company->settings; } /** @@ -66,6 +50,7 @@ class ZipInvoices implements ShouldQueue public function handle(): void { MultiDB::setDb($this->company->db); + $settings = $this->company->settings; // create new zip object $zipFile = new \PhpZip\ZipFile(); @@ -93,7 +78,7 @@ class ZipInvoices implements ShouldQueue $nmo = new NinjaMailerObject; $nmo->mailable = new DownloadInvoices(Storage::url($path.$file_name), $this->company); $nmo->to_user = $this->user; - $nmo->settings = $this->settings; + $nmo->settings = $settings; $nmo->company = $this->company; NinjaMailerJob::dispatch($nmo); diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index eed243b27d..9fc9acf136 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -492,7 +492,7 @@ class NinjaMailerJob implements ShouldQueue */ private function preFlightChecksFail(): bool { - /* Always send regardless */ + /* Always send regardless */ if($this->override) { return false; } diff --git a/app/Jobs/Ninja/CheckACHStatus.php b/app/Jobs/Ninja/CheckACHStatus.php index 371681cf73..32cf8bd789 100644 --- a/app/Jobs/Ninja/CheckACHStatus.php +++ b/app/Jobs/Ninja/CheckACHStatus.php @@ -11,14 +11,14 @@ namespace App\Jobs\Ninja; -use App\Models\Payment; use App\Libraries\MultiDB; -use Illuminate\Bus\Queueable; use App\Models\ClientGatewayToken; -use Illuminate\Queue\SerializesModels; -use Illuminate\Queue\InteractsWithQueue; +use App\Models\Payment; +use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\SerializesModels; class CheckACHStatus implements ShouldQueue { @@ -77,7 +77,7 @@ class CheckACHStatus implements ShouldQueue }); Payment::where('status_id', 1) - ->whereHas('company_gateway', function ($q){ + ->whereHas('company_gateway', function ($q) { $q->whereIn('gateway_key', ['d14dd26a47cecc30fdd65700bfb67b34', 'd14dd26a37cecc30fdd65700bfb55b23']); }) ->cursor() @@ -124,4 +124,4 @@ class CheckACHStatus implements ShouldQueue } } -} \ No newline at end of file +} diff --git a/app/Jobs/Ninja/CheckCompanyData.php b/app/Jobs/Ninja/CheckCompanyData.php index 9899bdff77..c26827ad99 100644 --- a/app/Jobs/Ninja/CheckCompanyData.php +++ b/app/Jobs/Ninja/CheckCompanyData.php @@ -11,7 +11,6 @@ namespace App\Jobs\Ninja; -use App\Models\ClientContact; use App\Models\Company; use App\Models\CompanyLedger; use App\Models\Credit; diff --git a/app/Jobs/Ninja/TaskScheduler.php b/app/Jobs/Ninja/TaskScheduler.php index 307ffb07bf..dfb7616993 100644 --- a/app/Jobs/Ninja/TaskScheduler.php +++ b/app/Jobs/Ninja/TaskScheduler.php @@ -11,14 +11,14 @@ namespace App\Jobs\Ninja; -use App\Models\Scheduler; use App\Libraries\MultiDB; +use App\Models\Scheduler; use Illuminate\Bus\Queueable; -use Illuminate\Support\Facades\Auth; -use Illuminate\Queue\SerializesModels; -use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Auth; //@rebuild it class TaskScheduler implements ShouldQueue diff --git a/app/Jobs/Payment/EmailPayment.php b/app/Jobs/Payment/EmailPayment.php index dd6a7f129a..f8729f707e 100644 --- a/app/Jobs/Payment/EmailPayment.php +++ b/app/Jobs/Payment/EmailPayment.php @@ -31,14 +31,8 @@ class EmailPayment implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public $payment; - public $email_builder; - private $contact; - - private $company; - public $settings; /** @@ -49,11 +43,8 @@ class EmailPayment implements ShouldQueue * @param $contact * @param $company */ - public function __construct(Payment $payment, Company $company, ?ClientContact $contact) + public function __construct(public Payment $payment, private Company $company, private ?ClientContact $contact) { - $this->payment = $payment; - $this->contact = $contact; - $this->company = $company; $this->settings = $payment->client->getMergedSettings(); } @@ -87,14 +78,15 @@ class EmailPayment implements ShouldQueue if ($this->payment->invoices && $this->payment->invoices->count() >= 1) { - if($this->contact){ + if($this->contact) { $invitation = $this->payment->invoices->first()->invitations()->where('client_contact_id', $this->contact->id)->first(); - } - else + } else { $invitation = $this->payment->invoices->first()->invitations()->first(); + } - if($invitation) + if($invitation) { $nmo->invitation = $invitation; + } } diff --git a/app/Jobs/Payment/EmailRefundPayment.php b/app/Jobs/Payment/EmailRefundPayment.php index bbc062f791..1ec374ab1b 100644 --- a/app/Jobs/Payment/EmailRefundPayment.php +++ b/app/Jobs/Payment/EmailRefundPayment.php @@ -32,14 +32,8 @@ class EmailRefundPayment implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public $payment; - public $email_builder; - private $contact; - - private $company; - public $settings; /** @@ -50,11 +44,8 @@ class EmailRefundPayment implements ShouldQueue * @param $contact * @param $company */ - public function __construct(Payment $payment, Company $company, ClientContact $contact) + public function __construct(public Payment $payment, private Company $company, private ?ClientContact $contact) { - $this->payment = $payment; - $this->contact = $contact; - $this->company = $company; $this->settings = $payment->client->getMergedSettings(); } @@ -84,7 +75,9 @@ class EmailRefundPayment implements ShouldQueue $template_data['body'] = ctrans('texts.refunded_payment').' $payment.refunded

$invoices'; $template_data['subject'] = ctrans('texts.refunded_payment'); - $email_builder = (new PaymentEmailEngine($this->payment, $this->contact, $template_data))->build(); + $email_builder = new PaymentEmailEngine($this->payment, $this->contact, $template_data); + $email_builder->is_refund = true; + $email_builder->build(); $invitation = null; @@ -98,8 +91,9 @@ class EmailRefundPayment implements ShouldQueue $invitation = $this->payment->invoices->first()->invitations()->first(); } - if($invitation) + if($invitation) { $nmo->invitation = $invitation; + } } diff --git a/app/Jobs/PostMark/ProcessPostmarkWebhook.php b/app/Jobs/PostMark/ProcessPostmarkWebhook.php index 25f2d57c8c..e2eae3bb8a 100644 --- a/app/Jobs/PostMark/ProcessPostmarkWebhook.php +++ b/app/Jobs/PostMark/ProcessPostmarkWebhook.php @@ -11,24 +11,24 @@ namespace App\Jobs\PostMark; -use App\Models\SystemLog; -use App\Libraries\MultiDB; -use Postmark\PostmarkClient; -use Illuminate\Bus\Queueable; +use App\DataMapper\Analytics\Mail\EmailBounce; +use App\DataMapper\Analytics\Mail\EmailSpam; use App\Jobs\Util\SystemLogger; -use App\Models\QuoteInvitation; +use App\Libraries\MultiDB; use App\Models\CreditInvitation; use App\Models\InvoiceInvitation; -use Illuminate\Queue\SerializesModels; -use Turbo124\Beacon\Facades\LightLogs; use App\Models\PurchaseOrderInvitation; -use Illuminate\Queue\InteractsWithQueue; +use App\Models\QuoteInvitation; use App\Models\RecurringInvoiceInvitation; +use App\Models\SystemLog; +use App\Notifications\Ninja\EmailSpamNotification; +use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; -use App\DataMapper\Analytics\Mail\EmailSpam; -use App\DataMapper\Analytics\Mail\EmailBounce; -use App\Notifications\Ninja\EmailSpamNotification; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\SerializesModels; +use Postmark\PostmarkClient; +use Turbo124\Beacon\Facades\LightLogs; class ProcessPostmarkWebhook implements ShouldQueue { @@ -62,7 +62,7 @@ class ProcessPostmarkWebhook implements ShouldQueue ->where('company_id', $this->invitation->company_id) ->where('type_id', SystemLog::TYPE_WEBHOOK_RESPONSE) ->whereJsonContains('log', ['MessageID' => $message_id]) - ->orderBy('id','desc') + ->orderBy('id', 'desc') ->first(); } @@ -108,42 +108,42 @@ class ProcessPostmarkWebhook implements ShouldQueue } } -// { -// "Metadata": { -// "example": "value", -// "example_2": "value" -// }, -// "RecordType": "Open", -// "FirstOpen": true, -// "Client": { -// "Name": "Chrome 35.0.1916.153", -// "Company": "Google", -// "Family": "Chrome" -// }, -// "OS": { -// "Name": "OS X 10.7 Lion", -// "Company": "Apple Computer, Inc.", -// "Family": "OS X 10" -// }, -// "Platform": "WebMail", -// "UserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36", -// "ReadSeconds": 5, -// "Geo": { -// "CountryISOCode": "RS", -// "Country": "Serbia", -// "RegionISOCode": "VO", -// "Region": "Autonomna Pokrajina Vojvodina", -// "City": "Novi Sad", -// "Zip": "21000", -// "Coords": "45.2517,19.8369", -// "IP": "188.2.95.4" -// }, -// "MessageID": "00000000-0000-0000-0000-000000000000", -// "MessageStream": "outbound", -// "ReceivedAt": "2022-02-06T06:37:48Z", -// "Tag": "welcome-email", -// "Recipient": "john@example.com" -// } + // { + // "Metadata": { + // "example": "value", + // "example_2": "value" + // }, + // "RecordType": "Open", + // "FirstOpen": true, + // "Client": { + // "Name": "Chrome 35.0.1916.153", + // "Company": "Google", + // "Family": "Chrome" + // }, + // "OS": { + // "Name": "OS X 10.7 Lion", + // "Company": "Apple Computer, Inc.", + // "Family": "OS X 10" + // }, + // "Platform": "WebMail", + // "UserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36", + // "ReadSeconds": 5, + // "Geo": { + // "CountryISOCode": "RS", + // "Country": "Serbia", + // "RegionISOCode": "VO", + // "Region": "Autonomna Pokrajina Vojvodina", + // "City": "Novi Sad", + // "Zip": "21000", + // "Coords": "45.2517,19.8369", + // "IP": "188.2.95.4" + // }, + // "MessageID": "00000000-0000-0000-0000-000000000000", + // "MessageStream": "outbound", + // "ReceivedAt": "2022-02-06T06:37:48Z", + // "Tag": "welcome-email", + // "Recipient": "john@example.com" + // } private function processOpen() { @@ -154,7 +154,7 @@ class ProcessPostmarkWebhook implements ShouldQueue $sl = $this->getSystemLog($this->request['MessageID']); - if($sl){ + if($sl) { $this->updateSystemLog($sl, $data); return; } @@ -169,20 +169,20 @@ class ProcessPostmarkWebhook implements ShouldQueue ))->handle(); } -// { -// "RecordType": "Delivery", -// "ServerID": 23, -// "MessageStream": "outbound", -// "MessageID": "00000000-0000-0000-0000-000000000000", -// "Recipient": "john@example.com", -// "Tag": "welcome-email", -// "DeliveredAt": "2021-02-21T16:34:52Z", -// "Details": "Test delivery webhook details", -// "Metadata": { -// "example": "value", -// "example_2": "value" -// } -// } + // { + // "RecordType": "Delivery", + // "ServerID": 23, + // "MessageStream": "outbound", + // "MessageID": "00000000-0000-0000-0000-000000000000", + // "Recipient": "john@example.com", + // "Tag": "welcome-email", + // "DeliveredAt": "2021-02-21T16:34:52Z", + // "Details": "Test delivery webhook details", + // "Metadata": { + // "example": "value", + // "example_2": "value" + // } + // } private function processDelivery() { $this->invitation->email_status = 'delivered'; @@ -207,31 +207,31 @@ class ProcessPostmarkWebhook implements ShouldQueue ))->handle(); } -// { -// "Metadata": { -// "example": "value", -// "example_2": "value" -// }, -// "RecordType": "Bounce", -// "ID": 42, -// "Type": "HardBounce", -// "TypeCode": 1, -// "Name": "Hard bounce", -// "Tag": "Test", -// "MessageID": "00000000-0000-0000-0000-000000000000", -// "ServerID": 1234, -// "MessageStream": "outbound", -// "Description": "The server was unable to deliver your message (ex: unknown user, mailbox not found).", -// "Details": "Test bounce details", -// "Email": "john@example.com", -// "From": "sender@example.com", -// "BouncedAt": "2021-02-21T16:34:52Z", -// "DumpAvailable": true, -// "Inactive": true, -// "CanActivate": true, -// "Subject": "Test subject", -// "Content": "Test content" -// } + // { + // "Metadata": { + // "example": "value", + // "example_2": "value" + // }, + // "RecordType": "Bounce", + // "ID": 42, + // "Type": "HardBounce", + // "TypeCode": 1, + // "Name": "Hard bounce", + // "Tag": "Test", + // "MessageID": "00000000-0000-0000-0000-000000000000", + // "ServerID": 1234, + // "MessageStream": "outbound", + // "Description": "The server was unable to deliver your message (ex: unknown user, mailbox not found).", + // "Details": "Test bounce details", + // "Email": "john@example.com", + // "From": "sender@example.com", + // "BouncedAt": "2021-02-21T16:34:52Z", + // "DumpAvailable": true, + // "Inactive": true, + // "CanActivate": true, + // "Subject": "Test subject", + // "Content": "Test content" + // } private function processBounce() { @@ -261,31 +261,31 @@ class ProcessPostmarkWebhook implements ShouldQueue // $this->invitation->company->notification(new EmailBounceNotification($this->invitation->company->account))->ninja(); } -// { -// "Metadata": { -// "example": "value", -// "example_2": "value" -// }, -// "RecordType": "SpamComplaint", -// "ID": 42, -// "Type": "SpamComplaint", -// "TypeCode": 100001, -// "Name": "Spam complaint", -// "Tag": "Test", -// "MessageID": "00000000-0000-0000-0000-000000000000", -// "ServerID": 1234, -// "MessageStream": "outbound", -// "Description": "The subscriber explicitly marked this message as spam.", -// "Details": "Test spam complaint details", -// "Email": "john@example.com", -// "From": "sender@example.com", -// "BouncedAt": "2021-02-21T16:34:52Z", -// "DumpAvailable": true, -// "Inactive": true, -// "CanActivate": false, -// "Subject": "Test subject", -// "Content": "Test content" -// } + // { + // "Metadata": { + // "example": "value", + // "example_2": "value" + // }, + // "RecordType": "SpamComplaint", + // "ID": 42, + // "Type": "SpamComplaint", + // "TypeCode": 100001, + // "Name": "Spam complaint", + // "Tag": "Test", + // "MessageID": "00000000-0000-0000-0000-000000000000", + // "ServerID": 1234, + // "MessageStream": "outbound", + // "Description": "The subscriber explicitly marked this message as spam.", + // "Details": "Test spam complaint details", + // "Email": "john@example.com", + // "From": "sender@example.com", + // "BouncedAt": "2021-02-21T16:34:52Z", + // "DumpAvailable": true, + // "Inactive": true, + // "CanActivate": false, + // "Subject": "Test subject", + // "Content": "Test content" + // } private function processSpamComplaint() { $this->invitation->email_status = 'spam'; @@ -367,9 +367,9 @@ class ProcessPostmarkWebhook implements ShouldQueue private function fetchMessage(): array { - if(strlen($this->request['MessageID']) < 1){ + if(strlen($this->request['MessageID']) < 1) { return $this->default_response; - } + } try { @@ -401,8 +401,7 @@ class ProcessPostmarkWebhook implements ShouldQueue 'events' => $events, ]; - } - catch (\Exception $e) { + } catch (\Exception $e) { return $this->default_response; diff --git a/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php b/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php index 00ee4aa39a..10699dcbc1 100644 --- a/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php +++ b/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php @@ -11,21 +11,21 @@ namespace App\Jobs\PurchaseOrder; +use App\Models\User; +use App\Models\Company; +use App\Libraries\MultiDB; +use App\Jobs\Util\UnlinkFile; +use Illuminate\Bus\Queueable; +use App\Jobs\Entity\CreateRawPdf; use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; -use App\Jobs\Util\UnlinkFile; -use App\Jobs\Vendor\CreatePurchaseOrderPdf; -use App\Libraries\MultiDB; use App\Mail\DownloadPurchaseOrders; -use App\Models\Company; +use Illuminate\Queue\SerializesModels; use App\Models\PurchaseOrderInvitation; -use App\Models\User; -use Illuminate\Bus\Queueable; +use Illuminate\Support\Facades\Storage; +use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Storage; class ZipPurchaseOrders implements ShouldQueue { @@ -63,7 +63,9 @@ class ZipPurchaseOrders implements ShouldQueue try { foreach ($invitations as $invitation) { - $file = (new \App\Jobs\Vendor\CreatePurchaseOrderPdf($invitation))->rawPdf(); + + $file = (new CreateRawPdf($invitation))->handle(); + $zipFile->addFromString($invitation->purchase_order->numberFormatter().".pdf", $file); } diff --git a/app/Jobs/Quote/ZipQuotes.php b/app/Jobs/Quote/ZipQuotes.php index b1675b7c0e..c4ce22fa66 100644 --- a/app/Jobs/Quote/ZipQuotes.php +++ b/app/Jobs/Quote/ZipQuotes.php @@ -11,20 +11,20 @@ namespace App\Jobs\Quote; -use App\Models\User; -use App\Models\Company; -use App\Libraries\MultiDB; -use App\Mail\DownloadQuotes; -use App\Jobs\Util\UnlinkFile; -use Illuminate\Bus\Queueable; -use App\Models\QuoteInvitation; use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; -use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Storage; -use Illuminate\Queue\InteractsWithQueue; +use App\Jobs\Util\UnlinkFile; +use App\Libraries\MultiDB; +use App\Mail\DownloadQuotes; +use App\Models\Company; +use App\Models\QuoteInvitation; +use App\Models\User; +use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Storage; class ZipQuotes implements ShouldQueue { @@ -60,7 +60,7 @@ class ZipQuotes implements ShouldQueue try { foreach ($invitations as $invitation) { - $file = (new \App\Jobs\Entity\CreateRawPdf($invitation, $this->company->db))->handle(); + $file = (new \App\Jobs\Entity\CreateRawPdf($invitation))->handle(); $zipFile->addFromString($invitation->quote->numberFormatter() . '.pdf', $file); } diff --git a/app/Jobs/RecurringInvoice/SendRecurring.php b/app/Jobs/RecurringInvoice/SendRecurring.php index 3c98c00a43..b2247a93f7 100644 --- a/app/Jobs/RecurringInvoice/SendRecurring.php +++ b/app/Jobs/RecurringInvoice/SendRecurring.php @@ -11,24 +11,24 @@ namespace App\Jobs\RecurringInvoice; -use Carbon\Carbon; -use App\Utils\Ninja; -use App\Models\Invoice; -use App\Jobs\Cron\AutoBill; -use Illuminate\Bus\Queueable; -use App\Utils\Traits\MakesHash; -use App\Jobs\Entity\EmailEntity; -use App\Models\RecurringInvoice; -use App\Utils\Traits\GeneratesCounter; -use Illuminate\Queue\SerializesModels; -use Turbo124\Beacon\Facades\LightLogs; -use Illuminate\Queue\InteractsWithQueue; +use App\DataMapper\Analytics\SendRecurringFailure; use App\Events\Invoice\InvoiceWasCreated; use App\Factory\InvoiceInvitationFactory; +use App\Factory\RecurringInvoiceToInvoiceFactory; +use App\Jobs\Cron\AutoBill; +use App\Jobs\Entity\EmailEntity; +use App\Models\Invoice; +use App\Models\RecurringInvoice; +use App\Utils\Ninja; +use App\Utils\Traits\GeneratesCounter; +use App\Utils\Traits\MakesHash; +use Carbon\Carbon; +use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; -use App\Factory\RecurringInvoiceToInvoiceFactory; -use App\DataMapper\Analytics\SendRecurringFailure; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\SerializesModels; +use Turbo124\Beacon\Facades\LightLogs; class SendRecurring implements ShouldQueue { @@ -115,11 +115,10 @@ class SendRecurring implements ShouldQueue AutoBill::dispatch($invoice->id, $this->db, true)->delay(rand(1, 2)); //04-08-2023 edge case to support where online payment notifications are not enabled - if(!$invoice->client->getSetting('client_online_payment_notification')){ + if(!$invoice->client->getSetting('client_online_payment_notification')) { $this->sendRecurringEmails($invoice); } - } - elseif ($invoice->auto_bill_enabled && $invoice->client->getSetting('auto_bill_date') == 'on_due_date' && $invoice->client->getSetting('auto_email_invoice') && ($invoice->due_date && Carbon::parse($invoice->due_date)->startOfDay()->lte(now()->startOfDay()))) { + } elseif ($invoice->auto_bill_enabled && $invoice->client->getSetting('auto_bill_date') == 'on_due_date' && $invoice->client->getSetting('auto_email_invoice') && ($invoice->due_date && Carbon::parse($invoice->due_date)->startOfDay()->lte(now()->startOfDay()))) { nlog("attempting to autobill {$invoice->number}"); AutoBill::dispatch($invoice->id, $this->db, true)->delay(rand(1, 2)); @@ -128,8 +127,7 @@ class SendRecurring implements ShouldQueue $this->sendRecurringEmails($invoice); } - } - elseif ($invoice->client->getSetting('auto_email_invoice')) { + } elseif ($invoice->client->getSetting('auto_email_invoice')) { $this->sendRecurringEmails($invoice); } @@ -138,7 +136,7 @@ class SendRecurring implements ShouldQueue /** * Sends the recurring invoice emails to * the designated contacts - * + * * @param Invoice $invoice * @return void */ @@ -199,4 +197,4 @@ class SendRecurring implements ShouldQueue nlog(print_r($exception->getMessage(), 1)); } -} \ No newline at end of file +} diff --git a/app/Jobs/Report/PreviewReport.php b/app/Jobs/Report/PreviewReport.php index c52cd1a3e1..d00a99d080 100644 --- a/app/Jobs/Report/PreviewReport.php +++ b/app/Jobs/Report/PreviewReport.php @@ -11,15 +11,15 @@ namespace App\Jobs\Report; -use App\Models\Company; use App\Libraries\MultiDB; +use App\Models\Company; use Illuminate\Bus\Queueable; -use Illuminate\Support\Facades\Cache; -use Illuminate\Queue\SerializesModels; -use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; +use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Cache; class PreviewReport implements ShouldQueue { @@ -38,10 +38,12 @@ class PreviewReport implements ShouldQueue /** @var \App\Export\CSV\CreditExport $export */ $export = new $this->report_class($this->company, $this->request); - $report = $export->returnJson(); - // nlog($report); - // nlog($this->report_class); - // nlog($report); + + if(isset($this->request['output']) && $this->request['output'] == 'json') + $report = $export->returnJson(); + else + $report = $export->run(); + Cache::put($this->hash, $report, 60 * 60); } diff --git a/app/Jobs/Report/SendToAdmin.php b/app/Jobs/Report/SendToAdmin.php index ca7e5c8812..e2cba7525d 100644 --- a/app/Jobs/Report/SendToAdmin.php +++ b/app/Jobs/Report/SendToAdmin.php @@ -20,8 +20,8 @@ use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\SerializesModels; use Illuminate\Queue\Middleware\WithoutOverlapping; +use Illuminate\Queue\SerializesModels; class SendToAdmin implements ShouldQueue { diff --git a/app/Jobs/Subscription/CleanStaleInvoiceOrder.php b/app/Jobs/Subscription/CleanStaleInvoiceOrder.php index b182a8831c..43ff28a56b 100644 --- a/app/Jobs/Subscription/CleanStaleInvoiceOrder.php +++ b/app/Jobs/Subscription/CleanStaleInvoiceOrder.php @@ -59,11 +59,11 @@ class CleanStaleInvoiceOrder implements ShouldQueue ->whereBetween('created_at', [now()->subHours(1), now()->subMinutes(10)]) ->where('balance', '>', 0) ->cursor() - ->each(function ($invoice){ + ->each(function ($invoice) { - if (collect($invoice->line_items)->contains('type_id', 3)) { - $invoice->service()->removeUnpaidGatewayFees(); - } + if (collect($invoice->line_items)->contains('type_id', 3)) { + $invoice->service()->removeUnpaidGatewayFees(); + } }); diff --git a/app/Jobs/Util/Import.php b/app/Jobs/Util/Import.php index bfb09f42ab..db4f2fe38b 100644 --- a/app/Jobs/Util/Import.php +++ b/app/Jobs/Util/Import.php @@ -11,89 +11,85 @@ namespace App\Jobs\Util; -use Exception; -use App\Models\Task; -use App\Models\User; -use App\Utils\Ninja; -use App\Models\Quote; -use App\Models\Client; -use App\Models\Credit; -use App\Models\Vendor; -use App\Models\Company; -use App\Models\Expense; -use App\Models\Invoice; -use App\Models\Payment; -use App\Models\Product; -use App\Models\Project; -use App\Models\TaxRate; -use App\Models\Activity; -use App\Models\Document; -use App\Libraries\MultiDB; -use App\Models\TaskStatus; -use App\Models\PaymentTerm; -use Illuminate\Support\Str; -use App\Factory\UserFactory; -use App\Factory\QuoteFactory; -use App\Models\ClientContact; -use Illuminate\Bus\Queueable; +use App\DataMapper\Analytics\MigrationFailure; +use App\DataMapper\CompanySettings; +use App\Exceptions\ClientHostedMigrationException; +use App\Exceptions\MigrationValidatorFailed; +use App\Exceptions\ResourceDependencyMissing; use App\Factory\ClientFactory; +use App\Factory\CompanyLedgerFactory; use App\Factory\CreditFactory; -use App\Factory\VendorFactory; -use App\Models\CompanyGateway; -use Illuminate\Support\Carbon; use App\Factory\InvoiceFactory; use App\Factory\PaymentFactory; use App\Factory\ProductFactory; +use App\Factory\QuoteFactory; +use App\Factory\RecurringInvoiceFactory; use App\Factory\TaxRateFactory; -use App\Jobs\Util\VersionCheck; -use App\Models\ExpenseCategory; -use App\Utils\Traits\MakesHash; -use App\Mail\MigrationCompleted; -use App\Models\RecurringExpense; -use App\Models\RecurringInvoice; -use App\Utils\Traits\Uploadable; +use App\Factory\UserFactory; +use App\Factory\VendorFactory; +use App\Http\Requests\Company\UpdateCompanyRequest; +use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule; +use App\Http\ValidationRules\ValidUserForCompany; +use App\Jobs\Company\CreateCompanyToken; use App\Jobs\Mail\NinjaMailerJob; -use Illuminate\Http\UploadedFile; -use App\Models\ClientGatewayToken; -use Illuminate\Support\Facades\DB; -use App\DataMapper\CompanySettings; -use Illuminate\Support\Facades\App; use App\Jobs\Mail\NinjaMailerObject; use App\Jobs\Ninja\CheckCompanyData; -use App\Repositories\UserRepository; -use App\Utils\Traits\CleanLineItems; -use App\Utils\Traits\SavesDocuments; -use Illuminate\Support\Facades\Mail; -use App\Factory\CompanyLedgerFactory; -use App\Repositories\ClientRepository; -use App\Repositories\CreditRepository; -use App\Repositories\VendorRepository; -use Illuminate\Queue\SerializesModels; -use Turbo124\Beacon\Facades\LightLogs; -use App\Repositories\CompanyRepository; -use App\Repositories\ProductRepository; -use App\Factory\RecurringInvoiceFactory; -use App\Jobs\Company\CreateCompanyToken; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Support\Facades\Validator; -use Modules\Admin\Jobs\Account\NinjaUser; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Foundation\Bus\Dispatchable; -use App\DataMapper\ClientRegistrationFields; -use App\Exceptions\MigrationValidatorFailed; -use App\Exceptions\ResourceDependencyMissing; -use App\Repositories\ClientContactRepository; -use App\Repositories\VendorContactRepository; -use App\DataMapper\Analytics\MigrationFailure; +use App\Libraries\MultiDB; use App\Mail\Migration\StripeConnectMigration; -use App\Http\ValidationRules\ValidUserForCompany; -use App\Exceptions\ClientHostedMigrationException; -use App\Http\Requests\Company\UpdateCompanyRequest; -use Illuminate\Queue\Middleware\WithoutOverlapping; -use App\Utils\Traits\CompanyGatewayFeesAndLimitsSaver; +use App\Mail\MigrationCompleted; +use App\Models\Activity; +use App\Models\Client; +use App\Models\ClientContact; +use App\Models\ClientGatewayToken; +use App\Models\Company; +use App\Models\CompanyGateway; +use App\Models\Credit; +use App\Models\Document; +use App\Models\Expense; +use App\Models\ExpenseCategory; +use App\Models\Invoice; +use App\Models\Payment; +use App\Models\PaymentTerm; +use App\Models\Product; +use App\Models\Project; +use App\Models\Quote; +use App\Models\RecurringExpense; +use App\Models\RecurringInvoice; +use App\Models\Task; +use App\Models\TaskStatus; +use App\Models\TaxRate; +use App\Models\User; +use App\Models\Vendor; +use App\Repositories\ClientContactRepository; +use App\Repositories\ClientRepository; +use App\Repositories\CompanyRepository; +use App\Repositories\CreditRepository; use App\Repositories\Migration\InvoiceMigrationRepository; use App\Repositories\Migration\PaymentMigrationRepository; -use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule; +use App\Repositories\ProductRepository; +use App\Repositories\UserRepository; +use App\Repositories\VendorContactRepository; +use App\Repositories\VendorRepository; +use App\Utils\Ninja; +use App\Utils\Traits\CleanLineItems; +use App\Utils\Traits\CompanyGatewayFeesAndLimitsSaver; +use App\Utils\Traits\MakesHash; +use App\Utils\Traits\SavesDocuments; +use App\Utils\Traits\Uploadable; +use Exception; +use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Http\UploadedFile; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\Middleware\WithoutOverlapping; +use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\Validator; +use Illuminate\Support\Str; +use Turbo124\Beacon\Facades\LightLogs; class Import implements ShouldQueue { @@ -266,8 +262,9 @@ class Import implements ShouldQueue $t = app('translator'); $t->replace(Ninja::transformTranslations($this->company->settings)); - if(!$this->silent_migration) + if(!$this->silent_migration) { Mail::to($this->user->email, $this->user->name())->send(new MigrationCompleted($this->company->id, $this->company->db, implode("
", $check_data))); + } } catch(\Exception $e) { nlog($e->getMessage()); @@ -399,7 +396,7 @@ class Import implements ShouldQueue if (Ninja::isHosted()) { - $data['subdomain'] = str_replace("_","",$data['subdomain']); + $data['subdomain'] = str_replace("_", "", $data['subdomain']); if (!MultiDB::checkDomainAvailable($data['subdomain'])) { $data['subdomain'] = MultiDB::randomSubdomainGenerator(); @@ -1186,8 +1183,7 @@ class Import implements ShouldQueue CreditFactory::create($this->company->id, $modified['user_id']) ); - if($credit->status_id == 4) - { + if($credit->status_id == 4) { $client = $credit->client; $client->balance -= $credit->balance; @@ -1616,8 +1612,9 @@ class Import implements ShouldQueue $nmo->settings = $this->company->settings; $nmo->to_user = $this->user; - if(!$this->silent_migration) + if(!$this->silent_migration) { NinjaMailerJob::dispatch($nmo, true); + } $modified['gateway_key'] = 'd14dd26a47cecc30fdd65700bfb67b34'; } @@ -1824,7 +1821,7 @@ class Import implements ShouldQueue private function processActivities(array $data): void { - Activity::query()->where('company_id', $this->company->id)->cursor()->each(function ($a){ + Activity::query()->where('company_id', $this->company->id)->cursor()->each(function ($a) { $a->forceDelete(); nlog("deleting {$a->id}"); }); @@ -1837,55 +1834,54 @@ class Import implements ShouldQueue $modified['company_id'] = $this->company->id; $modified['user_id'] = $this->processUserId($resource); -try { - if (isset($modified['client_id'])) { - $modified['client_id'] = $this->transformId('clients', $resource['client_id']); - } + try { + if (isset($modified['client_id'])) { + $modified['client_id'] = $this->transformId('clients', $resource['client_id']); + } - if (isset($modified['invoice_id'])) { - $modified['invoice_id'] = $this->transformId('invoices', $resource['invoice_id']); - } + if (isset($modified['invoice_id'])) { + $modified['invoice_id'] = $this->transformId('invoices', $resource['invoice_id']); + } - if (isset($modified['quote_id'])) { - $modified['quote_id'] = $this->transformId('quotes', $resource['quote_id']); - } + if (isset($modified['quote_id'])) { + $modified['quote_id'] = $this->transformId('quotes', $resource['quote_id']); + } - if (isset($modified['recurring_invoice_id'])) { - $modified['recurring_invoice_id'] = $this->transformId('recurring_invoices', $resource['recurring_invoice_id']); - } + if (isset($modified['recurring_invoice_id'])) { + $modified['recurring_invoice_id'] = $this->transformId('recurring_invoices', $resource['recurring_invoice_id']); + } - if (isset($modified['payment_id'])) { - $modified['payment_id'] = $this->transformId('payments', $resource['payment_id']); - } + if (isset($modified['payment_id'])) { + $modified['payment_id'] = $this->transformId('payments', $resource['payment_id']); + } - if (isset($modified['credit_id'])) { - $modified['credit_id'] = $this->transformId('credits', $resource['credit_id']); - } + if (isset($modified['credit_id'])) { + $modified['credit_id'] = $this->transformId('credits', $resource['credit_id']); + } - if (isset($modified['expense_id'])) { - $modified['expense_id'] = $this->transformId('expenses', $resource['expense_id']); - } + if (isset($modified['expense_id'])) { + $modified['expense_id'] = $this->transformId('expenses', $resource['expense_id']); + } - if (isset($modified['task_id'])) { - $modified['task_id'] = $this->transformId('tasks', $resource['task_id']); - } + if (isset($modified['task_id'])) { + $modified['task_id'] = $this->transformId('tasks', $resource['task_id']); + } - if (isset($modified['client_contact_id'])) { - $modified['client_contact_id'] = $this->transformId('client_contacts', $resource['client_contact_id']); - } + if (isset($modified['client_contact_id'])) { + $modified['client_contact_id'] = $this->transformId('client_contacts', $resource['client_contact_id']); + } - $modified['updated_at'] = $modified['created_at']; + $modified['updated_at'] = $modified['created_at']; - /** @var \App\Models\Activity $act **/ - $act = Activity::make($modified); + /** @var \App\Models\Activity $act **/ + $act = Activity::make($modified); - $act->save(['timestamps' => false]); -} -catch (\Exception $e) { + $act->save(['timestamps' => false]); + } catch (\Exception $e) { -nlog("could not import activity: {$e->getMessage()}"); + nlog("could not import activity: {$e->getMessage()}"); -} + } } diff --git a/app/Jobs/Util/RefreshPdfs.php b/app/Jobs/Util/RefreshPdfs.php deleted file mode 100644 index dba50bf67c..0000000000 --- a/app/Jobs/Util/RefreshPdfs.php +++ /dev/null @@ -1,61 +0,0 @@ -company = $company; - } - - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - MultiDB::setDb($this->company->db); - - InvoiceInvitation::where('company_id', $this->company->id)->cursor()->each(function ($invitation) { - nlog("generating invoice pdf for {$invitation->invoice_id}"); - CreateEntityPdf::dispatch($invitation); - }); - - QuoteInvitation::where('company_id', $this->company->id)->cursor()->each(function ($invitation) { - nlog("generating quote pdf for {$invitation->quote_id}"); - CreateEntityPdf::dispatch($invitation); - }); - - CreditInvitation::where('company_id', $this->company->id)->cursor()->each(function ($invitation) { - nlog("generating credit pdf for {$invitation->credit_id}"); - CreateEntityPdf::dispatch($invitation); - }); - } -} \ No newline at end of file diff --git a/app/Jobs/Util/ReminderJob.php b/app/Jobs/Util/ReminderJob.php index 6c0521bfe3..6974e58259 100644 --- a/app/Jobs/Util/ReminderJob.php +++ b/app/Jobs/Util/ReminderJob.php @@ -11,22 +11,22 @@ namespace App\Jobs\Util; -use App\Utils\Ninja; -use App\Models\Invoice; -use App\Libraries\MultiDB; -use Illuminate\Bus\Queueable; -use Illuminate\Support\Carbon; use App\DataMapper\InvoiceItem; use App\Factory\InvoiceFactory; use App\Jobs\Entity\EmailEntity; +use App\Libraries\MultiDB; +use App\Models\Invoice; +use App\Utils\Ninja; use App\Utils\Traits\MakesDates; -use Illuminate\Support\Facades\App; use App\Utils\Traits\MakesReminders; -use Illuminate\Support\Facades\Auth; -use Illuminate\Queue\SerializesModels; -use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Auth; class ReminderJob implements ShouldQueue { @@ -129,8 +129,9 @@ class ReminderJob implements ShouldQueue $invoice->service()->touchReminder($reminder_template)->save(); $fees = $this->calcLateFee($invoice, $reminder_template); - if($invoice->isLocked()) + if($invoice->isLocked()) { return $this->addFeeToNewInvoice($invoice, $reminder_template, $fees); + } $invoice = $this->setLateFee($invoice, $fees[0], $fees[1]); @@ -312,4 +313,4 @@ class ReminderJob implements ShouldQueue return $invoice; } -} \ No newline at end of file +} diff --git a/app/Jobs/Util/StartMigration.php b/app/Jobs/Util/StartMigration.php index 432b1c1b8b..06d30fbf18 100644 --- a/app/Jobs/Util/StartMigration.php +++ b/app/Jobs/Util/StartMigration.php @@ -11,27 +11,27 @@ namespace App\Jobs\Util; -use ZipArchive; -use App\Models\User; -use App\Utils\Ninja; -use App\Models\Company; -use App\Libraries\MultiDB; -use App\Mail\MigrationFailed; -use Illuminate\Bus\Queueable; -use Illuminate\Support\Facades\App; -use Illuminate\Support\Facades\Mail; -use Illuminate\Support\Facades\Cache; -use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Storage; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Foundation\Bus\Dispatchable; +use App\Exceptions\ClientHostedMigrationException; use App\Exceptions\MigrationValidatorFailed; use App\Exceptions\NonExistingMigrationFile; -use App\Exceptions\ResourceDependencyMissing; -use App\Exceptions\ClientHostedMigrationException; use App\Exceptions\ProcessingMigrationArchiveFailed; +use App\Exceptions\ResourceDependencyMissing; use App\Exceptions\ResourceNotAvailableForMigration; +use App\Libraries\MultiDB; +use App\Mail\MigrationFailed; +use App\Models\Company; +use App\Models\User; +use App\Utils\Ninja; +use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\Storage; +use ZipArchive; class StartMigration implements ShouldQueue { @@ -141,8 +141,9 @@ class StartMigration implements ShouldQueue app('sentry')->captureException($e); } - if(!$this->silent_migration) + if(!$this->silent_migration) { Mail::to($this->user->email, $this->user->name())->send(new MigrationFailed($e, $this->company, $e->getMessage())); + } if (Ninja::isHosted()) { $migration_failed = new MigrationFailed($e, $this->company, $e->getMessage()); diff --git a/app/Jobs/Vendor/CreatePurchaseOrderPdf.php b/app/Jobs/Vendor/CreatePurchaseOrderPdf.php index 5c8432603a..59777bed2a 100644 --- a/app/Jobs/Vendor/CreatePurchaseOrderPdf.php +++ b/app/Jobs/Vendor/CreatePurchaseOrderPdf.php @@ -11,29 +11,31 @@ namespace App\Jobs\Vendor; -use App\Exceptions\FilePermissionsFailure; -use App\Libraries\MultiDB; +use App\Utils\Ninja; use App\Models\Design; +use App\Libraries\MultiDB; +use Illuminate\Bus\Queueable; +use App\Utils\Traits\MakesHash; +use App\Utils\VendorHtmlEngine; +use App\Services\Pdf\PdfService; +use App\Utils\PhantomJS\Phantom; +use App\Utils\HostedPDF\NinjaPdf; +use App\Utils\Traits\Pdf\PdfMaker; +use Illuminate\Support\Facades\App; +use App\Utils\Traits\NumberFormatter; +use App\Utils\Traits\MakesInvoiceHtml; +use Illuminate\Queue\SerializesModels; +use App\Utils\Traits\Pdf\PageNumbering; +use Illuminate\Support\Facades\Storage; +use Illuminate\Queue\InteractsWithQueue; +use App\Exceptions\FilePermissionsFailure; +use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Foundation\Bus\Dispatchable; use App\Services\PdfMaker\Design as PdfDesignModel; use App\Services\PdfMaker\Design as PdfMakerDesign; use App\Services\PdfMaker\PdfMaker as PdfMakerService; -use App\Utils\HostedPDF\NinjaPdf; -use App\Utils\Ninja; -use App\Utils\PhantomJS\Phantom; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\MakesInvoiceHtml; -use App\Utils\Traits\NumberFormatter; -use App\Utils\Traits\Pdf\PageNumbering; -use App\Utils\Traits\Pdf\PdfMaker; -use App\Utils\VendorHtmlEngine; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\App; -use Illuminate\Support\Facades\Storage; +/** @deprecated 26-10-2023 5.7.30x */ class CreatePurchaseOrderPdf implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash, PageNumbering; @@ -79,6 +81,18 @@ class CreatePurchaseOrderPdf implements ShouldQueue public function handle() { + /** Testing this override to improve PDF generation performance */ + $ps = new PdfService($this->invitation, 'product', [ + 'client' => $this->entity->client ?? false, + 'vendor' => $this->entity->vendor ?? false, + "{$this->entity_string}s" => [$this->entity], + ]); + + nlog("returning purchase order"); + + return $ps->boot()->getPdf(); + + $pdf = $this->rawPdf(); if ($pdf) { @@ -154,6 +168,10 @@ class CreatePurchaseOrderPdf implements ShouldQueue 'options' => [ 'all_pages_header' => $this->entity->company->getSetting('all_pages_header'), 'all_pages_footer' => $this->entity->company->getSetting('all_pages_footer'), + 'client' => null, + 'vendor' => $this->vendor, + 'entity' => $this->entity, + 'variables' => $variables, ], 'process_markdown' => $this->entity->company->markdown_enabled, ]; diff --git a/app/Listeners/Account/StripeConnectFailureListener.php b/app/Listeners/Account/StripeConnectFailureListener.php index 12ff3f94b4..3002acdd9d 100644 --- a/app/Listeners/Account/StripeConnectFailureListener.php +++ b/app/Listeners/Account/StripeConnectFailureListener.php @@ -11,13 +11,13 @@ namespace App\Listeners\Account; -use App\Utils\Ninja; -use App\Libraries\MultiDB; use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; -use Illuminate\Support\Facades\Cache; +use App\Libraries\MultiDB; use App\Mail\Ninja\StripeConnectFailed; +use App\Utils\Ninja; use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Support\Facades\Cache; class StripeConnectFailureListener implements ShouldQueue { @@ -39,8 +39,7 @@ class StripeConnectFailureListener implements ShouldQueue { MultiDB::setDb($event->db); - if (Ninja::isHosted() && is_null(Cache::get("stripe_connect_notification:{$event->company->company_key}"))) - { + if (Ninja::isHosted() && is_null(Cache::get("stripe_connect_notification:{$event->company->company_key}"))) { $nmo = new NinjaMailerObject(); $nmo->mailable = new StripeConnectFailed($event->company->owner(), $event->company); diff --git a/app/Listeners/Activity/PaymentCreatedActivity.php b/app/Listeners/Activity/PaymentCreatedActivity.php index 12ed24c288..be114e0a09 100644 --- a/app/Listeners/Activity/PaymentCreatedActivity.php +++ b/app/Listeners/Activity/PaymentCreatedActivity.php @@ -39,15 +39,16 @@ class PaymentCreatedActivity implements ShouldQueue * @return void */ public function handle($event) - { + { MultiDB::setDb($event->company->db); $payment = $event->payment; $invoice_id = null; - if($payment->invoices()->exists()) + if($payment->invoices()->exists()) { $invoice_id = $payment->invoices()->first()->id; + } $user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->payment->user_id; diff --git a/app/Listeners/Invoice/CreateInvoicePdf.php b/app/Listeners/Invoice/CreateInvoicePdf.php deleted file mode 100644 index a50c13ef46..0000000000 --- a/app/Listeners/Invoice/CreateInvoicePdf.php +++ /dev/null @@ -1,57 +0,0 @@ -company->db); - - if (isset($event->invoice)) { - $event->invoice->invitations->each(function ($invitation) { - (new CreateEntityPdf($invitation->load('invoice', 'contact.client.company')))->handle(); - }); - } - - if (isset($event->quote)) { - $event->quote->invitations->each(function ($invitation) { - (new CreateEntityPdf($invitation->load('quote', 'contact.client.company')))->handle(); - }); - } - - if (isset($event->credit)) { - $event->credit->invitations->each(function ($invitation) { - (new CreateEntityPdf($invitation->load('credit', 'contact.client.company')))->handle(); - }); - } - } -} diff --git a/app/Listeners/Invoice/InvoiceReminderEmailActivity.php b/app/Listeners/Invoice/InvoiceReminderEmailActivity.php index 9b41a3b096..9fce655b83 100644 --- a/app/Listeners/Invoice/InvoiceReminderEmailActivity.php +++ b/app/Listeners/Invoice/InvoiceReminderEmailActivity.php @@ -46,7 +46,7 @@ class InvoiceReminderEmailActivity implements ShouldQueue $user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->invitation->invoice->user_id; - $reminder = match($event->template){ + $reminder = match($event->template) { 'reminder1' => 63, 'reminder2' => 64, 'reminder3' => 65, diff --git a/app/Listeners/Payment/PaymentNotification.php b/app/Listeners/Payment/PaymentNotification.php index f46c228b83..f9a33e0b37 100644 --- a/app/Listeners/Payment/PaymentNotification.php +++ b/app/Listeners/Payment/PaymentNotification.php @@ -11,16 +11,16 @@ namespace App\Listeners\Payment; -use App\Utils\Ninja; -use App\Libraries\MultiDB; +use App\DataMapper\Analytics\RevenueTrack; use App\Jobs\Mail\NinjaMailer; use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; +use App\Libraries\MultiDB; use App\Mail\Admin\EntityPaidObject; -use Turbo124\Beacon\Facades\LightLogs; -use App\DataMapper\Analytics\RevenueTrack; -use Illuminate\Contracts\Queue\ShouldQueue; +use App\Utils\Ninja; use App\Utils\Traits\Notifications\UserNotifies; +use Illuminate\Contracts\Queue\ShouldQueue; +use Turbo124\Beacon\Facades\LightLogs; class PaymentNotification implements ShouldQueue { @@ -59,7 +59,7 @@ class PaymentNotification implements ShouldQueue } /* Manual Payment Notifications */ - if($payment->is_manual){ + if($payment->is_manual) { foreach ($payment->company->company_users as $company_user) { $user = $company_user->user; @@ -175,7 +175,7 @@ class PaymentNotification implements ShouldQueue * @param string $url */ private function sendAnalytics($url) - { + { $data = mb_convert_encoding($url, 'UTF-8'); // $data = utf8_encode($data); $curl = curl_init(); diff --git a/app/Listeners/User/UpdateUserLastLogin.php b/app/Listeners/User/UpdateUserLastLogin.php index 94e20512ee..c1eec95de9 100644 --- a/app/Listeners/User/UpdateUserLastLogin.php +++ b/app/Listeners/User/UpdateUserLastLogin.php @@ -11,17 +11,17 @@ namespace App\Listeners\User; -use App\Models\SystemLog; -use App\Libraries\MultiDB; -use App\Jobs\Util\SystemLogger; -use App\Mail\User\UserLoggedIn; use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; -use Illuminate\Support\Facades\Cache; -use Illuminate\Queue\SerializesModels; +use App\Jobs\Util\SystemLogger; +use App\Libraries\MultiDB; +use App\Mail\User\UserLoggedIn; +use App\Models\SystemLog; +use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Events\Dispatchable; -use Illuminate\Broadcasting\InteractsWithSockets; +use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Cache; class UpdateUserLastLogin implements ShouldQueue { diff --git a/app/Listeners/Vendor/UpdateVendorContactLastLogin.php b/app/Listeners/Vendor/UpdateVendorContactLastLogin.php index 272b7144b1..d583a2fb8c 100644 --- a/app/Listeners/Vendor/UpdateVendorContactLastLogin.php +++ b/app/Listeners/Vendor/UpdateVendorContactLastLogin.php @@ -12,9 +12,8 @@ namespace App\Listeners\Vendor; use App\Libraries\MultiDB; -use Illuminate\Contracts\Queue\ShouldQueue; -class UpdateVendorContactLastLogin +class UpdateVendorContactLastLogin { /** * Create the event listener. diff --git a/app/Mail/Admin/AccountCreatedObject.php b/app/Mail/Admin/AccountCreatedObject.php index 2b18adf016..9ce3196580 100644 --- a/app/Mail/Admin/AccountCreatedObject.php +++ b/app/Mail/Admin/AccountCreatedObject.php @@ -11,9 +11,9 @@ namespace App\Mail\Admin; +use App\Models\Company; use App\Models\User; use App\Utils\Ninja; -use App\Models\Company; use Illuminate\Support\Facades\App; class AccountCreatedObject diff --git a/app/Mail/Admin/AutoBillingFailureObject.php b/app/Mail/Admin/AutoBillingFailureObject.php index a937aecf25..01a1f2823b 100644 --- a/app/Mail/Admin/AutoBillingFailureObject.php +++ b/app/Mail/Admin/AutoBillingFailureObject.php @@ -17,6 +17,7 @@ use App\Utils\Ninja; use App\Utils\Traits\MakesHash; use Illuminate\Support\Facades\App; use stdClass; + //@deprecated class AutoBillingFailureObject { diff --git a/app/Mail/DownloadDocuments.php b/app/Mail/DownloadDocuments.php index 70508f3a88..54e2ca0513 100644 --- a/app/Mail/DownloadDocuments.php +++ b/app/Mail/DownloadDocuments.php @@ -12,24 +12,14 @@ namespace App\Mail; use App\Models\Company; -use Illuminate\Bus\Queueable; use Illuminate\Mail\Mailable; -use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\App; class DownloadDocuments extends Mailable { - // use Queueable, SerializesModels; - public $file_path; - - public $company; - - public function __construct($file_path, Company $company) + public function __construct(public string $file_path, public Company $company) { - $this->file_path = $file_path; - - $this->company = $company; } /** diff --git a/app/Mail/Engine/CreditEmailEngine.php b/app/Mail/Engine/CreditEmailEngine.php index d6a7bda08b..e927fe287e 100644 --- a/app/Mail/Engine/CreditEmailEngine.php +++ b/app/Mail/Engine/CreditEmailEngine.php @@ -115,7 +115,7 @@ class CreditEmailEngine extends BaseEmailEngine if ($this->client->getSetting('pdf_email_attachment') !== false && $this->credit->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) { - $pdf = ((new CreateRawPdf($this->invitation, $this->invitation->company->db))->handle()); + $pdf = ((new CreateRawPdf($this->invitation))->handle()); $this->setAttachments([['file' => base64_encode($pdf), 'name' => $this->credit->numberFormatter().'.pdf']]); } @@ -123,7 +123,7 @@ class CreditEmailEngine extends BaseEmailEngine //attach third party documents if ($this->client->getSetting('document_email_attachment') !== false && $this->credit->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) { // Storage::url - $this->credit->documents()->where('is_public',true)->cursor()->each(function($document) { + $this->credit->documents()->where('is_public', true)->cursor()->each(function ($document) { if ($document->size > $this->max_attachment_size) { $this->setAttachmentLinks([" $document->hash]) ."'>". $document->name .""]); } else { @@ -131,7 +131,7 @@ class CreditEmailEngine extends BaseEmailEngine } }); - $this->credit->company->documents()->where('is_public',true)->cursor()->each(function($document) { + $this->credit->company->documents()->where('is_public', true)->cursor()->each(function ($document) { if ($document->size > $this->max_attachment_size) { $this->setAttachmentLinks([" $document->hash]) ."'>". $document->name .""]); } else { diff --git a/app/Mail/Engine/InvoiceEmailEngine.php b/app/Mail/Engine/InvoiceEmailEngine.php index 70ab7ee706..ac7cad3426 100644 --- a/app/Mail/Engine/InvoiceEmailEngine.php +++ b/app/Mail/Engine/InvoiceEmailEngine.php @@ -127,7 +127,7 @@ class InvoiceEmailEngine extends BaseEmailEngine ->setTextBody($text_body); if ($this->client->getSetting('pdf_email_attachment') !== false && $this->invoice->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) { - $pdf = ((new CreateRawPdf($this->invitation, $this->invitation->company->db))->handle()); + $pdf = ((new CreateRawPdf($this->invitation))->handle()); $this->setAttachments([['file' => base64_encode($pdf), 'name' => $this->invoice->numberFormatter().'.pdf']]); } @@ -135,7 +135,7 @@ class InvoiceEmailEngine extends BaseEmailEngine //attach third party documents if ($this->client->getSetting('document_email_attachment') !== false && $this->invoice->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) { if ($this->invoice->recurring_invoice()->exists()) { - $this->invoice->recurring_invoice->documents()->where('is_public',true)->cursor()->each(function ($document) { + $this->invoice->recurring_invoice->documents()->where('is_public', true)->cursor()->each(function ($document) { if ($document->size > $this->max_attachment_size) { $this->setAttachmentLinks([" $document->hash]) ."'>". $document->name .""]); } else { @@ -145,7 +145,7 @@ class InvoiceEmailEngine extends BaseEmailEngine } // Storage::url - $this->invoice->documents()->where('is_public',true)->cursor()->each(function ($document) { + $this->invoice->documents()->where('is_public', true)->cursor()->each(function ($document) { if ($document->size > $this->max_attachment_size) { $this->setAttachmentLinks([" $document->hash]) ."'>". $document->name .""]); } else { @@ -153,7 +153,7 @@ class InvoiceEmailEngine extends BaseEmailEngine } }); - $this->invoice->company->documents()->where('is_public',true)->cursor()->each(function ($document) { + $this->invoice->company->documents()->where('is_public', true)->cursor()->each(function ($document) { if ($document->size > $this->max_attachment_size) { $this->setAttachmentLinks([" $document->hash]) ."'>". $document->name .""]); } else { @@ -175,7 +175,7 @@ class InvoiceEmailEngine extends BaseEmailEngine ->where('invoice_documents', 1) ->cursor() ->each(function ($expense) { - $expense->documents()->where('is_public',true)->cursor()->each(function ($document) { + $expense->documents()->where('is_public', true)->cursor()->each(function ($document) { if ($document->size > $this->max_attachment_size) { $this->setAttachmentLinks([" $document->hash]) ."'>". $document->name .""]); } else { diff --git a/app/Mail/Engine/PaymentEmailEngine.php b/app/Mail/Engine/PaymentEmailEngine.php index fad158f721..39f3fe9afc 100644 --- a/app/Mail/Engine/PaymentEmailEngine.php +++ b/app/Mail/Engine/PaymentEmailEngine.php @@ -11,16 +11,17 @@ namespace App\Mail\Engine; +use App\DataMapper\EmailTemplateDefaults; +use App\Jobs\Entity\CreateRawPdf; +use App\Models\Account; +use App\Models\Payment; +use App\Services\Template\TemplateAction; +use App\Utils\Helpers; use App\Utils\Ninja; use App\Utils\Number; -use App\Utils\Helpers; -use App\Models\Account; use App\Utils\Traits\MakesDates; -use App\Jobs\Entity\CreateRawPdf; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\URL; -use Illuminate\Support\Facades\Storage; -use App\DataMapper\EmailTemplateDefaults; class PaymentEmailEngine extends BaseEmailEngine { @@ -44,6 +45,8 @@ class PaymentEmailEngine extends BaseEmailEngine private $payment_template_subject; + public bool $is_refund = false; + public function __construct($payment, $contact, $template_data = null) { $this->payment = $payment; @@ -91,14 +94,56 @@ class PaymentEmailEngine extends BaseEmailEngine ->setViewText(''); if ($this->client->getSetting('pdf_email_attachment') !== false && $this->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) { - $this->payment->invoices->each(function ($invoice) { - $pdf = ((new CreateRawPdf($invoice->invitations->first(), $invoice->company->db))->handle()); - $this->setAttachments([['file' => base64_encode($pdf), 'name' => $invoice->numberFormatter().'.pdf']]); + $template_in_use = false; + + if($this->is_refund && strlen($this->payment->client->getSetting('payment_refund_design_id')) > 2) { + $pdf = (new TemplateAction( + [$this->payment->hashed_id], + $this->payment->client->getSetting('payment_refund_design_id'), + Payment::class, + $this->payment->user_id, + $this->payment->company, + $this->payment->company->db, + 'nohash', + false + ))->handle(); + + $file_name = ctrans('texts.payment_refund_receipt', ['number' => $this->payment->number ]) . '.pdf'; + $file_name = str_replace(' ', '_', $file_name); + $this->setAttachments([['file' => base64_encode($pdf), 'name' => $file_name]]); + $template_in_use = true; + + } elseif(!$this->is_refund && strlen($this->payment->client->getSetting('payment_receipt_design_id')) > 2) { + $pdf = (new TemplateAction( + [$this->payment->hashed_id], + $this->payment->client->getSetting('payment_receipt_design_id'), + Payment::class, + $this->payment->user_id, + $this->payment->company, + $this->payment->company->db, + 'nohash', + false + ))->handle(); + + $file_name = ctrans('texts.payment_receipt', ['number' => $this->payment->number ]) . '.pdf'; + $file_name = str_replace(' ', '_', $file_name); + $this->setAttachments([['file' => base64_encode($pdf), 'name' => $file_name]]); + $template_in_use = true; + + } + + $this->payment->invoices->each(function ($invoice) use ($template_in_use) { + + if(!$template_in_use) { + $pdf = ((new CreateRawPdf($invoice->invitations->first()))->handle()); + $file_name = $invoice->numberFormatter().'.pdf'; + $this->setAttachments([['file' => base64_encode($pdf), 'name' => $file_name]]); + } //attach invoice documents also to payments if ($this->client->getSetting('document_email_attachment') !== false) { - $invoice->documents()->where('is_public', true)->cursor()->each(function ($document){ + $invoice->documents()->where('is_public', true)->cursor()->each(function ($document) { if ($document->size > $this->max_attachment_size) { $this->setAttachmentLinks([" $document->hash]) ."'>". $document->name .""]); } else { @@ -351,7 +396,7 @@ class PaymentEmailEngine extends BaseEmailEngine private function formatInvoiceReferencesSubject() { - $invoice_list = ''; + $invoice_list = ''; foreach ($this->payment->invoices as $invoice) { if (strlen($invoice->po_number) > 1) { @@ -362,8 +407,8 @@ class PaymentEmailEngine extends BaseEmailEngine } - if(strlen($invoice_list) < 4){ - $invoice_list = Number::formatMoney($this->payment->amount, $this->client) ?: ' '; + if(strlen($invoice_list) < 4) { + $invoice_list = Number::formatMoney($this->payment->amount, $this->client) ?: ' '; } @@ -371,7 +416,8 @@ class PaymentEmailEngine extends BaseEmailEngine } - private function formatInvoiceNumbersRaw(){ + private function formatInvoiceNumbersRaw() + { return collect($this->payment->invoices->pluck('number')->toArray())->implode(', '); diff --git a/app/Mail/Engine/PurchaseOrderEmailEngine.php b/app/Mail/Engine/PurchaseOrderEmailEngine.php index 51f6d5bd53..b1f4a3bb30 100644 --- a/app/Mail/Engine/PurchaseOrderEmailEngine.php +++ b/app/Mail/Engine/PurchaseOrderEmailEngine.php @@ -11,18 +11,17 @@ namespace App\Mail\Engine; -use App\DataMapper\EmailTemplateDefaults; -use App\Jobs\Vendor\CreatePurchaseOrderPdf; -use App\Models\Account; -use App\Models\PurchaseOrder; -use App\Models\Vendor; -use App\Utils\HtmlEngine; use App\Utils\Ninja; use App\Utils\Number; +use App\Models\Vendor; +use App\Models\Account; +use App\Models\PurchaseOrder; use App\Utils\Traits\MakesHash; use App\Utils\VendorHtmlEngine; +use App\Jobs\Entity\CreateRawPdf; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\URL; +use App\DataMapper\EmailTemplateDefaults; class PurchaseOrderEmailEngine extends BaseEmailEngine { @@ -119,7 +118,8 @@ class PurchaseOrderEmailEngine extends BaseEmailEngine ->setTextBody($text_body); if ($this->vendor->getSetting('pdf_email_attachment') !== false && $this->purchase_order->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) { - $pdf = (new CreatePurchaseOrderPdf($this->invitation))->rawPdf(); + + $pdf = (new CreateRawPdf($this->invitation))->handle(); $this->setAttachments([['file' => base64_encode($pdf), 'name' => $this->purchase_order->numberFormatter().'.pdf']]); } diff --git a/app/Mail/Engine/QuoteEmailEngine.php b/app/Mail/Engine/QuoteEmailEngine.php index 72f3a13db1..a4b3df35db 100644 --- a/app/Mail/Engine/QuoteEmailEngine.php +++ b/app/Mail/Engine/QuoteEmailEngine.php @@ -113,7 +113,7 @@ class QuoteEmailEngine extends BaseEmailEngine ->setTextBody($text_body); if ($this->client->getSetting('pdf_email_attachment') !== false && $this->quote->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) { - $pdf = ((new CreateRawPdf($this->invitation, $this->invitation->company->db))->handle()); + $pdf = ((new CreateRawPdf($this->invitation))->handle()); $this->setAttachments([['file' => base64_encode($pdf), 'name' => $this->quote->numberFormatter().'.pdf']]); } diff --git a/app/Mail/Ninja/StripeConnectFailed.php b/app/Mail/Ninja/StripeConnectFailed.php index 4c935cdf29..1e11c04306 100644 --- a/app/Mail/Ninja/StripeConnectFailed.php +++ b/app/Mail/Ninja/StripeConnectFailed.php @@ -11,12 +11,12 @@ namespace App\Mail\Ninja; -use App\Models\User; use App\Models\Company; +use App\Models\User; use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; -use Illuminate\Mail\Mailables\Headers; use Illuminate\Mail\Mailables\Envelope; +use Illuminate\Mail\Mailables\Headers; class StripeConnectFailed extends Mailable { diff --git a/app/Mail/RecurringInvoice/ClientContactRequestCancellationObject.php b/app/Mail/RecurringInvoice/ClientContactRequestCancellationObject.php index ab1011b08b..debd00b9d6 100644 --- a/app/Mail/RecurringInvoice/ClientContactRequestCancellationObject.php +++ b/app/Mail/RecurringInvoice/ClientContactRequestCancellationObject.php @@ -11,10 +11,10 @@ namespace App\Mail\RecurringInvoice; -use App\Utils\Ninja; -use App\Models\Company; use App\Models\ClientContact; +use App\Models\Company; use App\Models\RecurringInvoice; +use App\Utils\Ninja; use Illuminate\Support\Facades\App; class ClientContactRequestCancellationObject diff --git a/app/Mail/TestMailServer.php b/app/Mail/TestMailServer.php index 917a1fa9f0..1dd8ff783f 100644 --- a/app/Mail/TestMailServer.php +++ b/app/Mail/TestMailServer.php @@ -11,9 +11,7 @@ namespace App\Mail; -use Illuminate\Bus\Queueable; use Illuminate\Mail\Mailable; -use Illuminate\Queue\SerializesModels; class TestMailServer extends Mailable { diff --git a/app/Mail/VendorTemplateEmail.php b/app/Mail/VendorTemplateEmail.php index bdfb818b00..f51ea3f74c 100644 --- a/app/Mail/VendorTemplateEmail.php +++ b/app/Mail/VendorTemplateEmail.php @@ -11,11 +11,11 @@ namespace App\Mail; -use App\Utils\Ninja; use App\Models\VendorContact; -use Illuminate\Mail\Mailable; -use App\Utils\VendorHtmlEngine; use App\Services\PdfMaker\Designs\Utilities\DesignHelpers; +use App\Utils\Ninja; +use App\Utils\VendorHtmlEngine; +use Illuminate\Mail\Mailable; class VendorTemplateEmail extends Mailable { diff --git a/app/Models/Account.php b/app/Models/Account.php index d0f7feaefd..8bc8039a0e 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -86,7 +86,7 @@ use Laracasts\Presenter\PresentableTrait; * @method static \Illuminate\Database\Eloquent\Builder|BaseModel scope() * @method static \Illuminate\Database\Eloquent\Builder|Account first() * @method static \Illuminate\Database\Eloquent\Builder|Account with() - * @method static \Illuminate\Database\Eloquent\Builder|Account count() + * @method static \Illuminate\Database\Eloquent\Builder|Account count() * @method static \Illuminate\Database\Eloquent\Builder|Account where($query) * @property-read \Illuminate\Database\Eloquent\Collection $bank_integrations * @property-read \Illuminate\Database\Eloquent\Collection $companies @@ -270,7 +270,7 @@ class Account extends BaseModel case self::FEATURE_REMOVE_CREATED_BY: return ! empty($plan_details); // A plan is required even for self-hosted users - // Enterprise; No Trial allowed; grandfathered for old pro users + // Enterprise; No Trial allowed; grandfathered for old pro users case self::FEATURE_USERS:// Grandfathered for old Pro users if ($plan_details && $plan_details['trial']) { // Do they have a non-trial plan? @@ -584,8 +584,9 @@ class Account extends BaseModel if ($plan_expires->gt(now())) { $diff = $plan_expires->diffInDays(); - if ($diff > 14) + if ($diff > 14) { return 0; + } return $diff; } diff --git a/app/Models/Activity.php b/app/Models/Activity.php index 1b9d89ce59..772c234e69 100644 --- a/app/Models/Activity.php +++ b/app/Models/Activity.php @@ -13,7 +13,6 @@ namespace App\Models; use App\Utils\Number; use App\Utils\Traits\MakesHash; -use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * App\Models\Activity @@ -399,25 +398,29 @@ class Activity extends StaticModel ':balance', ':number', ':payment_amount', - ':gateway', - ':adjustment' + ':gateway', + ':adjustment' ]; - $found_variables = array_intersect(explode(" ",trans("texts.activity_{$this->activity_type_id}")), $intersect); + $found_variables = array_intersect(explode(" ", trans("texts.activity_{$this->activity_type_id}")), $intersect); $replacements = []; - foreach($found_variables as $var) + foreach($found_variables as $var) { $replacements = array_merge($replacements, $this->matchVar($var)); + } - if($this->client) + if($this->client) { $replacements['client'] = ['label' => $this?->client?->present()->name() ?? '', 'hashed_id' => $this->client->hashed_id ?? '']; + } - if($this->vendor) + if($this->vendor) { $replacements['vendor'] = ['label' => $this?->vendor?->present()->name() ?? '', 'hashed_id' => $this->vendor->hashed_id ?? '']; + } - if($this->activity_type_id == 4 && $this->recurring_invoice) + if($this->activity_type_id == 4 && $this->recurring_invoice) { $replacements['recurring_invoice'] = ['label' => $this?->recurring_invoice?->number ?? '', 'hashed_id' => $this->recurring_invoice->hashed_id ?? '']; + } $replacements['activity_type_id'] = $this->activity_type_id; $replacements['id'] = $this->id; @@ -449,7 +452,7 @@ class Activity extends StaticModel ':recurring_invoice' => $translation = [substr($variable, 1) =>[ 'label' => $this?->recurring_invoice?->number ??'', 'hashed_id' => $this->recurring_invoice->hashed_id ?? '']], ':recurring_expense' => $translation = [substr($variable, 1) => [ 'label' => $this?->recurring_expense?->number ??'', 'hashed_id' => $this->recurring_expense->hashed_id ?? '']], ':payment_amount' => $translation = [substr($variable, 1) =>[ 'label' => Number::formatMoney($this?->payment?->amount, $this?->payment?->client) ?? '', 'hashed_id' => '']], - ':adjustment' => $translation = [substr($variable, 1) =>[ 'label' => Number::formatMoney($this?->payment?->refunded, $this?->payment?->client) ?? '', 'hashed_id' => '']], + ':adjustment' => $translation = [substr($variable, 1) =>[ 'label' => Number::formatMoney($this?->payment?->refunded, $this?->payment?->client) ?? '', 'hashed_id' => '']], ':ip' => $translation = [ 'ip' => $this->ip ?? ''], ':contact' => $translation = $this->resolveContact(), default => $translation = [], diff --git a/app/Models/BankTransaction.php b/app/Models/BankTransaction.php index 3139ae436d..c3a7321e5c 100644 --- a/app/Models/BankTransaction.php +++ b/app/Models/BankTransaction.php @@ -11,9 +11,8 @@ namespace App\Models; -use App\Models\Expense; -use App\Utils\Traits\MakesHash; use App\Services\Bank\BankService; +use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\SoftDeletes; /** diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php index 20f119b0c9..3cbeef7b17 100644 --- a/app/Models/BaseModel.php +++ b/app/Models/BaseModel.php @@ -11,17 +11,16 @@ namespace App\Models; -use Illuminate\Support\Str; -use Illuminate\Support\Carbon; -use App\Utils\Traits\MakesHash; use App\Jobs\Entity\CreateRawPdf; use App\Jobs\Util\WebhookHandler; use App\Models\Traits\Excludable; -use Illuminate\Database\Eloquent\Model; -use App\Jobs\Vendor\CreatePurchaseOrderPdf; +use App\Utils\Traits\MakesHash; use App\Utils\Traits\UserSessionAttributes; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException; +use Illuminate\Support\Carbon; +use Illuminate\Support\Str; /** * Class BaseModel @@ -37,7 +36,7 @@ use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundExceptio * @property int $assigned_user_id * @method BaseModel service() * @property \App\Models\Company $company - * @method static BaseModel find($value) + * @method static BaseModel find($value) * @method static \Illuminate\Database\Eloquent\Builder|BaseModel company() * @method static \Illuminate\Database\Eloquent\Builder|BaseModel|Illuminate\Database\Eloquent\Relations\BelongsTo|\Awobaz\Compoships\Database\Eloquent\Relations\BelongsTo|\App\Models\Company company() * @method static \Illuminate\Database\Eloquent\Builder|BaseModel|Illuminate\Database\Eloquent\Relations\HasMany|BaseModel orderBy() @@ -68,7 +67,7 @@ use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundExceptio * @method static \Illuminate\Database\Eloquent\Builder|BaseModel|\Illuminate\Database\Query\Builder withoutTrashed() * @mixin \Eloquent * @mixin \Illuminate\Database\Eloquent\Builder - * + * * @property \Illuminate\Support\Collection $tax_map * @property array $total_tax_map */ @@ -237,10 +236,10 @@ class BaseModel extends Model return $this->numberFormatter().'.'.$extension; } - /** - * @param string $extension - * @return string - */ + /** + * @param string $extension + * @return string + */ public function getEFileName($extension = 'pdf') { return ctrans("texts.e_invoice"). "_" . $this->numberFormatter().'.'.$extension; @@ -302,11 +301,8 @@ class BaseModel extends Model if (! $invitation) { throw new \Exception('Hard fail, could not create an invitation.'); } - - if($this instanceof \App\Models\PurchaseOrder) - return "data:application/pdf;base64,".base64_encode((new CreatePurchaseOrderPdf($invitation, $invitation->company->db))->rawPdf()); - return "data:application/pdf;base64,".base64_encode((new CreateRawPdf($invitation, $invitation->company->db))->handle()); + return "data:application/pdf;base64,".base64_encode((new CreateRawPdf($invitation))->handle()); } } diff --git a/app/Models/Client.php b/app/Models/Client.php index a056a10349..689578df83 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -11,26 +11,24 @@ namespace App\Models; -use App\Models\GatewayType; -use App\Utils\Traits\AppSetup; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\MakesDates; -use App\DataMapper\FeesAndLimits; -use App\Models\Traits\Excludable; use App\DataMapper\ClientSettings; use App\DataMapper\CompanySettings; -use Illuminate\Support\Facades\Cache; -use App\Services\Client\ClientService; -use App\Utils\Traits\GeneratesCounter; -use Laracasts\Presenter\PresentableTrait; -use App\Models\Presenters\ClientPresenter; -use Illuminate\Database\Eloquent\SoftDeletes; -use App\Utils\Traits\ClientGroupSettingsSaver; +use App\DataMapper\FeesAndLimits; use App\Libraries\Currency\Conversion\CurrencyApi; -use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Database\Eloquent\Relations\MorphMany; +use App\Models\Presenters\ClientPresenter; +use App\Models\Traits\Excludable; +use App\Services\Client\ClientService; +use App\Utils\Traits\AppSetup; +use App\Utils\Traits\ClientGroupSettingsSaver; +use App\Utils\Traits\GeneratesCounter; +use App\Utils\Traits\MakesDates; +use App\Utils\Traits\MakesHash; use Illuminate\Contracts\Translation\HasLocalePreference; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Facades\Cache; +use Laracasts\Presenter\PresentableTrait; /** * App\Models\Client @@ -474,9 +472,9 @@ class Client extends BaseModel implements HasLocalePreference return $this->settings->{$setting}; } elseif (is_bool($this->settings->{$setting})) { return $this->settings->{$setting}; - } elseif (is_int($this->settings->{$setting})) { + } elseif (is_int($this->settings->{$setting})) { return $this->settings->{$setting}; - } elseif(is_float($this->settings->{$setting})) { + } elseif(is_float($this->settings->{$setting})) { return $this->settings->{$setting}; } } @@ -768,7 +766,7 @@ class Client extends BaseModel implements HasLocalePreference return $defaults; } - public function timezone_offset() :int + public function timezone_offset() :int { $offset = 0; diff --git a/app/Models/Company.php b/app/Models/Company.php index 8d097d3b8b..1815c6dd1e 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -11,20 +11,20 @@ namespace App\Models; -use App\Utils\Ninja; use App\Casts\EncryptedCast; -use App\Utils\Traits\AppSetup; -use App\Utils\Traits\MakesHash; use App\DataMapper\CompanySettings; +use App\Models\Presenters\CompanyPresenter; +use App\Services\Notification\NotificationService; +use App\Utils\Ninja; +use App\Utils\Traits\AppSetup; +use App\Utils\Traits\CompanySettingsSaver; +use App\Utils\Traits\MakesHash; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Notifications\Notification; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Cache; use Laracasts\Presenter\PresentableTrait; -use App\Utils\Traits\CompanySettingsSaver; -use Illuminate\Notifications\Notification; -use App\Models\Presenters\CompanyPresenter; -use App\Services\Notification\NotificationService; -use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * App\Models\Company @@ -621,7 +621,7 @@ class Company extends BaseModel return $item->id == $this->getSetting('country_id'); })->first(); -// return $this->belongsTo(Country::class); + // return $this->belongsTo(Country::class); // return Country::find($this->settings->country_id); } @@ -838,7 +838,7 @@ class Company extends BaseModel ->firstOrFail(); } - public function domain(): string + public function domain(): string { if (Ninja::isHosted()) { if ($this->portal_mode == 'domain' && strlen($this->portal_domain) > 3) { @@ -945,8 +945,9 @@ class Company extends BaseModel public function getInvoiceCert() { - if($this->e_invoice_certificate) + if($this->e_invoice_certificate) { return base64_decode($this->e_invoice_certificate); + } return false; } diff --git a/app/Models/CompanyGateway.php b/app/Models/CompanyGateway.php index 8525a88eb8..ccb25724ef 100644 --- a/app/Models/CompanyGateway.php +++ b/app/Models/CompanyGateway.php @@ -62,7 +62,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @method static \Illuminate\Database\Eloquent\Builder|CompanyGateway withTrashed() * @method static \Illuminate\Database\Eloquent\Builder|CompanyGateway withoutTrashed() * @property-read \Illuminate\Database\Eloquent\Collection $client_gateway_tokens - * @method static CompanyGateway find($value) + * @method static CompanyGateway find($value) * @mixin \Eloquent */ class CompanyGateway extends BaseModel diff --git a/app/Models/CompanyUser.php b/app/Models/CompanyUser.php index 347365a63d..dd022aad59 100644 --- a/app/Models/CompanyUser.php +++ b/app/Models/CompanyUser.php @@ -11,10 +11,9 @@ namespace App\Models; -use Illuminate\Database\Eloquent\SoftDeletes; -use Illuminate\Database\Eloquent\Relations\Pivot; -use Awobaz\Compoships\Exceptions\InvalidUsageException; use Awobaz\Compoships\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\Pivot; +use Illuminate\Database\Eloquent\SoftDeletes; /** * App\Models\CompanyUser @@ -149,7 +148,7 @@ class CompanyUser extends Pivot /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function user() + public function user() { return $this->belongsTo(User::class)->withTrashed(); } @@ -179,7 +178,7 @@ class CompanyUser extends Pivot } /** - * @return HasMany + * @return HasMany */ public function tokens() { @@ -206,4 +205,4 @@ class CompanyUser extends Pivot return isset($this->react_settings->react_notification_link) && $this->react_settings->react_notification_link; } -} \ No newline at end of file +} diff --git a/app/Models/Country.php b/app/Models/Country.php index 97efa4375f..6b3f2f2f5b 100644 --- a/app/Models/Country.php +++ b/app/Models/Country.php @@ -81,8 +81,8 @@ class Country extends StaticModel { return trans('texts.country_'.$this->name); } - public function getID() :string - { - return $this->id; - } + public function getID() :string + { + return $this->id; + } } diff --git a/app/Models/Credit.php b/app/Models/Credit.php index d7d5e91d86..d55a729c27 100644 --- a/app/Models/Credit.php +++ b/app/Models/Credit.php @@ -11,23 +11,18 @@ namespace App\Models; -use App\Utils\Ninja; -use Illuminate\Support\Carbon; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\MakesDates; use App\Helpers\Invoice\InvoiceSum; -use App\Jobs\Entity\CreateEntityPdf; -use App\Utils\Traits\MakesReminders; +use App\Helpers\Invoice\InvoiceSumInclusive; +use App\Models\Presenters\CreditPresenter; use App\Services\Credit\CreditService; use App\Services\Ledger\LedgerService; -use Illuminate\Support\Facades\Storage; +use App\Utils\Traits\MakesDates; +use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesInvoiceValues; -use Laracasts\Presenter\PresentableTrait; -use App\Models\Presenters\CreditPresenter; -use App\Helpers\Invoice\InvoiceSumInclusive; +use App\Utils\Traits\MakesReminders; use Illuminate\Database\Eloquent\SoftDeletes; -use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Database\Eloquent\Relations\MorphMany; +use Illuminate\Support\Carbon; +use Laracasts\Presenter\PresentableTrait; /** * App\Models\Credit @@ -125,7 +120,7 @@ use Illuminate\Database\Eloquent\Relations\MorphMany; * @property-read \Illuminate\Database\Eloquent\Collection $invitations * @property-read \Illuminate\Database\Eloquent\Collection $invoices * @property-read \Illuminate\Database\Eloquent\Collection $payments - * + * * @mixin \Eloquent */ class Credit extends BaseModel @@ -360,53 +355,6 @@ class Credit extends BaseModel $this->saveQuietly(); } - public function pdf_file_path($invitation = null, string $type = 'path', bool $portal = false) - { - if (! $invitation) { - if ($this->invitations()->exists()) { - $invitation = $this->invitations()->first(); - } else { - $this->service()->createInvitations(); - $invitation = $this->invitations()->first(); - } - } - - if (! $invitation) { - throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?'); - } - - $file_path = $this->client->credit_filepath($invitation).$this->numberFormatter().'.pdf'; - - if (Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)) { - return Storage::disk(config('filesystems.default'))->{$type}($file_path); - } elseif (Ninja::isHosted() && $portal) { - $file_path = (new CreateEntityPdf($invitation, config('filesystems.default')))->handle(); - - return Storage::disk(config('filesystems.default'))->{$type}($file_path); - } - - $file_exists = false; - - try { - $file_exists = Storage::disk(config('filesystems.default'))->exists($file_path); - } catch (\Exception $e) { - nlog($e->getMessage()); - } - - if ($file_exists) { - return Storage::disk(config('filesystems.default'))->{$type}($file_path); - } - - - if (Storage::disk('public')->exists($file_path)) { - return Storage::disk('public')->{$type}($file_path); - } - - $file_path = (new CreateEntityPdf($invitation))->handle(); - - return Storage::disk('public')->{$type}($file_path); - } - public function markInvitationsSent() { $this->invitations->each(function ($invitation) { diff --git a/app/Models/CreditInvitation.php b/app/Models/CreditInvitation.php index 2f8926700e..d5d449e388 100644 --- a/app/Models/CreditInvitation.php +++ b/app/Models/CreditInvitation.php @@ -11,13 +11,10 @@ namespace App\Models; -use App\Jobs\Entity\CreateEntityPdf; use App\Utils\Traits\Inviteable; use App\Utils\Traits\MakesDates; -use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\Storage; /** * App\Models\CreditInvitation @@ -155,14 +152,4 @@ class CreditInvitation extends BaseModel $this->save(); } - public function pdf_file_path() - { - $storage_path = Storage::url($this->credit->client->quote_filepath($this).$this->credit->numberFormatter().'.pdf'); - - if (! Storage::exists($this->credit->client->credit_filepath($this).$this->credit->numberFormatter().'.pdf')) { - (new CreateEntityPdf($this))->handle(); - } - - return $storage_path; - } } diff --git a/app/Models/Design.php b/app/Models/Design.php index c93453ed9e..19ac8b0197 100644 --- a/app/Models/Design.php +++ b/app/Models/Design.php @@ -11,6 +11,7 @@ namespace App\Models; +use App\Services\Template\TemplateService; use Illuminate\Database\Eloquent\SoftDeletes; /** @@ -29,7 +30,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property int|null $updated_at * @property int|null $deleted_at * @property-read \App\Models\Company|null $company - * @property-read mixed $hashed_id + * @property-read string $hashed_id * @method static \Illuminate\Database\Eloquent\Builder|BaseModel company() * @method static \Illuminate\Database\Eloquent\Builder|BaseModel exclude($columns) * @method static \Illuminate\Database\Eloquent\Builder|Design filter(\App\Filters\QueryFilters $filters) @@ -69,10 +70,17 @@ class Design extends BaseModel 'name', 'design', 'is_active', + 'is_template', + 'entities', ]; public function company() { return $this->belongsTo(Company::class); } + + public function service(): TemplateService + { + return (new TemplateService($this))->setCompany($this->company); + } } diff --git a/app/Models/Expense.php b/app/Models/Expense.php index 314e3961eb..fa7078a841 100644 --- a/app/Models/Expense.php +++ b/app/Models/Expense.php @@ -225,16 +225,17 @@ class Expense extends BaseModel public function stringStatus() { - if($this->is_deleted) + if($this->is_deleted) { return ctrans('texts.deleted'); - elseif($this->payment_date) - return ctrans('texts.paid'); - elseif($this->invoice_id) + } elseif($this->payment_date) { + return ctrans('texts.paid'); + } elseif($this->invoice_id) { return ctrans('texts.invoiced'); - elseif($this->should_be_invoiced) + } elseif($this->should_be_invoiced) { return ctrans('texts.pending'); - elseif($this->trashed()) + } elseif($this->trashed()) { return ctrans('texts.archived'); + } return ctrans('texts.logged'); } diff --git a/app/Models/GatewayType.php b/app/Models/GatewayType.php index bc207c4955..bcce2d84d4 100644 --- a/app/Models/GatewayType.php +++ b/app/Models/GatewayType.php @@ -163,4 +163,3 @@ class GatewayType extends StaticModel } } } - diff --git a/app/Models/GroupSetting.php b/app/Models/GroupSetting.php index 1a8bd5f093..190721c64a 100644 --- a/app/Models/GroupSetting.php +++ b/app/Models/GroupSetting.php @@ -11,11 +11,10 @@ namespace App\Models; -use App\Models\Filterable; use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException; +use Illuminate\Database\Eloquent\SoftDeletes; /** * App\Models\GroupSetting diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 22e9093f2d..2d9a5babc3 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -11,27 +11,22 @@ namespace App\Models; -use App\Utils\Ninja; -use Illuminate\Support\Carbon; -use App\Utils\Traits\MakesDates; -use App\Jobs\Entity\CreateRawPdf; +use App\Events\Invoice\InvoiceReminderWasEmailed; +use App\Events\Invoice\InvoiceWasEmailed; use App\Helpers\Invoice\InvoiceSum; -use App\Jobs\Entity\CreateEntityPdf; +use App\Helpers\Invoice\InvoiceSumInclusive; +use App\Models\Presenters\EntityPresenter; +use App\Services\Invoice\InvoiceService; +use App\Services\Ledger\LedgerService; +use App\Utils\Ninja; +use App\Utils\Traits\Invoice\ActionsInvoice; +use App\Utils\Traits\MakesDates; +use App\Utils\Traits\MakesInvoiceValues; use App\Utils\Traits\MakesReminders; use App\Utils\Traits\NumberFormatter; -use App\Services\Ledger\LedgerService; -use Illuminate\Support\Facades\Storage; -use App\Services\Invoice\InvoiceService; -use App\Utils\Traits\MakesInvoiceValues; -use App\Events\Invoice\InvoiceWasEmailed; -use Laracasts\Presenter\PresentableTrait; -use App\Models\Presenters\EntityPresenter; -use App\Models\Presenters\InvoicePresenter; -use App\Helpers\Invoice\InvoiceSumInclusive; -use App\Utils\Traits\Invoice\ActionsInvoice; use Illuminate\Database\Eloquent\SoftDeletes; -use App\Events\Invoice\InvoiceReminderWasEmailed; -use Illuminate\Database\Eloquent\Relations\HasManyThrough; +use Illuminate\Support\Carbon; +use Laracasts\Presenter\PresentableTrait; /** * App\Models\Invoice @@ -324,7 +319,7 @@ class Invoice extends BaseModel */ public function net_payments(): \Illuminate\Database\Eloquent\Relations\MorphToMany { - return $this->morphToMany(Payment::class, 'paymentable')->withTrashed()->where('is_deleted',0)->withPivot('amount', 'refunded', 'deleted_at')->withTimestamps(); + return $this->morphToMany(Payment::class, 'paymentable')->withTrashed()->where('is_deleted', 0)->withPivot('amount', 'refunded', 'deleted_at')->withTimestamps(); } /** @@ -529,66 +524,6 @@ class Invoice extends BaseModel return $invoice_calc->build(); } - public function pdf_file_path($invitation = null, string $type = 'path', bool $portal = false) - { - - if (! $invitation) { - if ($this->invitations()->exists()) { - $invitation = $this->invitations()->first(); - } else { - $this->service()->createInvitations(); - $invitation = $this->invitations()->first(); - } - } - - if (! $invitation) { - throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?'); - } - - $file_path = $this->client->invoice_filepath($invitation).$this->numberFormatter().'.pdf'; - - $file_exists = false; - - /* Flysystem throws an exception if the path is "corrupted" so lets wrap it in a try catch and return a bool 06/01/2022*/ - try { - $file_exists = Storage::disk(config('filesystems.default'))->exists($file_path); - } catch (\Exception $e) { - nlog($e->getMessage()); - } - - if (Ninja::isHosted() && $portal && $file_exists) { - return Storage::disk(config('filesystems.default'))->{$type}($file_path); - } elseif (Ninja::isHosted()) { - $file_path = (new CreateEntityPdf($invitation, config('filesystems.default')))->handle(); - - return Storage::disk(config('filesystems.default'))->{$type}($file_path); - } - - try { - $file_exists = Storage::disk(config('filesystems.default'))->exists($file_path); - } catch (\Exception $e) { - nlog($e->getMessage()); - } - - if ($file_exists) { - return Storage::disk(config('filesystems.default'))->{$type}($file_path); - } - - try { - $file_exists = Storage::disk('public')->exists($file_path); - } catch (\Exception $e) { - nlog($e->getMessage()); - } - - if ($file_exists) { - return Storage::disk('public')->{$type}($file_path); - } - - $file_path = (new CreateEntityPdf($invitation))->handle(); - - return Storage::disk('public')->{$type}($file_path); - } - public function markInvitationsSent() { $this->invitations->each(function ($invitation) { @@ -741,7 +676,7 @@ class Invoice extends BaseModel { $tax_type = ''; - match(intval($id)){ + match(intval($id)) { Product::PRODUCT_TYPE_PHYSICAL => $tax_type = ctrans('texts.physical_goods'), Product::PRODUCT_TYPE_SERVICE => $tax_type = ctrans('texts.services'), Product::PRODUCT_TYPE_DIGITAL => $tax_type = ctrans('texts.digital_products'), @@ -792,7 +727,7 @@ class Invoice extends BaseModel $schedule_2 = ctrans("texts.{$settings->schedule_reminder2}"); //after due date etc or disabled $label_2 = ctrans('texts.reminder2'); - $sends_email_3 = $settings->enable_reminder2 ? $send_email_enabled : $send_email_disabled; + $sends_email_3 = $settings->enable_reminder2 ? $send_email_enabled : $send_email_disabled; $days_3 = $settings->num_days_reminder3 . " " . ctrans('texts.days'); $schedule_3 = ctrans("texts.{$settings->schedule_reminder3}"); //after due date etc or disabled $label_3 = ctrans('texts.reminder3'); @@ -801,25 +736,29 @@ class Invoice extends BaseModel $days_endless = \App\Models\RecurringInvoice::frequencyForKey($settings->endless_reminder_frequency_id); $label_endless = ctrans('texts.reminder_endless'); - if($schedule_1 == ctrans('texts.disabled') || $settings->schedule_reminder1 == 'disabled' || $settings->schedule_reminder1 == '') + if($schedule_1 == ctrans('texts.disabled') || $settings->schedule_reminder1 == 'disabled' || $settings->schedule_reminder1 == '') { $reminder_schedule .= "{$label_1}: " . ctrans('texts.disabled') ."
"; - else + } else { $reminder_schedule .= "{$label_1}: {$days_1} {$schedule_1} [{$sends_email_1}]
"; + } - if($schedule_2 == ctrans('texts.disabled') || $settings->schedule_reminder2 == 'disabled' || $settings->schedule_reminder2 == '') + if($schedule_2 == ctrans('texts.disabled') || $settings->schedule_reminder2 == 'disabled' || $settings->schedule_reminder2 == '') { $reminder_schedule .= "{$label_2}: " . ctrans('texts.disabled') ."
"; - else + } else { $reminder_schedule .= "{$label_2}: {$days_2} {$schedule_2} [{$sends_email_2}]
"; + } - if($schedule_3 == ctrans('texts.disabled') || $settings->schedule_reminder3 == 'disabled' || $settings->schedule_reminder3 == '') + if($schedule_3 == ctrans('texts.disabled') || $settings->schedule_reminder3 == 'disabled' || $settings->schedule_reminder3 == '') { $reminder_schedule .= "{$label_3}: " . ctrans('texts.disabled') ."
"; - else + } else { $reminder_schedule .= "{$label_3}: {$days_3} {$schedule_3} [{$sends_email_3}]
"; + } - if($sends_email_endless == ctrans('texts.disabled') || $settings->endless_reminder_frequency_id == '0' || $settings->endless_reminder_frequency_id == '') + if($sends_email_endless == ctrans('texts.disabled') || $settings->endless_reminder_frequency_id == '0' || $settings->endless_reminder_frequency_id == '') { $reminder_schedule .= "{$label_endless}: " . ctrans('texts.disabled') ."
"; - else + } else { $reminder_schedule .= "{$label_endless}: {$days_endless} [{$sends_email_endless}]
"; + } return $reminder_schedule; diff --git a/app/Models/InvoiceInvitation.php b/app/Models/InvoiceInvitation.php index f8fb383db5..2f5c6c08a1 100644 --- a/app/Models/InvoiceInvitation.php +++ b/app/Models/InvoiceInvitation.php @@ -11,15 +11,10 @@ namespace App\Models; -use App\Events\Invoice\InvoiceWasUpdated; -use App\Jobs\Entity\CreateEntityPdf; -use App\Utils\Ninja; use App\Utils\Traits\Inviteable; use App\Utils\Traits\MakesDates; -use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\Storage; /** * App\Models\InvoiceInvitation @@ -158,15 +153,4 @@ class InvoiceInvitation extends BaseModel $this->save(); } - public function pdf_file_path(): string - { - $storage_path = Storage::url($this->invoice->client->invoice_filepath($this).$this->invoice->numberFormatter().'.pdf'); - - if (! Storage::exists($this->invoice->client->invoice_filepath($this).$this->invoice->numberFormatter().'.pdf')) { - event(new InvoiceWasUpdated($this->invoice, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); - (new CreateEntityPdf($this))->handle(); - } - - return $storage_path; - } } diff --git a/app/Models/Payment.php b/app/Models/Payment.php index 8be6f607e6..1f80aaed0a 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -15,13 +15,12 @@ use App\Events\Payment\PaymentWasRefunded; use App\Events\Payment\PaymentWasVoided; use App\Services\Ledger\LedgerService; use App\Services\Payment\PaymentService; -use App\Utils\Ninja; +use App\Utils\Ninja; use App\Utils\Number; use App\Utils\Traits\Inviteable; use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesHash; use App\Utils\Traits\Payment\Refundable; -use Awobaz\Compoships\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; /** @@ -30,6 +29,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property int $id * @property int $company_id * @property int $client_id + * @property int $category_id * @property int|null $project_id * @property int|null $vendor_id * @property int|null $user_id @@ -58,6 +58,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property int|null $exchange_currency_id * @property \App\Models\Paymentable $paymentable * @property object|null $meta + * @property object|null $refund_meta * @property string|null $custom_value1 * @property string|null $custom_value2 * @property string|null $custom_value3 @@ -151,12 +152,12 @@ class Payment extends BaseModel 'number', 'exchange_currency_id', 'exchange_rate', - // 'is_manual', 'private_notes', 'custom_value1', 'custom_value2', 'custom_value3', 'custom_value4', + 'category_id', ]; protected $casts = [ @@ -167,6 +168,7 @@ class Payment extends BaseModel 'deleted_at' => 'timestamp', 'is_deleted' => 'bool', 'meta' => 'object', + 'refund_meta' => 'array', ]; protected $with = [ @@ -454,11 +456,6 @@ class Payment extends BaseModel public function getLink() :string { - // if (Ninja::isHosted()) { - // $domain = isset($this->company->portal_domain) ? $this->company->portal_domain : $this->company->domain(); - // } else { - // $domain = config('ninja.app_url'); - // } if (Ninja::isHosted()) { $domain = $this->company->domain(); @@ -494,4 +491,11 @@ class Payment extends BaseModel return $use_react_url ? config('ninja.react_url')."/#/payments/{$this->hashed_id}/edit" : config('ninja.app_url'); } + public function setRefundMeta(array $data) + { + $tmp_meta = $this->refund_meta ?? []; + $tmp_meta[] = $data; + + $this->refund_meta = $tmp_meta; + } } diff --git a/app/Models/PaymentHash.php b/app/Models/PaymentHash.php index a870489ce0..ded9e912f3 100644 --- a/app/Models/PaymentHash.php +++ b/app/Models/PaymentHash.php @@ -38,13 +38,13 @@ class PaymentHash extends Model 'data' => 'object', ]; - /** - * @class \App\Models\PaymentHash $this - * @property \App\Models\PaymentHash $data - * @property \App\Modes\PaymentHash $hash 32 char length AlphaNum - * @class \stdClass $data - * @property string $raw_value - */ + /** + * @class \App\Models\PaymentHash $this + * @property \App\Models\PaymentHash $data + * @property \App\Modes\PaymentHash $hash 32 char length AlphaNum + * @class \stdClass $data + * @property string $raw_value + */ /** diff --git a/app/Models/Presenters/EntityPresenter.php b/app/Models/Presenters/EntityPresenter.php index 01cfbc3063..8176df9965 100644 --- a/app/Models/Presenters/EntityPresenter.php +++ b/app/Models/Presenters/EntityPresenter.php @@ -16,7 +16,7 @@ use Laracasts\Presenter\Presenter; /** * Class EntityPresenter. - * + * * @property \App\Models\Company | \App\Models\Client | \App\Models\ClientContact | \App\Models\Vendor | \App\Models\VendorContact $entity * @property \App\Models\Client $client * @property \App\Models\Company $company diff --git a/app/Models/Presenters/InvoicePresenter.php b/app/Models/Presenters/InvoicePresenter.php index d2d806f976..f9125a6786 100644 --- a/app/Models/Presenters/InvoicePresenter.php +++ b/app/Models/Presenters/InvoicePresenter.php @@ -23,7 +23,7 @@ use App\Utils\Traits\MakesDates; * * Shortcuts to other presenters are here to facilitate * a clean UI / UX - * + * * @property \App\Models\Invoice $entity */ class InvoicePresenter extends EntityPresenter diff --git a/app/Models/Presenters/UserPresenter.php b/app/Models/Presenters/UserPresenter.php index 68c5722581..10677add63 100644 --- a/app/Models/Presenters/UserPresenter.php +++ b/app/Models/Presenters/UserPresenter.php @@ -18,7 +18,7 @@ class UserPresenter extends EntityPresenter { /** * Returns the first and last names concatenated. - * + * * @return string */ public function name(): string diff --git a/app/Models/Project.php b/app/Models/Project.php index a256b3e74f..be9784d0a8 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -2,7 +2,6 @@ namespace App\Models; -use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; use Laracasts\Presenter\PresentableTrait; diff --git a/app/Models/PurchaseOrder.php b/app/Models/PurchaseOrder.php index 61f334b62c..0ac0ce6070 100644 --- a/app/Models/PurchaseOrder.php +++ b/app/Models/PurchaseOrder.php @@ -13,13 +13,10 @@ namespace App\Models; use App\Helpers\Invoice\InvoiceSum; use App\Helpers\Invoice\InvoiceSumInclusive; -use App\Jobs\Vendor\CreatePurchaseOrderPdf; use App\Services\PurchaseOrder\PurchaseOrderService; -use App\Utils\Ninja; use App\Utils\Traits\MakesDates; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\Storage; /** * App\Models\PurchaseOrder @@ -237,7 +234,7 @@ class PurchaseOrder extends BaseModel /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function vendor(): \Illuminate\Database\Eloquent\Relations\BelongsTo + public function vendor(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Vendor::class)->withTrashed(); } @@ -281,38 +278,6 @@ class PurchaseOrder extends BaseModel }); } - public function pdf_file_path($invitation = null, string $type = 'path', bool $portal = false) - { - if (! $invitation) { - if ($this->invitations()->exists()) { - $invitation = $this->invitations()->first(); - } else { - $this->service()->createInvitations(); - $invitation = $this->invitations()->first(); - } - } - - if (!$invitation) { - throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?'); - } - - $file_path = $this->vendor->purchase_order_filepath($invitation).$this->numberFormatter().'.pdf'; - - if (Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)) { - return Storage::disk(config('filesystems.default'))->{$type}($file_path); - } elseif (Ninja::isHosted() && $portal) { - $file_path = (new CreatePurchaseOrderPdf($invitation, config('filesystems.default')))->handle(); - return Storage::disk(config('filesystems.default'))->{$type}($file_path); - } - - if (Storage::disk('public')->exists($file_path)) { - return Storage::disk('public')->{$type}($file_path); - } - - $file_path = (new CreatePurchaseOrderPdf($invitation))->handle(); - return Storage::disk('public')->{$type}($file_path); - } - public function invitations(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(PurchaseOrderInvitation::class); @@ -393,7 +358,7 @@ class PurchaseOrder extends BaseModel { $tax_type = ''; - match(intval($id)){ + match(intval($id)) { Product::PRODUCT_TYPE_PHYSICAL => $tax_type = ctrans('texts.physical_goods'), Product::PRODUCT_TYPE_SERVICE => $tax_type = ctrans('texts.services'), Product::PRODUCT_TYPE_DIGITAL => $tax_type = ctrans('texts.digital_products'), diff --git a/app/Models/PurchaseOrderInvitation.php b/app/Models/PurchaseOrderInvitation.php index 8599090ed2..31fbf151aa 100644 --- a/app/Models/PurchaseOrderInvitation.php +++ b/app/Models/PurchaseOrderInvitation.php @@ -15,7 +15,6 @@ use App\Utils\Ninja; use App\Utils\Traits\Inviteable; use App\Utils\Traits\MakesDates; use Carbon\Carbon; -use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Str; @@ -194,5 +193,5 @@ class PurchaseOrderInvitation extends BaseModel return config('ninja.react_url')."/#/{$entity_type}s/{$this->{$entity_type}->hashed_id}/edit"; } - + } diff --git a/app/Models/Quote.php b/app/Models/Quote.php index 3ab63980e7..f6e0ceb9b1 100644 --- a/app/Models/Quote.php +++ b/app/Models/Quote.php @@ -13,17 +13,14 @@ namespace App\Models; use App\Helpers\Invoice\InvoiceSum; use App\Helpers\Invoice\InvoiceSumInclusive; -use App\Jobs\Entity\CreateEntityPdf; use App\Models\Presenters\QuotePresenter; use App\Services\Quote\QuoteService; -use App\Utils\Ninja; use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesInvoiceValues; use App\Utils\Traits\MakesReminders; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\Storage; use Laracasts\Presenter\PresentableTrait; /** @@ -109,7 +106,7 @@ use Laracasts\Presenter\PresentableTrait; * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read \Illuminate\Database\Eloquent\Collection $history * @property-read \Illuminate\Database\Eloquent\Collection $invitations - * + * * @mixin \Eloquent * @mixin \Illuminate\Database\Eloquent\Builder */ @@ -314,51 +311,6 @@ class Quote extends BaseModel return new QuoteService($this); } - public function pdf_file_path($invitation = null, string $type = 'path', bool $portal = false) - { - if (! $invitation) { - if ($this->invitations()->exists()) { - $invitation = $this->invitations()->first(); - } else { - $this->service()->createInvitations(); - $invitation = $this->invitations()->first(); - } - } - - if (! $invitation) { - throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?'); - } - - $file_path = $this->client->quote_filepath($invitation).$this->numberFormatter().'.pdf'; - - if (Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)) { - return Storage::disk(config('filesystems.default'))->{$type}($file_path); - } elseif (Ninja::isHosted() && $portal) { - $file_path = (new CreateEntityPdf($invitation, config('filesystems.default')))->handle(); - return Storage::disk(config('filesystems.default'))->{$type}($file_path); - } - - $file_exists = false; - - try { - $file_exists = Storage::disk(config('filesystems.default'))->exists($file_path); - } catch (\Exception $e) { - nlog($e->getMessage()); - } - - if ($file_exists) { - return Storage::disk(config('filesystems.default'))->{$type}($file_path); - } - - if (Storage::disk('public')->exists($file_path)) { - return Storage::disk('public')->{$type}($file_path); - } - - $file_path = (new CreateEntityPdf($invitation))->handle(); - - return Storage::disk('public')->{$type}($file_path); - } - /** * @param int $status * @return string diff --git a/app/Models/QuoteInvitation.php b/app/Models/QuoteInvitation.php index abe0f5668c..65aef2e255 100644 --- a/app/Models/QuoteInvitation.php +++ b/app/Models/QuoteInvitation.php @@ -11,13 +11,10 @@ namespace App\Models; -use App\Jobs\Entity\CreateEntityPdf; use App\Utils\Traits\Inviteable; use App\Utils\Traits\MakesDates; -use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\Storage; /** * App\Models\QuoteInvitation @@ -141,14 +138,4 @@ class QuoteInvitation extends BaseModel $this->save(); } - public function pdf_file_path() - { - $storage_path = Storage::url($this->quote->client->quote_filepath($this).$this->quote->numberFormatter().'.pdf'); - - if (! Storage::exists($this->quote->client->quote_filepath($this).$this->quote->numberFormatter().'.pdf')) { - (new CreateEntityPdf($this))->handle(); - } - - return $storage_path; - } } diff --git a/app/Models/RecurringQuote.php b/app/Models/RecurringQuote.php index 8652c8edca..24af3f9a24 100644 --- a/app/Models/RecurringQuote.php +++ b/app/Models/RecurringQuote.php @@ -482,9 +482,9 @@ class RecurringQuote extends BaseModel { $invoice_calc = null; - if ($this->uses_inclusive_taxes) { + if ($this->uses_inclusive_taxes) { $invoice_calc = new InvoiceSumInclusive($this); - } else { + } else { $invoice_calc = new InvoiceSum($this); } diff --git a/app/Models/Scheduler.php b/app/Models/Scheduler.php index b3aa8bc9ab..bff76f4d6f 100644 --- a/app/Models/Scheduler.php +++ b/app/Models/Scheduler.php @@ -11,9 +11,6 @@ namespace App\Models; -use App\Models\Company; -use App\Models\BaseModel; -use App\Models\RecurringInvoice; use App\Services\Scheduler\SchedulerService; use Illuminate\Database\Eloquent\SoftDeletes; diff --git a/app/Models/Task.php b/app/Models/Task.php index be0c86135e..9bbab1d15a 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -229,11 +229,13 @@ class Task extends BaseModel public function getRate(): float { - if($this->project && $this->project->task_rate > 0) + if($this->project && $this->project->task_rate > 0) { return $this->project->task_rate; + } - if($this->client) + if($this->client) { return $this->client->getSetting('default_task_rate'); + } return $this->company->settings->default_task_rate ?? 0; } diff --git a/app/Models/Traits/Excludable.php b/app/Models/Traits/Excludable.php index e310117936..e95e71eb25 100644 --- a/app/Models/Traits/Excludable.php +++ b/app/Models/Traits/Excludable.php @@ -35,10 +35,10 @@ trait Excludable /** * Exclude an array of elements from the result. - * + * * @method static \Illuminate\Database\Eloquent\Builder exclude($columns) * @method static \Illuminate\Database\Eloquent\Builder exclude($columns) - * + * * @param \Illuminate\Database\Eloquent\Builder $query * @param array $columns * diff --git a/app/Models/User.php b/app/Models/User.php index aec9b0e787..d6c023b612 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -11,26 +11,25 @@ namespace App\Models; -use App\Models\Company; -use App\Utils\TruthSource; use App\Jobs\Mail\NinjaMailer; -use Illuminate\Support\Carbon; -use App\Utils\Traits\MakesHash; use App\Jobs\Mail\NinjaMailerJob; -use App\Services\User\UserService; -use App\Utils\Traits\UserSettings; -use Illuminate\Support\Facades\App; use App\Jobs\Mail\NinjaMailerObject; use App\Mail\Admin\ResetPasswordObject; -use Illuminate\Database\Eloquent\Model; use App\Models\Presenters\UserPresenter; -use Illuminate\Notifications\Notifiable; -use Laracasts\Presenter\PresentableTrait; +use App\Services\User\UserService; +use App\Utils\Traits\MakesHash; use App\Utils\Traits\UserSessionAttributes; -use Illuminate\Database\Eloquent\SoftDeletes; +use App\Utils\Traits\UserSettings; +use App\Utils\TruthSource; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; +use Illuminate\Notifications\Notifiable; +use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\App; +use Laracasts\Presenter\PresentableTrait; /** * App\Models\User @@ -664,8 +663,9 @@ class User extends Authenticatable implements MustVerifyEmail { $locale = $this->language->locale ?? null; - if($locale) + if($locale) { App::setLocale($locale); + } return $locale; } diff --git a/app/Notifications/Admin/EntitySentNotification.php b/app/Notifications/Admin/EntitySentNotification.php index 5033d813d1..1aab619307 100644 --- a/app/Notifications/Admin/EntitySentNotification.php +++ b/app/Notifications/Admin/EntitySentNotification.php @@ -12,7 +12,6 @@ namespace App\Notifications\Admin; use App\Utils\Number; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; @@ -67,7 +66,7 @@ class EntitySentNotification extends Notification * Get the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Notifications/Admin/EntityViewedNotification.php b/app/Notifications/Admin/EntityViewedNotification.php index 69f7b7e003..6fa39e757f 100644 --- a/app/Notifications/Admin/EntityViewedNotification.php +++ b/app/Notifications/Admin/EntityViewedNotification.php @@ -12,7 +12,6 @@ namespace App\Notifications\Admin; use App\Utils\Number; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; @@ -67,7 +66,7 @@ class EntityViewedNotification extends Notification * Get the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Notifications/Admin/NewPaymentNotification.php b/app/Notifications/Admin/NewPaymentNotification.php index 70694ccc0d..5867382f4c 100644 --- a/app/Notifications/Admin/NewPaymentNotification.php +++ b/app/Notifications/Admin/NewPaymentNotification.php @@ -12,7 +12,6 @@ namespace App\Notifications\Admin; use App\Utils\Number; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; @@ -57,7 +56,7 @@ class NewPaymentNotification extends Notification * Get the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Notifications/ClientContactRequestCancellation.php b/app/Notifications/ClientContactRequestCancellation.php index 618dee1890..6384f0d638 100644 --- a/app/Notifications/ClientContactRequestCancellation.php +++ b/app/Notifications/ClientContactRequestCancellation.php @@ -14,7 +14,6 @@ namespace App\Notifications; use Closure; use Illuminate\Bus\Queueable; use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; use Illuminate\Queue\InteractsWithQueue; @@ -63,7 +62,7 @@ class ClientContactRequestCancellation extends Notification * Get the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Notifications/ClientContactResetPassword.php b/app/Notifications/ClientContactResetPassword.php index 2ba6593fb3..4f048de0ec 100644 --- a/app/Notifications/ClientContactResetPassword.php +++ b/app/Notifications/ClientContactResetPassword.php @@ -14,7 +14,6 @@ namespace App\Notifications; use Closure; use Illuminate\Bus\Queueable; use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; @@ -63,7 +62,7 @@ class ClientContactResetPassword extends Notification * Build the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Notifications/NewAccountCreated.php b/app/Notifications/NewAccountCreated.php index 845aecad8c..163a0625e2 100644 --- a/app/Notifications/NewAccountCreated.php +++ b/app/Notifications/NewAccountCreated.php @@ -13,7 +13,6 @@ namespace App\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; use Illuminate\Queue\InteractsWithQueue; @@ -57,7 +56,7 @@ class NewAccountCreated extends Notification * Get the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Notifications/Ninja/ClientAccountNotFound.php b/app/Notifications/Ninja/ClientAccountNotFound.php index 84087991f6..519c4f5e11 100644 --- a/app/Notifications/Ninja/ClientAccountNotFound.php +++ b/app/Notifications/Ninja/ClientAccountNotFound.php @@ -11,7 +11,6 @@ namespace App\Notifications\Ninja; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; @@ -42,7 +41,7 @@ class ClientAccountNotFound extends Notification * Get the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Notifications/Ninja/DomainFailureNotification.php b/app/Notifications/Ninja/DomainFailureNotification.php index 2e4dc99059..26b33f99e3 100644 --- a/app/Notifications/Ninja/DomainFailureNotification.php +++ b/app/Notifications/Ninja/DomainFailureNotification.php @@ -11,7 +11,6 @@ namespace App\Notifications\Ninja; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; @@ -45,7 +44,7 @@ class DomainFailureNotification extends Notification * Get the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Notifications/Ninja/DomainRenewalFailureNotification.php b/app/Notifications/Ninja/DomainRenewalFailureNotification.php index 719a93d5e1..dfb189253d 100644 --- a/app/Notifications/Ninja/DomainRenewalFailureNotification.php +++ b/app/Notifications/Ninja/DomainRenewalFailureNotification.php @@ -11,7 +11,6 @@ namespace App\Notifications\Ninja; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; @@ -45,7 +44,7 @@ class DomainRenewalFailureNotification extends Notification * Get the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Notifications/Ninja/EmailBounceNotification.php b/app/Notifications/Ninja/EmailBounceNotification.php index 796a1f6368..4ae6966d72 100644 --- a/app/Notifications/Ninja/EmailBounceNotification.php +++ b/app/Notifications/Ninja/EmailBounceNotification.php @@ -11,7 +11,6 @@ namespace App\Notifications\Ninja; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; @@ -44,7 +43,7 @@ class EmailBounceNotification extends Notification * Get the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Notifications/Ninja/EmailQuotaNotification.php b/app/Notifications/Ninja/EmailQuotaNotification.php index 8ef6cbf6ac..45f4fe79d2 100644 --- a/app/Notifications/Ninja/EmailQuotaNotification.php +++ b/app/Notifications/Ninja/EmailQuotaNotification.php @@ -11,7 +11,6 @@ namespace App\Notifications\Ninja; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; @@ -44,7 +43,7 @@ class EmailQuotaNotification extends Notification * Get the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Notifications/Ninja/EmailSpamNotification.php b/app/Notifications/Ninja/EmailSpamNotification.php index ca355ca871..e4cd98b6c5 100644 --- a/app/Notifications/Ninja/EmailSpamNotification.php +++ b/app/Notifications/Ninja/EmailSpamNotification.php @@ -11,7 +11,6 @@ namespace App\Notifications\Ninja; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; diff --git a/app/Notifications/Ninja/GenericNinjaAdminNotification.php b/app/Notifications/Ninja/GenericNinjaAdminNotification.php index 7b40141aa2..ee211d29e5 100644 --- a/app/Notifications/Ninja/GenericNinjaAdminNotification.php +++ b/app/Notifications/Ninja/GenericNinjaAdminNotification.php @@ -56,7 +56,7 @@ class GenericNinjaAdminNotification extends Notification public function toSlack($notifiable) { - $content = ''; + $content = ''; foreach($this->message_array as $message) { $content .= $message . "\n"; diff --git a/app/Notifications/Ninja/GmailCredentialNotification.php b/app/Notifications/Ninja/GmailCredentialNotification.php index 0bc8226e1e..893c5cc09a 100644 --- a/app/Notifications/Ninja/GmailCredentialNotification.php +++ b/app/Notifications/Ninja/GmailCredentialNotification.php @@ -11,7 +11,6 @@ namespace App\Notifications\Ninja; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; @@ -44,7 +43,7 @@ class GmailCredentialNotification extends Notification * Get the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Notifications/Ninja/NewAccountCreated.php b/app/Notifications/Ninja/NewAccountCreated.php index 77d570caa4..7e0636f4c3 100644 --- a/app/Notifications/Ninja/NewAccountCreated.php +++ b/app/Notifications/Ninja/NewAccountCreated.php @@ -13,7 +13,6 @@ namespace App\Notifications\Ninja; use Illuminate\Bus\Queueable; use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; use Illuminate\Queue\InteractsWithQueue; @@ -57,7 +56,7 @@ class NewAccountCreated extends Notification * Get the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Notifications/Ninja/NewAccountNotification.php b/app/Notifications/Ninja/NewAccountNotification.php index 2b21589768..2d7afdebe8 100644 --- a/app/Notifications/Ninja/NewAccountNotification.php +++ b/app/Notifications/Ninja/NewAccountNotification.php @@ -13,7 +13,6 @@ namespace App\Notifications\Ninja; use App\Models\Account; use App\Models\Client; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; @@ -50,7 +49,7 @@ class NewAccountNotification extends Notification * Get the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Notifications/Ninja/RenewalFailureNotification.php b/app/Notifications/Ninja/RenewalFailureNotification.php index 6d9adfc865..8231395b4f 100644 --- a/app/Notifications/Ninja/RenewalFailureNotification.php +++ b/app/Notifications/Ninja/RenewalFailureNotification.php @@ -11,7 +11,6 @@ namespace App\Notifications\Ninja; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; @@ -42,7 +41,7 @@ class RenewalFailureNotification extends Notification * Get the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Notifications/Ninja/SpamNotification.php b/app/Notifications/Ninja/SpamNotification.php index d81e790971..bf895e89de 100644 --- a/app/Notifications/Ninja/SpamNotification.php +++ b/app/Notifications/Ninja/SpamNotification.php @@ -11,7 +11,6 @@ namespace App\Notifications\Ninja; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; @@ -45,7 +44,7 @@ class SpamNotification extends Notification * Get the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Notifications/Ninja/UserQualityNotification.php b/app/Notifications/Ninja/UserQualityNotification.php index 4e1780665e..0a8dfc89f9 100644 --- a/app/Notifications/Ninja/UserQualityNotification.php +++ b/app/Notifications/Ninja/UserQualityNotification.php @@ -12,7 +12,6 @@ namespace App\Notifications\Ninja; use App\Models\User; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; @@ -49,7 +48,7 @@ class UserQualityNotification extends Notification * Get the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Notifications/Ninja/WePayFailureNotification.php b/app/Notifications/Ninja/WePayFailureNotification.php index e11e321c46..4af13a2b88 100644 --- a/app/Notifications/Ninja/WePayFailureNotification.php +++ b/app/Notifications/Ninja/WePayFailureNotification.php @@ -11,7 +11,6 @@ namespace App\Notifications\Ninja; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; @@ -44,7 +43,7 @@ class WePayFailureNotification extends Notification * Get the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Notifications/ResetPasswordNotification.php b/app/Notifications/ResetPasswordNotification.php index f3fc5497d4..84a16d032f 100644 --- a/app/Notifications/ResetPasswordNotification.php +++ b/app/Notifications/ResetPasswordNotification.php @@ -3,13 +3,12 @@ namespace App\Notifications; use Illuminate\Bus\Queueable; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; //@deprecated class ResetPasswordNotification extends Notification { -// use Queueable; + // use Queueable; public $token; @@ -38,7 +37,7 @@ class ResetPasswordNotification extends Notification * Get the mail representation of the notification. * * @param mixed $notifiable - * + * */ public function toMail($notifiable) { diff --git a/app/Observers/ClientObserver.php b/app/Observers/ClientObserver.php index e33316e183..902e111e53 100644 --- a/app/Observers/ClientObserver.php +++ b/app/Observers/ClientObserver.php @@ -11,44 +11,44 @@ namespace App\Observers; +use App\Jobs\Client\CheckVat; +use App\Jobs\Client\UpdateTaxData; +use App\Jobs\Util\WebhookHandler; use App\Models\Client; use App\Models\Webhook; -use App\Jobs\Client\CheckVat; -use App\Jobs\Util\WebhookHandler; -use App\Jobs\Client\UpdateTaxData; class ClientObserver { public $afterCommit = true; private $eu_country_codes = [ - 'AT' => '40', - 'BE' => '56', - 'BG' => '100', - 'CY' => '196', - 'CZ' => '203', - 'DE' => '276', - 'DK' => '208', - 'EE' => '233', - 'ES' => '724', - 'FI' => '246', - 'FR' => '250', - 'GR' => '300', - 'HR' => '191', - 'HU' => '348', - 'IE' => '372', - 'IT' => '380', - 'LT' => '440', - 'LU' => '442', - 'LV' => '428', - 'MT' => '470', - 'NL' => '528', - 'PL' => '616', - 'PT' => '620', - 'RO' => '642', - 'SE' => '752', - 'SI' => '705', - 'SK' => '703', + 'AT' => '40', + 'BE' => '56', + 'BG' => '100', + 'CY' => '196', + 'CZ' => '203', + 'DE' => '276', + 'DK' => '208', + 'EE' => '233', + 'ES' => '724', + 'FI' => '246', + 'FR' => '250', + 'GR' => '300', + 'HR' => '191', + 'HU' => '348', + 'IE' => '372', + 'IT' => '380', + 'LT' => '440', + 'LU' => '442', + 'LV' => '428', + 'MT' => '470', + 'NL' => '528', + 'PL' => '616', + 'PT' => '620', + 'RO' => '642', + 'SE' => '752', + 'SI' => '705', + 'SK' => '703', ]; /** diff --git a/app/Observers/CompanyObserver.php b/app/Observers/CompanyObserver.php index 15ed252749..28dda997c8 100644 --- a/app/Observers/CompanyObserver.php +++ b/app/Observers/CompanyObserver.php @@ -36,8 +36,9 @@ class CompanyObserver */ public function updated(Company $company) { - if (Ninja::isHosted() && $company->portal_mode == 'domain' && $company->isDirty('portal_domain')) + if (Ninja::isHosted() && $company->portal_mode == 'domain' && $company->isDirty('portal_domain')) { \Modules\Admin\Jobs\Domain\CustomDomain::dispatch($company->getOriginal('portal_domain'), $company)->onQueue('domain'); + } } diff --git a/app/PaymentDrivers/Authorize/AuthorizeCreateCustomer.php b/app/PaymentDrivers/Authorize/AuthorizeCreateCustomer.php index 8a65fc375a..c1ad0e744d 100644 --- a/app/PaymentDrivers/Authorize/AuthorizeCreateCustomer.php +++ b/app/PaymentDrivers/Authorize/AuthorizeCreateCustomer.php @@ -12,15 +12,15 @@ namespace App\PaymentDrivers\Authorize; +use App\Exceptions\GenericPaymentDriverFailure; use App\Models\Client; use App\PaymentDrivers\AuthorizePaymentDriver; -use App\Exceptions\GenericPaymentDriverFailure; +use net\authorize\api\contract\v1\CreateCustomerProfileRequest; use net\authorize\api\contract\v1\CustomerAddressType; use net\authorize\api\contract\v1\CustomerProfileType; use net\authorize\api\contract\v1\GetCustomerProfileRequest; -use net\authorize\api\controller\GetCustomerProfileController; -use net\authorize\api\contract\v1\CreateCustomerProfileRequest; use net\authorize\api\controller\CreateCustomerProfileController; +use net\authorize\api\controller\GetCustomerProfileController; /** * Class BaseDriver. @@ -137,27 +137,27 @@ class AuthorizeCreateCustomer } // This is how we can harvest client profiles and attach them within Invoice Ninja -// $request = new net\authorize\api\contract\v1\GetCustomerProfileRequest(); -// $request->setMerchantAuthentication($driver->merchant_authentication); -// $request->setCustomerProfileId($gateway_customer_reference); -// $controller = new net\authorize\api\controller\GetCustomerProfileController($request); -// $response = $controller->executeWithApiResponse($driver->mode()); + // $request = new net\authorize\api\contract\v1\GetCustomerProfileRequest(); + // $request->setMerchantAuthentication($driver->merchant_authentication); + // $request->setCustomerProfileId($gateway_customer_reference); + // $controller = new net\authorize\api\controller\GetCustomerProfileController($request); + // $response = $controller->executeWithApiResponse($driver->mode()); -// if (($response != null) && ($response->getMessages()->getResultCode() == "Ok") ) -// { -// echo "GetCustomerProfile SUCCESS : " . "\n"; -// $profileSelected = $response->getProfile(); -// $paymentProfilesSelected = $profileSelected->getPaymentProfiles(); -// echo "Profile Has " . count($paymentProfilesSelected). " Payment Profiles" . "\n"; + // if (($response != null) && ($response->getMessages()->getResultCode() == "Ok") ) + // { + // echo "GetCustomerProfile SUCCESS : " . "\n"; + // $profileSelected = $response->getProfile(); + // $paymentProfilesSelected = $profileSelected->getPaymentProfiles(); + // echo "Profile Has " . count($paymentProfilesSelected). " Payment Profiles" . "\n"; -// foreach ($profileSelected->getPaymentProfiles() as $paymentProfile) { -// echo "\nCustomer Profile ID: " . $paymentProfile->getCustomerProfileId() . "\n"; -// echo "Payment profile ID: " . $paymentProfile->getCustomerPaymentProfileId() . "\n"; -// echo "Credit Card Number: " . $paymentProfile->getPayment()->getCreditCard()->getCardNumber() . "\n"; -// if ($paymentProfile->getBillTo() != null) { -// echo "First Name in Billing Address: " . $paymentProfile->getBillTo()->getFirstName() . "\n"; -// } -// } + // foreach ($profileSelected->getPaymentProfiles() as $paymentProfile) { + // echo "\nCustomer Profile ID: " . $paymentProfile->getCustomerProfileId() . "\n"; + // echo "Payment profile ID: " . $paymentProfile->getCustomerPaymentProfileId() . "\n"; + // echo "Credit Card Number: " . $paymentProfile->getPayment()->getCreditCard()->getCardNumber() . "\n"; + // if ($paymentProfile->getBillTo() != null) { + // echo "First Name in Billing Address: " . $paymentProfile->getBillTo()->getFirstName() . "\n"; + // } + // } } // $request = new net\authorize\api\contract\v1\GetCustomerProfileIdsRequest(); @@ -174,7 +174,7 @@ class AuthorizeCreateCustomer // $request->setCustomerProfileId($customer_profile_id); // $controller = new net\authorize\api\controller\GetCustomerProfileController($request); // $response = $controller->executeWithApiResponse($auth->mode()); - + // $profileSelected = $response->getProfile(); // if($profileSelected->getEmail() == 'katnandan@gmail.com') diff --git a/app/PaymentDrivers/Authorize/AuthorizeCreditCard.php b/app/PaymentDrivers/Authorize/AuthorizeCreditCard.php index e1cbb863ce..be6dd72bca 100644 --- a/app/PaymentDrivers/Authorize/AuthorizeCreditCard.php +++ b/app/PaymentDrivers/Authorize/AuthorizeCreditCard.php @@ -12,21 +12,20 @@ namespace App\PaymentDrivers\Authorize; -use App\Models\Payment; -use App\Models\SystemLog; +use App\Exceptions\PaymentFailed; +use App\Jobs\Util\SystemLogger; +use App\Models\ClientGatewayToken; use App\Models\GatewayType; +use App\Models\Payment; use App\Models\PaymentHash; use App\Models\PaymentType; -use App\Jobs\Util\SystemLogger; -use App\Utils\Traits\MakesHash; -use App\Exceptions\PaymentFailed; -use App\Models\ClientGatewayToken; +use App\Models\SystemLog; use App\PaymentDrivers\AuthorizePaymentDriver; -use App\PaymentDrivers\Authorize\AuthorizeTransaction; -use net\authorize\api\contract\v1\DeleteCustomerProfileRequest; -use net\authorize\api\controller\DeleteCustomerProfileController; +use App\Utils\Traits\MakesHash; use net\authorize\api\contract\v1\DeleteCustomerPaymentProfileRequest; +use net\authorize\api\contract\v1\DeleteCustomerProfileRequest; use net\authorize\api\controller\DeleteCustomerPaymentProfileController; +use net\authorize\api\controller\DeleteCustomerProfileController; /** * Class AuthorizeCreditCard. @@ -118,7 +117,7 @@ class AuthorizeCreditCard // $response = $controller->executeWithApiResponse($this->authorize->mode()); // if (($response != null) && ($response->getMessages()->getResultCode() == "Ok") ) // { - // nlog("SUCCESS: Delete Customer Payment Profile SUCCESS"); + // nlog("SUCCESS: Delete Customer Payment Profile SUCCESS"); // } // else // nlog("unable to delete profile {$customer_profile_id}"); diff --git a/app/PaymentDrivers/Authorize/AuthorizeCustomer.php b/app/PaymentDrivers/Authorize/AuthorizeCustomer.php index 3e51b270db..a72fe5efe5 100644 --- a/app/PaymentDrivers/Authorize/AuthorizeCustomer.php +++ b/app/PaymentDrivers/Authorize/AuthorizeCustomer.php @@ -99,7 +99,7 @@ class AuthorizeCustomer $client = $client_gateway_token->client; } elseif ($client_contact = ClientContact::where('company_id', $company->id)->where('email', $profile['email'])->first()) { $client = $client_contact->client; - // nlog("found client through contact"); + // nlog("found client through contact"); } else { // nlog("creating client"); @@ -139,7 +139,7 @@ class AuthorizeCustomer continue; } -// $expiry = $payment_profile->getPayment()->getCreditCard()->getExpirationDate(); + // $expiry = $payment_profile->getPayment()->getCreditCard()->getExpirationDate(); $payment_meta = new \stdClass; $payment_meta->exp_month = 'xx'; diff --git a/app/PaymentDrivers/Authorize/AuthorizeTransaction.php b/app/PaymentDrivers/Authorize/AuthorizeTransaction.php index 20f184a17f..b727bf3745 100644 --- a/app/PaymentDrivers/Authorize/AuthorizeTransaction.php +++ b/app/PaymentDrivers/Authorize/AuthorizeTransaction.php @@ -13,17 +13,15 @@ namespace App\PaymentDrivers\Authorize; use App\Models\Invoice; -use App\Utils\Traits\MakesHash; -use net\authorize\api\contract\v1\OrderType; use App\PaymentDrivers\AuthorizePaymentDriver; +use App\Utils\Traits\MakesHash; +use net\authorize\api\contract\v1\CreateTransactionRequest; +use net\authorize\api\contract\v1\ExtendedAmountType; +use net\authorize\api\contract\v1\OpaqueDataType; +use net\authorize\api\contract\v1\OrderType; use net\authorize\api\contract\v1\PaymentType; use net\authorize\api\contract\v1\SettingType; -use net\authorize\api\contract\v1\OpaqueDataType; -use net\authorize\api\contract\v1\ExtendedAmountType; -use net\authorize\api\contract\v1\PaymentProfileType; use net\authorize\api\contract\v1\TransactionRequestType; -use net\authorize\api\contract\v1\CreateTransactionRequest; -use net\authorize\api\contract\v1\CustomerProfilePaymentType; use net\authorize\api\controller\CreateTransactionController; /** @@ -110,7 +108,7 @@ class AuthorizeTransaction $billto->setPhoneNumber(substr($this->authorize->client->phone, 0, 20)); } -//Assign to the transactionRequest field + //Assign to the transactionRequest field $transactionRequestType = new TransactionRequestType(); $transactionRequestType->setTransactionType('authCaptureTransaction'); @@ -123,8 +121,9 @@ class AuthorizeTransaction $transactionRequestType->setPayment($paymentOne); $transactionRequestType->setCurrencyCode($this->authorize->client->currency()->code); - if($billto) + if($billto) { $transactionRequestType->setBillTo($billto); + } $request = new CreateTransactionRequest(); $request->setMerchantAuthentication($this->authorize->merchant_authentication); diff --git a/app/PaymentDrivers/Authorize/RefundTransaction.php b/app/PaymentDrivers/Authorize/RefundTransaction.php index 998ffbad33..a667598a0e 100644 --- a/app/PaymentDrivers/Authorize/RefundTransaction.php +++ b/app/PaymentDrivers/Authorize/RefundTransaction.php @@ -12,16 +12,16 @@ namespace App\PaymentDrivers\Authorize; +use App\Jobs\Util\SystemLogger; use App\Models\Payment; use App\Models\SystemLog; -use App\Jobs\Util\SystemLogger; use App\PaymentDrivers\AuthorizePaymentDriver; -use net\authorize\api\contract\v1\PaymentType; -use net\authorize\api\contract\v1\CreditCardType; -use net\authorize\api\contract\v1\PaymentProfileType; -use net\authorize\api\contract\v1\TransactionRequestType; use net\authorize\api\contract\v1\CreateTransactionRequest; +use net\authorize\api\contract\v1\CreditCardType; use net\authorize\api\contract\v1\CustomerProfilePaymentType; +use net\authorize\api\contract\v1\PaymentProfileType; +use net\authorize\api\contract\v1\PaymentType; +use net\authorize\api\contract\v1\TransactionRequestType; use net\authorize\api\controller\CreateTransactionController; /** diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index 7919cbe238..9631b3b97c 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -15,7 +15,6 @@ use App\Events\Invoice\InvoiceWasPaid; use App\Events\Payment\PaymentWasCreated; use App\Exceptions\PaymentFailed; use App\Factory\PaymentFactory; -use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; use App\Jobs\Mail\NinjaMailer; use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; @@ -59,7 +58,7 @@ class BaseDriver extends AbstractPaymentDriver /** * The Client * - * @var \App\Models\Client|null $client + * @var \App\Models\Client|null $client */ public $client; @@ -733,19 +732,21 @@ class BaseDriver extends AbstractPaymentDriver $t->replace(Ninja::transformTranslations($this->client->getMergedSettings())); App::setLocale($this->client->company->locale()); - if (! $this->payment_hash || !$this->client) + if (! $this->payment_hash || !$this->client) { return 'Descriptor'; + } $invoices_string = \implode(', ', collect($this->payment_hash->invoices())->pluck('invoice_number')->toArray()) ?: null; - if (!$invoices_string) + if (!$invoices_string) { return str_replace(["*","<",">","'",'"'], "", $this->client->company->present()->name()); + } $invoices_string = str_replace(["*","<",">","'",'"'], "-", $invoices_string); $invoices_string = "I-".$invoices_string; - $invoices_string = substr($invoices_string,0,22); + $invoices_string = substr($invoices_string, 0, 22); $invoices_string = str_pad($invoices_string, 5, ctrans('texts.invoice'), STR_PAD_LEFT); @@ -769,7 +770,7 @@ class BaseDriver extends AbstractPaymentDriver $invoices_string = \implode(', ', collect($this->payment_hash->invoices())->pluck('invoice_number')->toArray()) ?: null; $amount = Number::formatMoney($this->payment_hash?->amount_with_fee() ?? 0, $this->client); - if($abbreviated && $invoices_string){ + if($abbreviated && $invoices_string) { return $invoices_string; } elseif ($abbreviated || ! $invoices_string) { return ctrans('texts.gateway_payment_text_no_invoice', [ diff --git a/app/PaymentDrivers/Braintree/ACH.php b/app/PaymentDrivers/Braintree/ACH.php index da8e220d3a..5dbbb56e65 100644 --- a/app/PaymentDrivers/Braintree/ACH.php +++ b/app/PaymentDrivers/Braintree/ACH.php @@ -42,8 +42,7 @@ class ACH implements MethodInterface try { $data['gateway'] = $this->braintree; $data['client_token'] = $this->braintree->gateway->clientToken()->generate(); - } - catch(\Exception $e){ + } catch(\Exception $e) { throw new PaymentFailed("Unable to generate client token, check your Braintree credentials. Error: " . $e->getMessage(), 500); diff --git a/app/PaymentDrivers/CheckoutCom/CheckoutSetupWebhook.php b/app/PaymentDrivers/CheckoutCom/CheckoutSetupWebhook.php index d35d07bd22..5c61547d21 100644 --- a/app/PaymentDrivers/CheckoutCom/CheckoutSetupWebhook.php +++ b/app/PaymentDrivers/CheckoutCom/CheckoutSetupWebhook.php @@ -11,28 +11,21 @@ namespace App\PaymentDrivers\CheckoutCom; -use App\Models\Payment; -use App\Models\SystemLog; use App\Libraries\MultiDB; -use App\Models\GatewayType; -use App\Models\PaymentHash; -use App\Models\PaymentType; -use Illuminate\Bus\Queueable; use App\Models\CompanyGateway; -use App\Jobs\Util\SystemLogger; -use Checkout\CheckoutApiException; -use Illuminate\Queue\SerializesModels; -use App\PaymentDrivers\Stripe\Utilities; -use Illuminate\Queue\InteractsWithQueue; -use App\PaymentDrivers\CheckoutCom\Webhook; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Foundation\Bus\Dispatchable; -use Checkout\CheckoutAuthorizationException; -use Checkout\Workflows\CreateWorkflowRequest; use App\PaymentDrivers\CheckoutComPaymentDriver; +use App\PaymentDrivers\Stripe\Utilities; +use Checkout\CheckoutApiException; +use Checkout\CheckoutAuthorizationException; use Checkout\Workflows\Actions\WebhookSignature; use Checkout\Workflows\Actions\WebhookWorkflowActionRequest; use Checkout\Workflows\Conditions\EventWorkflowConditionRequest; +use Checkout\Workflows\CreateWorkflowRequest; +use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\SerializesModels; class CheckoutSetupWebhook implements ShouldQueue { @@ -68,8 +61,9 @@ class CheckoutSetupWebhook implements ShouldQueue return $workflow['name'] == $this->authentication_webhook_name; }); - if($wf) + if($wf) { return; + } $this->createAuthenticationWorkflow(); } diff --git a/app/PaymentDrivers/CheckoutCom/CheckoutWebhook.php b/app/PaymentDrivers/CheckoutCom/CheckoutWebhook.php index c7c1069c63..2d162f9e8e 100644 --- a/app/PaymentDrivers/CheckoutCom/CheckoutWebhook.php +++ b/app/PaymentDrivers/CheckoutCom/CheckoutWebhook.php @@ -11,20 +11,20 @@ namespace App\PaymentDrivers\CheckoutCom; -use App\Models\Payment; -use App\Models\SystemLog; +use App\Jobs\Util\SystemLogger; use App\Libraries\MultiDB; +use App\Models\CompanyGateway; use App\Models\GatewayType; +use App\Models\Payment; use App\Models\PaymentHash; use App\Models\PaymentType; -use Illuminate\Bus\Queueable; -use App\Models\CompanyGateway; -use App\Jobs\Util\SystemLogger; -use Illuminate\Queue\SerializesModels; +use App\Models\SystemLog; use App\PaymentDrivers\Stripe\Utilities; -use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\SerializesModels; class CheckoutWebhook implements ShouldQueue { @@ -48,10 +48,11 @@ class CheckoutWebhook implements ShouldQueue $this->company_gateway = CompanyGateway::withTrashed()->find($this->company_gateway_id); - if(!isset($this->webhook_array['type'])) + if(!isset($this->webhook_array['type'])) { nlog("Checkout Webhook type not set"); + } - match($this->webhook_array['type']){ + match($this->webhook_array['type']) { 'payment_approved' => $this->paymentApproved(), }; @@ -62,7 +63,7 @@ class CheckoutWebhook implements ShouldQueue * "id":"evt_dli6ty4qo5vuxle5wklf5gwbwy","type":"payment_approved","version":"1.0.33","created_on":"2023-07-21T10:03:07.1555904Z", * "data":{"id":"pay_oqwbsd22kvpuvd35y5fhbdawxa","action_id":"act_buviezur7zsurnsorcgfn63e44","reference":"0014","amount":584168,"auth_code":"113059","currency":"USD","customer":{"id":"cus_6n4yt4q5kf4unn36o5qpbevxhe","email":"cypress@example.com"}, * "metadata":{"udf1":"Invoice Ninja","udf2":"ofhgiGjyQXbsbUwygURfYFT2C3E7iY7U"},"payment_type":"Regular","processed_on":"2023-07-21T10:02:57.4678165Z","processing":{"acquirer_transaction_id":"645272142084717830381","retrieval_reference_number":"183042259107"},"response_code":"10000","response_summary":"Approved","risk":{"flagged":false,"score":0},"3ds":{"version":"2.2.0","challenged":true,"challenge_indicator":"no_preference","exemption":"none","eci":"05","cavv":"AAABAVIREQAAAAAAAAAAAAAAAAA=","xid":"74afa3ac-25d3-4d95-b815-cefbdd7c8270","downgraded":false,"enrolled":"Y","authentication_response":"Y","flow_type":"challenged"},"scheme_id":"114455763095262", - * "source":{"id":"src_ghavmefpetjellmteqwj5jjcli","type":"card","billing_address":{},"expiry_month":10,"expiry_year":2025,"scheme":"VISA","last_4":"4242","fingerprint":"BD864B08D0B098DD83052A038FD2BA967DF2D48E375AAEEF54E37BC36B385E9A","bin":"424242","card_type":"CREDIT","card_category":"CONSUMER","issuer_country":"GB","product_id":"F","product_type":"Visa Classic","avs_check":"G","cvv_check":"Y"},"balances":{"total_authorized":584168,"total_voided":0,"available_to_void":584168,"total_captured":0,"available_to_capture":584168,"total_refunded":0,"available_to_refund":0},"event_links":{"payment":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa","payment_actions":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/actions","capture":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/captures","void":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/voids"}},"_links":{"self":{"href":"https://api.sandbox.checkout.com/workflows/events/evt_dli6ty4qo5vuxle5wklf5gwbwy"},"subject":{"href":"https://api.sandbox.checkout.com/workflows/events/subject/pay_oqwbsd22kvpuvd35y5fhbdawxa"},"payment":{"href":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa"},"payment_actions":{"href":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/actions"},"capture":{"href":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/captures"},"void":{"href":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/voids"}}} + * "source":{"id":"src_ghavmefpetjellmteqwj5jjcli","type":"card","billing_address":{},"expiry_month":10,"expiry_year":2025,"scheme":"VISA","last_4":"4242","fingerprint":"BD864B08D0B098DD83052A038FD2BA967DF2D48E375AAEEF54E37BC36B385E9A","bin":"424242","card_type":"CREDIT","card_category":"CONSUMER","issuer_country":"GB","product_id":"F","product_type":"Visa Classic","avs_check":"G","cvv_check":"Y"},"balances":{"total_authorized":584168,"total_voided":0,"available_to_void":584168,"total_captured":0,"available_to_capture":584168,"total_refunded":0,"available_to_refund":0},"event_links":{"payment":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa","payment_actions":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/actions","capture":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/captures","void":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/voids"}},"_links":{"self":{"href":"https://api.sandbox.checkout.com/workflows/events/evt_dli6ty4qo5vuxle5wklf5gwbwy"},"subject":{"href":"https://api.sandbox.checkout.com/workflows/events/subject/pay_oqwbsd22kvpuvd35y5fhbdawxa"},"payment":{"href":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa"},"payment_actions":{"href":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/actions"},"capture":{"href":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/captures"},"void":{"href":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/voids"}}} */ private function paymentApproved() @@ -71,10 +72,11 @@ class CheckoutWebhook implements ShouldQueue $payment = Payment::query()->withTrashed()->where('transaction_reference', $payment_object['id'])->first(); - if($payment && $payment->status_id == Payment::STATUS_COMPLETED) + if($payment && $payment->status_id == Payment::STATUS_COMPLETED) { return; + } - if($payment){ + if($payment) { $payment->status_id = Payment::STATUS_COMPLETED; $payment->save(); return; @@ -89,8 +91,8 @@ class CheckoutWebhook implements ShouldQueue $driver = $this->company_gateway->driver($payment_hash->fee_invoice->client)->init()->setPaymentMethod(); $payment_hash->data = array_merge((array) $payment_hash->data, $this->webhook_array); // @phpstan-ignore-line - $payment_hash->save(); - $driver->setPaymentHash($payment_hash); + $payment_hash->save(); + $driver->setPaymentHash($payment_hash); // @phpstan-ignore-line $data = [ diff --git a/app/PaymentDrivers/CheckoutCom/CreditCard.php b/app/PaymentDrivers/CheckoutCom/CreditCard.php index 8a0166eb0c..5436ce35ba 100644 --- a/app/PaymentDrivers/CheckoutCom/CreditCard.php +++ b/app/PaymentDrivers/CheckoutCom/CreditCard.php @@ -12,25 +12,25 @@ namespace App\PaymentDrivers\CheckoutCom; -use App\Models\SystemLog; -use Illuminate\View\View; -use App\Models\GatewayType; -use Illuminate\Http\Request; -use App\Jobs\Util\SystemLogger; -use App\Utils\Traits\MakesHash; use App\Exceptions\PaymentFailed; +use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; +use App\Jobs\Util\SystemLogger; use App\Models\ClientGatewayToken; +use App\Models\GatewayType; +use App\Models\SystemLog; +use App\PaymentDrivers\CheckoutComPaymentDriver; +use App\PaymentDrivers\Common\MethodInterface; +use App\Utils\Traits\MakesHash; use Checkout\CheckoutApiException; -use Illuminate\Contracts\View\Factory; use Checkout\CheckoutArgumentException; use Checkout\CheckoutAuthorizationException; -use Checkout\Payments\Request\PaymentRequest; -use App\PaymentDrivers\Common\MethodInterface; -use App\PaymentDrivers\CheckoutComPaymentDriver; -use Checkout\Payments\Previous\Source\RequestTokenSource; -use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; use Checkout\Payments\Previous\PaymentRequest as PreviousPaymentRequest; +use Checkout\Payments\Previous\Source\RequestTokenSource; +use Checkout\Payments\Request\PaymentRequest; use Checkout\Payments\Request\Source\RequestTokenSource as SourceRequestTokenSource; +use Illuminate\Contracts\View\Factory; +use Illuminate\Http\Request; +use Illuminate\View\View; class CreditCard implements MethodInterface { @@ -125,8 +125,7 @@ class CreditCard implements MethodInterface if (isset($e->error_details['error_codes']) ?? false) { $error_details = end($e->error_details['error_codes']); - } - else { + } else { $error_details = $e->getMessage(); } diff --git a/app/PaymentDrivers/CheckoutCom/Utilities.php b/app/PaymentDrivers/CheckoutCom/Utilities.php index 73b0710a62..296f506740 100644 --- a/app/PaymentDrivers/CheckoutCom/Utilities.php +++ b/app/PaymentDrivers/CheckoutCom/Utilities.php @@ -12,13 +12,12 @@ namespace App\PaymentDrivers\CheckoutCom; -use stdClass; -use Exception; -use App\Models\SystemLog; -use App\Models\GatewayType; -use App\Jobs\Util\SystemLogger; use App\Exceptions\PaymentFailed; -use Checkout\Payments\PaymentType; +use App\Jobs\Util\SystemLogger; +use App\Models\GatewayType; +use App\Models\SystemLog; +use Exception; +use stdClass; trait Utilities { diff --git a/app/PaymentDrivers/CheckoutCom/Webhook.php b/app/PaymentDrivers/CheckoutCom/Webhook.php index 8fc5744762..2fd3ab51c0 100644 --- a/app/PaymentDrivers/CheckoutCom/Webhook.php +++ b/app/PaymentDrivers/CheckoutCom/Webhook.php @@ -12,25 +12,9 @@ namespace App\PaymentDrivers\CheckoutCom; -use App\Exceptions\PaymentFailed; -use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; -use App\Jobs\Util\SystemLogger; -use App\Models\ClientGatewayToken; -use App\Models\GatewayType; -use App\Models\SystemLog; use App\PaymentDrivers\CheckoutComPaymentDriver; -use App\PaymentDrivers\Common\MethodInterface; -use App\Utils\Traits\MakesHash; use Checkout\CheckoutApiException; -use Checkout\CheckoutArgumentException; use Checkout\CheckoutAuthorizationException; -use Checkout\Payments\Four\Request\PaymentRequest; -use Checkout\Payments\Four\Request\Source\RequestTokenSource; -use Checkout\Payments\PaymentRequest as PaymentsPaymentRequest; -use Checkout\Payments\Source\RequestTokenSource as SourceRequestTokenSource; -use Illuminate\Contracts\View\Factory; -use Illuminate\Http\Request; -use Illuminate\View\View; class Webhook { @@ -86,4 +70,4 @@ class Webhook } -} \ No newline at end of file +} diff --git a/app/PaymentDrivers/CheckoutComPaymentDriver.php b/app/PaymentDrivers/CheckoutComPaymentDriver.php index 8c6c92b6af..7f4fea2ab7 100644 --- a/app/PaymentDrivers/CheckoutComPaymentDriver.php +++ b/app/PaymentDrivers/CheckoutComPaymentDriver.php @@ -148,7 +148,7 @@ class CheckoutComPaymentDriver extends BaseDriver /** * Process different view depending on payment type - * + * * @param int $gateway_type_id The gateway type * @return string The view string */ diff --git a/app/PaymentDrivers/GoCardless/ACH.php b/app/PaymentDrivers/GoCardless/ACH.php index 2f1c5f6a0b..78258f9ab2 100644 --- a/app/PaymentDrivers/GoCardless/ACH.php +++ b/app/PaymentDrivers/GoCardless/ACH.php @@ -142,7 +142,7 @@ class ACH implements MethodInterface * Show the payment page for ACH. * * @param array $data - * @return \Illuminate\View\View + * @return \Illuminate\View\View */ public function paymentView(array $data): View { diff --git a/app/PaymentDrivers/GoCardless/DirectDebit.php b/app/PaymentDrivers/GoCardless/DirectDebit.php index 48274891e8..f27696d862 100644 --- a/app/PaymentDrivers/GoCardless/DirectDebit.php +++ b/app/PaymentDrivers/GoCardless/DirectDebit.php @@ -97,8 +97,8 @@ class DirectDebit implements MethodInterface * } * } * } - * - * + * + * */ public function billingRequestFlows(array $data) { @@ -170,7 +170,7 @@ class DirectDebit implements MethodInterface public function authorizeResponse(Request $request) { - try{ + try { $billing_request = $this->go_cardless->gateway->billingRequests()->get($request->billing_request); @@ -194,8 +194,7 @@ class DirectDebit implements MethodInterface return redirect()->route('client.payment_methods.show', $payment_method->hashed_id); - } - catch (\Exception $exception) { + } catch (\Exception $exception) { return $this->processUnsuccessfulAuthorization($exception); } @@ -242,7 +241,7 @@ class DirectDebit implements MethodInterface * Payment view for Direct Debit. * * @param array $data - * @return \Illuminate\View\View + * @return \Illuminate\View\View */ public function paymentView(array $data): View { diff --git a/app/PaymentDrivers/GoCardless/SEPA.php b/app/PaymentDrivers/GoCardless/SEPA.php index 458b34d9e2..793f1f2b9f 100644 --- a/app/PaymentDrivers/GoCardless/SEPA.php +++ b/app/PaymentDrivers/GoCardless/SEPA.php @@ -141,7 +141,7 @@ class SEPA implements MethodInterface * Payment view for SEPA. * * @param array $data - * @return \Illuminate\View\View + * @return \Illuminate\View\View */ public function paymentView(array $data): View { diff --git a/app/PaymentDrivers/GoCardlessPaymentDriver.php b/app/PaymentDrivers/GoCardlessPaymentDriver.php index bd461197f6..3201890465 100644 --- a/app/PaymentDrivers/GoCardlessPaymentDriver.php +++ b/app/PaymentDrivers/GoCardlessPaymentDriver.php @@ -103,8 +103,7 @@ class GoCardlessPaymentDriver extends BaseDriver 'access_token' => $this->company_gateway->getConfigField('accessToken'), 'environment' => $this->company_gateway->getConfigField('testMode') ? \GoCardlessPro\Environment::SANDBOX : \GoCardlessPro\Environment::LIVE, ]); - } - catch(\GoCardlessPro\Core\Exception\AuthenticationException $e){ + } catch(\GoCardlessPro\Core\Exception\AuthenticationException $e) { throw new \Exception('GoCardless: Invalid Access Token', 403); diff --git a/app/PaymentDrivers/Mollie/Bancontact.php b/app/PaymentDrivers/Mollie/Bancontact.php index 8aaed1bd61..6af012b2f3 100644 --- a/app/PaymentDrivers/Mollie/Bancontact.php +++ b/app/PaymentDrivers/Mollie/Bancontact.php @@ -40,7 +40,7 @@ class Bancontact implements MethodInterface * Show the authorization page for Bancontact. * * @param array $data - * @return \Illuminate\View\View + * @return \Illuminate\View\View */ public function authorizeView(array $data): View { diff --git a/app/PaymentDrivers/Mollie/BankTransfer.php b/app/PaymentDrivers/Mollie/BankTransfer.php index 01cfc9c3c7..83d9ced223 100644 --- a/app/PaymentDrivers/Mollie/BankTransfer.php +++ b/app/PaymentDrivers/Mollie/BankTransfer.php @@ -43,7 +43,7 @@ class BankTransfer implements MethodInterface * Show the authorization page for bank transfer. * * @param array $data - * @return \Illuminate\View\View + * @return \Illuminate\View\View */ public function authorizeView(array $data): View { diff --git a/app/PaymentDrivers/Mollie/IDEAL.php b/app/PaymentDrivers/Mollie/IDEAL.php index 3e40df10b0..f14eaeb083 100644 --- a/app/PaymentDrivers/Mollie/IDEAL.php +++ b/app/PaymentDrivers/Mollie/IDEAL.php @@ -40,7 +40,7 @@ class IDEAL implements MethodInterface * Show the authorization page for iDEAL. * * @param array $data - * @return \Illuminate\View\View + * @return \Illuminate\View\View */ public function authorizeView(array $data): View { diff --git a/app/PaymentDrivers/Mollie/KBC.php b/app/PaymentDrivers/Mollie/KBC.php index 6d34591064..c5020cdd49 100644 --- a/app/PaymentDrivers/Mollie/KBC.php +++ b/app/PaymentDrivers/Mollie/KBC.php @@ -40,7 +40,7 @@ class KBC implements MethodInterface * Show the authorization page for KBC. * * @param array $data - * @return \Illuminate\View\View + * @return \Illuminate\View\View */ public function authorizeView(array $data): View { diff --git a/app/PaymentDrivers/PayFastPaymentDriver.php b/app/PaymentDrivers/PayFastPaymentDriver.php index 8e1ce921ac..707aabd999 100644 --- a/app/PaymentDrivers/PayFastPaymentDriver.php +++ b/app/PaymentDrivers/PayFastPaymentDriver.php @@ -53,7 +53,7 @@ class PayFastPaymentDriver extends BaseDriver if ($this->client->currency()->code == 'ZAR') { $types[] = GatewayType::CREDIT_CARD; } - + return $types; } diff --git a/app/PaymentDrivers/PayPalExpressPaymentDriver.php b/app/PaymentDrivers/PayPalExpressPaymentDriver.php index 72c05a6030..7f34936bfc 100644 --- a/app/PaymentDrivers/PayPalExpressPaymentDriver.php +++ b/app/PaymentDrivers/PayPalExpressPaymentDriver.php @@ -129,8 +129,7 @@ class PayPalExpressPaymentDriver extends BaseDriver if ($response->isCancelled() && $this->client->getSetting('enable_client_portal')) { return redirect()->route('client.invoices.index')->with('warning', ctrans('texts.status_cancelled')); - } - elseif($response->isCancelled() && !$this->client->getSetting('enable_client_portal')){ + } elseif($response->isCancelled() && !$this->client->getSetting('enable_client_portal')) { redirect()->route('client.invoices.show', ['invoice' => $this->payment_hash->fee_invoice])->with('warning', ctrans('texts.status_cancelled')); } diff --git a/app/PaymentDrivers/PayPalRestPaymentDriver.php b/app/PaymentDrivers/PayPalRestPaymentDriver.php index f60c4e3b17..efcdba1bfa 100644 --- a/app/PaymentDrivers/PayPalRestPaymentDriver.php +++ b/app/PaymentDrivers/PayPalRestPaymentDriver.php @@ -12,15 +12,15 @@ namespace App\PaymentDrivers; -use Omnipay\Omnipay; -use App\Models\Invoice; -use App\Models\SystemLog; -use App\Models\GatewayType; -use App\Models\PaymentType; -use App\Jobs\Util\SystemLogger; -use App\Utils\Traits\MakesHash; use App\Exceptions\PaymentFailed; +use App\Jobs\Util\SystemLogger; +use App\Models\GatewayType; +use App\Models\Invoice; +use App\Models\PaymentType; +use App\Models\SystemLog; +use App\Utils\Traits\MakesHash; use Illuminate\Support\Facades\Http; +use Omnipay\Omnipay; class PayPalRestPaymentDriver extends BaseDriver { @@ -87,8 +87,9 @@ class PayPalRestPaymentDriver extends BaseDriver public function setPaymentMethod($payment_method_id) { - if(!$payment_method_id) + if(!$payment_method_id) { return $this; + } $this->paypal_payment_method = $this->funding_options[$payment_method_id]; @@ -131,18 +132,18 @@ class PayPalRestPaymentDriver extends BaseDriver { $enums = [ - 3 => 'paypal', - 1 => 'card', - 25 => 'venmo', - // 9 => 'sepa', - // 12 => 'bancontact', - // 17 => 'eps', - // 15 => 'giropay', - // 13 => 'ideal', - // 26 => 'mercadopago', - // 27 => 'mybank', - // 28 => 'paylater', - // 16 => 'p24', + 3 => 'paypal', + 1 => 'card', + 25 => 'venmo', + // 9 => 'sepa', + // 12 => 'bancontact', + // 17 => 'eps', + // 15 => 'giropay', + // 13 => 'ideal', + // 26 => 'mercadopago', + // 27 => 'mybank', + // 28 => 'paylater', + // 16 => 'p24', // 7 => 'sofort' ]; @@ -167,7 +168,7 @@ class PayPalRestPaymentDriver extends BaseDriver $response = json_decode($request['gateway_response'], true); - if($response['status'] == 'COMPLETED' && isset($response['purchase_units'])){ + if($response['status'] == 'COMPLETED' && isset($response['purchase_units'])) { $data = [ 'payment_type' => PaymentType::PAYPAL, @@ -189,8 +190,7 @@ class PayPalRestPaymentDriver extends BaseDriver return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]); - } - else { + } else { SystemLogger::dispatch( ['response' => $response], @@ -211,8 +211,9 @@ class PayPalRestPaymentDriver extends BaseDriver $r = $this->gatewayRequest('/v1/identity/generate-token', 'post', ['body' => '']); - if($r->successful()) + if($r->successful()) { return $r->json()['client_token']; + } throw new PaymentFailed('Unable to gain client token from Paypal. Check your configuration', 401); @@ -235,9 +236,9 @@ class PayPalRestPaymentDriver extends BaseDriver "email_address" => $this->client->present()->email(), "address" => [ "address_line_1" => $this->client->address1, - "address_line_2" => $this->client->address2, - "admin_area_1" => $this->client->city, - "admin_area_2" => $this->client->state, + "address_line_2" => $this->client->address2, + "admin_area_1" => $this->client->city, + "admin_area_2" => $this->client->state, "postal_code" => $this->client->postal_code, "country_code" => $this->client->country->iso_3166_2, ] @@ -282,8 +283,9 @@ class PayPalRestPaymentDriver extends BaseDriver ->withHeaders($this->getHeaders($headers)) ->{$verb}("{$this->api_endpoint_url}{$uri}", $data); - if($r->successful()) + if($r->successful()) { return $r; + } throw new PaymentFailed("Gateway failure - {$r->body()}", 401); @@ -428,5 +430,5 @@ class PayPalRestPaymentDriver extends BaseDriver return 0; } - + } diff --git a/app/PaymentDrivers/PaytracePaymentDriver.php b/app/PaymentDrivers/PaytracePaymentDriver.php index b55e8857c1..830d7aecab 100644 --- a/app/PaymentDrivers/PaytracePaymentDriver.php +++ b/app/PaymentDrivers/PaytracePaymentDriver.php @@ -11,19 +11,19 @@ namespace App\PaymentDrivers; +use App\Exceptions\SystemError; +use App\Http\Requests\Payments\PaymentWebhookRequest; +use App\Jobs\Util\SystemLogger; +use App\Models\ClientGatewayToken; +use App\Models\GatewayType; use App\Models\Invoice; use App\Models\Payment; -use App\Utils\CurlUtils; -use App\Models\SystemLog; -use App\Models\GatewayType; use App\Models\PaymentHash; use App\Models\PaymentType; -use App\Exceptions\SystemError; -use App\Jobs\Util\SystemLogger; -use App\Utils\Traits\MakesHash; -use App\Models\ClientGatewayToken; +use App\Models\SystemLog; use App\PaymentDrivers\PayTrace\CreditCard; -use App\Http\Requests\Payments\PaymentWebhookRequest; +use App\Utils\CurlUtils; +use App\Utils\Traits\MakesHash; class PaytracePaymentDriver extends BaseDriver { diff --git a/app/PaymentDrivers/Razorpay/Hosted.php b/app/PaymentDrivers/Razorpay/Hosted.php index 1d5b42358a..694d55c9ee 100644 --- a/app/PaymentDrivers/Razorpay/Hosted.php +++ b/app/PaymentDrivers/Razorpay/Hosted.php @@ -41,7 +41,7 @@ class Hosted implements MethodInterface * Show the authorization page for Razorpay. * * @param array $data - * @return \Illuminate\View\View + * @return \Illuminate\View\View */ public function authorizeView(array $data): View { @@ -63,7 +63,7 @@ class Hosted implements MethodInterface * Payment view for the Razorpay. * * @param array $data - * @return \Illuminate\View\View + * @return \Illuminate\View\View */ public function paymentView(array $data): View { diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index 080356edc3..55b11b98f6 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -12,23 +12,22 @@ namespace App\PaymentDrivers\Square; +use App\Exceptions\PaymentFailed; +use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; +use App\Jobs\Util\SystemLogger; +use App\Models\ClientGatewayToken; +use App\Models\GatewayType; use App\Models\Invoice; use App\Models\Payment; -use App\Models\SystemLog; -use Illuminate\View\View; -use App\Models\GatewayType; use App\Models\PaymentType; -use Illuminate\Support\Str; -use Illuminate\Http\Request; -use Square\Http\ApiResponse; -use App\Jobs\Util\SystemLogger; -use App\Utils\Traits\MakesHash; -use App\Exceptions\PaymentFailed; -use App\Models\ClientGatewayToken; -use Illuminate\Http\RedirectResponse; -use App\PaymentDrivers\SquarePaymentDriver; +use App\Models\SystemLog; use App\PaymentDrivers\Common\MethodInterface; -use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; +use App\PaymentDrivers\SquarePaymentDriver; +use App\Utils\Traits\MakesHash; +use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; +use Illuminate\View\View; +use Square\Http\ApiResponse; class CreditCard implements MethodInterface { @@ -43,7 +42,7 @@ class CreditCard implements MethodInterface * Authorization page for credit card. * * @param array $data - * @return \Illuminate\View\View + * @return \Illuminate\View\View */ public function authorizeView($data): View { @@ -125,7 +124,7 @@ class CreditCard implements MethodInterface if ($request->shouldUseToken()) { $body->setCustomerId($cgt->gateway_customer_reference); - }elseif ($request->has('verificationToken') && $request->input('verificationToken')) { + } elseif ($request->has('verificationToken') && $request->input('verificationToken')) { $body->setVerificationToken($request->input('verificationToken')); } @@ -135,7 +134,7 @@ class CreditCard implements MethodInterface $body = json_decode($response->getBody()); - if($request->store_card){ + if($request->store_card) { $this->createCard($body->payment->id); } @@ -232,8 +231,7 @@ class CreditCard implements MethodInterface return $this->square_driver->processInternallyFailedPayment($this->square_driver, $e); } - } - else { + } else { throw new PaymentFailed($body->errors[0]->detail, 500); } diff --git a/app/PaymentDrivers/Square/SquareWebhook.php b/app/PaymentDrivers/Square/SquareWebhook.php index 67d656e8c9..42757f8a11 100644 --- a/app/PaymentDrivers/Square/SquareWebhook.php +++ b/app/PaymentDrivers/Square/SquareWebhook.php @@ -11,22 +11,22 @@ namespace App\PaymentDrivers\Square; -use App\Models\Payment; -use App\Models\SystemLog; +use App\Jobs\Mail\PaymentFailedMailer; +use App\Jobs\Util\SystemLogger; use App\Libraries\MultiDB; +use App\Models\CompanyGateway; use App\Models\GatewayType; +use App\Models\Payment; use App\Models\PaymentHash; use App\Models\PaymentType; -use Illuminate\Bus\Queueable; -use App\Models\CompanyGateway; -use App\Jobs\Util\SystemLogger; -use App\Jobs\Mail\PaymentFailedMailer; -use Illuminate\Queue\SerializesModels; -use App\PaymentDrivers\Stripe\Utilities; -use Illuminate\Queue\InteractsWithQueue; +use App\Models\SystemLog; use App\PaymentDrivers\SquarePaymentDriver; +use App\PaymentDrivers\Stripe\Utilities; +use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\SerializesModels; class SquareWebhook implements ShouldQueue { @@ -43,13 +43,13 @@ class SquareWebhook implements ShouldQueue public \Square\SquareClient $square; private array $source_type = [ - 'CARD' => PaymentType::CREDIT_CARD_OTHER, - 'BANK_ACCOUNT' => PaymentType::ACH, - 'WALLET' => PaymentType::CREDIT_CARD_OTHER, - 'BUY_NOW_PAY_LATER' => PaymentType::CREDIT_CARD_OTHER, - 'SQUARE_ACCOUNT' => PaymentType::CREDIT_CARD_OTHER, - 'CASH' => PaymentType::CASH, - 'EXTERNAL' =>PaymentType::CREDIT_CARD_OTHER + 'CARD' => PaymentType::CREDIT_CARD_OTHER, + 'BANK_ACCOUNT' => PaymentType::ACH, + 'WALLET' => PaymentType::CREDIT_CARD_OTHER, + 'BUY_NOW_PAY_LATER' => PaymentType::CREDIT_CARD_OTHER, + 'SQUARE_ACCOUNT' => PaymentType::CREDIT_CARD_OTHER, + 'CASH' => PaymentType::CASH, + 'EXTERNAL' =>PaymentType::CREDIT_CARD_OTHER ]; public function __construct(public array $webhook_array, public string $company_key, public int $company_gateway_id) @@ -71,7 +71,7 @@ class SquareWebhook implements ShouldQueue $payment_status = false; - match($status){ + match($status) { 'APPROVED' => $payment_status = false, 'COMPLETED' => $payment_status = Payment::STATUS_COMPLETED, 'PENDING' => $payment_status = Payment::STATUS_PENDING, @@ -80,7 +80,7 @@ class SquareWebhook implements ShouldQueue default => $payment_status = false, }; - if(!$payment_status){ + if(!$payment_status) { nlog("Square Webhook - Payment Status Not Found or not worthy of processing"); nlog($this->webhook_array); } @@ -88,13 +88,13 @@ class SquareWebhook implements ShouldQueue $payment = $this->retrieveOrCreatePayment($payment_id, $payment_status); /** If the status was pending and now is reporting as Failed / Cancelled - process failure path */ - if($payment->status_id == Payment::STATUS_PENDING && in_array($payment_status, [Payment::STATUS_CANCELLED, Payment::STATUS_FAILED])){ + if($payment->status_id == Payment::STATUS_PENDING && in_array($payment_status, [Payment::STATUS_CANCELLED, Payment::STATUS_FAILED])) { $payment->service()->deletePayment(); if ($this->driver->payment_hash) { $error = ctrans('texts.client_payment_failure_body', [ 'invoice' => implode(',', $payment->invoices->pluck('number')->toArray()), - 'amount' => array_sum(array_column($this->driver->payment_hash->invoices(), 'amount')) + $this->driver->payment_hash->fee_total, + 'amount' => array_sum(array_column($this->driver->payment_hash->invoices(), 'amount')) + $this->driver->payment_hash->fee_total, ]); } else { $error = 'Payment for '.$payment->client->present()->name()." for {$payment->amount} failed"; @@ -107,8 +107,7 @@ class SquareWebhook implements ShouldQueue $error ); - } - elseif($payment->status_id == Payment::STATUS_PENDING && in_array($payment_status, [Payment::STATUS_COMPLETED, Payment::STATUS_COMPLETED])){ + } elseif($payment->status_id == Payment::STATUS_PENDING && in_array($payment_status, [Payment::STATUS_COMPLETED, Payment::STATUS_COMPLETED])) { $payment->status_id = Payment::STATUS_COMPLETED; $payment->save(); } @@ -130,7 +129,7 @@ class SquareWebhook implements ShouldQueue nlog("searching square for payment"); - if($apiResponse->isSuccess()){ + if($apiResponse->isSuccess()) { nlog("Searching by payment hash"); @@ -166,11 +165,10 @@ class SquareWebhook implements ShouldQueue return $payment; - } - else{ + } else { nlog("Square Webhook - Payment not found: {$payment_reference}"); nlog($apiResponse->getErrors()); return null; } } -} \ No newline at end of file +} diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php index 5e6e3ecbb7..a52f0e44ad 100644 --- a/app/PaymentDrivers/SquarePaymentDriver.php +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -11,22 +11,22 @@ namespace App\PaymentDrivers; +use App\Http\Requests\Payments\PaymentWebhookRequest; +use App\Jobs\Util\SystemLogger; +use App\Models\ClientGatewayToken; +use App\Models\GatewayType; use App\Models\Invoice; use App\Models\Payment; -use App\Models\SystemLog; -use App\Models\GatewayType; use App\Models\PaymentHash; use App\Models\PaymentType; -use App\Jobs\Util\SystemLogger; -use App\Utils\Traits\MakesHash; -use Square\Utils\WebhooksHelper; -use App\Models\ClientGatewayToken; -use Square\Models\WebhookSubscription; +use App\Models\SystemLog; use App\PaymentDrivers\Square\CreditCard; use App\PaymentDrivers\Square\SquareWebhook; -use Square\Models\CreateWebhookSubscriptionRequest; -use App\Http\Requests\Payments\PaymentWebhookRequest; +use App\Utils\Traits\MakesHash; use Square\Models\Builders\RefundPaymentRequestBuilder; +use Square\Models\CreateWebhookSubscriptionRequest; +use Square\Models\WebhookSubscription; +use Square\Utils\WebhooksHelper; class SquarePaymentDriver extends BaseDriver { @@ -128,7 +128,7 @@ class SquarePaymentDriver extends BaseDriver $status = $refundPaymentResponse->getRefund()->getStatus(); - if(in_array($status, ['COMPLETED', 'PENDING'])){ + if(in_array($status, ['COMPLETED', 'PENDING'])) { $transaction_reference = $refundPaymentResponse->getRefund()->getId(); @@ -153,8 +153,7 @@ class SquarePaymentDriver extends BaseDriver ); return $data; - } - elseif(in_array($status, ['REJECTED', 'FAILED'])) { + } elseif(in_array($status, ['REJECTED', 'FAILED'])) { $transaction_reference = $refundPaymentResponse->getRefund()->getId(); @@ -194,17 +193,17 @@ class SquarePaymentDriver extends BaseDriver 'code' => $error->getCode(), ]; - SystemLogger::dispatch( - [ - 'server_response' => $data, - 'data' => request()->all() - ], - SystemLog::CATEGORY_GATEWAY_RESPONSE, - SystemLog::EVENT_GATEWAY_FAILURE, - SystemLog::TYPE_SQUARE, - $this->client, - $this->client->company - ); + SystemLogger::dispatch( + [ + 'server_response' => $data, + 'data' => request()->all() + ], + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_FAILURE, + SystemLog::TYPE_SQUARE, + $this->client, + $this->client->company + ); return $data; } @@ -234,7 +233,7 @@ class SquarePaymentDriver extends BaseDriver $body->setCustomerId($cgt->gateway_customer_reference); $body->setAmountMoney($amount_money); $body->setReferenceId($payment_hash->hash); - $body->setNote(substr($description,0,500)); + $body->setNote(substr($description, 0, 500)); $response = $this->square->getPaymentsApi()->createPayment($body); $body = json_decode($response->getBody()); @@ -292,10 +291,10 @@ class SquarePaymentDriver extends BaseDriver if ($api_response->isSuccess()) { //array of WebhookSubscription objects - foreach($api_response->getResult()->getSubscriptions() ?? [] as $subscription) - { - if($subscription->getName() == 'Invoice_Ninja_Webhook_Subscription') - return $subscription->getId(); + foreach($api_response->getResult()->getSubscriptions() ?? [] as $subscription) { + if($subscription->getName() == 'Invoice_Ninja_Webhook_Subscription') { + return $subscription->getId(); + } } } else { @@ -327,8 +326,9 @@ class SquarePaymentDriver extends BaseDriver public function createWebhooks(): void { - if($this->checkWebhooks()) + if($this->checkWebhooks()) { return; + } $this->init(); @@ -367,7 +367,7 @@ class SquarePaymentDriver extends BaseDriver $signature_key = $this->company_gateway->getConfigField('signatureKey'); $notification_url = $this->company_gateway->webhookUrl(); - $body = ''; + $body = ''; $handle = fopen('php://input', 'r'); while(!feof($handle)) { $body .= fread($handle, 1024); @@ -394,8 +394,8 @@ class SquarePaymentDriver extends BaseDriver //getsubscriptionid here $subscription_id = $this->checkWebhooks(); - if(!$subscription_id){ - nlog('No Subscription Found'); + if(!$subscription_id) { + nlog('No Subscription Found'); return; } diff --git a/app/PaymentDrivers/Stripe/ACH.php b/app/PaymentDrivers/Stripe/ACH.php index cf31a8a6dd..de2c4f3cf6 100644 --- a/app/PaymentDrivers/Stripe/ACH.php +++ b/app/PaymentDrivers/Stripe/ACH.php @@ -193,8 +193,9 @@ class ACH $meta = $token->meta; - if(isset($meta->state) && $meta->state == 'unauthorized') + if(isset($meta->state) && $meta->state == 'unauthorized') { return redirect()->route('client.payment_methods.show', $token->hashed_id); + } } if (count($data['tokens']) == 0) { @@ -595,8 +596,9 @@ class ACH 'company_id' => $this->stripe->client->company_id, ])->first(); - if($token) + if($token) { return $token; + } return $this->stripe->storeGatewayToken($data, ['gateway_customer_reference' => $customer->id]); } catch (Exception $e) { diff --git a/app/PaymentDrivers/Stripe/Jobs/ChargeRefunded.php b/app/PaymentDrivers/Stripe/Jobs/ChargeRefunded.php index d0393dd2e7..a3628e7d44 100644 --- a/app/PaymentDrivers/Stripe/Jobs/ChargeRefunded.php +++ b/app/PaymentDrivers/Stripe/Jobs/ChargeRefunded.php @@ -11,18 +11,18 @@ namespace App\PaymentDrivers\Stripe\Jobs; -use App\Models\Company; -use App\Models\Payment; use App\Libraries\MultiDB; -use App\Models\PaymentHash; -use Illuminate\Bus\Queueable; +use App\Models\Company; use App\Models\CompanyGateway; -use Illuminate\Queue\SerializesModels; +use App\Models\Payment; +use App\Models\PaymentHash; use App\PaymentDrivers\Stripe\Utilities; -use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; +use Illuminate\Queue\SerializesModels; class ChargeRefunded implements ShouldQueue { @@ -93,10 +93,10 @@ class ChargeRefunded implements ShouldQueue if($payment->status_id == Payment::STATUS_COMPLETED) { $invoice_collection = $payment->paymentables - ->where('paymentable_type','invoices') - ->map(function ($pivot){ + ->where('paymentable_type', 'invoices') + ->map(function ($pivot) { return [ - 'invoice_id' => $pivot->paymentable_id, + 'invoice_id' => $pivot->paymentable_id, 'amount' => $pivot->amount - $pivot->refunded ]; }); @@ -106,15 +106,14 @@ class ChargeRefunded implements ShouldQueue $invoice_collection = $payment->paymentables ->where('paymentable_type', 'invoices') - ->map(function ($pivot) use ($amount_refunded){ + ->map(function ($pivot) use ($amount_refunded) { return [ 'invoice_id' => $pivot->paymentable_id, 'amount' => $amount_refunded ]; }); - } - elseif($invoice_collection->sum('amount') != $amount_refunded) { + } elseif($invoice_collection->sum('amount') != $amount_refunded) { //too many edges cases at this point, return early return; } diff --git a/app/PaymentDrivers/Stripe/SEPA.php b/app/PaymentDrivers/Stripe/SEPA.php index 1023d8ff65..cd999ba76e 100644 --- a/app/PaymentDrivers/Stripe/SEPA.php +++ b/app/PaymentDrivers/Stripe/SEPA.php @@ -11,15 +11,15 @@ namespace App\PaymentDrivers\Stripe; -use App\Models\Payment; -use App\Models\SystemLog; -use App\Models\GatewayType; -use App\Models\PaymentType; -use App\Jobs\Util\SystemLogger; use App\Exceptions\PaymentFailed; -use App\Models\ClientGatewayToken; -use App\PaymentDrivers\StripePaymentDriver; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; +use App\Jobs\Util\SystemLogger; +use App\Models\ClientGatewayToken; +use App\Models\GatewayType; +use App\Models\Payment; +use App\Models\PaymentType; +use App\Models\SystemLog; +use App\PaymentDrivers\StripePaymentDriver; class SEPA { diff --git a/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php b/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php index 58ff84400f..5b7239f50f 100644 --- a/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php +++ b/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php @@ -208,24 +208,24 @@ class UpdatePaymentMethods switch ($type_id) { case GatewayType::CREDIT_CARD: - /** - * @class \Stripe\PaymentMethod $method - * @property \Stripe\StripeObject $card - * @class \Stripe\StripeObject $card - * @property string $exp_year - * @property string $exp_month - * @property string $brand - * @property string $last4 - */ + /** + * @class \Stripe\PaymentMethod $method + * @property \Stripe\StripeObject $card + * @class \Stripe\StripeObject $card + * @property string $exp_year + * @property string $exp_month + * @property string $brand + * @property string $last4 + */ - $payment_meta = new \stdClass; - $payment_meta->exp_month = (string) $method->card->exp_month; - $payment_meta->exp_year = (string) $method->card->exp_year; - $payment_meta->brand = (string) $method->card->brand; - $payment_meta->last4 = (string) $method->card->last4; - $payment_meta->type = GatewayType::CREDIT_CARD; + $payment_meta = new \stdClass; + $payment_meta->exp_month = (string) $method->card->exp_month; + $payment_meta->exp_year = (string) $method->card->exp_year; + $payment_meta->brand = (string) $method->card->brand; + $payment_meta->last4 = (string) $method->card->last4; + $payment_meta->type = GatewayType::CREDIT_CARD; - return $payment_meta; + return $payment_meta; case GatewayType::ALIPAY: case GatewayType::SOFORT: diff --git a/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index a80c09789d..e4b75a023a 100644 --- a/app/PaymentDrivers/StripePaymentDriver.php +++ b/app/PaymentDrivers/StripePaymentDriver.php @@ -345,7 +345,7 @@ class StripePaymentDriver extends BaseDriver if ($this->company_gateway->require_billing_address) { $fields[] = ['name' => 'client_address_line_1', 'label' => ctrans('texts.address1'), 'type' => 'text', 'validation' => 'required']; -// $fields[] = ['name' => 'client_address_line_2', 'label' => ctrans('texts.address2'), 'type' => 'text', 'validation' => 'nullable']; + // $fields[] = ['name' => 'client_address_line_2', 'label' => ctrans('texts.address2'), 'type' => 'text', 'validation' => 'nullable']; $fields[] = ['name' => 'client_city', 'label' => ctrans('texts.city'), 'type' => 'text', 'validation' => 'required']; $fields[] = ['name' => 'client_state', 'label' => ctrans('texts.state'), 'type' => 'text', 'validation' => 'required']; $fields[] = ['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'text', 'validation' => 'required']; @@ -357,7 +357,7 @@ class StripePaymentDriver extends BaseDriver if ($this->company_gateway->require_shipping_address) { $fields[] = ['name' => 'client_shipping_address_line_1', 'label' => ctrans('texts.shipping_address1'), 'type' => 'text', 'validation' => 'required']; -// $fields[] = ['name' => 'client_shipping_address_line_2', 'label' => ctrans('texts.shipping_address2'), 'type' => 'text', 'validation' => 'sometimes']; + // $fields[] = ['name' => 'client_shipping_address_line_2', 'label' => ctrans('texts.shipping_address2'), 'type' => 'text', 'validation' => 'sometimes']; $fields[] = ['name' => 'client_shipping_city', 'label' => ctrans('texts.shipping_city'), 'type' => 'text', 'validation' => 'required']; $fields[] = ['name' => 'client_shipping_state', 'label' => ctrans('texts.shipping_state'), 'type' => 'text', 'validation' => 'required']; $fields[] = ['name' => 'client_shipping_postal_code', 'label' => ctrans('texts.shipping_postal_code'), 'type' => 'text', 'validation' => 'required']; @@ -783,8 +783,9 @@ class StripePaymentDriver extends BaseDriver ->where('token', $request->data['object']['payment_method']) ->first(); - if($clientgateway) + if($clientgateway) { $clientgateway->delete(); + } return response()->json([], 200); } elseif ($request->data['object']['status'] == "pending") { diff --git a/app/PaymentDrivers/WePay/ACH.php b/app/PaymentDrivers/WePay/ACH.php index 945924ffc3..3835b9630b 100644 --- a/app/PaymentDrivers/WePay/ACH.php +++ b/app/PaymentDrivers/WePay/ACH.php @@ -113,7 +113,7 @@ class ACH } /* If the bank transfer token is PENDING - we need to verify!! */ -// + // public function verificationView(ClientGatewayToken $token) { diff --git a/app/PaymentDrivers/WePayPaymentDriver.php b/app/PaymentDrivers/WePayPaymentDriver.php index ec52144385..9bb9a44cfc 100644 --- a/app/PaymentDrivers/WePayPaymentDriver.php +++ b/app/PaymentDrivers/WePayPaymentDriver.php @@ -89,7 +89,7 @@ class WePayPaymentDriver extends BaseDriver * Setup the gateway * * @param array $data user_id + company - * @return \Illuminate\View\View + * @return \Illuminate\View\View */ public function setup(array $data) { @@ -330,7 +330,7 @@ class WePayPaymentDriver extends BaseDriver if ($this->company_gateway->require_billing_address) { $fields[] = ['name' => 'client_address_line_1', 'label' => ctrans('texts.address1'), 'type' => 'text', 'validation' => 'required']; -// $fields[] = ['name' => 'client_address_line_2', 'label' => ctrans('texts.address2'), 'type' => 'text', 'validation' => 'nullable']; + // $fields[] = ['name' => 'client_address_line_2', 'label' => ctrans('texts.address2'), 'type' => 'text', 'validation' => 'nullable']; $fields[] = ['name' => 'client_city', 'label' => ctrans('texts.city'), 'type' => 'text', 'validation' => 'required']; $fields[] = ['name' => 'client_state', 'label' => ctrans('texts.state'), 'type' => 'text', 'validation' => 'required']; $fields[] = ['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'text', 'validation' => 'required']; @@ -338,7 +338,7 @@ class WePayPaymentDriver extends BaseDriver if ($this->company_gateway->require_shipping_address) { $fields[] = ['name' => 'client_shipping_address_line_1', 'label' => ctrans('texts.shipping_address1'), 'type' => 'text', 'validation' => 'required']; -// $fields[] = ['name' => 'client_shipping_address_line_2', 'label' => ctrans('texts.shipping_address2'), 'type' => 'text', 'validation' => 'sometimes']; + // $fields[] = ['name' => 'client_shipping_address_line_2', 'label' => ctrans('texts.shipping_address2'), 'type' => 'text', 'validation' => 'sometimes']; $fields[] = ['name' => 'client_shipping_city', 'label' => ctrans('texts.shipping_city'), 'type' => 'text', 'validation' => 'required']; $fields[] = ['name' => 'client_shipping_state', 'label' => ctrans('texts.shipping_state'), 'type' => 'text', 'validation' => 'required']; $fields[] = ['name' => 'client_shipping_postal_code', 'label' => ctrans('texts.shipping_postal_code'), 'type' => 'text', 'validation' => 'required']; diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 089287512b..0b4a78cc15 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -11,25 +11,23 @@ namespace App\Providers; -use App\Utils\Ninja; -use Livewire\Livewire; +use App\Helpers\Mail\GmailTransport; +use App\Helpers\Mail\Office365MailTransport; +use App\Http\Middleware\SetDomainNameDb; use App\Models\Invoice; use App\Models\Proposal; +use App\Utils\Ninja; use App\Utils\TruthSource; +use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Mail\Mailer; +use Illuminate\Queue\Events\JobProcessing; use Illuminate\Support\Facades\App; -use App\Helpers\Mail\GmailTransport; -use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Blade; +use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Queue; use Illuminate\Support\Facades\Schema; -use Illuminate\Support\Facades\Artisan; use Illuminate\Support\ServiceProvider; -use App\Http\Middleware\SetDomainNameDb; -use Illuminate\Queue\Events\JobProcessing; -use App\Helpers\Mail\Office365MailTransport; -use Illuminate\Support\Facades\ParallelTesting; -use Illuminate\Database\Eloquent\Relations\Relation; +use Livewire\Livewire; class AppServiceProvider extends ServiceProvider { @@ -105,7 +103,7 @@ class AppServiceProvider extends ServiceProvider Mailer::macro('mailgun_config', function (string $secret, string $domain, string $endpoint = 'api.mailgun.net') { // @phpstan-ignore /** @phpstan-ignore-next-line **/ - Mailer::setSymfonyTransport(app('mail.manager')->createSymfonyTransport([ + Mailer::setSymfonyTransport(app('mail.manager')->createSymfonyTransport([ 'transport' => 'mailgun', 'secret' => $secret, 'domain' => $domain, diff --git a/app/Providers/ClientPortalServiceProvider.php b/app/Providers/ClientPortalServiceProvider.php index a4ac3c02d9..9c781b417b 100644 --- a/app/Providers/ClientPortalServiceProvider.php +++ b/app/Providers/ClientPortalServiceProvider.php @@ -14,7 +14,7 @@ class ClientPortalServiceProvider extends ServiceProvider */ public function register() { - app()->bind('customMessage', function () { + app()->bind('customMessage', function () { return new CustomMessage(); }); diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 028eeb4dff..8c4448b230 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -11,272 +11,268 @@ namespace App\Providers; -use App\Models\Task; -use App\Models\User; -use App\Models\Quote; -use App\Models\Client; -use App\Models\Credit; -use App\Models\Vendor; +use App\Events\Account\AccountCreated; +use App\Events\Account\StripeConnectFailure; +use App\Events\Client\ClientWasArchived; +use App\Events\Client\ClientWasCreated; +use App\Events\Client\ClientWasDeleted; +use App\Events\Client\ClientWasRestored; +use App\Events\Client\ClientWasUpdated; +use App\Events\Company\CompanyDocumentsDeleted; +use App\Events\Contact\ContactLoggedIn; +use App\Events\Credit\CreditWasArchived; +use App\Events\Credit\CreditWasCreated; +use App\Events\Credit\CreditWasDeleted; +use App\Events\Credit\CreditWasEmailed; +use App\Events\Credit\CreditWasEmailedAndFailed; +use App\Events\Credit\CreditWasMarkedSent; +use App\Events\Credit\CreditWasRestored; +use App\Events\Credit\CreditWasUpdated; +use App\Events\Credit\CreditWasViewed; +use App\Events\Design\DesignWasArchived; +use App\Events\Design\DesignWasDeleted; +use App\Events\Design\DesignWasRestored; +use App\Events\Design\DesignWasUpdated; +use App\Events\Document\DocumentWasArchived; +use App\Events\Document\DocumentWasCreated; +use App\Events\Document\DocumentWasDeleted; +use App\Events\Document\DocumentWasRestored; +use App\Events\Document\DocumentWasUpdated; +use App\Events\Expense\ExpenseWasArchived; +use App\Events\Expense\ExpenseWasCreated; +use App\Events\Expense\ExpenseWasDeleted; +use App\Events\Expense\ExpenseWasRestored; +use App\Events\Expense\ExpenseWasUpdated; +use App\Events\Invoice\InvoiceReminderWasEmailed; +use App\Events\Invoice\InvoiceWasArchived; +use App\Events\Invoice\InvoiceWasCancelled; +use App\Events\Invoice\InvoiceWasCreated; +use App\Events\Invoice\InvoiceWasDeleted; +use App\Events\Invoice\InvoiceWasEmailed; +use App\Events\Invoice\InvoiceWasEmailedAndFailed; +use App\Events\Invoice\InvoiceWasMarkedSent; +use App\Events\Invoice\InvoiceWasPaid; +use App\Events\Invoice\InvoiceWasRestored; +use App\Events\Invoice\InvoiceWasReversed; +use App\Events\Invoice\InvoiceWasUpdated; +use App\Events\Invoice\InvoiceWasViewed; +use App\Events\Misc\InvitationWasViewed; +use App\Events\Payment\PaymentWasArchived; +use App\Events\Payment\PaymentWasCreated; +use App\Events\Payment\PaymentWasDeleted; +use App\Events\Payment\PaymentWasEmailed; +use App\Events\Payment\PaymentWasEmailedAndFailed; +use App\Events\Payment\PaymentWasRefunded; +use App\Events\Payment\PaymentWasRestored; +use App\Events\Payment\PaymentWasUpdated; +use App\Events\Payment\PaymentWasVoided; +use App\Events\PurchaseOrder\PurchaseOrderWasAccepted; +use App\Events\PurchaseOrder\PurchaseOrderWasArchived; +use App\Events\PurchaseOrder\PurchaseOrderWasCreated; +use App\Events\PurchaseOrder\PurchaseOrderWasDeleted; +use App\Events\PurchaseOrder\PurchaseOrderWasEmailed; +use App\Events\PurchaseOrder\PurchaseOrderWasRestored; +use App\Events\PurchaseOrder\PurchaseOrderWasUpdated; +use App\Events\PurchaseOrder\PurchaseOrderWasViewed; +use App\Events\Quote\QuoteWasApproved; +use App\Events\Quote\QuoteWasArchived; +use App\Events\Quote\QuoteWasCreated; +use App\Events\Quote\QuoteWasDeleted; +use App\Events\Quote\QuoteWasEmailed; +use App\Events\Quote\QuoteWasRestored; +use App\Events\Quote\QuoteWasUpdated; +use App\Events\Quote\QuoteWasViewed; +use App\Events\RecurringExpense\RecurringExpenseWasArchived; +use App\Events\RecurringExpense\RecurringExpenseWasCreated; +use App\Events\RecurringExpense\RecurringExpenseWasDeleted; +use App\Events\RecurringExpense\RecurringExpenseWasRestored; +use App\Events\RecurringExpense\RecurringExpenseWasUpdated; +use App\Events\RecurringInvoice\RecurringInvoiceWasArchived; +use App\Events\RecurringInvoice\RecurringInvoiceWasCreated; +use App\Events\RecurringInvoice\RecurringInvoiceWasDeleted; +use App\Events\RecurringInvoice\RecurringInvoiceWasRestored; +use App\Events\RecurringInvoice\RecurringInvoiceWasUpdated; +use App\Events\RecurringQuote\RecurringQuoteWasArchived; +use App\Events\RecurringQuote\RecurringQuoteWasCreated; +use App\Events\RecurringQuote\RecurringQuoteWasDeleted; +use App\Events\RecurringQuote\RecurringQuoteWasRestored; +use App\Events\RecurringQuote\RecurringQuoteWasUpdated; +use App\Events\Subscription\SubscriptionWasArchived; +use App\Events\Subscription\SubscriptionWasCreated; +use App\Events\Subscription\SubscriptionWasDeleted; +use App\Events\Subscription\SubscriptionWasRestored; +use App\Events\Subscription\SubscriptionWasUpdated; +use App\Events\Task\TaskWasArchived; +use App\Events\Task\TaskWasCreated; +use App\Events\Task\TaskWasDeleted; +use App\Events\Task\TaskWasRestored; +use App\Events\Task\TaskWasUpdated; +use App\Events\User\UserLoggedIn; +use App\Events\User\UserWasArchived; +use App\Events\User\UserWasCreated; +use App\Events\User\UserWasDeleted; +use App\Events\User\UserWasRestored; +use App\Events\User\UserWasUpdated; +use App\Events\Vendor\VendorContactLoggedIn; +use App\Events\Vendor\VendorWasArchived; +use App\Events\Vendor\VendorWasCreated; +use App\Events\Vendor\VendorWasDeleted; +use App\Events\Vendor\VendorWasRestored; +use App\Events\Vendor\VendorWasUpdated; +use App\Listeners\Account\StripeConnectFailureListener; +use App\Listeners\Activity\ArchivedClientActivity; +use App\Listeners\Activity\ClientUpdatedActivity; +use App\Listeners\Activity\CreatedClientActivity; +use App\Listeners\Activity\CreatedCreditActivity; +use App\Listeners\Activity\CreatedExpenseActivity; +use App\Listeners\Activity\CreatedQuoteActivity; +use App\Listeners\Activity\CreatedSubscriptionActivity; +use App\Listeners\Activity\CreatedTaskActivity; +use App\Listeners\Activity\CreatedVendorActivity; +use App\Listeners\Activity\CreditArchivedActivity; +use App\Listeners\Activity\DeleteClientActivity; +use App\Listeners\Activity\DeleteCreditActivity; +use App\Listeners\Activity\ExpenseArchivedActivity; +use App\Listeners\Activity\ExpenseDeletedActivity; +use App\Listeners\Activity\ExpenseRestoredActivity; +use App\Listeners\Activity\ExpenseUpdatedActivity; +use App\Listeners\Activity\PaymentArchivedActivity; +use App\Listeners\Activity\PaymentCreatedActivity; +use App\Listeners\Activity\PaymentDeletedActivity; +use App\Listeners\Activity\PaymentRefundedActivity; +use App\Listeners\Activity\PaymentUpdatedActivity; +use App\Listeners\Activity\PaymentVoidedActivity; +use App\Listeners\Activity\QuoteUpdatedActivity; +use App\Listeners\Activity\RestoreClientActivity; +use App\Listeners\Activity\SubscriptionArchivedActivity; +use App\Listeners\Activity\SubscriptionDeletedActivity; +use App\Listeners\Activity\SubscriptionRestoredActivity; +use App\Listeners\Activity\SubscriptionUpdatedActivity; +use App\Listeners\Activity\TaskArchivedActivity; +use App\Listeners\Activity\TaskDeletedActivity; +use App\Listeners\Activity\TaskRestoredActivity; +use App\Listeners\Activity\TaskUpdatedActivity; +use App\Listeners\Activity\UpdatedCreditActivity; +use App\Listeners\Activity\VendorArchivedActivity; +use App\Listeners\Activity\VendorDeletedActivity; +use App\Listeners\Activity\VendorRestoredActivity; +use App\Listeners\Activity\VendorUpdatedActivity; +use App\Listeners\Contact\UpdateContactLastLogin; +use App\Listeners\Credit\CreditCreatedNotification; +use App\Listeners\Credit\CreditEmailedNotification; +use App\Listeners\Credit\CreditRestoredActivity; +use App\Listeners\Credit\CreditViewedActivity; +use App\Listeners\Document\DeleteCompanyDocuments; +use App\Listeners\Invoice\CreateInvoiceActivity; +use App\Listeners\Invoice\InvoiceArchivedActivity; +use App\Listeners\Invoice\InvoiceCancelledActivity; +use App\Listeners\Invoice\InvoiceCreatedNotification; +use App\Listeners\Invoice\InvoiceDeletedActivity; +use App\Listeners\Invoice\InvoiceEmailActivity; +use App\Listeners\Invoice\InvoiceEmailedNotification; +use App\Listeners\Invoice\InvoiceEmailFailedActivity; +use App\Listeners\Invoice\InvoiceFailedEmailNotification; +use App\Listeners\Invoice\InvoicePaidActivity; +use App\Listeners\Invoice\InvoiceReminderEmailActivity; +use App\Listeners\Invoice\InvoiceRestoredActivity; +use App\Listeners\Invoice\InvoiceReversedActivity; +use App\Listeners\Invoice\InvoiceViewedActivity; +use App\Listeners\Invoice\UpdateInvoiceActivity; +use App\Listeners\Mail\MailSentListener; +use App\Listeners\Misc\InvitationViewedListener; +use App\Listeners\Payment\PaymentBalanceActivity; +use App\Listeners\Payment\PaymentEmailedActivity; +use App\Listeners\Payment\PaymentEmailFailureActivity; +use App\Listeners\Payment\PaymentNotification; +use App\Listeners\Payment\PaymentRestoredActivity; +use App\Listeners\PurchaseOrder\CreatePurchaseOrderActivity; +use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedActivity; +use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedListener; +use App\Listeners\PurchaseOrder\PurchaseOrderArchivedActivity; +use App\Listeners\PurchaseOrder\PurchaseOrderCreatedListener; +use App\Listeners\PurchaseOrder\PurchaseOrderDeletedActivity; +use App\Listeners\PurchaseOrder\PurchaseOrderEmailActivity; +use App\Listeners\PurchaseOrder\PurchaseOrderEmailedNotification; +use App\Listeners\PurchaseOrder\PurchaseOrderRestoredActivity; +use App\Listeners\PurchaseOrder\PurchaseOrderViewedActivity; +use App\Listeners\PurchaseOrder\UpdatePurchaseOrderActivity; +use App\Listeners\Quote\QuoteApprovedActivity; +use App\Listeners\Quote\QuoteApprovedNotification; +use App\Listeners\Quote\QuoteApprovedWebhook; +use App\Listeners\Quote\QuoteArchivedActivity; +use App\Listeners\Quote\QuoteCreatedNotification; +use App\Listeners\Quote\QuoteDeletedActivity; +use App\Listeners\Quote\QuoteEmailActivity; +use App\Listeners\Quote\QuoteEmailedNotification; +use App\Listeners\Quote\QuoteRestoredActivity; +use App\Listeners\Quote\QuoteViewedActivity; +use App\Listeners\Quote\ReachWorkflowSettings; +use App\Listeners\RecurringExpense\CreatedRecurringExpenseActivity; +use App\Listeners\RecurringExpense\RecurringExpenseArchivedActivity; +use App\Listeners\RecurringExpense\RecurringExpenseDeletedActivity; +use App\Listeners\RecurringExpense\RecurringExpenseRestoredActivity; +use App\Listeners\RecurringExpense\RecurringExpenseUpdatedActivity; +use App\Listeners\RecurringInvoice\CreateRecurringInvoiceActivity; +use App\Listeners\RecurringInvoice\RecurringInvoiceArchivedActivity; +use App\Listeners\RecurringInvoice\RecurringInvoiceDeletedActivity; +use App\Listeners\RecurringInvoice\RecurringInvoiceRestoredActivity; +use App\Listeners\RecurringInvoice\UpdateRecurringInvoiceActivity; +use App\Listeners\RecurringQuote\CreateRecurringQuoteActivity; +use App\Listeners\RecurringQuote\RecurringQuoteArchivedActivity; +use App\Listeners\RecurringQuote\RecurringQuoteDeletedActivity; +use App\Listeners\RecurringQuote\RecurringQuoteRestoredActivity; +use App\Listeners\RecurringQuote\UpdateRecurringQuoteActivity; +use App\Listeners\SendVerificationNotification; +use App\Listeners\User\ArchivedUserActivity; +use App\Listeners\User\CreatedUserActivity; +use App\Listeners\User\DeletedUserActivity; +use App\Listeners\User\RestoredUserActivity; +use App\Listeners\User\UpdatedUserActivity; +use App\Listeners\User\UpdateUserLastLogin; +use App\Listeners\Vendor\UpdateVendorContactLastLogin; 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\Credit; use App\Models\Expense; use App\Models\Invoice; use App\Models\Payment; use App\Models\Product; use App\Models\Project; use App\Models\Proposal; -use App\Models\CompanyToken; -use App\Models\Subscription; -use App\Models\ClientContact; use App\Models\PurchaseOrder; +use App\Models\Quote; +use App\Models\Subscription; +use App\Models\Task; +use App\Models\User; +use App\Models\Vendor; use App\Models\VendorContact; -use App\Models\CompanyGateway; -use App\Observers\TaskObserver; -use App\Observers\UserObserver; -use App\Observers\QuoteObserver; -use App\Events\User\UserLoggedIn; -use App\Observers\ClientObserver; -use App\Observers\CreditObserver; -use App\Observers\VendorObserver; use App\Observers\AccountObserver; +use App\Observers\ClientContactObserver; +use App\Observers\ClientObserver; +use App\Observers\CompanyGatewayObserver; use App\Observers\CompanyObserver; +use App\Observers\CompanyTokenObserver; +use App\Observers\CreditObserver; use App\Observers\ExpenseObserver; use App\Observers\InvoiceObserver; use App\Observers\PaymentObserver; use App\Observers\ProductObserver; use App\Observers\ProjectObserver; -use App\Events\Task\TaskWasCreated; -use App\Events\Task\TaskWasDeleted; -use App\Events\Task\TaskWasUpdated; -use App\Events\User\UserWasCreated; -use App\Events\User\UserWasDeleted; -use App\Events\User\UserWasUpdated; use App\Observers\ProposalObserver; -use App\Events\Quote\QuoteWasViewed; -use App\Events\Task\TaskWasArchived; -use App\Events\Task\TaskWasRestored; -use App\Events\User\UserWasArchived; -use App\Events\User\UserWasRestored; -use App\Events\Quote\QuoteWasCreated; -use App\Events\Quote\QuoteWasDeleted; -use App\Events\Quote\QuoteWasEmailed; -use App\Events\Quote\QuoteWasUpdated; -use App\Events\Account\AccountCreated; -use App\Events\Credit\CreditWasViewed; -use App\Events\Invoice\InvoiceWasPaid; -use App\Events\Quote\QuoteWasApproved; -use App\Events\Quote\QuoteWasArchived; -use App\Events\Quote\QuoteWasRestored; -use App\Events\Client\ClientWasCreated; -use App\Events\Client\ClientWasDeleted; -use App\Events\Client\ClientWasUpdated; -use App\Events\Contact\ContactLoggedIn; -use App\Events\Credit\CreditWasCreated; -use App\Events\Credit\CreditWasDeleted; -use App\Events\Credit\CreditWasEmailed; -use App\Events\Credit\CreditWasUpdated; -use App\Events\Design\DesignWasDeleted; -use App\Events\Design\DesignWasUpdated; -use App\Events\Vendor\VendorWasCreated; -use App\Events\Vendor\VendorWasDeleted; -use App\Events\Vendor\VendorWasUpdated; -use App\Observers\CompanyTokenObserver; -use App\Observers\SubscriptionObserver; -use Illuminate\Mail\Events\MessageSent; -use App\Events\Client\ClientWasArchived; -use App\Events\Client\ClientWasRestored; -use App\Events\Credit\CreditWasArchived; -use App\Events\Credit\CreditWasRestored; -use App\Events\Design\DesignWasArchived; -use App\Events\Design\DesignWasRestored; -use App\Events\Invoice\InvoiceWasViewed; -use App\Events\Misc\InvitationWasViewed; -use App\Events\Payment\PaymentWasVoided; -use App\Events\Vendor\VendorWasArchived; -use App\Events\Vendor\VendorWasRestored; -use App\Events\Account\StripeConnectFailure; -use App\Listeners\Mail\MailSentListener; -use App\Observers\ClientContactObserver; use App\Observers\PurchaseOrderObserver; +use App\Observers\QuoteObserver; +use App\Observers\SubscriptionObserver; +use App\Observers\TaskObserver; +use App\Observers\UserObserver; use App\Observers\VendorContactObserver; -use App\Events\Expense\ExpenseWasCreated; -use App\Events\Expense\ExpenseWasDeleted; -use App\Events\Expense\ExpenseWasUpdated; -use App\Events\Invoice\InvoiceWasCreated; -use App\Events\Invoice\InvoiceWasDeleted; -use App\Events\Invoice\InvoiceWasEmailed; -use App\Events\Invoice\InvoiceWasUpdated; -use App\Events\Payment\PaymentWasCreated; -use App\Events\Payment\PaymentWasDeleted; -use App\Events\Payment\PaymentWasEmailed; -use App\Events\Payment\PaymentWasUpdated; -use App\Observers\CompanyGatewayObserver; -use App\Events\Credit\CreditWasMarkedSent; -use App\Events\Expense\ExpenseWasArchived; -use App\Events\Expense\ExpenseWasRestored; -use App\Events\Invoice\InvoiceWasArchived; -use App\Events\Invoice\InvoiceWasRestored; -use App\Events\Invoice\InvoiceWasReversed; -use App\Events\Payment\PaymentWasArchived; -use App\Events\Payment\PaymentWasRefunded; -use App\Events\Payment\PaymentWasRestored; -use Illuminate\Mail\Events\MessageSending; -use App\Events\Document\DocumentWasCreated; -use App\Events\Document\DocumentWasDeleted; -use App\Events\Document\DocumentWasUpdated; -use App\Events\Invoice\InvoiceWasCancelled; -use App\Listeners\Invoice\CreateInvoicePdf; -use App\Listeners\Quote\QuoteEmailActivity; -use App\Listeners\User\CreatedUserActivity; -use App\Listeners\User\DeletedUserActivity; -use App\Listeners\User\UpdatedUserActivity; -use App\Listeners\User\UpdateUserLastLogin; -use App\Events\Document\DocumentWasArchived; -use App\Events\Document\DocumentWasRestored; -use App\Events\Invoice\InvoiceWasMarkedSent; -use App\Events\Vendor\VendorContactLoggedIn; -use App\Listeners\Quote\QuoteViewedActivity; -use App\Listeners\User\ArchivedUserActivity; -use App\Listeners\User\RestoredUserActivity; -use App\Listeners\Quote\QuoteApprovedWebhook; -use App\Listeners\Quote\QuoteDeletedActivity; -use App\Listeners\Credit\CreditViewedActivity; -use App\Listeners\Invoice\InvoicePaidActivity; -use App\Listeners\Payment\PaymentNotification; -use App\Listeners\Quote\QuoteApprovedActivity; -use App\Listeners\Quote\QuoteArchivedActivity; -use App\Listeners\Quote\QuoteRestoredActivity; -use App\Listeners\Quote\ReachWorkflowSettings; -use App\Events\Company\CompanyDocumentsDeleted; -use App\Listeners\Activity\CreatedTaskActivity; -use App\Listeners\Activity\TaskDeletedActivity; -use App\Listeners\Activity\TaskUpdatedActivity; -use App\Listeners\Invoice\InvoiceEmailActivity; -use App\Listeners\SendVerificationNotification; -use App\Events\Credit\CreditWasEmailedAndFailed; -use App\Listeners\Activity\CreatedQuoteActivity; -use App\Listeners\Activity\DeleteClientActivity; -use App\Listeners\Activity\DeleteCreditActivity; -use App\Listeners\Activity\QuoteUpdatedActivity; -use App\Listeners\Activity\TaskArchivedActivity; -use App\Listeners\Activity\TaskRestoredActivity; -use App\Listeners\Credit\CreditRestoredActivity; -use App\Listeners\Invoice\CreateInvoiceActivity; -use App\Listeners\Invoice\InvoiceViewedActivity; -use App\Listeners\Invoice\UpdateInvoiceActivity; -use App\Listeners\Misc\InvitationViewedListener; -use App\Events\Invoice\InvoiceReminderWasEmailed; -use App\Listeners\Activity\ClientUpdatedActivity; -use App\Listeners\Activity\CreatedClientActivity; -use App\Listeners\Activity\CreatedCreditActivity; -use App\Listeners\Activity\CreatedVendorActivity; -use App\Listeners\Activity\PaymentVoidedActivity; -use App\Listeners\Activity\RestoreClientActivity; -use App\Listeners\Activity\UpdatedCreditActivity; -use App\Listeners\Activity\VendorDeletedActivity; -use App\Listeners\Activity\VendorUpdatedActivity; -use App\Listeners\Contact\UpdateContactLastLogin; -use App\Listeners\Invoice\InvoiceDeletedActivity; -use App\Listeners\Payment\PaymentBalanceActivity; -use App\Listeners\Payment\PaymentEmailedActivity; -use App\Listeners\Quote\QuoteCreatedNotification; -use App\Listeners\Quote\QuoteEmailedNotification; -use App\Events\Invoice\InvoiceWasEmailedAndFailed; -use App\Events\Payment\PaymentWasEmailedAndFailed; -use App\Listeners\Activity\ArchivedClientActivity; -use App\Listeners\Activity\CreatedExpenseActivity; -use App\Listeners\Activity\CreditArchivedActivity; -use App\Listeners\Activity\ExpenseDeletedActivity; -use App\Listeners\Activity\ExpenseUpdatedActivity; -use App\Listeners\Activity\PaymentCreatedActivity; -use App\Listeners\Activity\PaymentDeletedActivity; -use App\Listeners\Activity\PaymentUpdatedActivity; -use App\Listeners\Activity\VendorArchivedActivity; -use App\Listeners\Activity\VendorRestoredActivity; -use App\Listeners\Document\DeleteCompanyDocuments; -use App\Listeners\Invoice\InvoiceArchivedActivity; -use App\Listeners\Invoice\InvoiceRestoredActivity; -use App\Listeners\Invoice\InvoiceReversedActivity; -use App\Listeners\Payment\PaymentRestoredActivity; -use App\Listeners\Quote\QuoteApprovedNotification; -use SocialiteProviders\Apple\AppleExtendSocialite; -use SocialiteProviders\Manager\SocialiteWasCalled; -use App\Events\Subscription\SubscriptionWasCreated; -use App\Events\Subscription\SubscriptionWasDeleted; -use App\Events\Subscription\SubscriptionWasUpdated; -use App\Listeners\Activity\ExpenseArchivedActivity; -use App\Listeners\Activity\ExpenseRestoredActivity; -use App\Listeners\Activity\PaymentArchivedActivity; -use App\Listeners\Activity\PaymentRefundedActivity; -use App\Listeners\Credit\CreditCreatedNotification; -use App\Listeners\Credit\CreditEmailedNotification; -use App\Listeners\Invoice\InvoiceCancelledActivity; -use App\Events\PurchaseOrder\PurchaseOrderWasViewed; -use App\Events\Subscription\SubscriptionWasArchived; -use App\Events\Subscription\SubscriptionWasRestored; -use App\Events\PurchaseOrder\PurchaseOrderWasCreated; -use App\Events\PurchaseOrder\PurchaseOrderWasDeleted; -use App\Events\PurchaseOrder\PurchaseOrderWasEmailed; -use App\Events\PurchaseOrder\PurchaseOrderWasUpdated; -use App\Listeners\Invoice\InvoiceCreatedNotification; -use App\Listeners\Invoice\InvoiceEmailedNotification; -use App\Listeners\Invoice\InvoiceEmailFailedActivity; -use App\Events\PurchaseOrder\PurchaseOrderWasAccepted; -use App\Events\PurchaseOrder\PurchaseOrderWasArchived; -use App\Events\PurchaseOrder\PurchaseOrderWasRestored; -use App\Listeners\Payment\PaymentEmailFailureActivity; -use App\Listeners\Vendor\UpdateVendorContactLastLogin; -use App\Events\RecurringQuote\RecurringQuoteWasCreated; -use App\Events\RecurringQuote\RecurringQuoteWasDeleted; -use App\Events\RecurringQuote\RecurringQuoteWasUpdated; -use App\Listeners\Account\StripeConnectFailureListener; -use App\Listeners\Activity\CreatedSubscriptionActivity; -use App\Listeners\Activity\SubscriptionDeletedActivity; -use App\Listeners\Activity\SubscriptionUpdatedActivity; -use App\Listeners\Invoice\InvoiceReminderEmailActivity; -use App\Events\RecurringQuote\RecurringQuoteWasArchived; -use App\Events\RecurringQuote\RecurringQuoteWasRestored; -use App\Listeners\Activity\SubscriptionArchivedActivity; -use App\Listeners\Activity\SubscriptionRestoredActivity; -use App\Listeners\Invoice\InvoiceFailedEmailNotification; -use SocialiteProviders\Microsoft\MicrosoftExtendSocialite; -use App\Events\RecurringExpense\RecurringExpenseWasCreated; -use App\Events\RecurringExpense\RecurringExpenseWasDeleted; -use App\Events\RecurringExpense\RecurringExpenseWasUpdated; -use App\Events\RecurringInvoice\RecurringInvoiceWasCreated; -use App\Events\RecurringInvoice\RecurringInvoiceWasDeleted; -use App\Events\RecurringInvoice\RecurringInvoiceWasUpdated; -use App\Listeners\PurchaseOrder\PurchaseOrderEmailActivity; -use App\Events\RecurringExpense\RecurringExpenseWasArchived; -use App\Events\RecurringExpense\RecurringExpenseWasRestored; -use App\Events\RecurringInvoice\RecurringInvoiceWasArchived; -use App\Events\RecurringInvoice\RecurringInvoiceWasRestored; -use App\Listeners\PurchaseOrder\CreatePurchaseOrderActivity; -use App\Listeners\PurchaseOrder\PurchaseOrderViewedActivity; -use App\Listeners\PurchaseOrder\UpdatePurchaseOrderActivity; -use App\Listeners\PurchaseOrder\PurchaseOrderCreatedListener; -use App\Listeners\PurchaseOrder\PurchaseOrderDeletedActivity; -use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedActivity; -use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedListener; -use App\Listeners\PurchaseOrder\PurchaseOrderArchivedActivity; -use App\Listeners\PurchaseOrder\PurchaseOrderRestoredActivity; -use App\Listeners\RecurringQuote\CreateRecurringQuoteActivity; -use App\Listeners\RecurringQuote\UpdateRecurringQuoteActivity; -use App\Listeners\RecurringQuote\RecurringQuoteDeletedActivity; -use App\Listeners\RecurringQuote\RecurringQuoteArchivedActivity; -use App\Listeners\RecurringQuote\RecurringQuoteRestoredActivity; -use App\Listeners\PurchaseOrder\PurchaseOrderEmailedNotification; -use App\Listeners\RecurringInvoice\CreateRecurringInvoiceActivity; -use App\Listeners\RecurringInvoice\UpdateRecurringInvoiceActivity; -use App\Listeners\RecurringExpense\CreatedRecurringExpenseActivity; -use App\Listeners\RecurringExpense\RecurringExpenseDeletedActivity; -use App\Listeners\RecurringExpense\RecurringExpenseUpdatedActivity; -use App\Listeners\RecurringInvoice\RecurringInvoiceDeletedActivity; -use App\Listeners\RecurringExpense\RecurringExpenseArchivedActivity; -use App\Listeners\RecurringExpense\RecurringExpenseRestoredActivity; -use App\Listeners\RecurringInvoice\RecurringInvoiceArchivedActivity; -use App\Listeners\RecurringInvoice\RecurringInvoiceRestoredActivity; +use App\Observers\VendorObserver; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; +use Illuminate\Mail\Events\MessageSending; +use Illuminate\Mail\Events\MessageSent; class EventServiceProvider extends ServiceProvider { @@ -378,7 +374,6 @@ class EventServiceProvider extends ServiceProvider ], CreditWasUpdated::class => [ UpdatedCreditActivity::class, - CreateInvoicePdf::class, ], CreditWasEmailedAndFailed::class => [ ], @@ -459,7 +454,6 @@ class EventServiceProvider extends ServiceProvider ], InvoiceWasReversed::class => [ InvoiceReversedActivity::class, - CreateInvoicePdf::class, ], InvoiceWasCancelled::class => [ InvoiceCancelledActivity::class, @@ -515,7 +509,6 @@ class EventServiceProvider extends ServiceProvider ], QuoteWasUpdated::class => [ QuoteUpdatedActivity::class, - CreateInvoicePdf::class, ], QuoteWasEmailed::class => [ QuoteEmailActivity::class, diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 3d9dc44215..8884c9d29c 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -11,17 +11,17 @@ namespace App\Providers; -use App\Utils\Ninja; -use App\Models\Scheduler; -use Illuminate\Http\Request; -use App\Utils\Traits\MakesHash; -use Illuminate\Support\Facades\Route; -use Illuminate\Cache\RateLimiting\Limit; -use Illuminate\Support\Facades\RateLimiter; use App\Http\Middleware\ThrottleRequestsWithPredis; -use Illuminate\Routing\Middleware\ThrottleRequests; +use App\Models\Scheduler; +use App\Utils\Ninja; +use App\Utils\Traits\MakesHash; +use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; +use Illuminate\Http\Request; +use Illuminate\Routing\Middleware\ThrottleRequests; +use Illuminate\Support\Facades\RateLimiter; +use Illuminate\Support\Facades\Route; class RouteServiceProvider extends ServiceProvider { @@ -36,7 +36,7 @@ class RouteServiceProvider extends ServiceProvider { parent::boot(); - if (Ninja::isHosted() && !config('ninja.testvars.travis')){ + if (Ninja::isHosted() && !config('ninja.testvars.travis')) { app('router')->aliasMiddleware('throttle', ThrottleRequestsWithPredis::class); } else { app('router')->aliasMiddleware('throttle', ThrottleRequests::class); diff --git a/app/Repositories/ActivityRepository.php b/app/Repositories/ActivityRepository.php index 41afd48b96..88648aa313 100644 --- a/app/Repositories/ActivityRepository.php +++ b/app/Repositories/ActivityRepository.php @@ -11,23 +11,23 @@ namespace App\Repositories; -use App\Models\User; -use App\Models\Quote; +use App\Models\Activity; use App\Models\Backup; +use App\Models\CompanyToken; use App\Models\Credit; use App\Models\Design; use App\Models\Invoice; -use App\Models\Activity; -use App\Utils\HtmlEngine; -use App\Models\CompanyToken; use App\Models\PurchaseOrder; -use App\Utils\Traits\MakesHash; -use App\Utils\VendorHtmlEngine; +use App\Models\Quote; use App\Models\RecurringInvoice; -use App\Utils\Traits\MakesInvoiceHtml; +use App\Models\User; use App\Services\PdfMaker\Design as PdfDesignModel; use App\Services\PdfMaker\Design as PdfMakerDesign; use App\Services\PdfMaker\PdfMaker as PdfMakerService; +use App\Utils\HtmlEngine; +use App\Utils\Traits\MakesHash; +use App\Utils\Traits\MakesInvoiceHtml; +use App\Utils\VendorHtmlEngine; /** * Class for activity repository. @@ -52,8 +52,9 @@ class ActivityRepository extends BaseRepository $activity->{$key} = $value; } - if($entity->company) + if($entity->company) { $activity->account_id = $entity->company->account_id; + } if ($token_id = $this->getTokenId($event_vars)) { $activity->token_id = $token_id; @@ -97,8 +98,7 @@ class ActivityRepository extends BaseRepository return; } - if(get_class($entity) == PurchaseOrder::class) - { + if(get_class($entity) == PurchaseOrder::class) { $backup = new Backup(); $entity->load('client'); @@ -162,6 +162,8 @@ class ActivityRepository extends BaseRepository 'options' => [ 'all_pages_header' => $entity->vendor->getSetting('all_pages_header'), 'all_pages_footer' => $entity->vendor->getSetting('all_pages_footer'), + 'vendor' => $entity->vendor, + 'entity' => $entity, ], 'process_markdown' => $entity->vendor->company->markdown_enabled, ]; @@ -230,6 +232,8 @@ class ActivityRepository extends BaseRepository 'options' => [ 'all_pages_header' => $entity->client->getSetting('all_pages_header'), 'all_pages_footer' => $entity->client->getSetting('all_pages_footer'), + 'client' => $entity->client, + 'entity' => $entity, ], 'process_markdown' => $entity->client->company->markdown_enabled, ]; diff --git a/app/Repositories/BankIntegrationRepository.php b/app/Repositories/BankIntegrationRepository.php index bd4bfb2b2d..431ce46077 100644 --- a/app/Repositories/BankIntegrationRepository.php +++ b/app/Repositories/BankIntegrationRepository.php @@ -11,9 +11,9 @@ namespace App\Repositories; -use App\Utils\Ninja; -use App\Models\BankIntegration; use App\Helpers\Bank\Yodlee\Yodlee; +use App\Models\BankIntegration; +use App\Utils\Ninja; /** * Class for bank integration repository. @@ -30,21 +30,20 @@ class BankIntegrationRepository extends BaseRepository return $bank_integration->fresh(); } - /** - * Removes the bank integration from Yodlee - * - * @param BankIntegration $bank_integration - * - * @return BankIntegration $bank_integration - */ + /** + * Removes the bank integration from Yodlee + * + * @param BankIntegration $bank_integration + * + * @return BankIntegration $bank_integration + */ public function delete($bank_integration) :BankIntegration { if ($bank_integration->is_deleted) { return $bank_integration; } - if(Ninja::isHosted()) - { + if(Ninja::isHosted()) { $account = $bank_integration->account; @@ -54,8 +53,7 @@ class BankIntegrationRepository extends BaseRepository try { $yodlee->deleteAccount($bank_integration->bank_account_id); - } - catch(\Exception $e){ + } catch(\Exception $e) { } diff --git a/app/Repositories/BankTransactionRepository.php b/app/Repositories/BankTransactionRepository.php index 66e54d3ff9..3326d50a58 100644 --- a/app/Repositories/BankTransactionRepository.php +++ b/app/Repositories/BankTransactionRepository.php @@ -11,9 +11,9 @@ namespace App\Repositories; -use App\Models\Expense; -use App\Models\BankTransaction; use App\Jobs\Bank\MatchBankTransactions; +use App\Models\BankTransaction; +use App\Models\Expense; /** * Class for bank transaction repository. @@ -50,14 +50,14 @@ class BankTransactionRepository extends BaseRepository public function unlink($bt) { - if($bt->payment()->exists()){ + if($bt->payment()->exists()) { $bt->payment->transaction_id = null; $bt->payment_id = null; } $e = Expense::query()->whereIn('id', $this->transformKeys(explode(",", $bt->expense_id))) ->cursor() - ->each(function ($expense){ + ->each(function ($expense) { $expense->transaction_id = null; $expense->saveQuietly(); diff --git a/app/Repositories/BaseRepository.php b/app/Repositories/BaseRepository.php index f392d24f36..68ccb310cf 100644 --- a/app/Repositories/BaseRepository.php +++ b/app/Repositories/BaseRepository.php @@ -11,19 +11,19 @@ namespace App\Repositories; -use App\Utils\Ninja; -use App\Models\Quote; -use App\Models\Client; -use App\Models\Credit; -use App\Utils\Helpers; -use App\Models\Company; -use App\Models\Invoice; -use App\Models\ClientContact; -use App\Utils\Traits\MakesHash; -use App\Models\RecurringInvoice; use App\Jobs\Client\UpdateTaxData; -use App\Utils\Traits\SavesDocuments; use App\Jobs\Product\UpdateOrCreateProduct; +use App\Models\Client; +use App\Models\ClientContact; +use App\Models\Company; +use App\Models\Credit; +use App\Models\Invoice; +use App\Models\Quote; +use App\Models\RecurringInvoice; +use App\Utils\Helpers; +use App\Utils\Ninja; +use App\Utils\Traits\MakesHash; +use App\Utils\Traits\SavesDocuments; class BaseRepository { @@ -312,8 +312,9 @@ class BaseRepository } /** If the client does not have tax_data - then populate this now */ - if($client->country_id == 840 && !$client->tax_data && $model->company->calculate_taxes && !$model->company->account->isFreeHostedClient()) + if($client->country_id == 840 && !$client->tax_data && $model->company->calculate_taxes && !$model->company->account->isFreeHostedClient()) { UpdateTaxData::dispatch($client, $client->company); + } } diff --git a/app/Repositories/InvoiceRepository.php b/app/Repositories/InvoiceRepository.php index f3d4a4e6d4..2cbe775e69 100644 --- a/app/Repositories/InvoiceRepository.php +++ b/app/Repositories/InvoiceRepository.php @@ -98,8 +98,9 @@ class InvoiceRepository extends BaseRepository $invoice = $invoice->service()->handleRestore()->save(); /* If the reverse did not succeed due to rules, then do not restore / unarchive */ - if($invoice->is_deleted) + if($invoice->is_deleted) { return $invoice; + } parent::restore($invoice); diff --git a/app/Repositories/Migration/PaymentMigrationRepository.php b/app/Repositories/Migration/PaymentMigrationRepository.php index f8f6f686b7..6fb6b3455c 100644 --- a/app/Repositories/Migration/PaymentMigrationRepository.php +++ b/app/Repositories/Migration/PaymentMigrationRepository.php @@ -156,7 +156,7 @@ class PaymentMigrationRepository extends BaseRepository $payment->credits->each(function ($cre) use ($credit_totals) { $cre->pivot->amount = $credit_totals; - $cre->pivot->save(); + $cre->pivot->save(); $cre->paid_to_date += $credit_totals; $cre->balance -= $credit_totals; @@ -195,7 +195,7 @@ class PaymentMigrationRepository extends BaseRepository /** * If the client is paying in a currency other than * the company currency, we need to set a record. - * + * * @param array$data * @param \App\Models\Payment $payment * @return \App\Models\Payment diff --git a/app/Repositories/PaymentRepository.php b/app/Repositories/PaymentRepository.php index 6bc842e4b5..1402336745 100644 --- a/app/Repositories/PaymentRepository.php +++ b/app/Repositories/PaymentRepository.php @@ -11,20 +11,20 @@ namespace App\Repositories; -use App\Utils\Ninja; +use App\Events\Payment\PaymentWasCreated; +use App\Events\Payment\PaymentWasDeleted; +use App\Jobs\Credit\ApplyCreditPayment; +use App\Libraries\Currency\Conversion\CurrencyApi; use App\Models\Client; use App\Models\Credit; use App\Models\Invoice; use App\Models\Payment; use App\Models\Paymentable; -use Illuminate\Http\Request; -use Illuminate\Support\Carbon; +use App\Utils\Ninja; use App\Utils\Traits\MakesHash; use App\Utils\Traits\SavesDocuments; -use App\Jobs\Credit\ApplyCreditPayment; -use App\Events\Payment\PaymentWasCreated; -use App\Events\Payment\PaymentWasDeleted; -use App\Libraries\Currency\Conversion\CurrencyApi; +use Illuminate\Http\Request; +use Illuminate\Support\Carbon; /** * PaymentRepository. @@ -142,16 +142,13 @@ class PaymentRepository extends BaseRepository $invoices = Invoice::withTrashed()->whereIn('id', array_column($data['invoices'], 'invoice_id'))->get(); - // $payment->invoices()->saveMany($invoices); //25-06-2023 - //todo optimize this into a single query foreach ($data['invoices'] as $paid_invoice) { - // $invoice = Invoice::withTrashed()->whereId($paid_invoice['invoice_id'])->first(); $invoice = $invoices->firstWhere('id', $paid_invoice['invoice_id']); if ($invoice) { - //25-06-2023 + //25-06-2023 $paymentable = new Paymentable(); $paymentable->payment_id = $payment->id; diff --git a/app/Repositories/TaskRepository.php b/app/Repositories/TaskRepository.php index dbb954d35a..3bc2661e27 100644 --- a/app/Repositories/TaskRepository.php +++ b/app/Repositories/TaskRepository.php @@ -17,7 +17,7 @@ use App\Utils\Traits\GeneratesCounter; use Illuminate\Database\QueryException; /** - * TaskRepository. + * App\Repositories\TaskRepository. */ class TaskRepository extends BaseRepository { @@ -140,7 +140,7 @@ class TaskRepository extends BaseRepository private function harvestStartDate($time_log, $task) { - if(isset($time_log[0][0])){ + if(isset($time_log[0][0])) { return \Carbon\Carbon::createFromTimestamp($time_log[0][0])->addSeconds($task->company->utc_offset()); } diff --git a/app/Repositories/TaskStatusRepository.php b/app/Repositories/TaskStatusRepository.php index 34a1f8e636..d586c5cc97 100644 --- a/app/Repositories/TaskStatusRepository.php +++ b/app/Repositories/TaskStatusRepository.php @@ -64,7 +64,7 @@ class TaskStatusRepository extends BaseRepository ->where('id', '!=', $task_status->id) ->orderByRaw('ISNULL(status_order), status_order ASC') ->cursor() - ->each(function ($ts, $key) use($task_status){ + ->each(function ($ts, $key) use ($task_status) { if($ts->status_order < $task_status->status_order) { $ts->status_order--; diff --git a/app/Repositories/VendorContactRepository.php b/app/Repositories/VendorContactRepository.php index 6827b57d23..b2b1647fcf 100644 --- a/app/Repositories/VendorContactRepository.php +++ b/app/Repositories/VendorContactRepository.php @@ -61,13 +61,23 @@ class VendorContactRepository extends BaseRepository $update_contact->contact_key = Str::random(40); } + if (array_key_exists('email', $contact) && is_null($contact['email'])) { + $contact['email'] = ''; + } + $update_contact->fill($contact); if (array_key_exists('password', $contact) && strlen($contact['password']) > 1) { $update_contact->password = Hash::make($contact['password']); + $vendor->company->vendor_contacts()->where('email', $update_contact->email)->update(['password' => $update_contact->password]); + } + + if (array_key_exists('email', $contact)) { + $update_contact->email = trim($contact['email']); } $update_contact->saveQuietly(); + }); $vendor->load('contacts'); diff --git a/app/Services/Bank/ProcessBankRules.php b/app/Services/Bank/ProcessBankRules.php index f5dd8a9f9a..4657b7a8e1 100644 --- a/app/Services/Bank/ProcessBankRules.php +++ b/app/Services/Bank/ProcessBankRules.php @@ -139,7 +139,7 @@ class ProcessBankRules extends AbstractService } } - private function coalesceExpenses($expense): string + private function coalesceExpenses($expense): string { if (!$this->bank_transaction->expense_id || strlen($this->bank_transaction->expense_id) < 1) { diff --git a/app/Services/Chart/ChartService.php b/app/Services/Chart/ChartService.php index 3378c998ae..1815187921 100644 --- a/app/Services/Chart/ChartService.php +++ b/app/Services/Chart/ChartService.php @@ -11,10 +11,10 @@ namespace App\Services\Chart; -use App\Models\User; use App\Models\Client; use App\Models\Company; use App\Models\Expense; +use App\Models\User; use Illuminate\Support\Facades\Cache; class ChartService diff --git a/app/Services/Chart/ChartServiceLegacy.php b/app/Services/Chart/ChartServiceLegacy.php index 372d89ddb9..84e53cec93 100644 --- a/app/Services/Chart/ChartServiceLegacy.php +++ b/app/Services/Chart/ChartServiceLegacy.php @@ -15,7 +15,6 @@ use App\Models\Client; use App\Models\Company; use App\Models\Expense; use Illuminate\Support\Facades\Cache; -use App\Services\Chart\ChartQueriesLegacy; class ChartServiceLegacy { diff --git a/app/Services/Client/ClientService.php b/app/Services/Client/ClientService.php index 8aabdbcbf4..e8e39fb4ca 100644 --- a/app/Services/Client/ClientService.php +++ b/app/Services/Client/ClientService.php @@ -11,16 +11,16 @@ namespace App\Services\Client; +use App\Utils\Number; use App\Models\Client; use App\Models\Credit; +use App\Models\Invoice; use App\Models\Payment; use App\Services\Email\Email; -use App\Services\Email\EmailObject; -use App\Utils\Number; use App\Utils\Traits\MakesDates; -use Carbon\Carbon; -use Illuminate\Mail\Mailables\Address; use Illuminate\Support\Facades\DB; +use App\Services\Email\EmailObject; +use Illuminate\Mail\Mailables\Address; class ClientService { @@ -43,16 +43,12 @@ class ClientService $this->client->saveQuietly(); }, 2); } catch (\Throwable $throwable) { - nlog("DB ERROR " . $throwable->getMessage()); - DB::connection(config('database.default'))->rollBack(); if (DB::connection(config('database.default'))->transactionLevel() > 0) { DB::connection(config('database.default'))->rollBack(); } - } catch(\Exception $exception){ - nlog("DB ERROR " . $exception->getMessage()); - DB::connection(config('database.default'))->rollBack(); + } catch(\Exception $exception) { if (DB::connection(config('database.default'))->transactionLevel() > 0) { DB::connection(config('database.default'))->rollBack(); @@ -78,7 +74,7 @@ class ClientService DB::connection(config('database.default'))->rollBack(); } - } catch(\Exception $exception){ + } catch(\Exception $exception) { nlog("DB ERROR " . $exception->getMessage()); if (DB::connection(config('database.default'))->transactionLevel() > 0) { @@ -97,15 +93,14 @@ class ClientService $this->client->paid_to_date += $amount; $this->client->saveQuietly(); }, 2); - } - catch (\Throwable $throwable) { + } catch (\Throwable $throwable) { nlog("DB ERROR " . $throwable->getMessage()); if (DB::connection(config('database.default'))->transactionLevel() > 0) { DB::connection(config('database.default'))->rollBack(); } - } catch(\Exception $exception){ + } catch(\Exception $exception) { nlog("DB ERROR " . $exception->getMessage()); if (DB::connection(config('database.default'))->transactionLevel() > 0) { @@ -233,7 +228,7 @@ class ClientService $cc_contacts = $this->client ->contacts() ->where('send_email', true) - ->where('email', '!=', $email) + ->where('email', '!=', $email) ->get(); foreach ($cc_contacts as $contact) { @@ -242,8 +237,13 @@ class ClientService } + $invoice = $this->client->invoices()->whereHas('invitations')->first(); + $email_object->attachments = [['file' => base64_encode($pdf), 'name' => ctrans('texts.statement') . ".pdf"]]; $email_object->client_id = $this->client->id; + $email_object->entity_class = Invoice::class; + $email_object->entity_id = $invoice->id ?? null; + $email_object->invitation_id = $invoice->invitations->first()->id ?? null; $email_object->email_template_subject = 'email_subject_statement'; $email_object->email_template_body = 'email_template_statement'; $email_object->variables = [ diff --git a/app/Services/Client/PaymentMethod.php b/app/Services/Client/PaymentMethod.php index aa535e916a..e74874ad01 100644 --- a/app/Services/Client/PaymentMethod.php +++ b/app/Services/Client/PaymentMethod.php @@ -65,8 +65,9 @@ class PaymentMethod if ($company_gateways || $company_gateways == '0') { $transformed_ids = $this->transformKeys(explode(',', $company_gateways)); - if($company_gateways == '0') + if($company_gateways == '0') { $transformed_ids = []; + } $this->gateways = $this->client ->company @@ -78,6 +79,19 @@ class PaymentMethod ->sortby(function ($model) use ($transformed_ids) { //company gateways are sorted in order of priority return array_search($model->id, $transformed_ids); // this closure sorts for us }); + + if($this->gateways->count() == 0 && count($transformed_ids) >=1) { + + /** This is a fallback in case a user archives some gateways that have been ordered preferentially. */ + $this->gateways = CompanyGateway::query() + ->with('gateway') + ->where('company_id', $this->client->company_id) + ->where('gateway_key', '!=', '54faab2ab6e3223dbe848b1686490baa') + ->whereNull('deleted_at') + ->where('is_deleted', false)->get(); + + } + } else { $this->gateways = CompanyGateway::query() ->with('gateway') diff --git a/app/Services/Client/Statement.php b/app/Services/Client/Statement.php index 5eff1ff260..1fd06f8143 100644 --- a/app/Services/Client/Statement.php +++ b/app/Services/Client/Statement.php @@ -16,6 +16,7 @@ use App\Factory\InvoiceFactory; use App\Factory\InvoiceInvitationFactory; use App\Factory\InvoiceItemFactory; use App\Models\Client; +use App\Models\Credit; use App\Models\Design; use App\Models\Invoice; use App\Models\Payment; @@ -25,14 +26,18 @@ use App\Utils\HostedPDF\NinjaPdf; use App\Utils\HtmlEngine; use App\Utils\Number; use App\Utils\PhantomJS\Phantom; +use App\Utils\Traits\MakesDates; +use App\Utils\Traits\MakesHash; use App\Utils\Traits\Pdf\PdfMaker as PdfMakerTrait; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; -use App\Models\Credit; class Statement { use PdfMakerTrait; + use MakesHash; + use MakesDates; /** * @var Invoice|Payment|null @@ -41,6 +46,8 @@ class Statement protected bool $rollback = false; + private array $variables = []; + public function __construct(protected Client $client, public array $options) { } @@ -53,6 +60,20 @@ class Statement $html = new HtmlEngine($this->getInvitation()); + $variables = []; + + if($this->client->getSetting('statement_design_id') != '') { + + $variables['values']['$start_date'] = $this->translateDate($this->options['start_date'], $this->client->date_format(), $this->client->locale()); + $variables['values']['$end_date'] = $this->translateDate($this->options['end_date'], $this->client->date_format(), $this->client->locale()); + $variables['labels']['$start_date_label'] = ctrans('texts.start_date'); + $variables['labels']['$end_date_label'] = ctrans('texts.end_date'); + + return $this->templateStatement($variables); + } + + $variables = $html->generateLabelsAndValues(); + if ($this->getDesign()->is_custom) { $this->options['custom_partials'] = \json_decode(\json_encode($this->getDesign()->design), true); @@ -71,13 +92,21 @@ class Statement 'pdf_variables' => (array) $this->entity->company->settings->pdf_variables, '$product' => $this->getDesign()->design->product, 'variables' => $variables, - 'invoices' => $this->getInvoices(), - 'payments' => $this->getPayments(), - 'credits' => $this->getCredits(), + 'invoices' => $this->getInvoices()->cursor(), + 'payments' => $this->getPayments()->cursor(), + 'credits' => $this->getCredits()->cursor(), 'aging' => $this->getAging(), ], \App\Services\PdfMaker\Design::STATEMENT), 'variables' => $variables, - 'options' => [], + 'options' => [ + 'client' => $this->client, + 'entity' => $this->entity, + 'variables' => $variables, + 'invoices' => $this->getInvoices()->cursor(), + 'payments' => $this->getPayments()->cursor(), + 'credits' => $this->getCredits()->cursor(), + 'aging' => $this->getAging(), + ], 'process_markdown' => $this->entity->client->company->markdown_enabled, ]; @@ -88,29 +117,78 @@ class Statement ->build(); $pdf = null; - - try { - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - $pdf = (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true)); - } elseif (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - } else { - $pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true)); - } - } catch (\Exception $e) { - nlog(print_r($e->getMessage(), 1)); - } + $html = $maker->getCompiledHTML(true); if ($this->rollback) { \DB::connection(config('database.default'))->rollBack(); } + $pdf = $this->convertToPdf($html); + + $this->setVariables($variables); + $maker = null; $state = null; return $pdf; } + public function setVariables($variables): self + { + $this->variables = $variables; + + return $this; + } + + public function getVariables(): array + { + return $this->variables; + } + + private function templateStatement($variables) + { + if(isset($this->options['template'])) { + $statement_design_id = $this->options['template']; + } else { + $statement_design_id = $this->client->getSetting('statement_design_id'); + } + + $template = Design::where('id', $this->decodePrimaryKey($statement_design_id)) + ->where('company_id', $this->client->company_id) + ->first(); + + $ts = $template->service()->build([ + 'variables' => collect([$variables]), + 'invoices' => $this->getInvoices()->get(), + 'payments' => $this->options['show_payments_table'] ? $this->getPayments()->get() : collect([]), + 'credits' => $this->options['show_credits_table'] ? $this->getCredits()->get() : collect([]), + 'aging' => $this->options['show_aging_table'] ? $this->getAging() : collect([]), + ]); + + $html = $ts->getHtml(); + + return $this->convertToPdf($html); + } + + private function convertToPdf(string $html): mixed + { + $pdf = false; + + try { + if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { + $pdf = (new Phantom)->convertHtmlToPdf($html); + } elseif (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { + $pdf = (new NinjaPdf())->build($html); + } else { + $pdf = $this->makePdf(null, null, $html); + } + } catch (\Exception $e) { + nlog(print_r($e->getMessage(), 1)); + } + + + return $pdf; + } /** * Setup correct entity instance. * @@ -222,9 +300,9 @@ class Statement /** * The collection of invoices for the statement. * - * @return Invoice[]|\Illuminate\Support\LazyCollection + * @return Builder */ - public function getInvoices(): \Illuminate\Support\LazyCollection + public function getInvoices(): Builder { return Invoice::withTrashed() ->with('payments.type') @@ -234,8 +312,7 @@ class Statement ->whereIn('status_id', $this->invoiceStatuses()) ->whereBetween('date', [Carbon::parse($this->options['start_date']), Carbon::parse($this->options['end_date'])]) ->orderBy('due_date', 'ASC') - ->orderBy('date', 'ASC') - ->cursor(); + ->orderBy('date', 'ASC'); } private function invoiceStatuses() :array @@ -256,7 +333,6 @@ class Statement case 'unpaid': return [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]; - default: return [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID]; @@ -266,9 +342,9 @@ class Statement /** * The collection of payments for the statement. * - * @return Payment[]|\Illuminate\Support\LazyCollection + * @return Builder */ - protected function getPayments(): \Illuminate\Support\LazyCollection + protected function getPayments(): Builder { return Payment::withTrashed() ->with('client.country', 'invoices') @@ -277,19 +353,18 @@ class Statement ->where('client_id', $this->client->id) ->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED]) ->whereBetween('date', [Carbon::parse($this->options['start_date']), Carbon::parse($this->options['end_date'])]) - ->orderBy('date', 'ASC') - ->cursor(); + ->orderBy('date', 'ASC'); } /** * The collection of credits for the statement. * - * @return Credit[]|\Illuminate\Support\LazyCollection + * @return Builder */ - protected function getCredits(): \Illuminate\Support\LazyCollection + protected function getCredits(): Builder { return Credit::withTrashed() - ->with('client.country', 'invoices') + ->with('client.country', 'invoice') ->where('is_deleted', false) ->where('company_id', $this->client->company_id) ->where('client_id', $this->client->id) @@ -299,8 +374,7 @@ class Statement $query->whereDate('due_date', '>=', $this->options['end_date']) ->orWhereNull('due_date'); }) - ->orderBy('date', 'ASC') - ->cursor(); + ->orderBy('date', 'ASC'); } /** @@ -339,7 +413,7 @@ class Statement * @param mixed $range * @return string */ - private function getAgingAmount($range) + private function getAgingAmount($range): string { $ranges = $this->calculateDateRanges($range); diff --git a/app/Services/ClientPortal/InstantPayment.php b/app/Services/ClientPortal/InstantPayment.php index 909e4f003a..3484122541 100644 --- a/app/Services/ClientPortal/InstantPayment.php +++ b/app/Services/ClientPortal/InstantPayment.php @@ -178,7 +178,7 @@ class InstantPayment $contact_id = auth()->guard('contact')->user() ? auth()->guard('contact')->user()->id : null; - $invoices->each(function ($invoice) use($contact_id) { + $invoices->each(function ($invoice) use ($contact_id) { InjectSignature::dispatch($invoice, $contact_id, $this->request->signature, request()->getClientIp()); }); } diff --git a/app/Services/Credit/CreditService.php b/app/Services/Credit/CreditService.php index 7b00f0ead9..a6c5bbcdef 100644 --- a/app/Services/Credit/CreditService.php +++ b/app/Services/Credit/CreditService.php @@ -11,16 +11,14 @@ namespace App\Services\Credit; -use App\Utils\Ninja; +use App\Factory\PaymentFactory; use App\Models\Credit; use App\Models\Payment; use App\Models\PaymentType; -use App\Jobs\Util\UnlinkFile; -use App\Factory\PaymentFactory; -use App\Utils\Traits\MakesHash; -use App\Jobs\Entity\CreateEntityPdf; use App\Repositories\CreditRepository; use App\Repositories\PaymentRepository; +use App\Utils\Ninja; +use App\Utils\Traits\MakesHash; use Illuminate\Support\Facades\Storage; class CreditService @@ -180,32 +178,6 @@ class CreditService return $this; } - /** - * Sometimes we need to refresh the - * PDF when it is updated etc. - * @return self - */ - public function touchPdf($force = false) - { - try { - if ($force) { - $this->credit->invitations->each(function ($invitation) { - (new CreateEntityPdf($invitation))->handle(); - }); - - return $this; - } - - $this->credit->invitations->each(function ($invitation) { - CreateEntityPdf::dispatch($invitation); - }); - } catch (\Exception $e) { - nlog('failed creating invoices in Touch PDF'); - } - - return $this; - } - public function fillDefaults() { $settings = $this->credit->client->getMergedSettings(); diff --git a/app/Services/Credit/GetCreditPdf.php b/app/Services/Credit/GetCreditPdf.php index 0ad1d9aac2..d43bb335ae 100644 --- a/app/Services/Credit/GetCreditPdf.php +++ b/app/Services/Credit/GetCreditPdf.php @@ -11,38 +11,20 @@ namespace App\Services\Credit; -use App\Jobs\Entity\CreateEntityPdf; +use App\Jobs\Entity\CreateRawPdf; +use App\Models\CreditInvitation; use App\Services\AbstractService; class GetCreditPdf extends AbstractService { - public $credit; - - public $contact; - - public $invitation; - - public function __construct($invitation) + public function __construct(public CreditInvitation $invitation) { - $this->invitation = $invitation; - $this->credit = $invitation->credit; - $this->contact = $invitation->contact; } public function run() { - if (! $this->contact) { - $this->contact = $this->credit->client->primary_contact()->first() ?: $this->credit->client->contacts()->first(); - } - $path = $this->credit->client->credit_filepath($this->invitation); + return (new CreateRawPdf($this->invitation))->handle(); - $file_path = $path.$this->credit->numberFormatter().'.pdf'; - - // $disk = 'public'; - $disk = config('filesystems.default'); - - $file_path = (new CreateEntityPdf($this->invitation))->handle(); - return $file_path; } } diff --git a/app/Services/Credit/SendEmail.php b/app/Services/Credit/SendEmail.php index f17f5f902d..9c2fd948af 100644 --- a/app/Services/Credit/SendEmail.php +++ b/app/Services/Credit/SendEmail.php @@ -11,10 +11,10 @@ namespace App\Services\Credit; -use App\Utils\Ninja; -use App\Models\ClientContact; -use App\Jobs\Entity\EmailEntity; use App\Events\Credit\CreditWasEmailed; +use App\Jobs\Entity\EmailEntity; +use App\Models\ClientContact; +use App\Utils\Ninja; class SendEmail { diff --git a/app/Services/Email/AdminEmail.php b/app/Services/Email/AdminEmail.php new file mode 100644 index 0000000000..2a245337fa --- /dev/null +++ b/app/Services/Email/AdminEmail.php @@ -0,0 +1,666 @@ +company->db); + + $this->setOverride() + ->buildMailable(); + + if ($this->preFlightChecksFail()) { + return; + } + + $this->email(); + + } + + /** + * Sets the override flag + * + * @return self + */ + public function setOverride(): self + { + $this->override = $this->email_object->override; + + return $this; + } + + /** + * Populates the mailable + * + * @return self + */ + public function buildMailable(): self + { + $this->mailable = new AdminEmailMailable($this->email_object); + + return $this; + } + + /** + * Attempts to send the email + * + * @return void + */ + public function email() + { + $this->setMailDriver(); + + /* Init the mailer*/ + $mailer = Mail::mailer($this->mailer); + + /* Additional configuration if using a client third party mailer */ + if ($this->client_postmark_secret) { + $mailer->postmark_config($this->client_postmark_secret); + } + + if ($this->client_mailgun_secret) { + $mailer->mailgun_config($this->client_mailgun_secret, $this->client_mailgun_domain, $this->client_mailgun_endpoint); + } + + /* Attempt the send! */ + try { + nlog("Using mailer => ". $this->mailer. " ". now()->toDateTimeString()); + + $mailer->send($this->mailable); + + Cache::increment("email_quota".$this->company->account->key); + + LightLogs::create(new EmailSuccess($this->company->company_key)) + ->send(); + + } catch(\Symfony\Component\Mime\Exception\RfcComplianceException $e) { + nlog("Mailer failed with a Logic Exception {$e->getMessage()}"); + $this->fail(); + $this->cleanUpMailers(); + $this->logMailError($e->getMessage(), $this->company->clients()->first()); + return; + } catch(\Symfony\Component\Mime\Exception\LogicException $e) { + nlog("Mailer failed with a Logic Exception {$e->getMessage()}"); + $this->fail(); + $this->cleanUpMailers(); + $this->logMailError($e->getMessage(), $this->company->clients()->first()); + return; + } catch (\Exception | \RuntimeException | \Google\Service\Exception $e) { + nlog("Mailer failed with {$e->getMessage()}"); + $message = $e->getMessage(); + + if (stripos($e->getMessage(), 'code 406') || stripos($e->getMessage(), 'code 300') || stripos($e->getMessage(), 'code 413')) { + $message = "Either Attachment too large, or recipient has been suppressed."; + + $this->fail(); + $this->logMailError($e->getMessage(), $this->company->clients()->first()); + $this->cleanUpMailers(); + + return; + } + + /** + * Post mark buries the proper message in a a guzzle response + * this merges a text string with a json object + * need to harvest the ->Message property using the following + */ + if ($e instanceof ClientException) { //postmark specific failure + $response = $e->getResponse(); + $message_body = json_decode($response->getBody()->getContents()); + + if ($message_body && property_exists($message_body, 'Message')) { + $message = $message_body->Message; + nlog($message); + } + + $this->fail(); + $this->cleanUpMailers(); + return; + } + + //only report once, not on all tries + if ($this->attempts() == $this->tries) { + /* If the is an entity attached to the message send a failure mailer */ + $this->entityEmailFailed($message); + + /* Don't send postmark failures to Sentry */ + if (Ninja::isHosted() && (!$e instanceof ClientException)) { + app('sentry')->captureException($e); + } + } + + sleep(rand(0, 3)); + + $this->release($this->backoff()[$this->attempts()-1]); + + $message = null; + } + + $this->cleanUpMailers(); + } + + /** + * On the hosted platform we scan all outbound email for + * spam. This sequence processes the filters we use on all + * emails. + * + * @return bool + */ + public function preFlightChecksFail(): bool + { + /* Always send if disabled */ + if($this->override) { + return false; + } + + /* If we are migrating data we don't want to fire any emails */ + if ($this->company->is_disabled) { + return true; + } + + if (Ninja::isSelfHost()) { + return false; + } + + /* To handle spam users we drop all emails from flagged accounts */ + if ($this->company->account && $this->company->account->is_flagged) { + return true; + } + + /* On the hosted platform we set default contacts a @example.com email address - we shouldn't send emails to these types of addresses */ + if ($this->hasInValidEmails()) { + return true; + } + + /* GMail users are uncapped */ + if (in_array($this->email_object->settings->email_sending_method, ['gmail', 'office365', 'client_postmark', 'client_mailgun'])) { + return false; + } + + /* On the hosted platform, if the user is over the email quotas, we do not send the email. */ + if ($this->company->account && $this->company->account->emailQuotaExceeded()) { + return true; + } + + /* If the account is verified, we allow emails to flow */ + if ($this->company->account && $this->company->account->is_verified_account) { + //11-01-2022 + + /* Continue to analyse verified accounts in case they later start sending poor quality emails*/ + // if(class_exists(\Modules\Admin\Jobs\Account\EmailQuality::class)) + // (new \Modules\Admin\Jobs\Account\EmailQuality($this->nmo, $this->company))->run(); + + return false; + } + + /* On the hosted platform if the user has not verified their account we fail here - but still check what they are trying to send! */ + if ($this->company->account && !$this->company->account->account_sms_verified) { + if (class_exists(\Modules\Admin\Jobs\Account\EmailFilter::class)) { + return (new \Modules\Admin\Jobs\Account\EmailFilter($this->email_object, $this->company))->run(); + } + + return true; + } + + /* On the hosted platform we actively scan all outbound emails to ensure outbound email quality remains high */ + if (class_exists(\Modules\Admin\Jobs\Account\EmailFilter::class)) { + return (new \Modules\Admin\Jobs\Account\EmailFilter($this->email_object, $this->company))->run(); + } + + return false; + } + + /** + * hasInValidEmails + * + * @return bool + */ + private function hasInValidEmails(): bool + { + foreach ($this->email_object->to as $address_object) { + if (strpos($address_object->address, '@example.com') !== false) { + return true; + } + + if (!str_contains($address_object->address, "@")) { + return true; + } + + if ($address_object->address == " ") { + return true; + } + } + + + return false; + } + + /** + * Sets the mail driver to use and applies any specific configuration + * the the mailable + */ + private function setMailDriver(): self + { + switch ($this->email_object->settings->email_sending_method) { + case 'default': + $this->mailer = config('mail.default'); + break; + case 'gmail': + $this->mailer = 'gmail'; + $this->setGmailMailer(); + return $this; + case 'office365': + case 'microsoft': + $this->mailer = 'office365'; + $this->setOfficeMailer(); + return $this; + case 'client_postmark': + $this->mailer = 'postmark'; + $this->setPostmarkMailer(); + return $this; + case 'client_mailgun': + $this->mailer = 'mailgun'; + $this->setMailgunMailer(); + return $this; + + default: + $this->mailer = config('mail.default'); + return $this; + } + + if (Ninja::isSelfHost()) { + $this->setSelfHostMultiMailer(); + } + + return $this; + } + + /** + * Allows configuration of multiple mailers + * per company for use by self hosted users + */ + private function setSelfHostMultiMailer(): void + { + if (env($this->company->id . '_MAIL_HOST')) { + config([ + 'mail.mailers.smtp' => [ + 'transport' => 'smtp', + 'host' => env($this->company->id . '_MAIL_HOST'), + 'port' => env($this->company->id . '_MAIL_PORT'), + 'username' => env($this->company->id . '_MAIL_USERNAME'), + 'password' => env($this->company->id . '_MAIL_PASSWORD'), + ], + ]); + + if (env($this->company->id . '_MAIL_FROM_ADDRESS')) { + $this->mailable + ->from(env($this->company->id . '_MAIL_FROM_ADDRESS', env('MAIL_FROM_ADDRESS')), env($this->company->id . '_MAIL_FROM_NAME', env('MAIL_FROM_NAME'))); + } + } + } + + + /** + * Ensure we discard any data that is not required + * + * @return void + */ + private function cleanUpMailers(): void + { + $this->client_postmark_secret = null; + + $this->client_mailgun_secret = null; + + $this->client_mailgun_domain = null; + + $this->client_mailgun_endpoint = null; + + //always dump the drivers to prevent reuse + app('mail.manager')->forgetMailers(); + } + + + /** + * Check to ensure no cross account + * emails can be sent. + * + * @param User $user + */ + private function checkValidSendingUser($user) + { + /* Always ensure the user is set on the correct account */ + if ($user->account_id != $this->company->account_id) { + $this->email_object->settings->email_sending_method = 'default'; + + return $this->setMailDriver(); + } + } + + /** + * Resolves the sending user + * when configuring the Mailer + * on behalf of the client + * + * @return User $user + */ + private function resolveSendingUser(): ?User + { + $sending_user = $this->email_object->settings->gmail_sending_user_id; + + if ($sending_user == "0") { + $user = $this->company->owner(); + } else { + $user = User::find($this->decodePrimaryKey($sending_user)); + } + + return $user; + } + /** + * Configures Mailgun using client supplied secret + * as the Mailer + */ + private function setMailgunMailer() + { + if (strlen($this->email_object->settings->mailgun_secret) > 2 && strlen($this->email_object->settings->mailgun_domain) > 2) { + $this->client_mailgun_secret = $this->email_object->settings->mailgun_secret; + $this->client_mailgun_domain = $this->email_object->settings->mailgun_domain; + $this->client_mailgun_endpoint = $this->email_object->settings->mailgun_endpoint; + + } else { + $this->email_object->settings->email_sending_method = 'default'; + return $this->setMailDriver(); + } + + $user = $this->resolveSendingUser(); + + $sending_email = (isset($this->email_object->settings->custom_sending_email) && stripos($this->email_object->settings->custom_sending_email, "@")) ? $this->email_object->settings->custom_sending_email : $user->email; + $sending_user = (isset($this->email_object->settings->email_from_name) && strlen($this->email_object->settings->email_from_name) > 2) ? $this->email_object->settings->email_from_name : $user->name(); + + $this->mailable + ->from($sending_email, $sending_user); + } + + /** + * Configures Postmark using client supplied secret + * as the Mailer + */ + private function setPostmarkMailer() + { + if (strlen($this->email_object->settings->postmark_secret) > 2) { + $this->client_postmark_secret = $this->email_object->settings->postmark_secret; + } else { + $this->email_object->settings->email_sending_method = 'default'; + return $this->setMailDriver(); + } + + $user = $this->resolveSendingUser(); + + $sending_email = (isset($this->email_object->settings->custom_sending_email) && stripos($this->email_object->settings->custom_sending_email, "@")) ? $this->email_object->settings->custom_sending_email : $user->email; + $sending_user = (isset($this->email_object->settings->email_from_name) && strlen($this->email_object->settings->email_from_name) > 2) ? $this->email_object->settings->email_from_name : $user->name(); + + $this->mailable + ->from($sending_email, $sending_user); + } + + /** + * Configures Microsoft via Oauth + * as the Mailer + */ + private function setOfficeMailer() + { + $user = $this->resolveSendingUser(); + + $this->checkValidSendingUser($user); + + nlog("Sending via {$user->name()}"); + + $token = $this->refreshOfficeToken($user); + + if ($token) { + $user->oauth_user_token = $token; + $user->save(); + } else { + $this->email_object->settings->email_sending_method = 'default'; + return $this->setMailDriver(); + } + + $this->mailable + ->from($user->email, $user->name()) + ->withSymfonyMessage(function ($message) use ($token) { + $message->getHeaders()->addTextHeader('gmailtoken', $token); + }); + } + + /** + * Configures GMail via Oauth + * as the Mailer + */ + private function setGmailMailer() + { + $user = $this->resolveSendingUser(); + + $this->checkValidSendingUser($user); + + nlog("Sending via {$user->name()}"); + + $google = (new Google())->init(); + + try { + if ($google->getClient()->isAccessTokenExpired()) { + $google->refreshToken($user); + $user = $user->fresh(); + } + + $google->getClient()->setAccessToken(json_encode($user->oauth_user_token)); + } catch(\Exception $e) { + $this->logMailError('Gmail Token Invalid', $this->company->clients()->first()); + $this->email_object->settings->email_sending_method = 'default'; + return $this->setMailDriver(); + } + + /** + * If the user doesn't have a valid token, notify them + */ + + if (!$user->oauth_user_token) { + $this->company->account->gmailCredentialNotification(); + $this->email_object->settings->email_sending_method = 'default'; + return $this->setMailDriver(); + } + + /* + * Now that our token is refreshed and valid we can boot the + * mail driver at runtime and also set the token which will persist + * just for this request. + */ + + $token = $user->oauth_user_token->access_token; + + if (!$token) { + $this->company->account->gmailCredentialNotification(); + $this->email_object->settings->email_sending_method = 'default'; + return $this->setMailDriver(); + } + + $this->mailable + ->from($user->email, $user->name()) + ->withSymfonyMessage(function ($message) use ($token) { + $message->getHeaders()->addTextHeader('gmailtoken', $token); + }); + } + + /** + * Logs any errors to the SystemLog + * + * @param string $errors + * @param null | \App\Models\Client $recipient_object + * @return void + */ + private function logMailError($errors, $recipient_object) :void + { + (new SystemLogger( + $errors, + SystemLog::CATEGORY_MAIL, + SystemLog::EVENT_MAIL_SEND, + SystemLog::TYPE_FAILURE, + $recipient_object, + $this->company + ))->handle(); + + $job_failure = new EmailFailure($this->company->company_key); + $job_failure->string_metric5 = 'failed_email'; + $job_failure->string_metric6 = substr($errors, 0, 150); + + LightLogs::create($job_failure) + ->send(); + + $job_failure = null; + } + + /** + * Attempts to refresh the Microsoft refreshToken + * + * @param \App\Models\User $user + * @return mixed + */ + private function refreshOfficeToken(User $user): mixed + { + $expiry = $user->oauth_user_token_expiry ?: now()->subDay(); + + if ($expiry->lt(now())) { + $guzzle = new \GuzzleHttp\Client(); + $url = 'https://login.microsoftonline.com/common/oauth2/v2.0/token'; + + $token = json_decode($guzzle->post($url, [ + 'form_params' => [ + 'client_id' => config('ninja.o365.client_id') , + 'client_secret' => config('ninja.o365.client_secret') , + 'scope' => 'email Mail.Send offline_access profile User.Read openid', + 'grant_type' => 'refresh_token', + 'refresh_token' => $user->oauth_user_refresh_token + ], + ])->getBody()->getContents()); + + if ($token) { + $user->oauth_user_refresh_token = property_exists($token, 'refresh_token') ? $token->refresh_token : $user->oauth_user_refresh_token; + $user->oauth_user_token = $token->access_token; + $user->oauth_user_token_expiry = now()->addSeconds($token->expires_in); + $user->save(); + + return $token->access_token; + } + + return false; + } + + return $user->oauth_user_token; + } + + /** + * Entity notification when an email fails to send + * + * @param string $message + * @return void + */ + private function entityEmailFailed($message): void + { + $class = get_class($this->email_object->entity); + + switch ($class) { + case Invoice::class: + event(new InvoiceWasEmailedAndFailed($this->email_object->invitation, $this->company, $message, $this->email_object->html_template, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); + break; + case Payment::class: + event(new PaymentWasEmailedAndFailed($this->email_object->entity, $this->company, $message, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); + break; + default: + # code... + break; + } + + if ($this->email_object->client) { + $this->logMailError($message, $this->email_object->client); + } + } + + + public function failed($exception = null) + { + if ($exception) { + nlog($exception->getMessage()); + } + + config(['queue.failed.driver' => null]); + } +} diff --git a/app/Services/Email/AdminEmailMailable.php b/app/Services/Email/AdminEmailMailable.php new file mode 100644 index 0000000000..f0e18158d1 --- /dev/null +++ b/app/Services/Email/AdminEmailMailable.php @@ -0,0 +1,104 @@ +", "", $this->email_object->subject), + tags: [$this->email_object->company_key], + replyTo: $this->email_object->reply_to, + from: $this->email_object->from, + to: $this->email_object->to, + bcc: $this->email_object->bcc, + cc: $this->email_object->cc, + ); + } + + /** + * Get the message content definition. + * + * @return \Illuminate\Mail\Mailables\Content + */ + public function content() + { + + return new Content( + view: 'email.admin.generic', + text: 'email.admin.generic_text', + with: [ + 'title' => $this->email_object->subject, + 'message' => $this->email_object->body, + 'url' => $this->email_object->url ?? null, + 'button' => $this->email_object->button ?? null, + 'signature' => $this->email_object->company->owner()->signature, + 'logo' => $this->email_object->company->present()->logo(), + 'settings' => $this->email_object->settings, + 'whitelabel' => $this->email_object->company->account->isPaid() ? true : false, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments() + { + $attachments = []; + + $attachments = collect($this->email_object->attachments)->map(function ($file) { + return Attachment::fromData(fn () => base64_decode($file['file']), $file['name']); + }); + + return $attachments->toArray(); + } + + /** + * Get the message headers. + * + * @return \Illuminate\Mail\Mailables\Headers + */ + public function headers() + { + return new Headers( + messageId: null, + references: [], + text: $this->email_object->headers, + ); + } +} diff --git a/app/Services/Email/Email.php b/app/Services/Email/Email.php index c539d075d4..093febece8 100644 --- a/app/Services/Email/Email.php +++ b/app/Services/Email/Email.php @@ -75,7 +75,6 @@ class Email implements ShouldQueue */ public function backoff() { - // return [10, 30, 60, 240]; return [rand(10, 20), rand(30, 45), rand(60, 79), rand(160, 400)]; } @@ -157,7 +156,7 @@ class Email implements ShouldQueue /** * Generates the correct set of variables - * + * @todo handle payment engine here also * @return self */ private function resolveVariables(): self @@ -323,13 +322,13 @@ class Email implements ShouldQueue $this->cleanUpMailers(); } - /** - * On the hosted platform we scan all outbound email for - * spam. This sequence processes the filters we use on all - * emails. - * - * @return bool - */ + /** + * On the hosted platform we scan all outbound email for + * spam. This sequence processes the filters we use on all + * emails. + * + * @return bool + */ public function preFlightChecksFail(): bool { /* Always send if disabled */ @@ -427,7 +426,7 @@ class Email implements ShouldQueue return false; } - /** + /** * Sets the mail driver to use and applies any specific configuration * the the mailable */ @@ -467,7 +466,7 @@ class Email implements ShouldQueue return $this; } - /** + /** * Allows configuration of multiple mailers * per company for use by self hosted users */ diff --git a/app/Services/Email/EmailDefaults.php b/app/Services/Email/EmailDefaults.php index 1f78facaa1..45ae94ed7a 100644 --- a/app/Services/Email/EmailDefaults.php +++ b/app/Services/Email/EmailDefaults.php @@ -11,23 +11,18 @@ namespace App\Services\Email; -use App\Models\Task; -use App\Utils\Ninja; -use App\Models\Quote; -use App\Models\Credit; +use App\DataMapper\EmailTemplateDefaults; +use App\Jobs\Entity\CreateRawPdf; +use App\Jobs\Invoice\CreateUbl; use App\Models\Account; use App\Models\Expense; use App\Models\Invoice; -use App\Models\PurchaseOrder; -use App\Jobs\Invoice\CreateUbl; +use App\Models\Task; +use App\Utils\Ninja; use App\Utils\Traits\MakesHash; -use App\Jobs\Entity\CreateRawPdf; -use Illuminate\Support\Facades\App; use Illuminate\Mail\Mailables\Address; -use Illuminate\Support\Facades\Storage; -use App\DataMapper\EmailTemplateDefaults; +use Illuminate\Support\Facades\App; use League\CommonMark\CommonMarkConverter; -use App\Jobs\Vendor\CreatePurchaseOrderPdf; class EmailDefaults { @@ -256,7 +251,7 @@ class EmailDefaults if (strlen($this->email->email_object->settings->bcc_email) > 1) { if (Ninja::isHosted() && $this->email->company->account->isPaid()) { $bccs = array_slice(explode(',', str_replace(' ', '', $this->email->email_object->settings->bcc_email)), 0, 5); - } else { + } else { $bccs = (explode(',', str_replace(' ', '', $this->email->email_object->settings->bcc_email))); } } @@ -300,15 +295,8 @@ class EmailDefaults } /** Purchase Order / Invoice / Credit / Quote PDF */ - if ($this->email->email_object->settings->pdf_email_attachment && $this->email->email_object->entity instanceof PurchaseOrder) { - $pdf = (new CreatePurchaseOrderPdf($this->email->email_object->invitation))->rawPdf(); - - $this->email->email_object->attachments = array_merge($this->email->email_object->attachments, [['file' => base64_encode($pdf), 'name' => $this->email->email_object->entity->numberFormatter().'.pdf']]); - } elseif ($this->email->email_object->settings->pdf_email_attachment && - ($this->email->email_object->entity instanceof Invoice || - $this->email->email_object->entity instanceof Quote || - $this->email->email_object->entity instanceof Credit)) { - $pdf = ((new CreateRawPdf($this->email->email_object->invitation, $this->email->company->db))->handle()); + if ($this->email->email_object->settings->pdf_email_attachment){ + $pdf = ((new CreateRawPdf($this->email->email_object->invitation))->handle()); $this->email->email_object->attachments = array_merge($this->email->email_object->attachments, [['file' => base64_encode($pdf), 'name' => $this->email->email_object->entity->numberFormatter().'.pdf']]); } @@ -324,8 +312,9 @@ class EmailDefaults if ($this->email->email_object->settings->enable_e_invoice && $this->email->email_object->entity instanceof Invoice) { $xml_string = $this->email->email_object->entity->service()->getEInvoice(); - if($xml_string) + if($xml_string) { $this->email->email_object->attachments = array_merge($this->email->email_object->attachments, [['file' => base64_encode($xml_string), 'name' => explode(".", $this->email->email_object->entity->getFileName('xml'))[0]."-e_invoice.xml"]]); + } } if (!$this->email->email_object->settings->document_email_attachment || !$this->email->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) { @@ -365,7 +354,7 @@ class EmailDefaults ->where('invoice_documents', 1) ->cursor() ->each(function ($expense) { - $this->email->email_object->documents = array_merge($this->email->email_object->documents, $expense->documents()->where('is_public',true)->pluck('id')->toArray()); + $this->email->email_object->documents = array_merge($this->email->email_object->documents, $expense->documents()->where('is_public', true)->pluck('id')->toArray()); }); } @@ -373,7 +362,7 @@ class EmailDefaults Task::query()->whereIn('id', $this->transformKeys($task_ids)) ->cursor() ->each(function ($task) { - $this->email->email_object->documents = array_merge($this->email->email_object->documents, $task->documents()->where('is_public',true)->pluck('id')->toArray()); + $this->email->email_object->documents = array_merge($this->email->email_object->documents, $task->documents()->where('is_public', true)->pluck('id')->toArray()); }); } } diff --git a/app/Services/Email/EmailMailable.php b/app/Services/Email/EmailMailable.php index 8c7c2c5e43..dffb081a1d 100644 --- a/app/Services/Email/EmailMailable.php +++ b/app/Services/Email/EmailMailable.php @@ -96,7 +96,7 @@ class EmailMailable extends Mailable $documents = Document::query()->whereIn('id', $this->email_object->documents) ->where('size', '<', $this->max_attachment_size) - ->where('is_public',1) + ->where('is_public', 1) ->cursor() ->map(function ($document) { return Attachment::fromData(fn () => $document->getFile(), $document->name); diff --git a/app/Services/Email/EmailObject.php b/app/Services/Email/EmailObject.php index 4eb7c0b28e..86b57e934c 100644 --- a/app/Services/Email/EmailObject.php +++ b/app/Services/Email/EmailObject.php @@ -121,4 +121,8 @@ class EmailObject public ?string $template = null; //invoice //quote //reminder1 public array $links = []; + + public ?string $button = null; + + public ?string $url = null; } diff --git a/app/Services/Invoice/ApplyPayment.php b/app/Services/Invoice/ApplyPayment.php index 058d9949c5..9c63664948 100644 --- a/app/Services/Invoice/ApplyPayment.php +++ b/app/Services/Invoice/ApplyPayment.php @@ -49,6 +49,9 @@ class ApplyPayment extends AbstractService $this->invoice->service()->clearPartial()->setDueDate()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($amount_paid)->updatePaidToDate($amount_paid*-1)->save(); } + + $this->invoice->service()->checkReminderStatus()->save(); + } else { if ($this->payment_amount == $this->invoice->balance) { $amount_paid = $this->payment_amount * -1; diff --git a/app/Services/Invoice/ApplyPaymentAmount.php b/app/Services/Invoice/ApplyPaymentAmount.php index f075a3992b..5b96c51029 100644 --- a/app/Services/Invoice/ApplyPaymentAmount.php +++ b/app/Services/Invoice/ApplyPaymentAmount.php @@ -65,15 +65,27 @@ class ApplyPaymentAmount extends AbstractService 'amount' => $payment->amount, ]); - $this->invoice->next_send_date = null; - $this->invoice->service() + $has_partial = $this->invoice->hasPartial(); + + $invoice_service = $this->invoice->service() ->setExchangeRate() ->updateBalance($payment->amount * -1) ->updatePaidToDate($payment->amount) ->setCalculatedStatus() - ->applyNumber() - ->save(); + ->applyNumber(); + + + if ($has_partial) { + $this->invoice->partial = max(0, $this->invoice->partial - $payment->amount); + $invoice_service->checkReminderStatus(); + } + + if($this->invoice->balance == 0) { + $this->invoice->next_send_date = null; + } + + $this->invoice = $invoice_service->save(); $this->invoice ->client diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php index 19a366fdc5..c16fe0c6ee 100644 --- a/app/Services/Invoice/AutoBillInvoice.php +++ b/app/Services/Invoice/AutoBillInvoice.php @@ -11,21 +11,20 @@ namespace App\Services\Invoice; -use App\Utils\Ninja; +use App\Events\Invoice\InvoiceWasPaid; +use App\Events\Payment\PaymentWasCreated; +use App\Factory\PaymentFactory; +use App\Libraries\MultiDB; use App\Models\Client; +use App\Models\ClientGatewayToken; use App\Models\Credit; use App\Models\Invoice; use App\Models\Payment; -use App\Libraries\MultiDB; use App\Models\PaymentHash; use App\Models\PaymentType; -use Illuminate\Support\Str; -use App\DataMapper\InvoiceItem; -use App\Factory\PaymentFactory; use App\Services\AbstractService; -use App\Models\ClientGatewayToken; -use App\Events\Invoice\InvoiceWasPaid; -use App\Events\Payment\PaymentWasCreated; +use App\Utils\Ninja; +use Illuminate\Support\Str; class AutoBillInvoice extends AbstractService { @@ -230,7 +229,7 @@ class AutoBillInvoice extends AbstractService event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars())); //if we have paid the invoice in full using credits, then we need to fire the event - if($this->invoice->balance == 0){ + if($this->invoice->balance == 0) { event(new InvoiceWasPaid($this->invoice, $payment, $payment->company, Ninja::eventVars())); diff --git a/app/Services/Invoice/EInvoice/FacturaEInvoice.php b/app/Services/Invoice/EInvoice/FacturaEInvoice.php index 4ec4ca779e..24371ad2a2 100644 --- a/app/Services/Invoice/EInvoice/FacturaEInvoice.php +++ b/app/Services/Invoice/EInvoice/FacturaEInvoice.php @@ -13,14 +13,13 @@ namespace App\Services\Invoice\EInvoice; use App\Models\Invoice; use App\Models\PaymentType; -use josemmo\Facturae\Facturae; use App\Services\AbstractService; +use Illuminate\Support\Facades\Storage; +use josemmo\Facturae\Facturae; +use josemmo\Facturae\FacturaeCentre; use josemmo\Facturae\FacturaeItem; use josemmo\Facturae\FacturaeParty; -use josemmo\Facturae\FacturaeCentre; use josemmo\Facturae\FacturaePayment; -use Illuminate\Support\Facades\Storage; -use josemmo\Facturae\Common\FacturaeSigner; class FacturaEInvoice extends AbstractService { @@ -193,14 +192,11 @@ class FacturaEInvoice extends AbstractService { $facturae_centres = []; - if($this->invoice->client->custom_value1 == 'yes') - { + if($this->invoice->client->custom_value1 == 'yes') { - foreach($this->invoice->client->contacts()->whereNotNull('custom_value1')->whereNull('deleted_at')->cursor() as $contact) - { + foreach($this->invoice->client->contacts()->whereNotNull('custom_value1')->whereNull('deleted_at')->cursor() as $contact) { - if(in_array($contact->custom_value1, array_keys($this->centre_codes))) - { + if(in_array($contact->custom_value1, array_keys($this->centre_codes))) { $facturae_centres[] = new FacturaeCentre([ 'role' => $this->centre_codes[$contact->custom_value1], 'code' => $contact->custom_value2, @@ -221,7 +217,7 @@ class FacturaEInvoice extends AbstractService $transaction_reference = (isset($this->invoice->custom_value1) && strlen($this->invoice->custom_value1) > 2) ? substr($this->invoice->custom_value1, 0, 20) : null; $contract_reference = (isset($this->invoice->custom_value2) && strlen($this->invoice->custom_value2) > 2) ? $this->invoice->custom_value2: null; - $this->fac->setReferences($po, $transaction_reference, $contract_reference); + $this->fac->setReferences($po, $transaction_reference, $contract_reference); return $this; } @@ -237,15 +233,16 @@ class FacturaEInvoice extends AbstractService private function setLegalTerms(): self { - $this->fac->addLegalLiteral(substr($this->invoice->public_notes,0,250)); + $this->fac->addLegalLiteral(substr($this->invoice->public_notes, 0, 250)); return $this; } private function setBillingPeriod(): self { - if(!$this->invoice->custom_value3) + if(!$this->invoice->custom_value3) { return $this; + } try { if (\Carbon\Carbon::createFromFormat('Y-m-d', $this->invoice->custom_value3)->format('Y-m-d') === $this->invoice->custom_value3 && @@ -253,8 +250,7 @@ class FacturaEInvoice extends AbstractService ) { $this->fac->setBillingPeriod(\Carbon\Carbon::parse($this->invoice->custom_value3)->format('Y-m-d'), \Carbon\Carbon::parse($this->invoice->custom_value4)->format('Y-m-d')); } - } - catch(\Exception $e) { + } catch(\Exception $e) { nlog($e->getMessage()); } @@ -263,7 +259,7 @@ class FacturaEInvoice extends AbstractService private function setPayments(): self { - $this->invoice->payments()->each(function ($payment){ + $this->invoice->payments()->each(function ($payment) { $payment_data = [ "dueDate" => \Carbon\Carbon::parse($payment->date)->format('Y-m-d'), @@ -280,7 +276,7 @@ class FacturaEInvoice extends AbstractService } /** - * + * * FacturaePayment::TYPE_CASH Cash * FacturaePayment::TYPE_DEBIT Domiciled receipt * FacturaePayment::TYPE_RECEIPT Receipt @@ -309,7 +305,7 @@ class FacturaEInvoice extends AbstractService $data = []; $method = FacturaePayment::TYPE_CARD; - match($payment->type_id){ + match($payment->type_id) { PaymentType::BANK_TRANSFER => $method = FacturaePayment::TYPE_TRANSFER , PaymentType::CASH => $method = FacturaePayment::TYPE_CASH , PaymentType::ACH => $method = FacturaePayment::TYPE_TRANSFER , @@ -361,9 +357,8 @@ class FacturaEInvoice extends AbstractService $data['method'] = $method; - if($method == FacturaePayment::TYPE_TRANSFER) - { - $data['iban'] = $payment->custom_value1; + if($method == FacturaePayment::TYPE_TRANSFER) { + $data['iban'] = $payment->custom_value1; $data['bic'] = $payment->custom_value2; } @@ -420,8 +415,9 @@ class FacturaEInvoice extends AbstractService } - if(count($data) == 0) + if(count($data) == 0) { $data[Facturae::TAX_IVA] = 0; + } return $data; } @@ -468,8 +464,9 @@ class FacturaEInvoice extends AbstractService { $company = $this->invoice->company; - if($company->getSetting('classification') == 'individual') + if($company->getSetting('classification') == 'individual') { return $this->setIndividualSeller(); + } $seller = new FacturaeParty([ "isLegalEntity" => true, @@ -548,18 +545,18 @@ class FacturaEInvoice extends AbstractService $buyer = new FacturaeParty([ "isLegalEntity" => $this->invoice->client->classification === 'individual' ? false : true, "taxNumber" => $this->invoice->client->vat_number, - "name" => substr($this->invoice->client->present()->name(),0, 40), - "firstSurname" => substr($this->invoice->client->present()->first_name(),0, 40), - "lastSurname" => substr($this->invoice->client->present()->last_name(),0, 40), - "address" => substr($this->invoice->client->address1,0, 80), - "postCode" => substr($this->invoice->client->postal_code,0,5), - "town" => substr($this->invoice->client->city,0, 50), - "province" => substr($this->invoice->client->state,0, 20), + "name" => substr($this->invoice->client->present()->name(), 0, 40), + "firstSurname" => substr($this->invoice->client->present()->first_name(), 0, 40), + "lastSurname" => substr($this->invoice->client->present()->last_name(), 0, 40), + "address" => substr($this->invoice->client->address1, 0, 80), + "postCode" => substr($this->invoice->client->postal_code, 0, 5), + "town" => substr($this->invoice->client->city, 0, 50), + "province" => substr($this->invoice->client->state, 0, 20), "countryCode" => $this->invoice->client->country->iso_3166_3, // Se asume España si se omite - "email" => substr($this->invoice->client->present()->email(),0, 60), - "phone" => substr($this->invoice->client->present()->phone(),0, 15), + "email" => substr($this->invoice->client->present()->email(), 0, 60), + "phone" => substr($this->invoice->client->present()->phone(), 0, 15), "fax" => "", - "website" => substr($this->invoice->client->present()->website(), 0 ,60), + "website" => substr($this->invoice->client->present()->website(), 0, 60), "contactPeople" => substr($this->invoice->client->present()->first_name()." ".$this->invoice->client->present()->last_name(), 0, 40), 'centres' => $this->setFace(), // "cnoCnae" => "04791", // Clasif. Nacional de Act. Económicas @@ -577,8 +574,9 @@ class FacturaEInvoice extends AbstractService $ssl_cert = $this->invoice->company->getInvoiceCert(); $ssl_passphrase = $this->invoice->company->getSslPassPhrase(); - if($ssl_cert) + if($ssl_cert) { $this->fac->sign($ssl_cert, null, $ssl_passphrase); + } return $this; } diff --git a/app/Services/Invoice/EInvoice/FatturaPA.php b/app/Services/Invoice/EInvoice/FatturaPA.php index 9d2dbe69a2..0540275ae6 100644 --- a/app/Services/Invoice/EInvoice/FatturaPA.php +++ b/app/Services/Invoice/EInvoice/FatturaPA.php @@ -11,9 +11,10 @@ namespace App\Services\Invoice\EInvoice; -use SimpleXMLElement; use App\Models\Invoice; use App\Services\AbstractService; +use SimpleXMLElement; + /* @@ -69,12 +70,14 @@ class FatturaPA extends AbstractService return $this->addHeader()->getXml(); } - public function addHeader() { + public function addHeader() + { $this->xml->addChild('FatturaElettronicaHeader'); return $this; } - public function addTrasmissioneData($idPaese, $idCodice, $progressivoInvio, $formatoTrasmissione, $codiceDestinatario) { + public function addTrasmissioneData($idPaese, $idCodice, $progressivoInvio, $formatoTrasmissione, $codiceDestinatario) + { $datiTrasmissione = $this->xml->FatturaElettronicaHeader->addChild('DatiTrasmissione'); $idTrasmittente = $datiTrasmissione->addChild('IdTrasmittente'); $idTrasmittente->addChild('IdPaese', $idPaese); @@ -85,24 +88,29 @@ class FatturaPA extends AbstractService return $this; } - public function addCedentePrestatore($data) { + public function addCedentePrestatore($data) + { // Add CedentePrestatore data } - public function addCessionarioCommittente($data) { + public function addCessionarioCommittente($data) + { // Add CessionarioCommittente data } - public function addBody() { + public function addBody() + { $this->xml->addChild('FatturaElettronicaBody'); return $this; } - public function addDatiGenerali($data) { + public function addDatiGenerali($data) + { // Add DatiGenerali data } - public function addLineItem($data) { + public function addLineItem($data) + { if (!isset($this->xml->FatturaElettronicaBody->DatiBeniServizi)) { $this->xml->FatturaElettronicaBody->addChild('DatiBeniServizi'); } @@ -121,7 +129,8 @@ class FatturaPA extends AbstractService return $this; } - public function addDatiPagamento($data) { + public function addDatiPagamento($data) + { // Add DatiPagamento data } diff --git a/app/Services/Invoice/EInvoice/ZugferdEInvoice.php b/app/Services/Invoice/EInvoice/ZugferdEInvoice.php index 539e78f6e2..56e0e71611 100644 --- a/app/Services/Invoice/EInvoice/ZugferdEInvoice.php +++ b/app/Services/Invoice/EInvoice/ZugferdEInvoice.php @@ -14,11 +14,9 @@ namespace App\Services\Invoice\EInvoice; use App\Models\Invoice; use App\Models\Product; use App\Services\AbstractService; -use horstoeko\zugferd\ZugferdProfiles; -use Illuminate\Support\Facades\Storage; -use horstoeko\zugferd\ZugferdDocumentBuilder; -use horstoeko\zugferd\ZugferdDocumentPdfBuilder; use horstoeko\zugferd\codelists\ZugferdDutyTaxFeeCategories; +use horstoeko\zugferd\ZugferdDocumentBuilder; +use horstoeko\zugferd\ZugferdProfiles; class ZugferdEInvoice extends AbstractService { @@ -58,13 +56,13 @@ class ZugferdEInvoice extends AbstractService ->setDocumentBuyerContact($client->primary_contact()->first()->first_name . " " . $client->primary_contact()->first()->last_name, "", $client->primary_contact()->first()->phone, "", $client->primary_contact()->first()->email) ->addDocumentPaymentTerm(ctrans("texts.xinvoice_payable", ['payeddue' => date_create($this->invoice->date ?? now()->format('Y-m-d'))->diff(date_create($this->invoice->due_date ?? now()->format('Y-m-d')))->format("%d"), 'paydate' => $this->invoice->due_date])); - if (!empty($this->invoice->public_notes)) { + if (!empty($this->invoice->public_notes)) { $this->xrechnung->addDocumentNote($this->invoice->public_notes); } - if (empty($this->invoice->number)){ - $this->xrechnung->setDocumentInformation("DRAFT", "380", date_create($this->invoice->date ?? now()->format('Y-m-d')), $this->invoice->client->getCurrencyCode()); + if (empty($this->invoice->number)) { + $this->xrechnung->setDocumentInformation("DRAFT", "380", date_create($this->invoice->date ?? now()->format('Y-m-d')), $this->invoice->client->getCurrencyCode()); } else { - $this->xrechnung->setDocumentInformation($this->invoice->number, "380", date_create($this->invoice->date ?? now()->format('Y-m-d')), $this->invoice->client->getCurrencyCode()); + $this->xrechnung->setDocumentInformation($this->invoice->number, "380", date_create($this->invoice->date ?? now()->format('Y-m-d')), $this->invoice->client->getCurrencyCode()); } if (!empty($this->invoice->po_number)) { $this->xrechnung->setDocumentBuyerOrderReferencedDocument($this->invoice->po_number); @@ -75,7 +73,7 @@ class ZugferdEInvoice extends AbstractService } else { $this->xrechnung->setDocumentBuyerReference($client->routing_id); } - if (!empty($client->shipping_address1) && $client->shipping_country->exists()){ + if (!empty($client->shipping_address1) && $client->shipping_country->exists()) { $this->xrechnung->setDocumentShipToAddress($client->shipping_address1, $client->shipping_address2, "", $client->shipping_postal_code, $client->shipping_city, $client->shipping_country->iso_3166_2, $client->shipping_state); } @@ -95,19 +93,16 @@ class ZugferdEInvoice extends AbstractService $this->xrechnung->addNewPosition($index) ->setDocumentPositionGrossPrice($item->gross_line_total) ->setDocumentPositionNetPrice($item->line_total); - if (!empty($item->product_key)){ - if (!empty($item->notes)){ - $this->xrechnung->setDocumentPositionProductDetails($item->product_key, $item->notes); - } - else { + if (!empty($item->product_key)) { + if (!empty($item->notes)) { + $this->xrechnung->setDocumentPositionProductDetails($item->product_key, $item->notes); + } else { $this->xrechnung->setDocumentPositionProductDetails($item->product_key); } - } - else { - if (!empty($item->notes)){ + } else { + if (!empty($item->notes)) { $this->xrechnung->setDocumentPositionProductDetails($item->notes); - } - else { + } else { $this->xrechnung->setDocumentPositionProductDetails("no product name defined"); } } @@ -164,7 +159,7 @@ class ZugferdEInvoice extends AbstractService $this->xrechnung->setDocumentSummation($this->invoice->amount, $this->invoice->balance, $invoicing_data->getSubTotal(), $invoicing_data->getTotalSurcharges(), $invoicing_data->getTotalDiscount(), $invoicing_data->getSubTotal(), $invoicing_data->getItemTotalTaxes(), 0.0, $this->invoice->amount-$this->invoice->balance); - foreach ($this->tax_map as $item){ + foreach ($this->tax_map as $item) { $this->xrechnung->addDocumentTax($item["tax_type"], "VAT", $item["net_amount"], $item["tax_rate"]*$item["net_amount"], $item["tax_rate"]*100); } @@ -225,10 +220,9 @@ class ZugferdEInvoice extends AbstractService private function addtoTaxMap(string $tax_type, float $net_amount, float $tax_rate): void { $hash = hash("md5", $tax_type."-".$tax_rate); - if (array_key_exists($hash, $this->tax_map)){ + if (array_key_exists($hash, $this->tax_map)) { $this->tax_map[$hash]["net_amount"] += $net_amount; - } - else{ + } else { $this->tax_map[$hash] = [ "tax_type" => $tax_type, "net_amount" => $net_amount, diff --git a/app/Services/Invoice/GenerateDeliveryNote.php b/app/Services/Invoice/GenerateDeliveryNote.php index 86ba365545..aec054eb73 100644 --- a/app/Services/Invoice/GenerateDeliveryNote.php +++ b/app/Services/Invoice/GenerateDeliveryNote.php @@ -17,6 +17,7 @@ use App\Models\Design; use App\Models\Invoice; use App\Services\PdfMaker\Design as PdfMakerDesign; use App\Services\PdfMaker\PdfMaker as PdfMakerService; +use App\Services\Template\TemplateService; use App\Utils\HostedPDF\NinjaPdf; use App\Utils\HtmlEngine; use App\Utils\PhantomJS\Phantom; @@ -40,6 +41,21 @@ class GenerateDeliveryNote public function run() { + + $delivery_note_design_id = $this->invoice->client->getSetting('delivery_note_design_id'); + $design = Design::withTrashed()->find($this->decodePrimaryKey($delivery_note_design_id)); + + if($design && $design->is_template) { + + $ts = new TemplateService($design); + $pdf = $ts->build([ + 'invoices' => collect([$this->invoice]), + ])->getPdf(); + + return $pdf; + + } + $design_id = $this->invoice->design_id ? $this->invoice->design_id : $this->decodePrimaryKey($this->invoice->client->getSetting('invoice_design_id')); @@ -70,6 +86,11 @@ class GenerateDeliveryNote 'contact' => $this->contact, ], 'delivery_note'), 'variables' => $html->generateLabelsAndValues(), + 'options' => [ + 'client' => $this->invoice->client, + 'entity' => $this->invoice, + 'contact' => $this->contact, + ], 'process_markdown' => $this->invoice->client->company->markdown_enabled, ]; @@ -79,8 +100,6 @@ class GenerateDeliveryNote ->design($template) ->build(); - // Storage::makeDirectory($this->invoice->client->invoice_filepath(), 0775); - if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); } else { @@ -91,11 +110,12 @@ class GenerateDeliveryNote info($maker->getCompiledHTML()); } - Storage::disk($this->disk)->put($file_path, $pdf); + return $pdf; + // Storage::disk($this->disk)->put($file_path, $pdf); $maker = null; $state = null; - return $file_path; + // return $file_path; } } diff --git a/app/Services/Invoice/GetInvoicePdf.php b/app/Services/Invoice/GetInvoicePdf.php index 9211a15bb8..736df18c9d 100644 --- a/app/Services/Invoice/GetInvoicePdf.php +++ b/app/Services/Invoice/GetInvoicePdf.php @@ -11,12 +11,10 @@ namespace App\Services\Invoice; -use App\Jobs\Entity\CreateEntityPdf; -use App\Jobs\Invoice\CreateEInvoice; +use App\Jobs\Entity\CreateRawPdf; use App\Models\ClientContact; use App\Models\Invoice; use App\Services\AbstractService; -use Illuminate\Support\Facades\Storage; class GetInvoicePdf extends AbstractService { @@ -36,19 +34,7 @@ class GetInvoicePdf extends AbstractService $invitation = $this->invoice->invitations->first(); } - $path = $this->invoice->client->invoice_filepath($invitation); + return (new CreateRawPdf($invitation))->handle(); - $file_path = $path.$this->invoice->numberFormatter().'.pdf'; - - // $disk = 'public'; - $disk = config('filesystems.default'); - - $file = Storage::disk($disk)->exists($file_path); - - if (! $file) { - $file_path = (new CreateEntityPdf($invitation))->handle(); - } - - return $file_path; } } diff --git a/app/Services/Invoice/HandleRestore.php b/app/Services/Invoice/HandleRestore.php index 45a960d4e8..ee1c94717a 100644 --- a/app/Services/Invoice/HandleRestore.php +++ b/app/Services/Invoice/HandleRestore.php @@ -17,7 +17,6 @@ use App\Models\Paymentable; use App\Services\AbstractService; use App\Utils\Ninja; use App\Utils\Traits\GeneratesCounter; -use Illuminate\Support\Facades\DB; class HandleRestore extends AbstractService { @@ -115,9 +114,9 @@ class HandleRestore extends AbstractService if ($this->adjustment_amount == $this->total_payments) { $this->invoice->payments()->update(['payments.deleted_at' => null, 'payments.is_deleted' => false]); - } - else + } else { $this->invoice->net_payments()->update(['payments.deleted_at' => null, 'payments.is_deleted' => false]); + } //adjust payments down by the amount applied to the invoice payment. diff --git a/app/Services/Invoice/InvoiceService.php b/app/Services/Invoice/InvoiceService.php index c039078fc6..78fdf1cc27 100644 --- a/app/Services/Invoice/InvoiceService.php +++ b/app/Services/Invoice/InvoiceService.php @@ -12,7 +12,6 @@ namespace App\Services\Invoice; use App\Events\Invoice\InvoiceWasArchived; -use App\Jobs\Entity\CreateEntityPdf; use App\Jobs\Entity\CreateRawPdf; use App\Jobs\Inventory\AdjustProductInventory; use App\Jobs\Invoice\CreateEInvoice; @@ -20,7 +19,6 @@ use App\Libraries\Currency\Conversion\CurrencyApi; use App\Models\CompanyGateway; use App\Models\Expense; use App\Models\Invoice; -use App\Models\InvoiceInvitation; use App\Models\Payment; use App\Models\Task; use App\Utils\Ninja; @@ -192,7 +190,7 @@ class InvoiceService { $invitation = $contact ? $this->invoice->invitations()->where('contact_id', $contact->id)->first() : $this->invoice->invitations()->first(); - return (new CreateRawPdf($invitation, $invitation->company->db))->handle(); + return (new CreateRawPdf($invitation))->handle(); } public function getInvoiceDeliveryNote(Invoice $invoice, \App\Models\ClientContact $contact = null) @@ -290,6 +288,34 @@ class InvoiceService return $this; } + + /** + * Reset the reminders if only the + * partial has been paid. + * + * We can _ONLY_ call this _IF_ a partial + * amount has been paid, otherwise we end up wiping + * all reminders regardless + * + * @return self + */ + public function checkReminderStatus(): self + { + + if($this->invoice->partial == 0) { + $this->invoice->partial_due_date = null; + } + + if($this->invoice->partial == 0 && $this->invoice->balance > 0) { + $this->invoice->reminder1_sent = null; + $this->invoice->reminder2_sent = null; + $this->invoice->reminder3_sent = null; + + $this->setReminder(); + } + + return $this; + } public function setReminder($settings = null) { @@ -360,7 +386,7 @@ class InvoiceService $this->invoice->invitations->each(function ($invitation) { try { // if (Storage::disk(config('filesystems.default'))->exists($this->invoice->client->invoice_filepath($invitation).$this->invoice->numberFormatter().'.pdf')) { - Storage::disk(config('filesystems.default'))->delete($this->invoice->client->invoice_filepath($invitation).$this->invoice->numberFormatter().'.pdf'); + Storage::disk(config('filesystems.default'))->delete($this->invoice->client->invoice_filepath($invitation).$this->invoice->numberFormatter().'.pdf'); // } // if (Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->invoice_filepath($invitation).$this->invoice->numberFormatter().'.pdf')) { @@ -382,7 +408,7 @@ class InvoiceService $this->invoice->invitations->each(function ($invitation) { try { // if (Storage::disk(config('filesystems.default'))->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) { - Storage::disk(config('filesystems.default'))->delete($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml")); + Storage::disk(config('filesystems.default'))->delete($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml")); // } // if (Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) { @@ -458,39 +484,6 @@ class InvoiceService return $this; } - /** - * Sometimes we need to refresh the - * PDF when it is updated etc. - * @return InvoiceService - */ - public function touchPdf($force = false) - { - try { - if ($force) { - $this->invoice->invitations->each(function ($invitation) { - (new CreateEntityPdf($invitation))->handle(); - }); - - return $this; - } - - - $this->invoice->invitations->each(function ($invitation) { - CreateEntityPdf::dispatch($invitation); - - if ($invitation->invoice->client->getSetting('enable_e_invoice') && $invitation instanceof InvoiceInvitation) { - CreateEInvoice::dispatch($invitation->invoice); - } - - }); - - } catch (\Exception $e) { - nlog('failed creating invoices in Touch PDF'); - } - - return $this; - } - /*When a reminder is sent we want to touch the dates they were sent*/ public function touchReminder(string $reminder_template) { diff --git a/app/Services/Invoice/MarkInvoiceDeleted.php b/app/Services/Invoice/MarkInvoiceDeleted.php index 318737cae1..524e1e933f 100644 --- a/app/Services/Invoice/MarkInvoiceDeleted.php +++ b/app/Services/Invoice/MarkInvoiceDeleted.php @@ -11,12 +11,10 @@ namespace App\Services\Invoice; -use App\Models\Credit; +use App\Jobs\Inventory\AdjustProductInventory; use App\Models\Invoice; use App\Services\AbstractService; -use Illuminate\Support\Facades\DB; use App\Utils\Traits\GeneratesCounter; -use App\Jobs\Inventory\AdjustProductInventory; class MarkInvoiceDeleted extends AbstractService { diff --git a/app/Services/Invoice/SendEmail.php b/app/Services/Invoice/SendEmail.php index 7cbc7a0cc0..365201a5ac 100644 --- a/app/Services/Invoice/SendEmail.php +++ b/app/Services/Invoice/SendEmail.php @@ -11,12 +11,12 @@ namespace App\Services\Invoice; -use App\Utils\Ninja; -use App\Models\Invoice; -use App\Models\ClientContact; -use App\Jobs\Entity\EmailEntity; -use App\Services\AbstractService; use App\Events\Invoice\InvoiceWasEmailed; +use App\Jobs\Entity\EmailEntity; +use App\Models\ClientContact; +use App\Models\Invoice; +use App\Services\AbstractService; +use App\Utils\Ninja; class SendEmail extends AbstractService { diff --git a/app/Services/Invoice/UpdateReminder.php b/app/Services/Invoice/UpdateReminder.php index f552fb141e..0d8c96f3f4 100644 --- a/app/Services/Invoice/UpdateReminder.php +++ b/app/Services/Invoice/UpdateReminder.php @@ -49,27 +49,32 @@ class UpdateReminder extends AbstractService $this->settings->schedule_reminder1 == 'after_invoice_date') { $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder1); - if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { + if ($reminder_date->gt(now())) { $date_collection->push($reminder_date); } } if (is_null($this->invoice->reminder1_sent) && - $this->invoice->due_date && + ($this->invoice->partial_due_date || $this->invoice->due_date) && $this->settings->schedule_reminder1 == 'before_due_date') { - $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder1); + $partial_or_due_date = ($this->invoice->partial > 0 && isset($this->invoice->partial_due_date)) ? $this->invoice->partial_due_date : $this->invoice->due_date; + $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->subDays($this->settings->num_days_reminder1); + // nlog("1. {$reminder_date->format('Y-m-d')}"); - if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { + if ($reminder_date->gt(now())) { $date_collection->push($reminder_date); } } if (is_null($this->invoice->reminder1_sent) && - $this->invoice->due_date && + ($this->invoice->partial_due_date || $this->invoice->due_date) && $this->settings->schedule_reminder1 == 'after_due_date') { - $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder1); + + $partial_or_due_date = ($this->invoice->partial > 0 && isset($this->invoice->partial_due_date)) ? $this->invoice->partial_due_date : $this->invoice->due_date; + $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->addDays($this->settings->num_days_reminder1); + // nlog("2. {$reminder_date->format('Y-m-d')}"); - if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { + if ($reminder_date->gt(now())) { $date_collection->push($reminder_date); } } @@ -78,27 +83,33 @@ class UpdateReminder extends AbstractService $this->settings->schedule_reminder2 == 'after_invoice_date') { $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder2); - if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { + if ($reminder_date->gt(now())) { $date_collection->push($reminder_date); } } if (is_null($this->invoice->reminder2_sent) && - $this->invoice->due_date && + ($this->invoice->partial_due_date || $this->invoice->due_date) && $this->settings->schedule_reminder2 == 'before_due_date') { - $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder2); - if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { + $partial_or_due_date = ($this->invoice->partial > 0 && isset($this->invoice->partial_due_date)) ? $this->invoice->partial_due_date : $this->invoice->due_date; + $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->subDays($this->settings->num_days_reminder2); + // nlog("3. {$reminder_date->format('Y-m-d')}"); + + if ($reminder_date->gt(now())) { $date_collection->push($reminder_date); } } if (is_null($this->invoice->reminder2_sent) && - $this->invoice->due_date && + ($this->invoice->partial_due_date || $this->invoice->due_date) && $this->settings->schedule_reminder2 == 'after_due_date') { - $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder2); + + $partial_or_due_date = ($this->invoice->partial > 0 && isset($this->invoice->partial_due_date)) ? $this->invoice->partial_due_date : $this->invoice->due_date; + $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->addDays($this->settings->num_days_reminder2); + // nlog("4. {$reminder_date->format('Y-m-d')}"); - if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { + if ($reminder_date->gt(now())) { $date_collection->push($reminder_date); } } @@ -107,27 +118,33 @@ class UpdateReminder extends AbstractService $this->settings->schedule_reminder3 == 'after_invoice_date') { $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder3); - if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { + if ($reminder_date->gt(now())) { $date_collection->push($reminder_date); } } if (is_null($this->invoice->reminder3_sent) && - $this->invoice->due_date && + ($this->invoice->partial_due_date || $this->invoice->due_date) && $this->settings->schedule_reminder3 == 'before_due_date') { - $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder3); + + $partial_or_due_date = ($this->invoice->partial > 0 && isset($this->invoice->partial_due_date)) ? $this->invoice->partial_due_date : $this->invoice->due_date; + $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->subDays($this->settings->num_days_reminder3); + // nlog("5. {$reminder_date->format('Y-m-d')}"); - if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { + if ($reminder_date->gt(now())) { $date_collection->push($reminder_date); } } if (is_null($this->invoice->reminder3_sent) && - $this->invoice->due_date && + ($this->invoice->partial_due_date || $this->invoice->due_date) && $this->settings->schedule_reminder3 == 'after_due_date') { - $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder3); + + $partial_or_due_date = ($this->invoice->partial > 0 && isset($this->invoice->partial_due_date)) ? $this->invoice->partial_due_date : $this->invoice->due_date; + $reminder_date = Carbon::parse($partial_or_due_date)->startOfDay()->addDays($this->settings->num_days_reminder3); + // nlog("6. {$reminder_date->format('Y-m-d')}"); - if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { + if ($reminder_date->gt(now())) { $date_collection->push($reminder_date); } } @@ -140,7 +157,7 @@ class UpdateReminder extends AbstractService $reminder_date = $this->addTimeInterval($this->invoice->last_sent_date, (int) $this->settings->endless_reminder_frequency_id); if ($reminder_date) { - if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { + if ($reminder_date->gt(now())) { $date_collection->push($reminder_date); } } diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php index 8884dbdef6..caac1125f5 100644 --- a/app/Services/Payment/DeletePayment.php +++ b/app/Services/Payment/DeletePayment.php @@ -11,10 +11,10 @@ namespace App\Services\Payment; +use App\Models\BankTransaction; use App\Models\Credit; use App\Models\Invoice; use App\Models\Payment; -use App\Models\BankTransaction; use Illuminate\Contracts\Container\BindingResolutionException; class DeletePayment @@ -58,7 +58,7 @@ class DeletePayment $this->payment->is_deleted = true; $this->payment->delete(); - BankTransaction::query()->where('payment_id', $this->payment->id)->cursor()->each(function ($bt){ + BankTransaction::query()->where('payment_id', $this->payment->id)->cursor()->each(function ($bt) { $bt->payment_id = null; $bt->status_id = 1; $bt->save(); @@ -109,11 +109,9 @@ class DeletePayment if ($paymentable_invoice->balance == $paymentable_invoice->amount) { $paymentable_invoice->service()->setStatus(Invoice::STATUS_SENT)->save(); - } - elseif($paymentable_invoice->balance == 0){ + } elseif($paymentable_invoice->balance == 0) { $paymentable_invoice->service()->setStatus(Invoice::STATUS_PAID)->save(); - } - else { + } else { $paymentable_invoice->service()->setStatus(Invoice::STATUS_PARTIAL)->save(); } } else { diff --git a/app/Services/Payment/RefundPayment.php b/app/Services/Payment/RefundPayment.php index 3a649d4444..b06833eeff 100644 --- a/app/Services/Payment/RefundPayment.php +++ b/app/Services/Payment/RefundPayment.php @@ -23,11 +23,6 @@ use stdClass; class RefundPayment { - public $payment; - - public $refund_data; - - private $credit_note; private float $total_refund = 0; @@ -41,12 +36,8 @@ class RefundPayment private string $refund_failed_message = ''; - public function __construct($payment, $refund_data) + public function __construct(public Payment $payment, public array $refund_data) { - $this->payment = $payment; - - $this->refund_data = $refund_data; - $this->gateway_refund_status = false; $this->activity_repository = new ActivityRepository(); @@ -79,8 +70,9 @@ class RefundPayment private function finalize(): self { - if($this->refund_failed) + if($this->refund_failed) { throw new PaymentRefundFailed($this->refund_failed_message); + } return $this; } @@ -98,7 +90,7 @@ class RefundPayment * 'payment_id' => (int), * 'amount' => (float), * ]; - * + * * @return $this * @throws PaymentRefundFailed */ @@ -110,14 +102,14 @@ class RefundPayment if ($this->payment->company_gateway) { $response = $this->payment->company_gateway->driver($this->payment->client)->refund($this->payment, $net_refund); - if($response['amount'] ?? false) + if($response['amount'] ?? false) { $net_refund = $response['amount']; + } - if($response['voided'] ?? false) - { - //When a transaction is voided - all invoices attached to the payment need to be reversed, this + if($response['voided'] ?? false) { + //When a transaction is voided - all invoices attached to the payment need to be reversed, this //block prevents the edge case where a partial refund was attempted. - $this->refund_data['invoices'] = $this->payment->invoices->map(function ($invoice){ + $this->refund_data['invoices'] = $this->payment->invoices->map(function ($invoice) { return [ 'invoice_id' => $invoice->id, 'amount' => $invoice->pivot->amount, @@ -137,6 +129,8 @@ class RefundPayment $this->payment->refunded += $net_refund; } + $this->payment->setRefundMeta($this->refund_data); + return $this; } @@ -284,8 +278,7 @@ class RefundPayment private function adjustInvoices() { if (isset($this->refund_data['invoices']) && count($this->refund_data['invoices']) > 0) { - foreach ($this->refund_data['invoices'] as $refunded_invoice) - { + foreach ($this->refund_data['invoices'] as $refunded_invoice) { $invoice = Invoice::withTrashed()->find($refunded_invoice['invoice_id']); if ($invoice->trashed()) { @@ -349,4 +342,4 @@ class RefundPayment return $this->payment; } -} \ No newline at end of file +} diff --git a/app/Services/Payment/UpdateInvoicePayment.php b/app/Services/Payment/UpdateInvoicePayment.php index 10243007fc..4f123701ac 100644 --- a/app/Services/Payment/UpdateInvoicePayment.php +++ b/app/Services/Payment/UpdateInvoicePayment.php @@ -55,6 +55,8 @@ class UpdateInvoicePayment $invoice->restore(); } + // $has_partial = $invoice->hasPartial(); + if ($invoice->id == $this->payment_hash->fee_invoice_id) { $paid_amount = $paid_invoice->amount + $this->payment_hash->fee_total; } else { @@ -63,6 +65,8 @@ class UpdateInvoicePayment $client->service()->updatePaidToDate($paid_amount); //always use the payment->amount + $has_partial = $invoice->hasPartial(); + /* Need to determine here is we have an OVER payment - if YES only apply the max invoice amount */ if ($paid_amount > $invoice->partial && $paid_amount > $invoice->balance) { $paid_amount = $invoice->balance; @@ -76,12 +80,16 @@ class UpdateInvoicePayment $invoice->paid_to_date += $paid_amount; $invoice->save(); - $invoice = $invoice->service() - ->clearPartial() - ->updateStatus() - // ->deletePdf() - ->workFlow() - ->save(); + $invoice_service = $invoice->service() + ->clearPartial() + ->updateStatus() + ->workFlow(); + + if ($has_partial) { + $invoice_service->checkReminderStatus(); + } + + $invoice = $invoice_service->save(); if ($invoice->is_proforma) { //keep proforma's hidden diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 8a8226e09e..7a496e1757 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -13,6 +13,7 @@ namespace App\Services\Pdf; use App\Models\Credit; use App\Models\Quote; +use App\Services\Template\TemplateService; use App\Utils\Helpers; use App\Utils\Traits\MakesDates; use DOMDocument; @@ -67,17 +68,18 @@ class PdfBuilder ->buildSections() ->getEmptyElements() ->updateElementProperties() + ->parseTwigElements() ->updateVariables(); return $this; } - /** - * Final method to get compiled HTML. - * - * @param bool $final @deprecated // is it? i still see it being called elsewhere - * @return string - */ + /** + * Final method to get compiled HTML. + * + * @param bool $final @deprecated // is it? i still see it being called elsewhere + * @return string + */ public function getCompiledHTML($final = false) { $html = $this->document->saveHTML(); @@ -104,6 +106,40 @@ class PdfBuilder return $this; } + private function parseTwigElements() + { + + $replacements = []; + $contents = $this->document->getElementsByTagName('ninja'); + + $template_service = new TemplateService(); + $data = $template_service->processData($this->service->options)->getData(); + + $twig = $template_service->twig; + + foreach ($contents as $content) { + + $template = $content->ownerDocument->saveHTML($content); + + $template = $twig->createTemplate(html_entity_decode($template)); + $template = $template->render($data); + + $f = $this->document->createDocumentFragment(); + $f->appendXML($template); + $replacements[] = $f; + + } + + foreach($contents as $key => $content) { + $content->parentNode->replaceChild($replacements[$key], $content); + } + + $contents = null; + + return $this; + + } + public function setDocument($document): self { $this->document = $document; @@ -728,13 +764,13 @@ class PdfBuilder return $data; } - /** - * Generate the structure of table headers. () - * - * @param string $type "product" or "task" - * @return array - * - */ + /** + * Generate the structure of table headers. () + * + * @param string $type "product" or "task" + * @return array + * + */ public function buildTableHeader(string $type): array { $this->processTaxColumns($type); @@ -891,7 +927,7 @@ class PdfBuilder */ private function getProductEntityDetails(): self { - if ($this->service->config->entity_string == 'invoice') { + if (in_array($this->service->config->entity_string, ['recurring_invoice', 'invoice'])) { $this->mergeSections([ 'entity-details' => [ 'id' => 'entity-details', @@ -1091,7 +1127,8 @@ class PdfBuilder } elseif (Str::startsWith($variable, '$custom_surcharge')) { $_variable = ltrim($variable, '$'); // $custom_surcharge1 -> custom_surcharge1 - $visible = intval($this->service->config->entity->{$_variable}) != 0; + // $visible = intval($this->service->config->entity->{$_variable}) != 0; + $visible = intval(str_replace(['0','.'], '', $this->service->config->entity->{$_variable})) != 0; $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ ['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], @@ -1318,7 +1355,6 @@ class PdfBuilder { $elements = []; - foreach ($variables as $variable) { $_variable = explode('.', $variable)[1]; $_customs = ['custom1', 'custom2', 'custom3', 'custom4']; diff --git a/app/Services/Pdf/PdfConfiguration.php b/app/Services/Pdf/PdfConfiguration.php index e62c4c8ba9..fcca214819 100644 --- a/app/Services/Pdf/PdfConfiguration.php +++ b/app/Services/Pdf/PdfConfiguration.php @@ -25,6 +25,7 @@ use App\Models\PurchaseOrder; use App\Models\PurchaseOrderInvitation; use App\Models\Quote; use App\Models\QuoteInvitation; +use App\Models\RecurringInvoice; use App\Models\RecurringInvoiceInvitation; use App\Models\Vendor; use App\Models\VendorContact; @@ -51,7 +52,7 @@ class PdfConfiguration public Design $design; - public Invoice | Credit | Quote | PurchaseOrder $entity; + public Invoice | Credit | Quote | PurchaseOrder | RecurringInvoice $entity; public string $entity_design_id; diff --git a/app/Services/Pdf/PdfMock.php b/app/Services/Pdf/PdfMock.php index 3ee76fbaed..e08da50cd5 100644 --- a/app/Services/Pdf/PdfMock.php +++ b/app/Services/Pdf/PdfMock.php @@ -69,10 +69,11 @@ class PdfMock $pdf_service->config = $pdf_config; - if(isset($this->request['design'])) + if(isset($this->request['design'])) { $pdf_designer = (new PdfDesigner($pdf_service))->buildFromPartials($this->request['design']); - else + } else { $pdf_designer = (new PdfDesigner($pdf_service))->build(); + } $pdf_service->designer = $pdf_designer; @@ -195,24 +196,25 @@ class PdfMock { return ['values' => [ - '$client.shipping_postal_code' => '46420', '$client.billing_postal_code' => '11243', '$company.city_state_postal' => 'Beveley Hills, CA, 90210', '$company.postal_city_state' => 'CA', '$company.postal_city' => '90210, CA', '$product.gross_line_total' => '100', + '$client.classification' => 'Individual', + '$company.classification' => 'Business', '$client.postal_city_state' => '11243 Aufderharchester, North Carolina', '$client.postal_city' => '11243 Aufderharchester, North Carolina', '$client.shipping_address1' => '453', '$client.shipping_address2' => '66327 Waters Trail', '$client.city_state_postal' => 'Aufderharchester, North Carolina 11243', - '$client.shipping_address' => '453
66327 Waters Trail
Aufderharchester, North Carolina 11243
Afghanistan
', + '$client.shipping_address' => '453
66327 Waters Trail
Aufderharchester, North Carolina 11243
United States
', '$client.billing_address2' => '63993 Aiyana View', '$client.billing_address1' => '8447', '$client.shipping_country' => 'USA', '$invoiceninja.whitelabel' => 'https://invoicing.co/images/new_logo.png', - '$client.billing_address' => '8447
63993 Aiyana View
Aufderharchester, North Carolina 11243
Afghanistan
', + '$client.billing_address' => '8447
63993 Aiyana View
Aufderharchester, North Carolina 11243
United States
', '$client.billing_country' => 'USA', '$task.gross_line_total' => '100', '$contact.portal_button' => 'View client portal', @@ -272,8 +274,8 @@ class PdfMock '$product.quantity' => '', '$total_tax_labels' => '', '$total_tax_values' => '', - '$invoice.discount' => '$0.00', - '$invoice.subtotal' => '$0.00', + '$invoice.discount' => '$5.00', + '$invoice.subtotal' => '$100.00', '$company.address2' => $this->settings->address2, '$partial_due_date' => ' ', '$invoice.due_date' => '2023-10-24', @@ -310,7 +312,7 @@ class PdfMock '$created_by_user' => 'Derrick Monahan DDS Erna Wunsch', '$client.currency' => 'USD', '$company.country' => 'United States', - '$company.address' => 'United States
', + '$company.address' => 'Christiansen Garden
70218 Lori Station Suite 529
New Loy, Delaware 29359
United States
Phone: 1-240-886-2233
Email: immanuel53@example.net
', '$tech_hero_image' => 'http://ninja.test:8000/images/pdf-designs/tech-hero-image.jpg', '$task.tax_name1' => '', '$task.tax_name2' => '', @@ -329,14 +331,14 @@ class PdfMock '$invoice.number' => '0029', '$quote.quote_no' => '0029', '$quote.datetime' => '25/Feb/2023 1:10 am', - '$client_address' => '8447
63993 Aiyana View
Aufderharchester, North Carolina 11243
Afghanistan
', - '$client.address' => '8447
63993 Aiyana View
Aufderharchester, North Carolina 11243
Afghanistan
', + '$client_address' => '8447
63993 Aiyana View
Aufderharchester, North Carolina 11243
United States
', + '$client.address' => '8447
63993 Aiyana View
Aufderharchester, North Carolina 11243
United States
', '$payment_button' => 'Pay Now', '$payment_qrcode' => ' ', - '$client.country' => 'Afghanistan', + '$client.country' => 'United States', '$user.last_name' => 'Erna Wunsch', '$client.website' => 'http://www.parisian.org/', '$dir_text_align' => 'left', @@ -344,9 +346,9 @@ class PdfMock '$task.discount' => '', '$contact.email' => 'bob@gmail.com', '$primary_color' => isset($this->settings->primary_color) ? $this->settings->primary_color : '#4e4e4e', - '$credit_amount' => '$0.00', - '$invoice.total' => '$0.00', - '$invoice.taxes' => '$0.00', + '$credit_amount' => '$40.00', + '$invoice.total' => '$330.00', + '$invoice.taxes' => '$10.00', '$quote.custom1' => 'custom value', '$quote.custom2' => 'custom value', '$quote.custom3' => 'custom value', @@ -393,10 +395,10 @@ class PdfMock '$valid_until' => '', '$your_entity' => '', '$shipping' => ctrans('texts.shipping_address'), - '$balance_due' => '$0.00', - '$outstanding' => '$0.00', - '$partial_due' => '$0.00', - '$quote.total' => '$0.00', + '$balance_due' => '$1110.00', + '$outstanding' => '$440.00', + '$partial_due' => '$50.00', + '$quote.total' => '$10.00', '$payment_due' => ' ', '$credit.date' => '25/Feb/2023', '$invoiceDate' => '25/Feb/2023', @@ -454,8 +456,8 @@ class PdfMock '$view_url' => 'http://ninja.test:8000/client/invoice/UAUY8vIPuno72igmXbbpldwo5BDDKIqs', '$font_url' => 'https://fonts.googleapis.com/css2?family=Roboto&display=swap', '$details' => '', - '$balance' => '$0.00', - '$partial' => '$0.00', + '$balance' => '$40.00', + '$partial' => '$30.00', '$client1' => 'custom value', '$client2' => 'custom value', '$client3' => 'custom value', @@ -469,7 +471,7 @@ class PdfMock '$website' => 'http://www.parisian.org/', '$entity' => '', '$thanks' => 'Thanks!', - '$amount' => '$0.00', + '$amount' => '$30.00', '$method' => ' ', '$number' => '0029', '$footer' => 'Default invoice footer', @@ -479,8 +481,8 @@ class PdfMock '_rate1' => '', '_rate2' => '', '_rate3' => '', - '$taxes' => '$0.00', - '$total' => '$0.00', + '$taxes' => '$40.00', + '$total' => '$10.00', '$phone' => ' ', '$terms' => 'Default company invoice terms', '$from' => 'Bob Jones', @@ -494,6 +496,8 @@ class PdfMock '$show_shipping_address' => $this->settings->show_shipping_address ? 'flex' : 'none', '$show_shipping_address_block' => $this->settings->show_shipping_address ? 'block' : 'none', '$show_shipping_address_visibility' => $this->settings->show_shipping_address ? 'visible' : 'hidden', + '$start_date' => '31/01/2023', + '$end_date' => '31/12/2023', ], 'labels' => $this->mockTranslatedLabels(), ]; @@ -509,6 +513,8 @@ class PdfMock '$client.billing_postal_code_label' => ctrans('texts.billing_postal_code'), '$company.postal_city_state_label' => ctrans('texts.postal_city_state'), '$company.city_state_postal_label' => ctrans('texts.city_state_postal'), + '$client.classification_label' => ctrans('texts.classification'), + '$company.classification_label' => ctrans('texts.classification'), '$product.gross_line_total_label' => ctrans('texts.gross_line_total'), '$client.shipping_address1_label' => ctrans('texts.shipping_address1'), '$client.postal_city_state_label' => ctrans('texts.postal_city_state'), @@ -807,29 +813,31 @@ class PdfMock '$tax_label' => ctrans('texts.tax'), '$dir_label' => '', '$to_label' => ctrans('texts.to'), + '$start_date_label' => ctrans('texts.start_date'), + '$end_date_label' => ctrans('texts.end_date'), ]; } - + private function getVendorStubVariables() { return ['values' => [ - '$vendor.billing_postal_code' => '06270-5526', - '$company.postal_city_state' => '29359 New Loy, Delaware', - '$company.city_state_postal' => 'New Loy, Delaware 29359', - '$product.gross_line_total' => '', - '$purchase_order.po_number' => 'PO12345', - '$vendor.postal_city_state' => '06270-5526 Jameyhaven, West Virginia', - '$vendor.city_state_postal' => 'Jameyhaven, West Virginia 06270-5526', - '$purchase_order.due_date' => '02-12-2021', - '$vendor.billing_address1' => '589', + '$vendor.billing_postal_code' => '06270-5526', + '$company.postal_city_state' => '29359 New Loy, Delaware', + '$company.city_state_postal' => 'New Loy, Delaware 29359', + '$product.gross_line_total' => '', + '$purchase_order.po_number' => 'PO12345', + '$vendor.postal_city_state' => '06270-5526 Jameyhaven, West Virginia', + '$vendor.city_state_postal' => 'Jameyhaven, West Virginia 06270-5526', + '$purchase_order.due_date' => '02-12-2021', + '$vendor.billing_address1' => '589', '$vendor.billing_address2' => '761 Odessa Centers Suite 673', '$invoiceninja.whitelabel' => 'https://invoicing.co/images/new_logo.png', '$purchase_order.custom1' => 'Custom 1', '$purchase_order.custom2' => 'Custom 2', '$purchase_order.custom3' => 'Custom 3', '$purchase_order.custom4' => 'Custom 4', - '$vendor.billing_address' => '589
761 Odessa Centers Suite 673
New Loy, Delaware 29359
Afghanistan
', - '$vendor.billing_country' => 'Afghanistan', + '$vendor.billing_address' => '589
761 Odessa Centers Suite 673
New Loy, Delaware 29359
United States
', + '$vendor.billing_country' => 'United States', '$purchase_order.number' => 'Live Preview #790', '$purchase_order.total' => '$10,256.40', '$vendor.billing_state' => 'West Virginia', @@ -903,9 +911,9 @@ class PdfMock '$company.website' => 'http://www.dare.com/vero-consequatur-eveniet-dolorum-exercitationem-alias-repellat.html', '$gross_subtotal' => '$10,256.40', '$emailSignature' => ' ', - '$vendor_address' => '589
761 Odessa Centers Suite 673
New Loy, Delaware 29359
Afghanistan
', - '$vendor.address' => '589
761 Odessa Centers Suite 673
New Loy, Delaware 29359
Afghanistan
', - '$vendor.country' => 'Afghanistan', + '$vendor_address' => '589
761 Odessa Centers Suite 673
New Loy, Delaware 29359
United States
', + '$vendor.address' => '589
761 Odessa Centers Suite 673
New Loy, Delaware 29359
United States
', + '$vendor.country' => 'United States', '$vendor.custom3' => 'Ea quia tempore.', '$vendor.custom1' => 'Necessitatibus aut.', '$vendor.custom4' => 'Nobis aut harum.', @@ -1085,7 +1093,7 @@ class PdfMock '$custom3' => ' ', '$custom4' => ' ', '$dueDate' => ' ', - '$country' => 'Afghanistan', + '$country' => 'United States', '$vendor3' => 'Ea quia tempore.', '$contact' => 'Geo Maggio', '$account' => 'Mrs. Kristina Powlowski', diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index d7fde2b8cc..f879d4d231 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -12,17 +12,20 @@ namespace App\Services\Pdf; use App\Models\Company; -use App\Models\CreditInvitation; -use App\Models\InvoiceInvitation; -use App\Models\PurchaseOrderInvitation; -use App\Models\QuoteInvitation; -use App\Models\RecurringInvoiceInvitation; -use App\Utils\HostedPDF\NinjaPdf; +use App\Models\Invoice; use App\Utils\HtmlEngine; -use App\Utils\PhantomJS\Phantom; -use App\Utils\Traits\Pdf\PageNumbering; -use App\Utils\Traits\Pdf\PdfMaker; +use App\Models\QuoteInvitation; use App\Utils\VendorHtmlEngine; +use App\Models\CreditInvitation; +use App\Utils\PhantomJS\Phantom; +use App\Models\InvoiceInvitation; +use App\Utils\HostedPDF\NinjaPdf; +use App\Utils\Traits\Pdf\PdfMaker; +use App\Jobs\Invoice\CreateEInvoice; +use App\Models\PurchaseOrderInvitation; +use App\Utils\Traits\Pdf\PageNumbering; +use App\Models\RecurringInvoiceInvitation; +use horstoeko\zugferd\ZugferdDocumentPdfBuilder; class PdfService { @@ -44,6 +47,10 @@ class PdfService public array $options; + private float $start_time; + + public float $execution_time; + const DELIVERY_NOTE = 'delivery_note'; const STATEMENT = 'statement'; const PURCHASE_ORDER = 'purchase_order'; @@ -58,10 +65,13 @@ class PdfService $this->document_type = $document_type; $this->options = $options; + + $this->start_time = microtime(true); } public function boot(): self { + $this->init(); return $this; @@ -85,11 +95,18 @@ class PdfService if ($numbered_pdf) { $pdf = $numbered_pdf; } + + if($this->config->entity_string == "invoice" && $this->config->settings->enable_e_invoice) { + $pdf = $this->checkEInvoice($pdf); + } + } catch (\Exception $e) { nlog(print_r($e->getMessage(), 1)); throw new \Exception($e->getMessage(), $e->getCode()); } + $this->execution_time = microtime(true) - $this->start_time; + return $pdf; } @@ -101,12 +118,15 @@ class PdfService */ public function getHtml(): string { + $html = $this->builder->getCompiledHTML(); if (config('ninja.log_pdf_html')) { - info($html); + nlog($html); } + $this->execution_time = microtime(true) - $this->start_time; + return $html; } @@ -117,6 +137,8 @@ class PdfService */ public function init(): self { + $this->start_time = microtime(true); + $this->config = (new PdfConfiguration($this))->init(); @@ -148,4 +170,61 @@ class PdfService return $pdf; } + + /** + * Switch to determine if we need to embed the xml into the PDF itself + * + * @param string $pdf + * @return string + */ + private function checkEInvoice(string $pdf): string + { + if(!$this->config->entity instanceof Invoice) + return $pdf; + + $e_invoice_type = $this->config->settings->e_invoice_type; + + switch ($e_invoice_type) { + case "EN16931": + case "XInvoice_2_2": + case "XInvoice_2_1": + case "XInvoice_2_0": + case "XInvoice_1_0": + case "XInvoice-Extended": + case "XInvoice-BasicWL": + case "XInvoice-Basic": + return $this->embedEInvoiceZuGFerD($pdf) ?? $pdf; + //case "Facturae_3.2": + //case "Facturae_3.2.1": + //case "Facturae_3.2.2": + // + default: + return $pdf; + } + + } + + /** + * Embed the .xml file into the PDF + * + * @param string $pdf + * @return string + */ + private function embedEInvoiceZuGFerD(string $pdf): string + { + try { + + $e_rechnung = (new CreateEInvoice($this->config->entity, true))->handle(); + $pdfBuilder = new ZugferdDocumentPdfBuilder($e_rechnung, $pdf); + $pdfBuilder->generateDocument(); + + return $pdfBuilder->downloadString(basename($this->config->entity->getFileName())); + + } catch (\Exception $e) { + nlog("E_Invoice Merge failed - " . $e->getMessage()); + } + + return $pdf; + } + } diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php index d0e54a9b18..e9ffb03898 100644 --- a/app/Services/PdfMaker/Design.php +++ b/app/Services/PdfMaker/Design.php @@ -265,9 +265,9 @@ class Design extends BaseDesign $variables = $this->context['pdf_variables']['client_details']; - $elements = collect($variables)->filter(function ($variable) use ($address_variables){ + $elements = collect($variables)->filter(function ($variable) use ($address_variables) { return in_array($variable, $address_variables); - })->map(function ($variable){ + })->map(function ($variable) { $variable = str_replace('$client.', '$client.shipping_', $variable); return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => "client_details-shipping-" . substr($variable, 1)]]; @@ -322,8 +322,9 @@ class Design extends BaseDesign public function entityDetails(): array { if ($this->type === 'statement') { - // $s_date = $this->translateDate(now(), $this->client->date_format(), $this->client->locale()); + $variables = $this->context['pdf_variables']['statement_details'] ?? []; + $s_date = $this->translateDate($this->options['start_date'], $this->client->date_format(), $this->client->locale()) . " - " . $this->translateDate($this->options['end_date'], $this->client->date_format(), $this->client->locale()); return [ @@ -934,7 +935,7 @@ class Design extends BaseDesign //07/09/2023 don't show custom values if they are empty // $visible = intval($this->entity->{$_variable}) != 0; - $visible = intval(str_replace(['0','.'],'', $this->entity->{$_variable})) != 0; + $visible = intval(str_replace(['0','.'], '', $this->entity->{$_variable})) != 0; $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ ['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], diff --git a/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php b/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php index 89aa0ea214..47c7266674 100644 --- a/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php +++ b/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php @@ -241,12 +241,13 @@ trait DesignHelpers { // We want to show headers for statements, no exceptions. $statements = " - document.querySelectorAll('#statement-invoice-table > thead > tr > th, #statement-payment-table > thead > tr > th, #statement-aging-table > thead > tr > th').forEach(t => { + document.querySelectorAll('#statement-credit-table > thead > tr > th, #statement-invoice-table > thead > tr > th, #statement-payment-table > thead > tr > th, #statement-aging-table > thead > tr > th').forEach(t => { t.hidden = false; }); "; - $javascript = 'document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(t=>{if(""!==t.innerText){let e=t.getAttribute("data-ref").slice(0,-3);document.querySelector(`th[data-ref="${e}-th"]`).removeAttribute("hidden")}}),document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(t=>{let e=t.getAttribute("data-ref").slice(0,-3);(e=document.querySelector(`th[data-ref="${e}-th"]`)).hasAttribute("hidden")&&""==t.innerText&&t.setAttribute("hidden","true")})},!1);'; + // $javascript = 'document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(t=>{if(""!==t.innerText){let e=t.getAttribute("data-ref").slice(0,-3);document.querySelector(`th[data-ref="${e}-th"]`).removeAttribute("hidden")}}),document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(t=>{let e=t.getAttribute("data-ref").slice(0,-3);(e=document.querySelector(`th[data-ref="${e}-th"]`)).hasAttribute("hidden")&&""==t.innerText&&t.setAttribute("hidden","true")})},!1);'; + $javascript = 'document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll("#custom-table > tbody > tr >td, #product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(t=>{if(""!==t.innerText){let e=t.getAttribute("data-ref").slice(0,-3);document.querySelector(`th[data-ref="${e}-th"]`).removeAttribute("hidden")}}),document.querySelectorAll("#custom-table > tbody > tr > td, #product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(t=>{let e=t.getAttribute("data-ref").slice(0,-3);(e=document.querySelector(`th[data-ref="${e}-th"]`)).hasAttribute("hidden")&&""==t.innerText&&t.setAttribute("hidden","true")})},!1);'; // Previously we've been decoding the HTML on the backend and XML parsing isn't good options because it requires, // strict & valid HTML to even output/decode. Decoding is now done on the frontend with this piece of Javascript. diff --git a/app/Services/PdfMaker/PdfMaker.php b/app/Services/PdfMaker/PdfMaker.php index 26c375c567..680bf7ef81 100644 --- a/app/Services/PdfMaker/PdfMaker.php +++ b/app/Services/PdfMaker/PdfMaker.php @@ -12,6 +12,7 @@ namespace App\Services\PdfMaker; +use App\Services\Template\TemplateService; use League\CommonMark\CommonMarkConverter; class PdfMaker @@ -74,6 +75,34 @@ class PdfMaker $this->updateElementProperties($this->data['template']); } + if(isset($this->options)) { + + $replacements = []; + $contents = $this->document->getElementsByTagName('ninja'); + + $ts = new TemplateService(); + $data = $ts->processData($this->options)->getData(); + $twig = $ts->twig; + + foreach ($contents as $content) { + + $template = $content->ownerDocument->saveHTML($content); + + $template = $twig->createTemplate(html_entity_decode($template)); + $template = $template->render($data); + + $f = $this->document->createDocumentFragment(); + $f->appendXML($template); + $replacements[] = $f; + + } + + foreach($contents as $key => $content) { + $content->parentNode->replaceChild($replacements[$key], $content); + } + + } + if (isset($this->data['variables'])) { $this->updateVariables($this->data['variables']); } @@ -84,13 +113,14 @@ class PdfMaker /** * Final method to get compiled HTML. * - * @param bool $final @deprecated // is it? i still see it being called elsewhere + * @param bool $final * @return mixed */ public function getCompiledHTML($final = false) { - $html = $this->document->saveHTML(); + $html = $this->document->saveHTML(); + // nlog($html); return str_replace('%24', '$', $html); } } diff --git a/app/Services/Preview/StubBuilder.php b/app/Services/Preview/StubBuilder.php deleted file mode 100644 index 8d3cce3076..0000000000 --- a/app/Services/Preview/StubBuilder.php +++ /dev/null @@ -1,319 +0,0 @@ -entity_type = $entity_type; - - return $this; - } - - public function build(): self - { - try { - DB::connection(config('database.default'))->transaction(function () { - $this->createRecipient() - ->initializeSettings() - ->createEntity() - ->linkRelations() - ->buildHtml(); - }); - } catch (\Throwable $throwable) { - nlog("DB ERROR " . $throwable->getMessage()); - - if (DB::connection(config('database.default'))->transactionLevel() > 0) { - DB::connection(config('database.default'))->rollBack(); - } - } catch(\Exception $e) { - nlog($e->getMessage()); - - if (DB::connection(config('database.default'))->transactionLevel() > 0) { - DB::connection(config('database.default'))->rollBack(); - } - } - - return $this; - } - - public function getPdf(): mixed - { - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - return (new Phantom)->convertHtmlToPdf($this->html); - } - - if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - $pdf = (new NinjaPdf())->build($this->html); - - $numbered_pdf = $this->pageNumbering($pdf, $this->company); - - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } - - return $pdf; - } - - return (new PreviewPdf($this->html, $this->company))->handle(); - } - - private function initializeSettings(): self - { - $this->dynamic_settings_type = 'company'; - - match ($this->dynamic_settings_type) { - 'company' => $this->setCompanySettings(), - 'client' => $this->setClientSettings(), - 'group' => $this->setGroupSettings(), - }; - - - return $this; - } - - private function setCompanySettings(): self - { - $this->company->settings = $this->settings; - $this->company->save(); - - return $this; - } - - private function setClientSettings(): self - { - $this->recipient->settings = $this->settings; - $this->recipient->save(); - - return $this; - } - - private function setGroupSettings(): self - { - $g = GroupSettingFactory::create($this->company->id, $this->user->id); - $g->name = Str::random(10); - $g->settings = $this->settings; - $g->save(); - - $this->recipient->group_settings_id = $g->id; - $this->recipient->save(); - - return $this; - } - - public function setSettings($settings): self - { - $this->settings = $settings; - - return $this; - } - - public function setSettingsType($type): self - { - $this->dynamic_settings_type = $type; - - return $this; - } - - private function buildHtml(): self - { - $html = new HtmlEngine($this->invitation); - - $design_string = "{$this->entity_type}_design_id"; - - $design = DesignModel::query()->withTrashed()->find($this->decodePrimaryKey($html->settings->{$design_string})); - - $template = new PdfMakerDesign(strtolower($design->name)); - - $state = [ - 'template' => $template->elements([ - 'client' => $this->recipient, - 'entity' => $this->entity, - 'pdf_variables' => (array) $html->settings->pdf_variables, - '$product' => $design->design->product, - ]), - 'variables' => $html->generateLabelsAndValues(), - 'process_markdown' => $this->company->markdown_enabled, - ]; - - $maker = new PdfMaker($state); - - $this->html = $maker->design($template) - ->build() - ->getCompiledHTML(); - - return $this; - } - - private function linkRelations(): self - { - $this->entity->setRelation('invitations', $this->invitation); - $this->entity->setRelation($this->recipient_string, $this->recipient); - $this->entity->setRelation('company', $this->company); - $this->entity->load("{$this->recipient_string}.company"); - - return $this; - } - - private function createRecipient(): self - { - match ($this->entity_type) { - 'invoice' => $this->createClient(), - 'quote' => $this->createClient(), - 'credit' => $this->createClient(), - 'purchase_order' => $this->createVendor(), - }; - - return $this; - } - - private function createClient(): self - { - /** @var \App\Models\Client $client */ - $client = Client::factory()->create([ - 'user_id' => $this->user->id, - 'company_id' => $this->company->id, - ]); - - $this->recipient = $client; - - $this->contact = ClientContact::factory()->create([ - 'user_id' => $this->user->id, - 'company_id' => $this->company->id, - 'client_id' => $this->recipient->id, - ]); - - $this->recipient_string = 'client'; - - return $this; - } - - private function createVendor(): self - { - /** @var \App\Models\Vendor $vendor */ - $vendor = Vendor::factory()->create([ - 'user_id' => $this->user->id, - 'company_id' => $this->user->company()->id, - ]); - - $this->recipient = $vendor; - - $this->contact = VendorContact::factory()->create([ - 'user_id' => $this->user->id, - 'company_id' => $this->company->id, - 'vendor_id' => $this->recipient->id, - ]); - - $this->recipient_string = 'vendor'; - - return $this; - } - - - private function createEntity(): self - { - match ($this->entity_type) { - 'invoice' => $this->createInvoice(), - 'quote' => $this->createQuote(), - 'credit' => $this->createCredit(), - 'purchase_order' => $this->createPurchaseOrder(), - }; - - return $this; - } - - private function createInvoice() - { - /** @var \App\Models\Invoice $invoice */ - $invoice = Invoice::factory()->create([ - 'user_id' => $this->user->id, - 'company_id' => $this->company->id, - 'client_id' => $this->recipient->id, - 'terms' => $this->company->settings->invoice_terms, - 'footer' => $this->company->settings->invoice_footer, - 'status_id' => Invoice::STATUS_PAID, - ]); - - $this->entity = $invoice; - - $this->invitation = InvoiceInvitation::factory()->create([ - 'user_id' => $this->user->id, - 'company_id' => $this->company->id, - 'invoice_id' => $this->entity->id, - 'client_contact_id' => $this->contact->id, - ]); - } - - private function createQuote() - { - $this->entity->save(); - } - - private function createCredit() - { - $this->entity->save(); - } - - private function createPurchaseOrder() - { - $this->entity->save(); - } -} diff --git a/app/Services/PurchaseOrder/GetPurchaseOrderPdf.php b/app/Services/PurchaseOrder/GetPurchaseOrderPdf.php index 32dc93f108..620836ba9b 100644 --- a/app/Services/PurchaseOrder/GetPurchaseOrderPdf.php +++ b/app/Services/PurchaseOrder/GetPurchaseOrderPdf.php @@ -11,11 +11,10 @@ namespace App\Services\PurchaseOrder; +use App\Jobs\Entity\CreateRawPdf; use App\Models\PurchaseOrder; use App\Models\VendorContact; use App\Services\AbstractService; -use Illuminate\Support\Facades\Storage; -use App\Jobs\Vendor\CreatePurchaseOrderPdf; class GetPurchaseOrderPdf extends AbstractService { @@ -35,19 +34,7 @@ class GetPurchaseOrderPdf extends AbstractService $invitation = $this->purchase_order->invitations()->first(); } - $path = $this->purchase_order->vendor->purchase_order_filepath($invitation); - - $file_path = $path.$this->purchase_order->numberFormatter().'.pdf'; - - // $disk = 'public'; - $disk = config('filesystems.default'); - - $file = Storage::disk($disk)->exists($file_path); - - if (! $file) { - $file_path = (new CreatePurchaseOrderPdf($invitation))->handle(); - } - - return $file_path; + return (new CreateRawPdf($invitation))->handle(); + } } diff --git a/app/Services/PurchaseOrder/PurchaseOrderService.php b/app/Services/PurchaseOrder/PurchaseOrderService.php index 3445e261dd..2f40b51f00 100644 --- a/app/Services/PurchaseOrder/PurchaseOrderService.php +++ b/app/Services/PurchaseOrder/PurchaseOrderService.php @@ -13,8 +13,6 @@ namespace App\Services\PurchaseOrder; use App\Models\PurchaseOrder; use App\Utils\Traits\MakesHash; -use App\Services\PurchaseOrder\SendEmail; -use App\Jobs\Vendor\CreatePurchaseOrderPdf; class PurchaseOrderService { @@ -98,27 +96,6 @@ class PurchaseOrderService return $this; } - public function touchPdf($force = false) - { - try { - if ($force) { - $this->purchase_order->invitations->each(function ($invitation) { - (new CreatePurchaseOrderPdf($invitation))->handle(); - }); - - return $this; - } - - $this->purchase_order->invitations->each(function ($invitation) { - CreatePurchaseOrderPdf::dispatch($invitation); - }); - } catch(\Exception $e) { - nlog("failed creating purchase orders in Touch PDF"); - } - - return $this; - } - public function add_to_inventory() { if ($this->purchase_order->status_id >= PurchaseOrder::STATUS_RECEIVED) { @@ -153,7 +130,7 @@ class PurchaseOrderService /** * Saves the purchase order. - * @return \App\Models\PurchaseOrder + * @return \App\Models\PurchaseOrder */ public function save(): ?PurchaseOrder { diff --git a/app/Services/PurchaseOrder/SendEmail.php b/app/Services/PurchaseOrder/SendEmail.php index 900d5380bc..a6b2fa1c3a 100644 --- a/app/Services/PurchaseOrder/SendEmail.php +++ b/app/Services/PurchaseOrder/SendEmail.php @@ -11,16 +11,16 @@ namespace App\Services\PurchaseOrder; -use App\Utils\Ninja; -use App\Models\PurchaseOrder; -use App\Models\VendorContact; +use App\Events\PurchaseOrder\PurchaseOrderWasEmailed; use App\Jobs\Mail\NinjaMailerJob; -use App\Mail\VendorTemplateEmail; -use App\Services\AbstractService; -use Illuminate\Support\Facades\App; use App\Jobs\Mail\NinjaMailerObject; use App\Mail\Engine\PurchaseOrderEmailEngine; -use App\Events\PurchaseOrder\PurchaseOrderWasEmailed; +use App\Mail\VendorTemplateEmail; +use App\Models\PurchaseOrder; +use App\Models\VendorContact; +use App\Services\AbstractService; +use App\Utils\Ninja; +use Illuminate\Support\Facades\App; class SendEmail extends AbstractService { @@ -69,48 +69,3 @@ class SendEmail extends AbstractService } } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/Services/PurchaseOrder/TriggeredActions.php b/app/Services/PurchaseOrder/TriggeredActions.php index 624efc3ac4..9e4fb63ace 100644 --- a/app/Services/PurchaseOrder/TriggeredActions.php +++ b/app/Services/PurchaseOrder/TriggeredActions.php @@ -38,7 +38,6 @@ class TriggeredActions extends AbstractService $this->purchase_order ->service() ->markSent() - // ->touchPdf() ->save(); $this->sendEmail(); @@ -48,7 +47,6 @@ class TriggeredActions extends AbstractService $this->purchase_order = $this->purchase_order ->service() ->markSent() - // ->touchPdf() ->save(); } diff --git a/app/Services/Quote/ConvertQuoteToProject.php b/app/Services/Quote/ConvertQuoteToProject.php index 59227fcfc5..4b37a3c3e8 100644 --- a/app/Services/Quote/ConvertQuoteToProject.php +++ b/app/Services/Quote/ConvertQuoteToProject.php @@ -12,11 +12,10 @@ namespace App\Services\Quote; -use App\DataMapper\InvoiceItem; -use App\Models\Quote; use App\Factory\ProjectFactory; use App\Factory\TaskFactory; use App\Models\Project; +use App\Models\Quote; use App\Repositories\TaskRepository; use App\Utils\Traits\GeneratesCounter; @@ -31,9 +30,9 @@ class ConvertQuoteToProject public function run(): Project { - $quote_items = collect($this->quote->line_items)->filter(function ($item){ - return $item->type_id == '2'; - }); + $quote_items = collect($this->quote->line_items)->filter(function ($item) { + return $item->type_id == '2'; + }); $project = ProjectFactory::create($this->quote->company_id, $this->quote->user_id); $project->name = ctrans('texts.quote_number_short'). " " . $this->quote->number . " [{$this->quote->client->present()->name()}]"; @@ -57,20 +56,20 @@ class ConvertQuoteToProject $task_repo = new TaskRepository(); - $quote_items->each(function($item) use($task_repo, $task_status){ + $quote_items->each(function ($item) use ($task_repo, $task_status) { - $task = TaskFactory::create($this->quote->company_id, $this->quote->user_id); - $task->client_id = $this->quote->client_id; - $task->project_id = $this->quote->project_id; - $task->description = $item->notes; - $task->status_id = $task_status->id; - $task->rate = $item->cost; - $task_repo->save([], $task); + $task = TaskFactory::create($this->quote->company_id, $this->quote->user_id); + $task->client_id = $this->quote->client_id; + $task->project_id = $this->quote->project_id; + $task->description = $item->notes; + $task->status_id = $task_status->id; + $task->rate = $item->cost; + $task_repo->save([], $task); - }); + }); event('eloquent.created: App\Models\Project', $project); return $project->fresh(); } -} \ No newline at end of file +} diff --git a/app/Services/Quote/CreateInvitations.php b/app/Services/Quote/CreateInvitations.php index 933bbdbe9d..47c435a58f 100644 --- a/app/Services/Quote/CreateInvitations.php +++ b/app/Services/Quote/CreateInvitations.php @@ -42,7 +42,7 @@ class CreateInvitations $contacts->each(function ($contact) { $invitation = QuoteInvitation::query() - ->where('company_id',$this->quote->company_id) + ->where('company_id', $this->quote->company_id) ->whereClientContactId($contact->id) ->whereQuoteId($this->quote->id) ->withTrashed() diff --git a/app/Services/Quote/GetQuotePdf.php b/app/Services/Quote/GetQuotePdf.php index 5ae880e330..cd1a537aaf 100644 --- a/app/Services/Quote/GetQuotePdf.php +++ b/app/Services/Quote/GetQuotePdf.php @@ -11,7 +11,7 @@ namespace App\Services\Quote; -use App\Jobs\Entity\CreateEntityPdf; +use App\Jobs\Entity\CreateRawPdf; use App\Models\ClientContact; use App\Models\Quote; use App\Services\AbstractService; @@ -20,9 +20,6 @@ class GetQuotePdf extends AbstractService { public function __construct(public Quote $quote, public ?ClientContact $contact = null) { - $this->quote = $quote; - - $this->contact = $contact; } public function run() @@ -37,16 +34,7 @@ class GetQuotePdf extends AbstractService $invitation = $this->quote->invitations->first(); } - $path = $this->quote->client->quote_filepath($invitation); + return (new CreateRawPdf($invitation))->handle(); - $file_path = $path . $this->quote->numberFormatter() . '.pdf'; - - // $disk = 'public'; - $disk = config('filesystems.default'); - - - $file_path = (new CreateEntityPdf($invitation))->handle(); - - return $file_path; } } diff --git a/app/Services/Quote/MarkSent.php b/app/Services/Quote/MarkSent.php index 263ce043ce..f7c32b0ef0 100644 --- a/app/Services/Quote/MarkSent.php +++ b/app/Services/Quote/MarkSent.php @@ -11,12 +11,12 @@ namespace App\Services\Quote; -use Carbon\Carbon; -use App\Utils\Ninja; -use App\Models\Quote; -use App\Models\Client; -use App\Models\Webhook; use App\Events\Quote\QuoteWasMarkedSent; +use App\Models\Client; +use App\Models\Quote; +use App\Models\Webhook; +use App\Utils\Ninja; +use Carbon\Carbon; class MarkSent { diff --git a/app/Services/Quote/QuoteService.php b/app/Services/Quote/QuoteService.php index e12168af52..fc6b51b456 100644 --- a/app/Services/Quote/QuoteService.php +++ b/app/Services/Quote/QuoteService.php @@ -11,16 +11,14 @@ namespace App\Services\Quote; -use App\Utils\Ninja; -use App\Models\Quote; -use App\Models\Project; -use App\Utils\Traits\MakesHash; -use App\Exceptions\QuoteConversion; -use App\Jobs\Entity\CreateEntityPdf; -use App\Repositories\QuoteRepository; use App\Events\Quote\QuoteWasApproved; +use App\Exceptions\QuoteConversion; +use App\Models\Project; +use App\Models\Quote; +use App\Repositories\QuoteRepository; +use App\Utils\Ninja; +use App\Utils\Traits\MakesHash; use Illuminate\Support\Facades\Storage; -use App\Services\Quote\ConvertQuoteToProject; class QuoteService { @@ -133,32 +131,7 @@ class QuoteService return $this; } - /** - * Sometimes we need to refresh the - * PDF when it is updated etc. - * - * @return QuoteService - */ - public function touchPdf($force = false) - { - try { - if ($force) { - $this->quote->invitations->each(function ($invitation) { - (new CreateEntityPdf($invitation))->handle(); - }); - return $this; - } - - $this->quote->invitations->each(function ($invitation) { - CreateEntityPdf::dispatch($invitation); - }); - } catch (\Exception $e) { - nlog('failed creating invoices in Touch PDF'); - } - - return $this; - } public function approveWithNoCoversion($contact = null) :self { diff --git a/app/Services/Quote/SendEmail.php b/app/Services/Quote/SendEmail.php index d157792aa0..3cd0cf301b 100644 --- a/app/Services/Quote/SendEmail.php +++ b/app/Services/Quote/SendEmail.php @@ -11,10 +11,8 @@ namespace App\Services\Quote; -use App\Utils\Ninja; -use App\Models\ClientContact; use App\Jobs\Entity\EmailEntity; -use App\Events\Quote\QuoteWasEmailed; +use App\Models\ClientContact; class SendEmail { diff --git a/app/Services/Recurring/GetInvoicePdf.php b/app/Services/Recurring/GetInvoicePdf.php index 079bd55eea..019d094e6f 100644 --- a/app/Services/Recurring/GetInvoicePdf.php +++ b/app/Services/Recurring/GetInvoicePdf.php @@ -11,22 +11,15 @@ namespace App\Services\Recurring; -use App\Jobs\Entity\CreateEntityPdf; +use App\Jobs\Entity\CreateRawPdf; use App\Models\ClientContact; use App\Services\AbstractService; -use Illuminate\Support\Facades\Storage; class GetInvoicePdf extends AbstractService { - public $entity; - - public $contact; - public function __construct($entity, ClientContact $contact = null) + public function __construct(public $entity, public ClientContact $contact = null) { - $this->entity = $entity; - - $this->contact = $contact; } public function run() @@ -37,18 +30,11 @@ class GetInvoicePdf extends AbstractService $invitation = $this->entity->invitations->where('client_contact_id', $this->contact->id)->first(); - $path = $this->entity->client->recurring_invoice_filepath($invitation); - - $file_path = $path.$this->entity->hashed_id.'.pdf'; - - $disk = config('filesystems.default'); - - $file = Storage::disk($disk)->exists($file_path); - - if (! $file) { - $file_path = (new CreateEntityPdf($invitation))->handle(); + if (! $invitation) { + $invitation = $this->entity->invitations->first(); } - return $file_path; + return (new CreateRawPdf($invitation))->handle(); + } } diff --git a/app/Services/Recurring/RecurringService.php b/app/Services/Recurring/RecurringService.php index eca206c746..61ae416956 100644 --- a/app/Services/Recurring/RecurringService.php +++ b/app/Services/Recurring/RecurringService.php @@ -11,14 +11,13 @@ namespace App\Services\Recurring; -use App\Utils\Ninja; -use App\Jobs\Util\UnlinkFile; -use App\Models\RecurringQuote; -use Illuminate\Support\Carbon; +use App\Jobs\RecurringInvoice\SendRecurring; use App\Models\RecurringExpense; use App\Models\RecurringInvoice; +use App\Models\RecurringQuote; +use App\Utils\Ninja; +use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Storage; -use App\Jobs\RecurringInvoice\SendRecurring; class RecurringService { @@ -94,7 +93,7 @@ class RecurringService //30-06-2023 try { Storage::disk(config('filesystems.default'))->delete($this->recurring_entity->client->recurring_invoice_filepath($invitation) . $this->recurring_entity->numberFormatter().'.pdf'); - Storage::disk('public')->delete($this->recurring_entity->client->recurring_invoice_filepath($invitation) . $this->recurring_entity->numberFormatter().'.pdf'); + Storage::disk('public')->delete($this->recurring_entity->client->recurring_invoice_filepath($invitation) . $this->recurring_entity->numberFormatter().'.pdf'); if (Ninja::isHosted()) { } } catch (\Exception $e) { diff --git a/app/Services/Report/ARDetailReport.php b/app/Services/Report/ARDetailReport.php index 3787acd07c..70526195c8 100644 --- a/app/Services/Report/ARDetailReport.php +++ b/app/Services/Report/ARDetailReport.php @@ -11,17 +11,17 @@ namespace App\Services\Report; -use Carbon\Carbon; -use App\Utils\Ninja; -use App\Utils\Number; +use App\Export\CSV\BaseExport; +use App\Libraries\MultiDB; use App\Models\Client; -use League\Csv\Writer; use App\Models\Company; use App\Models\Invoice; -use App\Libraries\MultiDB; -use App\Export\CSV\BaseExport; +use App\Utils\Ninja; +use App\Utils\Number; use App\Utils\Traits\MakesDates; +use Carbon\Carbon; use Illuminate\Support\Facades\App; +use League\Csv\Writer; class ARDetailReport extends BaseExport { @@ -102,7 +102,7 @@ class ARDetailReport extends BaseExport $query->cursor() ->each(function ($invoice) { - $this->csv->insertOne($this->buildRow($invoice)); + $this->csv->insertOne($this->buildRow($invoice)); }); return $this->csv->toString(); @@ -138,4 +138,4 @@ class ARDetailReport extends BaseExport return $header; } -} \ No newline at end of file +} diff --git a/app/Services/Report/ARSummaryReport.php b/app/Services/Report/ARSummaryReport.php index 0fd0d5b83d..9193a8d773 100644 --- a/app/Services/Report/ARSummaryReport.php +++ b/app/Services/Report/ARSummaryReport.php @@ -106,7 +106,7 @@ class ARSummaryReport extends BaseExport $this->client->present()->name(), $this->client->number, $this->client->id_number, - $this->getCurrent(), + $this->getCurrent(), $this->getAgingAmount('30'), $this->getAgingAmount('60'), $this->getAgingAmount('90'), @@ -128,7 +128,7 @@ class ARSummaryReport extends BaseExport ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) ->where('balance', '>', 0) ->where('is_deleted', 0) - ->where(function ($query){ + ->where(function ($query) { $query->where('due_date', '>', now()->startOfDay()) ->orWhereNull('due_date'); }) @@ -161,7 +161,7 @@ class ARSummaryReport extends BaseExport ->whereBetween('due_date', [$to, $from]) ->sum('balance'); - $this->total += $amount; + $this->total += $amount; return Number::formatMoney($amount, $this->client); } diff --git a/app/Services/Report/ClientBalanceReport.php b/app/Services/Report/ClientBalanceReport.php index 0c0eb85aca..405cfdbe29 100644 --- a/app/Services/Report/ClientBalanceReport.php +++ b/app/Services/Report/ClientBalanceReport.php @@ -11,15 +11,15 @@ namespace App\Services\Report; -use App\Utils\Ninja; +use App\Export\CSV\BaseExport; +use App\Libraries\MultiDB; use App\Models\Client; -use League\Csv\Writer; use App\Models\Company; use App\Models\Invoice; -use App\Libraries\MultiDB; -use App\Export\CSV\BaseExport; +use App\Utils\Ninja; use App\Utils\Traits\MakesDates; use Illuminate\Support\Facades\App; +use League\Csv\Writer; class ClientBalanceReport extends BaseExport { @@ -83,7 +83,7 @@ class ClientBalanceReport extends BaseExport ->where('is_deleted', 0) ->orderBy('balance', 'desc') ->cursor() - ->each(function ($client){ + ->each(function ($client) { $this->csv->insertOne($this->buildRow($client)); @@ -97,8 +97,9 @@ class ClientBalanceReport extends BaseExport { $headers = []; - foreach($this->report_keys as $key) + foreach($this->report_keys as $key) { $headers[] = ctrans("texts.{$key}"); + } return $headers; @@ -119,4 +120,4 @@ class ClientBalanceReport extends BaseExport $client->credit_balance, ]; } -} \ No newline at end of file +} diff --git a/app/Services/Report/ClientSalesReport.php b/app/Services/Report/ClientSalesReport.php index 7bb5b47d83..66cd6016ca 100644 --- a/app/Services/Report/ClientSalesReport.php +++ b/app/Services/Report/ClientSalesReport.php @@ -11,16 +11,16 @@ namespace App\Services\Report; -use App\Utils\Ninja; -use App\Utils\Number; +use App\Export\CSV\BaseExport; +use App\Libraries\MultiDB; use App\Models\Client; -use League\Csv\Writer; use App\Models\Company; use App\Models\Invoice; -use App\Libraries\MultiDB; -use App\Export\CSV\BaseExport; +use App\Utils\Ninja; +use App\Utils\Number; use App\Utils\Traits\MakesDates; use Illuminate\Support\Facades\App; +use League\Csv\Writer; class ClientSalesReport extends BaseExport { diff --git a/app/Services/Report/ProfitLoss.php b/app/Services/Report/ProfitLoss.php index b950584813..0f7e2ca97b 100644 --- a/app/Services/Report/ProfitLoss.php +++ b/app/Services/Report/ProfitLoss.php @@ -11,19 +11,19 @@ namespace App\Services\Report; -use App\Utils\Ninja; -use App\Utils\Number; -use League\Csv\Writer; +use App\Libraries\Currency\Conversion\CurrencyApi; +use App\Libraries\MultiDB; use App\Models\Company; +use App\Models\Currency; use App\Models\Expense; use App\Models\Invoice; use App\Models\Payment; -use App\Models\Currency; -use App\Libraries\MultiDB; -use Illuminate\Support\Str; +use App\Utils\Ninja; +use App\Utils\Number; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\App; -use App\Libraries\Currency\Conversion\CurrencyApi; +use Illuminate\Support\Str; +use League\Csv\Writer; class ProfitLoss { @@ -107,7 +107,7 @@ class ProfitLoss if ($this->is_income_billed) { //get invoiced amounts $this->filterIncome(); } else { - //$this->filterPaymentIncome(); + $this->filterInvoicePaymentIncome(); } diff --git a/app/Services/Report/TaxSummaryReport.php b/app/Services/Report/TaxSummaryReport.php index 661f741865..aaf313eaf2 100644 --- a/app/Services/Report/TaxSummaryReport.php +++ b/app/Services/Report/TaxSummaryReport.php @@ -88,16 +88,14 @@ class TaxSummaryReport extends BaseExport $accrual_map = []; $cash_map = []; - foreach($query->cursor() as $invoice) - { + foreach($query->cursor() as $invoice) { $calc = $invoice->calc(); //Combine the line taxes with invoice taxes here to get a total tax amount $taxes = array_merge($calc->getTaxMap()->merge($calc->getTotalTaxMap())->toArray()); //filter into two arrays for accrual + cash - foreach($taxes as $tax) - { + foreach($taxes as $tax) { $key = $tax['name']; if(!isset($accrual_map[$key])) { @@ -113,12 +111,13 @@ class TaxSummaryReport extends BaseExport $cash_map[$key]['tax_amount'] = 0; } - if(in_array($invoice->status_id, [Invoice::STATUS_PARTIAL,Invoice::STATUS_PAID])){ + if(in_array($invoice->status_id, [Invoice::STATUS_PARTIAL,Invoice::STATUS_PAID])) { - if($invoice->status_id == Invoice::STATUS_PAID) - $cash_map[$key]['tax_amount'] += $tax['total']; - else - $cash_map[$key]['tax_amount'] += (($invoice->amount - $invoice->balance) / $invoice->balance) * $tax['total'] ?? 0; + if($invoice->status_id == Invoice::STATUS_PAID) { + $cash_map[$key]['tax_amount'] += $tax['total']; + } else { + $cash_map[$key]['tax_amount'] += (($invoice->amount - $invoice->balance) / $invoice->balance) * $tax['total'] ?? 0; + } } } @@ -130,8 +129,7 @@ class TaxSummaryReport extends BaseExport $this->csv->insertOne($this->buildHeader()); - foreach($accrual_map as $key => $value) - { + foreach($accrual_map as $key => $value) { $this->csv->insertOne([$key, Number::formatMoney($value['tax_amount'], $this->company)]); } diff --git a/app/Services/Report/UserSalesReport.php b/app/Services/Report/UserSalesReport.php index 9780e8a9ec..f533bc7dc1 100644 --- a/app/Services/Report/UserSalesReport.php +++ b/app/Services/Report/UserSalesReport.php @@ -11,16 +11,15 @@ namespace App\Services\Report; -use App\Models\User; -use App\Utils\Ninja; -use App\Utils\Number; -use League\Csv\Writer; +use App\Export\CSV\BaseExport; +use App\Libraries\MultiDB; use App\Models\Company; use App\Models\Invoice; -use App\Libraries\MultiDB; -use App\Export\CSV\BaseExport; +use App\Utils\Ninja; +use App\Utils\Number; use App\Utils\Traits\MakesDates; use Illuminate\Support\Facades\App; +use League\Csv\Writer; class UserSalesReport extends BaseExport { @@ -40,7 +39,7 @@ class UserSalesReport extends BaseExport 'total_taxes', ]; /** - @param array $input + @param array $input [ 'date_range', 'start_date', @@ -83,7 +82,7 @@ class UserSalesReport extends BaseExport $users = $this->company->users; - $report = $users->map(function ($user) use($query){ + $report = $users->map(function ($user) use ($query) { $new_query = $query; $new_query->where('user_id', $user->id); diff --git a/app/Services/Scheduler/EmailRecord.php b/app/Services/Scheduler/EmailRecord.php index 1f5886226f..9c0b498bb3 100644 --- a/app/Services/Scheduler/EmailRecord.php +++ b/app/Services/Scheduler/EmailRecord.php @@ -12,8 +12,8 @@ namespace App\Services\Scheduler; use App\Models\Scheduler; -use Illuminate\Support\Str; use App\Utils\Traits\MakesHash; +use Illuminate\Support\Str; class EmailRecord { @@ -29,8 +29,9 @@ class EmailRecord $entity = $class::find($this->decodePrimaryKey($this->scheduler->parameters['entity_id'])); - if($entity) + if($entity) { $entity->service()->sendEmail(); + } $this->scheduler->forceDelete(); } diff --git a/app/Services/Scheduler/EmailReport.php b/app/Services/Scheduler/EmailReport.php index f1def5d9b6..9b8acf7934 100644 --- a/app/Services/Scheduler/EmailReport.php +++ b/app/Services/Scheduler/EmailReport.php @@ -11,34 +11,34 @@ namespace App\Services\Scheduler; -use App\Models\Client; -use App\Models\Scheduler; -use App\Mail\DownloadReport; -use App\Export\CSV\TaskExport; -use App\Export\CSV\QuoteExport; -use App\Utils\Traits\MakesHash; use App\Export\CSV\ClientExport; -use App\Export\CSV\CreditExport; -use App\Utils\Traits\MakesDates; use App\Export\CSV\ContactExport; +use App\Export\CSV\CreditExport; +use App\Export\CSV\DocumentExport; use App\Export\CSV\ExpenseExport; use App\Export\CSV\InvoiceExport; +use App\Export\CSV\InvoiceItemExport; use App\Export\CSV\PaymentExport; use App\Export\CSV\ProductExport; -use App\Jobs\Mail\NinjaMailerJob; -use App\Export\CSV\DocumentExport; -use App\Export\CSV\QuoteItemExport; -use App\Services\Report\ProfitLoss; -use App\Jobs\Mail\NinjaMailerObject; -use App\Export\CSV\InvoiceItemExport; use App\Export\CSV\ProductSalesExport; +use App\Export\CSV\QuoteExport; +use App\Export\CSV\QuoteItemExport; +use App\Export\CSV\RecurringInvoiceExport; +use App\Export\CSV\TaskExport; +use App\Jobs\Mail\NinjaMailerJob; +use App\Jobs\Mail\NinjaMailerObject; +use App\Mail\DownloadReport; +use App\Models\Client; +use App\Models\Scheduler; use App\Services\Report\ARDetailReport; use App\Services\Report\ARSummaryReport; -use App\Services\Report\UserSalesReport; -use App\Services\Report\TaxSummaryReport; -use App\Export\CSV\RecurringInvoiceExport; -use App\Services\Report\ClientSalesReport; use App\Services\Report\ClientBalanceReport; +use App\Services\Report\ClientSalesReport; +use App\Services\Report\ProfitLoss; +use App\Services\Report\TaxSummaryReport; +use App\Services\Report\UserSalesReport; +use App\Utils\Traits\MakesDates; +use App\Utils\Traits\MakesHash; class EmailReport { @@ -76,8 +76,7 @@ class EmailReport $export = false; - match($this->scheduler->parameters['report_name']) - { + match($this->scheduler->parameters['report_name']) { 'product_sales' => $export = (new ProductSalesExport($this->scheduler->company, $data)), 'ar_detailed' => $export = (new ARDetailReport($this->scheduler->company, $data)), 'ar_summary' => $export = (new ARSummaryReport($this->scheduler->company, $data)), diff --git a/app/Services/Scheduler/SchedulerService.php b/app/Services/Scheduler/SchedulerService.php index ddf327ab35..d870a1cf3f 100644 --- a/app/Services/Scheduler/SchedulerService.php +++ b/app/Services/Scheduler/SchedulerService.php @@ -12,9 +12,8 @@ namespace App\Services\Scheduler; use App\Models\Scheduler; -use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesDates; -use App\Services\Scheduler\EmailReport; +use App\Utils\Traits\MakesHash; class SchedulerService { diff --git a/app/Services/Subscription/SubscriptionService.php b/app/Services/Subscription/SubscriptionService.php index 3794273d82..e2e331b3f6 100644 --- a/app/Services/Subscription/SubscriptionService.php +++ b/app/Services/Subscription/SubscriptionService.php @@ -234,8 +234,9 @@ class SubscriptionService // Redirects from here work just fine. Livewire will respect it. $client_contact = ClientContact::find($this->decodePrimaryKey($data['contact_id'])); - if(is_string($data['client_id'])) + if(is_string($data['client_id'])) { $data['client_id'] = $this->decodePrimaryKey($data['client_id']); + } if (!$this->subscription->trial_enabled) { return new \Exception("Trials are disabled for this product"); @@ -788,8 +789,8 @@ class SubscriptionService //do nothing } elseif ($last_invoice->balance > 0) { $last_invoice = null; - // $pro_rata_charge_amount = $this->calculateProRataCharge($last_invoice, $old_subscription); - // nlog("pro rata charge = {$pro_rata_charge_amount}"); + // $pro_rata_charge_amount = $this->calculateProRataCharge($last_invoice, $old_subscription); + // nlog("pro rata charge = {$pro_rata_charge_amount}"); } else { $pro_rata_refund_amount = $this->calculateProRataRefund($last_invoice, $old_subscription) * -1; nlog("pro rata refund = {$pro_rata_refund_amount}"); @@ -888,6 +889,7 @@ class SubscriptionService $credit_repo = new CreditRepository(); $credit = CreditFactory::create($this->subscription->company_id, $this->subscription->user_id); + $credit->status_id = Credit::STATUS_SENT; $credit->date = now()->format('Y-m-d'); $credit->subscription_id = $this->subscription->id; $credit->discount = $last_invoice->discount; @@ -968,11 +970,11 @@ class SubscriptionService ->fillDefaults() ->save(); - if($invoice->fresh()->balance == 0){ + if($invoice->fresh()->balance == 0) { $invoice->service()->markPaid()->save(); } - return $invoice; + return $invoice->fresh(); } diff --git a/app/Services/Subscription/ZeroCostProduct.php b/app/Services/Subscription/ZeroCostProduct.php index b73cc146cf..245419edfd 100644 --- a/app/Services/Subscription/ZeroCostProduct.php +++ b/app/Services/Subscription/ZeroCostProduct.php @@ -79,8 +79,9 @@ class ZeroCostProduct extends AbstractService 'redirect_url' => "/client/recurring_invoices/{$recurring_invoice->hashed_id}", ]; - if(isset($this->data['campaign'])) + if(isset($this->data['campaign'])) { $context['campaign'] = $this->data['campaign']; + } return $context; } diff --git a/app/Services/Tax/Providers/TaxProvider.php b/app/Services/Tax/Providers/TaxProvider.php index 00b540bbc0..1136b226e0 100644 --- a/app/Services/Tax/Providers/TaxProvider.php +++ b/app/Services/Tax/Providers/TaxProvider.php @@ -13,7 +13,6 @@ namespace App\Services\Tax\Providers; use App\Models\Client; use App\Models\Company; -use App\Services\Tax\Providers\EuTax; class TaxProvider { @@ -78,7 +77,7 @@ class TaxProvider try { - $this->configureProvider($this->provider, $this->company->country()->iso_3166_2); //hard coded for now to one provider, but we'll be able to swap these out later + $this->configureProvider($this->provider, $this->company->country()->iso_3166_2); //hard coded for now to one provider, but we'll be able to swap these out later $company_details = [ 'address2' => $this->company->settings->address2, @@ -101,8 +100,7 @@ class TaxProvider $this->updated_client = true; } - } - catch(\Exception $e){ + } catch(\Exception $e) { nlog("Could not updated company tax data: " . $e->getMessage()); } @@ -165,8 +163,9 @@ class TaxProvider private function taxShippingAddress(): bool { - if($this->client->shipping_country_id == "840" && strlen($this->client->shipping_postal_code) > 3) + if($this->client->shipping_country_id == "840" && strlen($this->client->shipping_postal_code) > 3) { return true; + } return false; @@ -182,7 +181,7 @@ class TaxProvider private function configureProvider(?string $provider, string $country_code): self { - match($country_code){ + match($country_code) { 'US' => $this->configureZipTax(), "AT" => $this->configureEuTax(), "BE" => $this->configureEuTax(), @@ -251,8 +250,9 @@ class TaxProvider */ private function configureZipTax(): self { - if(!config('services.tax.zip_tax.key')) + if(!config('services.tax.zip_tax.key')) { throw new \Exception("ZipTax API key not set in .env file"); + } $this->api_credentials = config('services.tax.zip_tax.key'); @@ -262,4 +262,4 @@ class TaxProvider } -} \ No newline at end of file +} diff --git a/app/Services/Tax/Providers/ZipTax.php b/app/Services/Tax/Providers/ZipTax.php index dad7ef0768..fa8e1bb31f 100644 --- a/app/Services/Tax/Providers/ZipTax.php +++ b/app/Services/Tax/Providers/ZipTax.php @@ -31,15 +31,16 @@ class ZipTax implements TaxProviderInterface $response = $this->callApi(['key' => $this->api_key, 'address' => $string_address]); - if($response->successful()){ + if($response->successful()) { return $this->parseResponse($response->json()); } if(isset($this->address['postal_code'])) { - $response = $this->callApi(['key' => $this->api_key, 'address' => $this->address['postal_code']]); + $response = $this->callApi(['key' => $this->api_key, 'address' => $this->address['postal_code']]); - if($response->successful()) + if($response->successful()) { return $this->parseResponse($response->json()); + } } @@ -69,11 +70,13 @@ class ZipTax implements TaxProviderInterface private function parseResponse($response) { - if(isset($response['rCode']) && $response['rCode'] == 100 && isset($response['results']['0'])) + if(isset($response['rCode']) && $response['rCode'] == 100 && isset($response['results']['0'])) { return $response['results']['0']; + } - if(isset($response['rCode']) && class_exists(\Modules\Admin\Events\TaxProviderException::class)) + if(isset($response['rCode']) && class_exists(\Modules\Admin\Events\TaxProviderException::class)) { event(new \Modules\Admin\Events\TaxProviderException($response['rCode'])); + } return null; diff --git a/app/Services/Tax/TaxService.php b/app/Services/Tax/TaxService.php index d724e175ac..b81bdaa065 100644 --- a/app/Services/Tax/TaxService.php +++ b/app/Services/Tax/TaxService.php @@ -37,13 +37,13 @@ class TaxService $this->client->has_valid_vat_number = true; - if(!$this->client->name && strlen($vat_check->getName()) > 2) { - $this->client->name = $vat_check->getName(); - } + if(!$this->client->name && strlen($vat_check->getName()) > 2) { + $this->client->name = $vat_check->getName(); + } - if(empty($this->client->private_notes) && strlen($vat_check->getAddress()) > 2) { - $this->client->private_notes = $vat_check->getAddress(); - } + if(empty($this->client->private_notes) && strlen($vat_check->getAddress()) > 2) { + $this->client->private_notes = $vat_check->getAddress(); + } $this->client->saveQuietly(); } @@ -56,4 +56,4 @@ class TaxService { } -} \ No newline at end of file +} diff --git a/app/Services/Tax/VatNumberCheck.php b/app/Services/Tax/VatNumberCheck.php index 61c5f2bb50..e7f7cf5873 100644 --- a/app/Services/Tax/VatNumberCheck.php +++ b/app/Services/Tax/VatNumberCheck.php @@ -48,7 +48,7 @@ class VatNumberCheck } } catch (\SoapFault $e) { - $this->response = ['valid' => false, 'error' => $e->getMessage()]; + $this->response = ['valid' => false, 'error' => $e->getMessage()]; } return $this; diff --git a/app/Services/Template/TemplateAction.php b/app/Services/Template/TemplateAction.php new file mode 100644 index 0000000000..2c753a754d --- /dev/null +++ b/app/Services/Template/TemplateAction.php @@ -0,0 +1,179 @@ +db); + + $key = $this->resolveEntityString(); + + $resource = $this->entity::query(); + + $template = Design::withTrashed()->find($this->decodePrimaryKey($this->template)); + + $template_service = new TemplateService($template); + + match($this->entity) { + Invoice::class => $resource->with('payments', 'client'), + Quote::class => $resource->with('client'), + Task::class => $resource->with('client'), + Credit::class => $resource->with('client'), + RecurringInvoice::class => $resource->with('client'), + Project::class => $resource->with('client'), + Expense::class => $resource->with('client'), + Payment::class => $resource->with('invoices', 'client'), + }; + + $result = $resource->withTrashed() + ->whereIn('id', $this->transformKeys($this->ids)) + ->where('company_id', $this->company->id) + ->get(); + + if($result->count() <= 1) { + $data[$key] = collect($result); + } else { + $data[$key] = $result; + } + + $ts = $template_service->build($data); + + // nlog($ts->getHtml()); + + if($this->send_email) { + $pdf = $ts->getPdf(); + $this->sendEmail($pdf, $template); + } else { + $pdf = $ts->getPdf(); + $filename = "templates/{$this->hash}.pdf"; + Storage::disk(config('filesystems.default'))->put($filename, $pdf); + return $pdf; + } + } + + private function sendEmail(mixed $pdf, Design $template) + { + $user = $this->user_id ? User::find($this->user_id) : $this->company->owner(); + + $template_name = " [{$template->name}]"; + $email_object = new EmailObject; + $email_object->to = [new Address($user->email, $user->present()->name())]; + $email_object->attachments = [['file' => base64_encode($pdf), 'name' => ctrans('texts.template') . ".pdf"]]; + $email_object->company_key = $this->company->company_key; + $email_object->company = $this->company; + $email_object->settings = $this->company->settings; + $email_object->logo = $this->company->present()->logo(); + $email_object->whitelabel = $this->company->account->isPaid() ? true : false; + $email_object->user_id = $user->id; + $email_object->text_body = ctrans('texts.download_report_description') . $template_name; + $email_object->body = ctrans('texts.download_report_description') . $template_name; + $email_object->subject = ctrans('texts.download_report_description') . $template_name; + + (new AdminEmail($email_object, $this->company))->handle(); + } + + /** + * Context + * + * If I have an array of invoices, what could I possib + * + * + */ + private function resolveEntityString() + { + return match ($this->entity) { + Invoice::class => 'invoices', + Quote::class => 'quotes', + Task::class => 'tasks', + Credit::class => 'credits', + RecurringInvoice::class => 'recurring_invoices', + Project::class => 'projects', + Expense::class => 'expenses', + Payment::class => 'payments', + Product::class => 'products', + PurchaseOrder::class => 'purchase_orders', + Project::class => 'projects', + Client::class => 'clients', + Vendor::class => 'vendors', + }; + } + + public function middleware() + { + return [new WithoutOverlapping("template-{$this->company->company_key}")]; + } + +} diff --git a/app/Services/Template/TemplateMock.php b/app/Services/Template/TemplateMock.php new file mode 100644 index 0000000000..400276a217 --- /dev/null +++ b/app/Services/Template/TemplateMock.php @@ -0,0 +1,88 @@ +Voluptatem et aliquid enim dolorem rerum voluptatem. Doloremque magni nesciunt unde nobis omnis quas. Aut totam adipisci voluptatum explicabo qui fugit assumenda. Soluta architecto sit exercitationem amet doloribus ad vel qui. Eos porro qui voluptatum sint. Consequatur voluptatem eveniet vitae illo ea. Rem enim sit impedit odit.<\/p>","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"GST","tax_rate1":10,"tax_name2":"VAT","tax_rate2":17.5,"tax_name3":"","tax_rate3":0,"total_taxes":5.5,"is_amount_discount":true,"footer":"","partial":0,"partial_due_date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","has_tasks":false,"has_expenses":false,"custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"quantity":1,"cost":22,"product_key":"Sed.","notes":"Est sequi.","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":22,"gross_line_total":22,"custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"56","custom_value3":"Dolores porro quia.","custom_value4":"Perferendis itaque.","type_id":"1","product_cost":0,"tax_amount":0,"date":"","tax_id":"1","task_id":"","expense_id":"","_id":"ab67ad52-725e-4106-b80b-1eabe84ca90a"}],"entity_type":"credit","exchange_rate":1,"paid_to_date":0,"subscription_id":"","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}}}]'; + + public string $payment_data = '[{"status":"Refunded","badge":"
Refunded<\/span><\/h6>","amount":"$6,077.51","applied":"$6,077.51","balance":"-$6,077.51","refunded":"$6,077.51","amount_raw":"6077.510000","applied_raw":"6077.510000","refunded_raw":"6077.510000","balance_raw":-6077.51,"date":"30\/Sep\/2023","method":"EuroCard","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0001","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"cypress","balance":"0.000000","payment_balance":"0.000000","credit_balance":"11661.820000"},"paymentables":[{"invoice":"0019","amount_raw":"6077.5100","refunded_raw":"6077.5100","net_raw":0,"amount":"$6,077.51","refunded":"$6,077.51","net":"$0.00","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696150843}]},{"status":"Refunded","badge":"
Refunded<\/span><\/h6>","amount":"$4,090.64","applied":"$4,090.64","balance":"-$4,090.64","refunded":"$4,090.64","amount_raw":"4090.640000","applied_raw":"4090.640000","refunded_raw":"4090.640000","balance_raw":-4090.64,"date":"30\/Sep\/2023","method":"Discover Card","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0002","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"cypress","balance":"0.000000","payment_balance":"0.000000","credit_balance":"11661.820000"},"paymentables":[{"invoice":"0020","amount_raw":"4090.6400","refunded_raw":"4090.6400","net_raw":0,"amount":"$4,090.64","refunded":"$4,090.64","net":"$0.00","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696150843}]},{"status":"Refunded","badge":"
Refunded<\/span><\/h6>","amount":"$2,197.26","applied":"$2,197.26","balance":"-$2,197.26","refunded":"$2,197.26","amount_raw":"2197.260000","applied_raw":"2197.260000","refunded_raw":"2197.260000","balance_raw":-2197.26,"date":"30\/Sep\/2023","method":"Diners Card","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0003","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"Jakubowski Group","balance":"28296.170000","payment_balance":"0.000000","credit_balance":"1084.840000"},"paymentables":[{"invoice":"0021","amount_raw":"2197.2600","refunded_raw":"2197.2600","net_raw":0,"amount":"$2,197.26","refunded":"$2,197.26","net":"$0.00","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696150843}]},{"status":"Partially Refunded","badge":"
Partially Refunded<\/span><\/h6>","amount":"$4,955.50","applied":"$4,955.50","balance":"-$66.00","refunded":"$66.00","amount_raw":"4955.500000","applied_raw":"4955.500000","refunded_raw":"66.000000","balance_raw":-66,"date":"30\/Sep\/2023","method":"Maestro","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0004","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"Jakubowski Group","balance":"28296.170000","payment_balance":"0.000000","credit_balance":"1084.840000"},"paymentables":[{"invoice":"0022","amount_raw":"4955.5000","refunded_raw":"66.0000","net_raw":4889.5,"amount":"$4,955.50","refunded":"$66.00","net":"$4,889.50","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696150843}]},{"status":"Partially Refunded","badge":"
Partially Refunded<\/span><\/h6>","amount":"$2,290.75","applied":"$2,290.75","balance":"-$34.00","refunded":"$34.00","amount_raw":"2290.750000","applied_raw":"2290.750000","refunded_raw":"34.000000","balance_raw":-34,"date":"30\/Sep\/2023","method":"Diners Card","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0005","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"Jakubowski Group","balance":"28296.170000","payment_balance":"0.000000","credit_balance":"1084.840000"},"paymentables":[{"invoice":"0023","amount_raw":"2290.7500","refunded_raw":"34.0000","net_raw":2256.75,"amount":"$2,290.75","refunded":"$34.00","net":"$2,256.75","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696150843}]},{"status":"Partially Refunded","badge":"
Partially Refunded<\/span><\/h6>","amount":"$6,802.13","applied":"$6,802.13","balance":"-$444.00","refunded":"$444.00","amount_raw":"6802.130000","applied_raw":"6802.130000","refunded_raw":"444.000000","balance_raw":-444,"date":"30\/Sep\/2023","method":"Maestro","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0006","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"Jakubowski Group","balance":"28296.170000","payment_balance":"0.000000","credit_balance":"1084.840000"},"paymentables":[{"invoice":"0024","amount_raw":"6802.1300","refunded_raw":"444.0000","net_raw":6358.13,"amount":"$6,802.13","refunded":"$444.00","net":"$6,358.13","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696150843}]},{"status":"Partially Refunded","badge":"
Partially Refunded<\/span><\/h6>","amount":"$10,986.26","applied":"$10,986.26","balance":"-$146.26","refunded":"$146.26","amount_raw":"10986.260000","applied_raw":"10986.260000","refunded_raw":"146.260000","balance_raw":-146.26000000000022,"date":"30\/Sep\/2023","method":"UnionPay","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0007","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"Jakubowski Group","balance":"28296.170000","payment_balance":"0.000000","credit_balance":"1084.840000"},"paymentables":[{"invoice":"0025","amount_raw":"10986.2600","refunded_raw":"146.2600","net_raw":10840,"amount":"$10,986.26","refunded":"$146.26","net":"$10,840.00","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696150843}]},{"status":"Completed","badge":"
Completed<\/span><\/h6>","amount":"$6,054.13","applied":"$6,054.13","balance":"$0.00","refunded":"$0.00","amount_raw":"6054.130000","applied_raw":"6054.130000","refunded_raw":"0.000000","balance_raw":0,"date":"30\/Sep\/2023","method":"","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0008","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"Lowe-Paucek","balance":"38124.670000","payment_balance":"0.000000","credit_balance":"2270.590000"},"paymentables":[{"invoice":"0015","amount_raw":"6054.1300","refunded_raw":"0.0000","net_raw":6054.13,"amount":"$6,054.13","refunded":"$0.00","net":"$6,054.13","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696151008}]},{"status":"Completed","badge":"
Completed<\/span><\/h6>","amount":"$3,132.25","applied":"$3,132.25","balance":"$0.00","refunded":"$0.00","amount_raw":"3132.250000","applied_raw":"3132.250000","refunded_raw":"0.000000","balance_raw":0,"date":"30\/Sep\/2023","method":"","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0009","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"cypress","balance":"0.000000","payment_balance":"0.000000","credit_balance":"11661.820000"},"paymentables":[{"invoice":"0016","amount_raw":"3132.2500","refunded_raw":"0.0000","net_raw":3132.25,"amount":"$3,132.25","refunded":"$0.00","net":"$3,132.25","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696151008}]},{"status":"Completed","badge":"
Completed<\/span><\/h6>","amount":"$9,396.77","applied":"$9,396.77","balance":"$0.00","refunded":"$0.00","amount_raw":"9396.770000","applied_raw":"9396.770000","refunded_raw":"0.000000","balance_raw":0,"date":"30\/Sep\/2023","method":"","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0010","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"cypress","balance":"0.000000","payment_balance":"0.000000","credit_balance":"11661.820000"},"paymentables":[{"invoice":"0017","amount_raw":"9396.7700","refunded_raw":"0.0000","net_raw":9396.77,"amount":"$9,396.77","refunded":"$0.00","net":"$9,396.77","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696151008}]}]'; + + + public string $purchase_order_data = '[{"id":"l4zbq2dprO","user_id":"wMvbmOeYAl","project_id":"","assigned_user_id":"","vendor_id":"xYRdG7dDzO","amount":14,"balance":0,"client_id":"","status_id":"1","design_id":"Wpmbk5ezJn","created_at":1695799265,"updated_at":1695799268,"archived_at":0,"is_deleted":false,"number":"0001","discount":0,"po_number":"","date":"2023-09-27","last_sent_date":"","next_send_date":"","reminder1_sent":"","reminder2_sent":"","reminder3_sent":"","reminder_last_sent":"","due_date":"2023-09-28","terms":"","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"total_taxes":0,"is_amount_discount":true,"footer":"","partial":0,"partial_due_date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","has_tasks":false,"has_expenses":false,"custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"_id":"16ae59d4-60a8-4edf-b3a3-58882df1c5bf","quantity":1,"cost":14,"product_key":"enterprise_plan","product_cost":0,"notes":"The Enterprise Plan","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":14,"gross_line_total":14,"date":"","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"73","custom_value3":"Nesciunt sequi.","custom_value4":"Ratione inventore.","type_id":"1","tax_id":"1","tax_amount":0,"task_id":"","expense_id":""},{"_id":"c7ef7af8-2713-4376-89c5-6f2c9c148ea7","quantity":0,"cost":0,"product_key":"","product_cost":0,"notes":"","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":0,"gross_line_total":0,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"1","tax_id":"1","tax_amount":0,"task_id":"","expense_id":""}],"entity_type":"purchaseOrder","exchange_rate":1,"paid_to_date":0,"subscription_id":"","expense_id":"","currency_id":"","vendor":{"id":"xYRdG7dDzO","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Dr. Ottilie Gorczany PhD","website":"http:\/\/www.kihn.biz\/","private_notes":"Ullam ullam aut sed accusantium. Et amet ut et cumque. Dolorem rem doloremque eius aut laudantium soluta nihil.","public_notes":"","last_login":0,"address1":"93105","address2":"797 Medhurst Radial Suite 030","phone":"","city":"Lamberttown","state":"Kentucky","postal_code":"86694-1500","country_id":"4","currency_id":"1","custom_value1":"Et similique.","custom_value2":"Ducimus est.","custom_value3":"Ut unde aut quia.","custom_value4":"Rerum odio maxime.","is_deleted":false,"vat_number":"Hic sed vel sint eos et.","id_number":"Quod aut autem.","updated_at":1695796653,"archived_at":0,"created_at":1695796652,"number":"0004","language_id":"","contacts":{"App\\Models\\VendorContact":[{"id":"VolejRRejN","first_name":"Aisha","last_name":"Jerde","send_email":true,"email":"mariam40@example.com","created_at":1695796652,"updated_at":1695796652,"archived_at":0,"is_primary":true,"phone":"+1-828-839-4171","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","link":"http:\/\/ninja.test:8000\/vendor\/key_login\/vqQKXcwO2WsvkN4MPOAS6fpirMZP7gfc","last_login":0},{"id":"WpmbkR5azJ","first_name":"Willy","last_name":"Jacobs","send_email":true,"email":"qwolff@example.org","created_at":1695796652,"updated_at":1695796652,"archived_at":0,"is_primary":false,"phone":"979.822.6527","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","link":"http:\/\/ninja.test:8000\/vendor\/key_login\/A8g2xp3OLbNzpWlfPGIML2lk1LYNFJpx","last_login":0}]},"documents":{"App\\Models\\Document":[]}}},{"id":"kzPdy7aQro","user_id":"wMvbmOeYAl","project_id":"","assigned_user_id":"","vendor_id":"xYRdG7dDzO","amount":24,"balance":24,"client_id":"","status_id":"2","design_id":"Wpmbk5ezJn","created_at":1695799278,"updated_at":1695799290,"archived_at":0,"is_deleted":false,"number":"0002","discount":0,"po_number":"","date":"2023-09-27","last_sent_date":"","next_send_date":"","reminder1_sent":"","reminder2_sent":"","reminder3_sent":"","reminder_last_sent":"","due_date":"2023-09-28","terms":"","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"total_taxes":0,"is_amount_discount":true,"footer":"","partial":0,"partial_due_date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","has_tasks":false,"has_expenses":false,"custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"_id":"a9fa64d0-e11b-47ce-8d75-d126769cd17d","quantity":1,"cost":14,"product_key":"enterprise_plan","product_cost":0,"notes":"The Enterprise Plan","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":14,"gross_line_total":14,"date":"","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"73","custom_value3":"Nesciunt sequi.","custom_value4":"Ratione inventore.","type_id":"1","tax_id":"1","tax_amount":0,"task_id":"","expense_id":""},{"_id":"6b8a7e41-f140-499a-ac3d-7f1916aa7e00","quantity":1,"cost":10,"product_key":"pro_plan","product_cost":0,"notes":"The Pro Plan","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":10,"gross_line_total":10,"date":"","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"81","custom_value3":"Enim quis deleniti.","custom_value4":"Numquam quia quas.","type_id":"1","tax_id":"1","tax_amount":0,"task_id":"","expense_id":""}],"entity_type":"purchaseOrder","exchange_rate":1,"paid_to_date":0,"subscription_id":"","expense_id":"","currency_id":"","vendor":{"id":"xYRdG7dDzO","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Dr. Ottilie Gorczany PhD","website":"http:\/\/www.kihn.biz\/","private_notes":"Ullam ullam aut sed accusantium. Et amet ut et cumque. Dolorem rem doloremque eius aut laudantium soluta nihil.","public_notes":"","last_login":0,"address1":"93105","address2":"797 Medhurst Radial Suite 030","phone":"","city":"Lamberttown","state":"Kentucky","postal_code":"86694-1500","country_id":"4","currency_id":"1","custom_value1":"Et similique.","custom_value2":"Ducimus est.","custom_value3":"Ut unde aut quia.","custom_value4":"Rerum odio maxime.","is_deleted":false,"vat_number":"Hic sed vel sint eos et.","id_number":"Quod aut autem.","updated_at":1695796653,"archived_at":0,"created_at":1695796652,"number":"0004","language_id":"","contacts":{"App\\Models\\VendorContact":[{"id":"VolejRRejN","first_name":"Aisha","last_name":"Jerde","send_email":true,"email":"mariam40@example.com","created_at":1695796652,"updated_at":1695796652,"archived_at":0,"is_primary":true,"phone":"+1-828-839-4171","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","link":"http:\/\/ninja.test:8000\/vendor\/key_login\/vqQKXcwO2WsvkN4MPOAS6fpirMZP7gfc","last_login":0},{"id":"WpmbkR5azJ","first_name":"Willy","last_name":"Jacobs","send_email":true,"email":"qwolff@example.org","created_at":1695796652,"updated_at":1695796652,"archived_at":0,"is_primary":false,"phone":"979.822.6527","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","link":"http:\/\/ninja.test:8000\/vendor\/key_login\/A8g2xp3OLbNzpWlfPGIML2lk1LYNFJpx","last_login":0}]},"documents":{"App\\Models\\Document":[]}}}]'; + + public string $project_data = '[{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":{"App\\Models\\ClientContact":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}]},"documents":{"App\\Models\\Document":[]},"gateway_tokens":{"App\\Models\\ClientGatewayToken":[]}},"tasks":{"App\\Models\\Task":[{"id":"k8mepZ1bMy","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0009","description":"Tenetur molestiae voluptatem voluptate nostrum.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"l4zbqQ3bpr","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0010","description":"Vitae eum amet magni.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"yMYer2weOB","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0011","description":"Eos facilis maiores enim harum.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"gl9avQVeG1","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0012","description":"Vero iure veritatis odio voluptate dolorem.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"7LDdw0Je1Y","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0013","description":"Nulla quisquam autem blanditiis sed tempore.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"JX7ax6Jdyv","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0014","description":"Eveniet quos est odit quam repellat.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"kzPdywVaQr","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0015","description":"Molestiae facilis magni vitae ducimus eius et.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"mxkaz6raJ0","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0016","description":"Quia nam et et excepturi explicabo.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"4w9aA6BbvM","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0017","description":"Velit et reiciendis accusamus qui quo.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"X46dBrxd79","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0018","description":"Possimus ipsum dolorum accusamus.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"oBDbDqBdl2","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0019","description":"Ipsam ex aut rerum eius id.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"K4oeEqNe0B","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0020","description":"Et et et occaecati non quis ut ea enim.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"xYRdGRybDz","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0021","description":"Rerum voluptatum nihil impedit doloribus enim.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"1YQdJNDdOG","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0022","description":"Corrupti quos voluptas ut veniam perferendis.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"4y1aKLrbQG","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0023","description":"Est nobis aut a corporis ullam consequatur illum.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"q9wdLvgajP","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0024","description":"Sed sunt labore ea dolorum eligendi et.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"7N1aMJQbWm","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0025","description":"Fugit tempora iure aut aut exercitationem a.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"QJ0dNYLbLO","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0026","description":"Sed enim et rem maxime ipsam vitae labore.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"z3YaOPgbxq","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0027","description":"Animi ex dolores illum accusantium eos.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"mWZdPzzaKg","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0028","description":"Amet quam eligendi assumenda sapiente fugiat.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","documents":{"App\\Models\\Document":[]},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}}]}},{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":{"App\\Models\\ClientContact":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}]},"documents":{"App\\Models\\Document":[]},"gateway_tokens":{"App\\Models\\ClientGatewayToken":[]}},"tasks":{"App\\Models\\Task":[{"id":"LYqaQ67bnj","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0029","description":"Optio illum sapiente explicabo qui molestias.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"JAPdRPLaGy","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0030","description":"Ratione culpa tenetur molestiae beatae.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"Oy5eV75aEP","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0031","description":"Deleniti mollitia qui sint dicta quod.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"rlNbWQWeyg","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0032","description":"In illo omnis dolores.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"kQBeXrkbyK","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0033","description":"Ut quo impedit et et architecto.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"W4QbYy0bzq","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0034","description":"Quo recusandae quam debitis dolorem facere aut.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"joQeZzvepZ","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0035","description":"At magni sed inventore molestiae.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"EKQe1G3bJY","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0036","description":"Placeat delectus beatae qui enim consequuntur ea.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"yJrb27JdWL","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0037","description":"Iste nulla id quisquam.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"GELe3yrb69","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0038","description":"Quia laborum quis sed qui sunt.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695799099,"archived_at":0,"invoice_id":"mxkazm8eJ0","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"KGRb4z2eBL","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0039","description":"Omnis amet illum inventore.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695799099,"archived_at":0,"invoice_id":"mxkazm8eJ0","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"qM7e5Rxa2v","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0040","description":"Tempore dolore aut necessitatibus ex illo.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695799099,"archived_at":0,"invoice_id":"mxkazm8eJ0","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"QK9b6B7bEv","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0041","description":"Ipsam eos deleniti delectus.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695799099,"archived_at":0,"invoice_id":"mxkazm8eJ0","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"O5xe7pwa7r","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0042","description":"Aperiam quae voluptas voluptatem vero.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695799099,"archived_at":0,"invoice_id":"mxkazm8eJ0","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"MVyb8MldvA","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0043","description":"Voluptatem iusto quod cupiditate.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"VWPe9DPdLy","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0044","description":"Ab occaecati ut in qui.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798950,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"X46dBXa79j","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"QnXe007exr","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0045","description":"At ratione dicta dicta ab earum.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"Wjnegn9dwZ","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0046","description":"Omnis dolores ducimus iusto maxime aut iure.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798928,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"4w9aAOdvMR","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"Volej0WbjN","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0047","description":"Possimus ut quia commodi voluptatem.","duration":0,"rate":33,"created_at":1695797987,"updated_at":1695799099,"archived_at":0,"invoice_id":"mxkazm8eJ0","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"mxkazYeJ0P","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}},{"id":"WpmbkrxazJ","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0048","description":"Blanditiis ad mollitia ratione veritatis.","duration":0,"rate":55,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"kzPdy7aQro","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","documents":{"App\\Models\\Document":[]},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"App\\Models\\Document":[]}}}]}}]'; + + public string $task_data = '[{"id":"k8mepZ1bMy","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0009","description":"Tenetur molestiae voluptatem voluptate nostrum.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"l4zbqQ3bpr","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0010","description":"Vitae eum amet magni.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"yMYer2weOB","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0011","description":"Eos facilis maiores enim harum.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"gl9avQVeG1","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0012","description":"Vero iure veritatis odio voluptate dolorem.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"7LDdw0Je1Y","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0013","description":"Nulla quisquam autem blanditiis sed tempore.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"JX7ax6Jdyv","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0014","description":"Eveniet quos est odit quam repellat.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"kzPdywVaQr","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0015","description":"Molestiae facilis magni vitae ducimus eius et.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"mxkaz6raJ0","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0016","description":"Quia nam et et excepturi explicabo.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"4w9aA6BbvM","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0017","description":"Velit et reiciendis accusamus qui quo.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"X46dBrxd79","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0018","description":"Possimus ipsum dolorum accusamus.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"oBDbDqBdl2","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0019","description":"Ipsam ex aut rerum eius id.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"K4oeEqNe0B","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0020","description":"Et et et occaecati non quis ut ea enim.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"xYRdGRybDz","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0021","description":"Rerum voluptatum nihil impedit doloribus enim.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"1YQdJNDdOG","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0022","description":"Corrupti quos voluptas ut veniam perferendis.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"4y1aKLrbQG","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0023","description":"Est nobis aut a corporis ullam consequatur illum.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"q9wdLvgajP","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0024","description":"Sed sunt labore ea dolorum eligendi et.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"7N1aMJQbWm","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0025","description":"Fugit tempora iure aut aut exercitationem a.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"QJ0dNYLbLO","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0026","description":"Sed enim et rem maxime ipsam vitae labore.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"z3YaOPgbxq","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0027","description":"Animi ex dolores illum accusantium eos.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"mWZdPzzaKg","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0028","description":"Amet quam eligendi assumenda sapiente fugiat.","duration":0,"rate":0,"created_at":1695797958,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"qM7e5Ba2vp","is_deleted":false,"time_log":"[[1694933958,1695365958,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-16","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"qM7e5Ba2vp","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Jace Windler DVM","number":"0002","created_at":1695797958,"updated_at":1695798905,"archived_at":0,"is_deleted":false,"task_rate":38,"due_date":"","private_notes":"Private Project Notes","public_notes":"Rerum voluptas incidunt velit.","budgeted_hours":826,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"LYqaQ67bnj","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0029","description":"Optio illum sapiente explicabo qui molestias.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"JAPdRPLaGy","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0030","description":"Ratione culpa tenetur molestiae beatae.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"Oy5eV75aEP","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0031","description":"Deleniti mollitia qui sint dicta quod.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"rlNbWQWeyg","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0032","description":"In illo omnis dolores.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"kQBeXrkbyK","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0033","description":"Ut quo impedit et et architecto.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"W4QbYy0bzq","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0034","description":"Quo recusandae quam debitis dolorem facere aut.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"joQeZzvepZ","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0035","description":"At magni sed inventore molestiae.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"EKQe1G3bJY","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0036","description":"Placeat delectus beatae qui enim consequuntur ea.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"yJrb27JdWL","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0037","description":"Iste nulla id quisquam.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"GELe3yrb69","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0038","description":"Quia laborum quis sed qui sunt.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695799099,"archived_at":0,"invoice_id":"mxkazm8eJ0","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":{"data":{"id":"mxkazm8eJ0","user_id":"wMvbmOeYAl","project_id":"","assigned_user_id":"","amount":1190,"balance":746,"client_id":"QJ0dN6dLOv","vendor_id":"","status_id":"3","design_id":"Wpmbk5ezJn","recurring_id":"","created_at":1695798363,"updated_at":1695799116,"archived_at":0,"is_deleted":false,"number":"0026","discount":0,"po_number":"","date":"2023-09-27","last_sent_date":"","next_send_date":"","due_date":"","terms":"

Default company invoice terms<\/p>","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"total_taxes":0,"is_amount_discount":true,"footer":"

Default invoice footer<\/p>","partial":0,"partial_due_date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","has_tasks":false,"has_expenses":false,"custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"exchange_rate":1,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"_id":"1d9727b4-bc32-4c62-a291-3b595e638a3b","quantity":0,"cost":33,"product_key":"t1","product_cost":0,"notes":"task 1 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":0,"gross_line_total":0,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"Volej0WbjN","tax_amount":0,"expense_id":""},{"_id":"37e6f099-d7c9-47ad-aaf3-2de38942959c","quantity":5,"cost":123,"product_key":"t2","product_cost":0,"notes":"task 2 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":615,"gross_line_total":615,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"O5xe7pwa7r","tax_amount":0,"expense_id":""},{"_id":"d109e819-e839-4eeb-9c24-cd91cbfed7b5","quantity":5,"cost":33,"product_key":"t3","product_cost":0,"notes":"task 3 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":165,"gross_line_total":165,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"QK9b6B7bEv","tax_amount":0,"expense_id":""},{"_id":"ac64e02b-219c-4a18-9a7c-191dd5b8f21f","quantity":5,"cost":67,"product_key":"t4","product_cost":0,"notes":"task 4 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":335,"gross_line_total":335,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"qM7e5Rxa2v","tax_amount":0,"expense_id":""},{"_id":"7b6dcee1-7b07-49b7-bcfa-2129ef844f07","quantity":5,"cost":4,"product_key":"t5","product_cost":0,"notes":"task 5 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":20,"gross_line_total":20,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"KGRb4z2eBL","tax_amount":0,"expense_id":""},{"_id":"a8ef31c5-49ac-43f7-a40e-0e311b940589","quantity":5,"cost":11,"product_key":"t6","product_cost":0,"notes":"task 6 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":55,"gross_line_total":55,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"GELe3yrb69","tax_amount":0,"expense_id":""}],"entity_type":"invoice","reminder1_sent":"","reminder2_sent":"","reminder3_sent":"","reminder_last_sent":"","paid_to_date":444,"subscription_id":"","auto_bill_enabled":false,"tax_info":{},"invitations":{"data":[{"id":"WJxboZzagw","client_contact_id":"yMYerkEaOB","key":"i6oaEtKOSJGW8ArM7tzz0zWVASYmSebr","link":"http:\/\/ninja.test:8000\/client\/invoice\/i6oaEtKOSJGW8ArM7tzz0zWVASYmSebr","sent_date":"2023-09-27 07:06:09","viewed_date":"","opened_date":"","updated_at":1695798369,"archived_at":0,"created_at":1695798363,"email_status":"","email_error":""},{"id":"k8mepZ1bMy","client_contact_id":"gl9av2maG1","key":"H6tz4cLXorbZPJHwF1b08QAZwF7zMQUZ","link":"http:\/\/ninja.test:8000\/client\/invoice\/H6tz4cLXorbZPJHwF1b08QAZwF7zMQUZ","sent_date":"2023-09-27 07:06:09","viewed_date":"","opened_date":"","updated_at":1695798369,"archived_at":0,"created_at":1695798363,"email_status":"","email_error":""},{"id":"l4zbqQ3bpr","client_contact_id":"7LDdwpRe1Y","key":"viWjxxto5SAr8pUqqgSIdrJbLuAHl3sL","link":"http:\/\/ninja.test:8000\/client\/invoice\/viWjxxto5SAr8pUqqgSIdrJbLuAHl3sL","sent_date":"2023-09-27 07:06:09","viewed_date":"","opened_date":"","updated_at":1695798369,"archived_at":0,"created_at":1695798363,"email_status":"","email_error":""}]},"documents":{"data":[]}}}},{"id":"KGRb4z2eBL","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0039","description":"Omnis amet illum inventore.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695799099,"archived_at":0,"invoice_id":"mxkazm8eJ0","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":{"data":{"id":"mxkazm8eJ0","user_id":"wMvbmOeYAl","project_id":"","assigned_user_id":"","amount":1190,"balance":746,"client_id":"QJ0dN6dLOv","vendor_id":"","status_id":"3","design_id":"Wpmbk5ezJn","recurring_id":"","created_at":1695798363,"updated_at":1695799116,"archived_at":0,"is_deleted":false,"number":"0026","discount":0,"po_number":"","date":"2023-09-27","last_sent_date":"","next_send_date":"","due_date":"","terms":"

Default company invoice terms<\/p>","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"total_taxes":0,"is_amount_discount":true,"footer":"

Default invoice footer<\/p>","partial":0,"partial_due_date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","has_tasks":false,"has_expenses":false,"custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"exchange_rate":1,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"_id":"1d9727b4-bc32-4c62-a291-3b595e638a3b","quantity":0,"cost":33,"product_key":"t1","product_cost":0,"notes":"task 1 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":0,"gross_line_total":0,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"Volej0WbjN","tax_amount":0,"expense_id":""},{"_id":"37e6f099-d7c9-47ad-aaf3-2de38942959c","quantity":5,"cost":123,"product_key":"t2","product_cost":0,"notes":"task 2 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":615,"gross_line_total":615,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"O5xe7pwa7r","tax_amount":0,"expense_id":""},{"_id":"d109e819-e839-4eeb-9c24-cd91cbfed7b5","quantity":5,"cost":33,"product_key":"t3","product_cost":0,"notes":"task 3 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":165,"gross_line_total":165,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"QK9b6B7bEv","tax_amount":0,"expense_id":""},{"_id":"ac64e02b-219c-4a18-9a7c-191dd5b8f21f","quantity":5,"cost":67,"product_key":"t4","product_cost":0,"notes":"task 4 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":335,"gross_line_total":335,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"qM7e5Rxa2v","tax_amount":0,"expense_id":""},{"_id":"7b6dcee1-7b07-49b7-bcfa-2129ef844f07","quantity":5,"cost":4,"product_key":"t5","product_cost":0,"notes":"task 5 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":20,"gross_line_total":20,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"KGRb4z2eBL","tax_amount":0,"expense_id":""},{"_id":"a8ef31c5-49ac-43f7-a40e-0e311b940589","quantity":5,"cost":11,"product_key":"t6","product_cost":0,"notes":"task 6 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":55,"gross_line_total":55,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"GELe3yrb69","tax_amount":0,"expense_id":""}],"entity_type":"invoice","reminder1_sent":"","reminder2_sent":"","reminder3_sent":"","reminder_last_sent":"","paid_to_date":444,"subscription_id":"","auto_bill_enabled":false,"tax_info":{},"invitations":{"data":[{"id":"WJxboZzagw","client_contact_id":"yMYerkEaOB","key":"i6oaEtKOSJGW8ArM7tzz0zWVASYmSebr","link":"http:\/\/ninja.test:8000\/client\/invoice\/i6oaEtKOSJGW8ArM7tzz0zWVASYmSebr","sent_date":"2023-09-27 07:06:09","viewed_date":"","opened_date":"","updated_at":1695798369,"archived_at":0,"created_at":1695798363,"email_status":"","email_error":""},{"id":"k8mepZ1bMy","client_contact_id":"gl9av2maG1","key":"H6tz4cLXorbZPJHwF1b08QAZwF7zMQUZ","link":"http:\/\/ninja.test:8000\/client\/invoice\/H6tz4cLXorbZPJHwF1b08QAZwF7zMQUZ","sent_date":"2023-09-27 07:06:09","viewed_date":"","opened_date":"","updated_at":1695798369,"archived_at":0,"created_at":1695798363,"email_status":"","email_error":""},{"id":"l4zbqQ3bpr","client_contact_id":"7LDdwpRe1Y","key":"viWjxxto5SAr8pUqqgSIdrJbLuAHl3sL","link":"http:\/\/ninja.test:8000\/client\/invoice\/viWjxxto5SAr8pUqqgSIdrJbLuAHl3sL","sent_date":"2023-09-27 07:06:09","viewed_date":"","opened_date":"","updated_at":1695798369,"archived_at":0,"created_at":1695798363,"email_status":"","email_error":""}]},"documents":{"data":[]}}}},{"id":"qM7e5Rxa2v","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0040","description":"Tempore dolore aut necessitatibus ex illo.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695799099,"archived_at":0,"invoice_id":"mxkazm8eJ0","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":{"data":{"id":"mxkazm8eJ0","user_id":"wMvbmOeYAl","project_id":"","assigned_user_id":"","amount":1190,"balance":746,"client_id":"QJ0dN6dLOv","vendor_id":"","status_id":"3","design_id":"Wpmbk5ezJn","recurring_id":"","created_at":1695798363,"updated_at":1695799116,"archived_at":0,"is_deleted":false,"number":"0026","discount":0,"po_number":"","date":"2023-09-27","last_sent_date":"","next_send_date":"","due_date":"","terms":"

Default company invoice terms<\/p>","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"total_taxes":0,"is_amount_discount":true,"footer":"

Default invoice footer<\/p>","partial":0,"partial_due_date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","has_tasks":false,"has_expenses":false,"custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"exchange_rate":1,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"_id":"1d9727b4-bc32-4c62-a291-3b595e638a3b","quantity":0,"cost":33,"product_key":"t1","product_cost":0,"notes":"task 1 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":0,"gross_line_total":0,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"Volej0WbjN","tax_amount":0,"expense_id":""},{"_id":"37e6f099-d7c9-47ad-aaf3-2de38942959c","quantity":5,"cost":123,"product_key":"t2","product_cost":0,"notes":"task 2 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":615,"gross_line_total":615,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"O5xe7pwa7r","tax_amount":0,"expense_id":""},{"_id":"d109e819-e839-4eeb-9c24-cd91cbfed7b5","quantity":5,"cost":33,"product_key":"t3","product_cost":0,"notes":"task 3 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":165,"gross_line_total":165,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"QK9b6B7bEv","tax_amount":0,"expense_id":""},{"_id":"ac64e02b-219c-4a18-9a7c-191dd5b8f21f","quantity":5,"cost":67,"product_key":"t4","product_cost":0,"notes":"task 4 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":335,"gross_line_total":335,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"qM7e5Rxa2v","tax_amount":0,"expense_id":""},{"_id":"7b6dcee1-7b07-49b7-bcfa-2129ef844f07","quantity":5,"cost":4,"product_key":"t5","product_cost":0,"notes":"task 5 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":20,"gross_line_total":20,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"KGRb4z2eBL","tax_amount":0,"expense_id":""},{"_id":"a8ef31c5-49ac-43f7-a40e-0e311b940589","quantity":5,"cost":11,"product_key":"t6","product_cost":0,"notes":"task 6 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":55,"gross_line_total":55,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"GELe3yrb69","tax_amount":0,"expense_id":""}],"entity_type":"invoice","reminder1_sent":"","reminder2_sent":"","reminder3_sent":"","reminder_last_sent":"","paid_to_date":444,"subscription_id":"","auto_bill_enabled":false,"tax_info":{},"invitations":{"data":[{"id":"WJxboZzagw","client_contact_id":"yMYerkEaOB","key":"i6oaEtKOSJGW8ArM7tzz0zWVASYmSebr","link":"http:\/\/ninja.test:8000\/client\/invoice\/i6oaEtKOSJGW8ArM7tzz0zWVASYmSebr","sent_date":"2023-09-27 07:06:09","viewed_date":"","opened_date":"","updated_at":1695798369,"archived_at":0,"created_at":1695798363,"email_status":"","email_error":""},{"id":"k8mepZ1bMy","client_contact_id":"gl9av2maG1","key":"H6tz4cLXorbZPJHwF1b08QAZwF7zMQUZ","link":"http:\/\/ninja.test:8000\/client\/invoice\/H6tz4cLXorbZPJHwF1b08QAZwF7zMQUZ","sent_date":"2023-09-27 07:06:09","viewed_date":"","opened_date":"","updated_at":1695798369,"archived_at":0,"created_at":1695798363,"email_status":"","email_error":""},{"id":"l4zbqQ3bpr","client_contact_id":"7LDdwpRe1Y","key":"viWjxxto5SAr8pUqqgSIdrJbLuAHl3sL","link":"http:\/\/ninja.test:8000\/client\/invoice\/viWjxxto5SAr8pUqqgSIdrJbLuAHl3sL","sent_date":"2023-09-27 07:06:09","viewed_date":"","opened_date":"","updated_at":1695798369,"archived_at":0,"created_at":1695798363,"email_status":"","email_error":""}]},"documents":{"data":[]}}}},{"id":"QK9b6B7bEv","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0041","description":"Ipsam eos deleniti delectus.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695799099,"archived_at":0,"invoice_id":"mxkazm8eJ0","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":{"data":{"id":"mxkazm8eJ0","user_id":"wMvbmOeYAl","project_id":"","assigned_user_id":"","amount":1190,"balance":746,"client_id":"QJ0dN6dLOv","vendor_id":"","status_id":"3","design_id":"Wpmbk5ezJn","recurring_id":"","created_at":1695798363,"updated_at":1695799116,"archived_at":0,"is_deleted":false,"number":"0026","discount":0,"po_number":"","date":"2023-09-27","last_sent_date":"","next_send_date":"","due_date":"","terms":"

Default company invoice terms<\/p>","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"total_taxes":0,"is_amount_discount":true,"footer":"

Default invoice footer<\/p>","partial":0,"partial_due_date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","has_tasks":false,"has_expenses":false,"custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"exchange_rate":1,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"_id":"1d9727b4-bc32-4c62-a291-3b595e638a3b","quantity":0,"cost":33,"product_key":"t1","product_cost":0,"notes":"task 1 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":0,"gross_line_total":0,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"Volej0WbjN","tax_amount":0,"expense_id":""},{"_id":"37e6f099-d7c9-47ad-aaf3-2de38942959c","quantity":5,"cost":123,"product_key":"t2","product_cost":0,"notes":"task 2 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":615,"gross_line_total":615,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"O5xe7pwa7r","tax_amount":0,"expense_id":""},{"_id":"d109e819-e839-4eeb-9c24-cd91cbfed7b5","quantity":5,"cost":33,"product_key":"t3","product_cost":0,"notes":"task 3 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":165,"gross_line_total":165,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"QK9b6B7bEv","tax_amount":0,"expense_id":""},{"_id":"ac64e02b-219c-4a18-9a7c-191dd5b8f21f","quantity":5,"cost":67,"product_key":"t4","product_cost":0,"notes":"task 4 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":335,"gross_line_total":335,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"qM7e5Rxa2v","tax_amount":0,"expense_id":""},{"_id":"7b6dcee1-7b07-49b7-bcfa-2129ef844f07","quantity":5,"cost":4,"product_key":"t5","product_cost":0,"notes":"task 5 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":20,"gross_line_total":20,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"KGRb4z2eBL","tax_amount":0,"expense_id":""},{"_id":"a8ef31c5-49ac-43f7-a40e-0e311b940589","quantity":5,"cost":11,"product_key":"t6","product_cost":0,"notes":"task 6 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":55,"gross_line_total":55,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"GELe3yrb69","tax_amount":0,"expense_id":""}],"entity_type":"invoice","reminder1_sent":"","reminder2_sent":"","reminder3_sent":"","reminder_last_sent":"","paid_to_date":444,"subscription_id":"","auto_bill_enabled":false,"tax_info":{},"invitations":{"data":[{"id":"WJxboZzagw","client_contact_id":"yMYerkEaOB","key":"i6oaEtKOSJGW8ArM7tzz0zWVASYmSebr","link":"http:\/\/ninja.test:8000\/client\/invoice\/i6oaEtKOSJGW8ArM7tzz0zWVASYmSebr","sent_date":"2023-09-27 07:06:09","viewed_date":"","opened_date":"","updated_at":1695798369,"archived_at":0,"created_at":1695798363,"email_status":"","email_error":""},{"id":"k8mepZ1bMy","client_contact_id":"gl9av2maG1","key":"H6tz4cLXorbZPJHwF1b08QAZwF7zMQUZ","link":"http:\/\/ninja.test:8000\/client\/invoice\/H6tz4cLXorbZPJHwF1b08QAZwF7zMQUZ","sent_date":"2023-09-27 07:06:09","viewed_date":"","opened_date":"","updated_at":1695798369,"archived_at":0,"created_at":1695798363,"email_status":"","email_error":""},{"id":"l4zbqQ3bpr","client_contact_id":"7LDdwpRe1Y","key":"viWjxxto5SAr8pUqqgSIdrJbLuAHl3sL","link":"http:\/\/ninja.test:8000\/client\/invoice\/viWjxxto5SAr8pUqqgSIdrJbLuAHl3sL","sent_date":"2023-09-27 07:06:09","viewed_date":"","opened_date":"","updated_at":1695798369,"archived_at":0,"created_at":1695798363,"email_status":"","email_error":""}]},"documents":{"data":[]}}}},{"id":"O5xe7pwa7r","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0042","description":"Aperiam quae voluptas voluptatem vero.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695799099,"archived_at":0,"invoice_id":"mxkazm8eJ0","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":{"data":{"id":"mxkazm8eJ0","user_id":"wMvbmOeYAl","project_id":"","assigned_user_id":"","amount":1190,"balance":746,"client_id":"QJ0dN6dLOv","vendor_id":"","status_id":"3","design_id":"Wpmbk5ezJn","recurring_id":"","created_at":1695798363,"updated_at":1695799116,"archived_at":0,"is_deleted":false,"number":"0026","discount":0,"po_number":"","date":"2023-09-27","last_sent_date":"","next_send_date":"","due_date":"","terms":"

Default company invoice terms<\/p>","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"total_taxes":0,"is_amount_discount":true,"footer":"

Default invoice footer<\/p>","partial":0,"partial_due_date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","has_tasks":false,"has_expenses":false,"custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"exchange_rate":1,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"_id":"1d9727b4-bc32-4c62-a291-3b595e638a3b","quantity":0,"cost":33,"product_key":"t1","product_cost":0,"notes":"task 1 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":0,"gross_line_total":0,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"Volej0WbjN","tax_amount":0,"expense_id":""},{"_id":"37e6f099-d7c9-47ad-aaf3-2de38942959c","quantity":5,"cost":123,"product_key":"t2","product_cost":0,"notes":"task 2 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":615,"gross_line_total":615,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"O5xe7pwa7r","tax_amount":0,"expense_id":""},{"_id":"d109e819-e839-4eeb-9c24-cd91cbfed7b5","quantity":5,"cost":33,"product_key":"t3","product_cost":0,"notes":"task 3 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":165,"gross_line_total":165,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"QK9b6B7bEv","tax_amount":0,"expense_id":""},{"_id":"ac64e02b-219c-4a18-9a7c-191dd5b8f21f","quantity":5,"cost":67,"product_key":"t4","product_cost":0,"notes":"task 4 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":335,"gross_line_total":335,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"qM7e5Rxa2v","tax_amount":0,"expense_id":""},{"_id":"7b6dcee1-7b07-49b7-bcfa-2129ef844f07","quantity":5,"cost":4,"product_key":"t5","product_cost":0,"notes":"task 5 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":20,"gross_line_total":20,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"KGRb4z2eBL","tax_amount":0,"expense_id":""},{"_id":"a8ef31c5-49ac-43f7-a40e-0e311b940589","quantity":5,"cost":11,"product_key":"t6","product_cost":0,"notes":"task 6 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":55,"gross_line_total":55,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"GELe3yrb69","tax_amount":0,"expense_id":""}],"entity_type":"invoice","reminder1_sent":"","reminder2_sent":"","reminder3_sent":"","reminder_last_sent":"","paid_to_date":444,"subscription_id":"","auto_bill_enabled":false,"tax_info":{},"invitations":{"data":[{"id":"WJxboZzagw","client_contact_id":"yMYerkEaOB","key":"i6oaEtKOSJGW8ArM7tzz0zWVASYmSebr","link":"http:\/\/ninja.test:8000\/client\/invoice\/i6oaEtKOSJGW8ArM7tzz0zWVASYmSebr","sent_date":"2023-09-27 07:06:09","viewed_date":"","opened_date":"","updated_at":1695798369,"archived_at":0,"created_at":1695798363,"email_status":"","email_error":""},{"id":"k8mepZ1bMy","client_contact_id":"gl9av2maG1","key":"H6tz4cLXorbZPJHwF1b08QAZwF7zMQUZ","link":"http:\/\/ninja.test:8000\/client\/invoice\/H6tz4cLXorbZPJHwF1b08QAZwF7zMQUZ","sent_date":"2023-09-27 07:06:09","viewed_date":"","opened_date":"","updated_at":1695798369,"archived_at":0,"created_at":1695798363,"email_status":"","email_error":""},{"id":"l4zbqQ3bpr","client_contact_id":"7LDdwpRe1Y","key":"viWjxxto5SAr8pUqqgSIdrJbLuAHl3sL","link":"http:\/\/ninja.test:8000\/client\/invoice\/viWjxxto5SAr8pUqqgSIdrJbLuAHl3sL","sent_date":"2023-09-27 07:06:09","viewed_date":"","opened_date":"","updated_at":1695798369,"archived_at":0,"created_at":1695798363,"email_status":"","email_error":""}]},"documents":{"data":[]}}}},{"id":"MVyb8MldvA","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0043","description":"Voluptatem iusto quod cupiditate.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"VWPe9DPdLy","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0044","description":"Ab occaecati ut in qui.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798950,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"X46dBXa79j","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"QnXe007exr","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0045","description":"At ratione dicta dicta ab earum.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"Wjnegn9dwZ","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0046","description":"Omnis dolores ducimus iusto maxime aut iure.","duration":0,"rate":0,"created_at":1695797987,"updated_at":1695798928,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"4w9aAOdvMR","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]},{"id":"Volej0WbjN","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0047","description":"Possimus ut quia commodi voluptatem.","duration":0,"rate":33,"created_at":1695797987,"updated_at":1695799099,"archived_at":0,"invoice_id":"mxkazm8eJ0","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"mxkazYeJ0P","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":{"data":{"id":"mxkazm8eJ0","user_id":"wMvbmOeYAl","project_id":"","assigned_user_id":"","amount":1190,"balance":746,"client_id":"QJ0dN6dLOv","vendor_id":"","status_id":"3","design_id":"Wpmbk5ezJn","recurring_id":"","created_at":1695798363,"updated_at":1695799116,"archived_at":0,"is_deleted":false,"number":"0026","discount":0,"po_number":"","date":"2023-09-27","last_sent_date":"","next_send_date":"","due_date":"","terms":"

Default company invoice terms<\/p>","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"total_taxes":0,"is_amount_discount":true,"footer":"

Default invoice footer<\/p>","partial":0,"partial_due_date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","has_tasks":false,"has_expenses":false,"custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"exchange_rate":1,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"_id":"1d9727b4-bc32-4c62-a291-3b595e638a3b","quantity":0,"cost":33,"product_key":"t1","product_cost":0,"notes":"task 1 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":0,"gross_line_total":0,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"Volej0WbjN","tax_amount":0,"expense_id":""},{"_id":"37e6f099-d7c9-47ad-aaf3-2de38942959c","quantity":5,"cost":123,"product_key":"t2","product_cost":0,"notes":"task 2 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":615,"gross_line_total":615,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"O5xe7pwa7r","tax_amount":0,"expense_id":""},{"_id":"d109e819-e839-4eeb-9c24-cd91cbfed7b5","quantity":5,"cost":33,"product_key":"t3","product_cost":0,"notes":"task 3 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":165,"gross_line_total":165,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"QK9b6B7bEv","tax_amount":0,"expense_id":""},{"_id":"ac64e02b-219c-4a18-9a7c-191dd5b8f21f","quantity":5,"cost":67,"product_key":"t4","product_cost":0,"notes":"task 4 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":335,"gross_line_total":335,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"qM7e5Rxa2v","tax_amount":0,"expense_id":""},{"_id":"7b6dcee1-7b07-49b7-bcfa-2129ef844f07","quantity":5,"cost":4,"product_key":"t5","product_cost":0,"notes":"task 5 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":20,"gross_line_total":20,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"KGRb4z2eBL","tax_amount":0,"expense_id":""},{"_id":"a8ef31c5-49ac-43f7-a40e-0e311b940589","quantity":5,"cost":11,"product_key":"t6","product_cost":0,"notes":"task 6 description","discount":0,"is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":"0","line_total":55,"gross_line_total":55,"date":"","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","type_id":"2","tax_id":"2","task_id":"GELe3yrb69","tax_amount":0,"expense_id":""}],"entity_type":"invoice","reminder1_sent":"","reminder2_sent":"","reminder3_sent":"","reminder_last_sent":"","paid_to_date":444,"subscription_id":"","auto_bill_enabled":false,"tax_info":{},"invitations":{"data":[{"id":"WJxboZzagw","client_contact_id":"yMYerkEaOB","key":"i6oaEtKOSJGW8ArM7tzz0zWVASYmSebr","link":"http:\/\/ninja.test:8000\/client\/invoice\/i6oaEtKOSJGW8ArM7tzz0zWVASYmSebr","sent_date":"2023-09-27 07:06:09","viewed_date":"","opened_date":"","updated_at":1695798369,"archived_at":0,"created_at":1695798363,"email_status":"","email_error":""},{"id":"k8mepZ1bMy","client_contact_id":"gl9av2maG1","key":"H6tz4cLXorbZPJHwF1b08QAZwF7zMQUZ","link":"http:\/\/ninja.test:8000\/client\/invoice\/H6tz4cLXorbZPJHwF1b08QAZwF7zMQUZ","sent_date":"2023-09-27 07:06:09","viewed_date":"","opened_date":"","updated_at":1695798369,"archived_at":0,"created_at":1695798363,"email_status":"","email_error":""},{"id":"l4zbqQ3bpr","client_contact_id":"7LDdwpRe1Y","key":"viWjxxto5SAr8pUqqgSIdrJbLuAHl3sL","link":"http:\/\/ninja.test:8000\/client\/invoice\/viWjxxto5SAr8pUqqgSIdrJbLuAHl3sL","sent_date":"2023-09-27 07:06:09","viewed_date":"","opened_date":"","updated_at":1695798369,"archived_at":0,"created_at":1695798363,"email_status":"","email_error":""}]},"documents":{"data":[]}}}},{"id":"WpmbkrxazJ","user_id":"wMvbmOeYAl","assigned_user_id":"","number":"0048","description":"Blanditiis ad mollitia ratione veritatis.","duration":0,"rate":55,"created_at":1695797987,"updated_at":1695798883,"archived_at":0,"invoice_id":"","client_id":"QJ0dN6dLOv","project_id":"QK9b6nbEvY","is_deleted":false,"time_log":"[[1695761987,1695779987,\"description\",false]]","is_running":false,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","status_id":"kzPdy7aQro","status_sort_order":0,"is_date_based":false,"status_order":null,"date":"2023-09-26","client":{"id":"QJ0dN6dLOv","user_id":"wMvbmOeYAl","assigned_user_id":"","name":"Paucek and Sons","website":"http:\/\/www.wiegand.com\/sit-repellendus-enim-porro-aut-voluptas-voluptates.html","private_notes":"In nihil non voluptas voluptatibus molestiae et. Nihil labore eligendi omnis id. Et cumque reiciendis blanditiis fugiat.","balance":19958.86,"group_settings_id":"","paid_to_date":18895.75,"payment_balance":0,"credit_balance":573.53,"last_login":0,"size_id":"","public_notes":"","client_hash":"ngQe8O6SInYp1D0SKacHE8f6nzr3Gc1UkkoyGMKI","address1":"873","address2":"220 Hand Glen","phone":"","city":"Lake Kallie","state":"Ohio","postal_code":"77890","country_id":"535","industry_id":"","custom_value1":"1974-10-02 21:56:55","custom_value2":"Aqua","custom_value3":"modi","custom_value4":"xhomenick@hotmail.com","shipping_address1":"89064","shipping_address2":"87854 Meda Junction","shipping_city":"South Aryanna","shipping_state":"Connecticut","shipping_postal_code":"52527-1873","shipping_country_id":"4","settings":{"entity":"App\\Models\\Client","industry_id":"","size_id":"","currency_id":"1"},"is_deleted":false,"vat_number":"881403416","id_number":"MU19IWUA7340242407223765691CMM","updated_at":1695811138,"archived_at":0,"created_at":1695796650,"display_name":"Paucek and Sons","number":"0005","has_valid_vat_number":false,"is_tax_exempt":false,"routing_id":"","tax_info":{},"contacts":[{"id":"yMYerkEaOB","first_name":"Bret","last_name":"Beatty","email":"user@example.com","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":true,"is_locked":false,"phone":"1-985-403-1242","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/juMXBqyvDashDUvoYXsdcbZV2SRrFf0BOUFjsc6G"},{"id":"gl9av2maG1","first_name":"Sandra","last_name":"Daniel","email":"linnie.aufderhar@example.net","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"+17319151379","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/sm53aNvvsGUmLXFku0RLPUrP0zLj3rzBcTaGMjHd"},{"id":"7LDdwpRe1Y","first_name":"Leone","last_name":"Bode","email":"ipollich@example.org","created_at":1695796650,"updated_at":1695796650,"archived_at":0,"is_primary":false,"is_locked":false,"phone":"(424) 789-3412","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","contact_key":"gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma","send_email":true,"last_login":0,"password":"**********","link":"http:\/\/ninja.test:8000\/client\/key_login\/gDUwiOqRehgCHWbbctW8qgKiLmma3xvsZi10a5Ma"}],"documents":{"data":[]},"gateway_tokens":{"data":[]}},"project":{"id":"QK9b6nbEvY","user_id":"wMvbmOeYAl","assigned_user_id":"","client_id":"QJ0dN6dLOv","name":"Linnie Pfannerstill","number":"0001","created_at":1695797987,"updated_at":1695798405,"archived_at":0,"is_deleted":false,"task_rate":97,"due_date":"2023-09-30","private_notes":"These are the private notes for this user.","public_notes":"Quo sed quis reprehenderit est consequatur et.","budgeted_hours":738,"custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","color":"#fff","current_hours":0,"documents":{"data":[]}},"invoice":[]}]'; + + public string $invoice_data = '[{"amount":"$6,054.13","balance":"$0.00","balance_raw":"0.000000","number":"0015","discount":1,"po_number":"","date":"20\/Sep\/2023","last_sent_date":"","next_send_date":"","due_date":"","terms":"","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"total_taxes":"$444.13","total_taxes_raw":"444.130000","is_amount_discount":true,"footer":"","partial":"0.000000","partial_due_date":"","custom_value1":"1984-10-01","custom_value2":"no","custom_value3":"","custom_value4":"","custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"exchange_rate":1,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$981.75","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$46.75","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":981.75,"tax_amount_raw":46.75,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$935.00","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$0.00","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":935,"tax_amount_raw":0,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"GST","tax_rate1":10,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,028.50","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$93.50","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1028.5,"tax_amount_raw":93.5,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"VAT","tax_rate1":17.5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,098.63","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$163.63","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1098.63,"tax_amount_raw":163.63,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"GST","tax_rate1":10,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,028.50","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$93.50","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1028.5,"tax_amount_raw":93.5,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$981.75","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$46.75","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":981.75,"tax_amount_raw":46.75,"product_cost_raw":0}],"reminder1_sent":"","reminder2_sent":"","reminder3_sent":"","reminder_last_sent":"","paid_to_date":"$6,054.13","auto_bill_enabled":false,"client":{"name":"Lowe-Paucek","balance":"38124.670000","payment_balance":"0.000000","credit_balance":"2270.590000"},"payments":[{"status":"Completed","badge":"

Completed<\/span><\/h6>","amount":"$6,054.13","applied":"$6,054.13","balance":"$0.00","refunded":"$0.00","amount_raw":"6054.130000","applied_raw":"6054.130000","refunded_raw":"0.000000","balance_raw":0,"date":"30\/Sep\/2023","method":"","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0008","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"Lowe-Paucek","balance":"38124.670000","payment_balance":"0.000000","credit_balance":"2270.590000"},"paymentables":[{"invoice":"0015","amount_raw":"6054.1300","refunded_raw":"0.0000","net_raw":6054.13,"amount":"$6,054.13","refunded":"$0.00","net":"$6,054.13","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696151008}]}],"total_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}],"line_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}]},{"amount":"$3,132.25","balance":"$0.00","balance_raw":"0.000000","number":"0016","discount":0,"po_number":"","date":"17\/Jul\/2023","last_sent_date":"","next_send_date":"","due_date":"","terms":"","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"CA Sales Tax","tax_rate3":5,"total_taxes":"$327.25","total_taxes_raw":"327.250000","is_amount_discount":true,"footer":"","partial":"0.000000","partial_due_date":"","custom_value1":"1990-05-13","custom_value2":"yes","custom_value3":"","custom_value4":"","custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"exchange_rate":1,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$981.75","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$46.75","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":981.75,"tax_amount_raw":46.75,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"GST","tax_rate1":10,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,028.50","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$93.50","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1028.5,"tax_amount_raw":93.5,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$981.75","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$46.75","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":981.75,"tax_amount_raw":46.75,"product_cost_raw":0}],"reminder1_sent":"","reminder2_sent":"","reminder3_sent":"","reminder_last_sent":"","paid_to_date":"$3,132.25","auto_bill_enabled":false,"client":{"name":"cypress","balance":"0.000000","payment_balance":"0.000000","credit_balance":"11661.820000"},"payments":[{"status":"Completed","badge":"
Completed<\/span><\/h6>","amount":"$3,132.25","applied":"$3,132.25","balance":"$0.00","refunded":"$0.00","amount_raw":"3132.250000","applied_raw":"3132.250000","refunded_raw":"0.000000","balance_raw":0,"date":"30\/Sep\/2023","method":"","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0009","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"cypress","balance":"0.000000","payment_balance":"0.000000","credit_balance":"11661.820000"},"paymentables":[{"invoice":"0016","amount_raw":"3132.2500","refunded_raw":"0.0000","net_raw":3132.25,"amount":"$3,132.25","refunded":"$0.00","net":"$3,132.25","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696151008}]}],"total_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}],"line_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}]},{"amount":"$9,396.77","balance":"$0.00","balance_raw":"0.000000","number":"0017","discount":0,"po_number":"","date":"04\/Sep\/2023","last_sent_date":"","next_send_date":"","due_date":"","terms":"","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"GST","tax_rate1":10,"tax_name2":"VAT","tax_rate2":17.5,"tax_name3":"CA Sales Tax","tax_rate3":5,"total_taxes":"$2,851.77","total_taxes_raw":"2851.770000","is_amount_discount":true,"footer":"","partial":"0.000000","partial_due_date":"","custom_value1":"1989-04-20","custom_value2":"yes","custom_value3":"","custom_value4":"","custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"exchange_rate":1,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$981.75","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$46.75","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":981.75,"tax_amount_raw":46.75,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$935.00","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$0.00","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":935,"tax_amount_raw":0,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"VAT","tax_rate1":17.5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,098.63","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$163.63","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1098.63,"tax_amount_raw":163.63,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"GST","tax_rate1":10,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,028.50","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$93.50","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1028.5,"tax_amount_raw":93.5,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"VAT","tax_rate1":17.5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,098.63","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$163.63","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1098.63,"tax_amount_raw":163.63,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"GST","tax_rate1":10,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,028.50","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$93.50","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1028.5,"tax_amount_raw":93.5,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"VAT","tax_rate1":17.5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,098.63","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$163.63","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1098.63,"tax_amount_raw":163.63,"product_cost_raw":0}],"reminder1_sent":"","reminder2_sent":"","reminder3_sent":"","reminder_last_sent":"","paid_to_date":"$9,396.77","auto_bill_enabled":false,"client":{"name":"cypress","balance":"0.000000","payment_balance":"0.000000","credit_balance":"11661.820000"},"payments":[{"status":"Completed","badge":"
Completed<\/span><\/h6>","amount":"$9,396.77","applied":"$9,396.77","balance":"$0.00","refunded":"$0.00","amount_raw":"9396.770000","applied_raw":"9396.770000","refunded_raw":"0.000000","balance_raw":0,"date":"30\/Sep\/2023","method":"","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0010","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"cypress","balance":"0.000000","payment_balance":"0.000000","credit_balance":"11661.820000"},"paymentables":[{"invoice":"0017","amount_raw":"9396.7700","refunded_raw":"0.0000","net_raw":9396.77,"amount":"$9,396.77","refunded":"$0.00","net":"$9,396.77","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696151008}]}],"total_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}],"line_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}]},{"amount":"$6,077.51","balance":"$0.00","balance_raw":"0.000000","number":"0019","discount":0,"po_number":"","date":"01\/Aug\/2023","last_sent_date":"","next_send_date":"","due_date":"","terms":"","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"","tax_rate1":0,"tax_name2":"VAT","tax_rate2":17.5,"tax_name3":"CA Sales Tax","tax_rate3":5,"total_taxes":"$1,402.51","total_taxes_raw":"1402.510000","is_amount_discount":true,"footer":"","partial":"0.000000","partial_due_date":"","custom_value1":"1992-08-20","custom_value2":"no","custom_value3":"","custom_value4":"","custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"exchange_rate":1,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$981.75","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$46.75","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":981.75,"tax_amount_raw":46.75,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"GST","tax_rate1":10,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,028.50","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$93.50","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1028.5,"tax_amount_raw":93.5,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"VAT","tax_rate1":17.5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,098.63","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$163.63","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1098.63,"tax_amount_raw":163.63,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$981.75","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$46.75","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":981.75,"tax_amount_raw":46.75,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$935.00","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$0.00","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":935,"tax_amount_raw":0,"product_cost_raw":0}],"reminder1_sent":"","reminder2_sent":"","reminder3_sent":"","reminder_last_sent":"","paid_to_date":"$6,077.51","auto_bill_enabled":false,"client":{"name":"cypress","balance":"0.000000","payment_balance":"0.000000","credit_balance":"11661.820000"},"payments":[{"status":"Refunded","badge":"
Refunded<\/span><\/h6>","amount":"$6,077.51","applied":"$6,077.51","balance":"-$6,077.51","refunded":"$6,077.51","amount_raw":"6077.510000","applied_raw":"6077.510000","refunded_raw":"6077.510000","balance_raw":-6077.51,"date":"30\/Sep\/2023","method":"EuroCard","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0001","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"cypress","balance":"0.000000","payment_balance":"0.000000","credit_balance":"11661.820000"},"paymentables":[{"invoice":"0019","amount_raw":"6077.5100","refunded_raw":"6077.5100","net_raw":0,"amount":"$6,077.51","refunded":"$6,077.51","net":"$0.00","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696150843}]},{"status":"Completed","badge":"
Completed<\/span><\/h6>","amount":"$6,077.51","applied":"$6,077.51","balance":"$0.00","refunded":"$0.00","amount_raw":"6077.510000","applied_raw":"6077.510000","refunded_raw":"0.000000","balance_raw":0,"date":"30\/Sep\/2023","method":"","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0011","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"cypress","balance":"0.000000","payment_balance":"0.000000","credit_balance":"11661.820000"},"paymentables":[{"invoice":"0019","amount_raw":"6077.5100","refunded_raw":"0.0000","net_raw":6077.51,"amount":"$6,077.51","refunded":"$0.00","net":"$6,077.51","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696151008}]}],"total_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}],"line_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}]},{"amount":"$4,090.64","balance":"$0.00","balance_raw":"0.000000","number":"0020","discount":0,"po_number":"","date":"26\/Aug\/2023","last_sent_date":"","next_send_date":"","due_date":"","terms":"","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"GST","tax_rate1":10,"tax_name2":"VAT","tax_rate2":17.5,"tax_name3":"CA Sales Tax","tax_rate3":5,"total_taxes":"$1,285.64","total_taxes_raw":"1285.640000","is_amount_discount":true,"footer":"","partial":"0.000000","partial_due_date":"","custom_value1":"1979-06-26","custom_value2":"yes","custom_value3":"","custom_value4":"","custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"exchange_rate":1,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$981.75","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$46.75","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":981.75,"tax_amount_raw":46.75,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"VAT","tax_rate1":17.5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,098.63","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$163.63","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1098.63,"tax_amount_raw":163.63,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"VAT","tax_rate1":17.5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,098.63","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$163.63","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1098.63,"tax_amount_raw":163.63,"product_cost_raw":0}],"reminder1_sent":"","reminder2_sent":"","reminder3_sent":"","reminder_last_sent":"","paid_to_date":"$4,090.64","auto_bill_enabled":false,"client":{"name":"cypress","balance":"0.000000","payment_balance":"0.000000","credit_balance":"11661.820000"},"payments":[{"status":"Refunded","badge":"
Refunded<\/span><\/h6>","amount":"$4,090.64","applied":"$4,090.64","balance":"-$4,090.64","refunded":"$4,090.64","amount_raw":"4090.640000","applied_raw":"4090.640000","refunded_raw":"4090.640000","balance_raw":-4090.64,"date":"30\/Sep\/2023","method":"Discover Card","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0002","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"cypress","balance":"0.000000","payment_balance":"0.000000","credit_balance":"11661.820000"},"paymentables":[{"invoice":"0020","amount_raw":"4090.6400","refunded_raw":"4090.6400","net_raw":0,"amount":"$4,090.64","refunded":"$4,090.64","net":"$0.00","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696150843}]},{"status":"Completed","badge":"
Completed<\/span><\/h6>","amount":"$4,090.64","applied":"$4,090.64","balance":"$0.00","refunded":"$0.00","amount_raw":"4090.640000","applied_raw":"4090.640000","refunded_raw":"0.000000","balance_raw":0,"date":"30\/Sep\/2023","method":"","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0012","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"cypress","balance":"0.000000","payment_balance":"0.000000","credit_balance":"11661.820000"},"paymentables":[{"invoice":"0020","amount_raw":"4090.6400","refunded_raw":"0.0000","net_raw":4090.64,"amount":"$4,090.64","refunded":"$0.00","net":"$4,090.64","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696151008}]}],"total_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}],"line_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}]},{"amount":"$2,197.26","balance":"$0.00","balance_raw":"0.000000","number":"0021","discount":0,"po_number":"","date":"08\/Sep\/2023","last_sent_date":"","next_send_date":"","due_date":"","terms":"","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"total_taxes":"$327.26","total_taxes_raw":"327.260000","is_amount_discount":true,"footer":"","partial":"0.000000","partial_due_date":"","custom_value1":"1980-11-12","custom_value2":"no","custom_value3":"","custom_value4":"","custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"exchange_rate":1,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"VAT","tax_rate1":17.5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,098.63","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$163.63","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1098.63,"tax_amount_raw":163.63,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"VAT","tax_rate1":17.5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,098.63","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$163.63","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1098.63,"tax_amount_raw":163.63,"product_cost_raw":0}],"reminder1_sent":"","reminder2_sent":"","reminder3_sent":"","reminder_last_sent":"","paid_to_date":"$2,197.26","auto_bill_enabled":false,"client":{"name":"Jakubowski Group","balance":"28296.170000","payment_balance":"0.000000","credit_balance":"1084.840000"},"payments":[{"status":"Refunded","badge":"
Refunded<\/span><\/h6>","amount":"$2,197.26","applied":"$2,197.26","balance":"-$2,197.26","refunded":"$2,197.26","amount_raw":"2197.260000","applied_raw":"2197.260000","refunded_raw":"2197.260000","balance_raw":-2197.26,"date":"30\/Sep\/2023","method":"Diners Card","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0003","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"Jakubowski Group","balance":"28296.170000","payment_balance":"0.000000","credit_balance":"1084.840000"},"paymentables":[{"invoice":"0021","amount_raw":"2197.2600","refunded_raw":"2197.2600","net_raw":0,"amount":"$2,197.26","refunded":"$2,197.26","net":"$0.00","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696150843}]},{"status":"Completed","badge":"
Completed<\/span><\/h6>","amount":"$2,197.26","applied":"$2,197.26","balance":"$0.00","refunded":"$0.00","amount_raw":"2197.260000","applied_raw":"2197.260000","refunded_raw":"0.000000","balance_raw":0,"date":"30\/Sep\/2023","method":"","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0013","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"Jakubowski Group","balance":"28296.170000","payment_balance":"0.000000","credit_balance":"1084.840000"},"paymentables":[{"invoice":"0021","amount_raw":"2197.2600","refunded_raw":"0.0000","net_raw":2197.26,"amount":"$2,197.26","refunded":"$0.00","net":"$2,197.26","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696151008}]}],"total_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}],"line_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}]},{"amount":"$4,955.50","balance":"$66.00","balance_raw":"66.000000","number":"0022","discount":0,"po_number":"","date":"27\/Sep\/2023","last_sent_date":"","next_send_date":"","due_date":"","terms":"","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"GST","tax_rate1":10,"tax_name2":"VAT","tax_rate2":17.5,"tax_name3":"","tax_rate3":0,"total_taxes":"$1,215.50","total_taxes_raw":"1215.500000","is_amount_discount":true,"footer":"","partial":"0.000000","partial_due_date":"","custom_value1":"1999-08-20","custom_value2":"yes","custom_value3":"","custom_value4":"","custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"exchange_rate":1,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$981.75","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$46.75","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":981.75,"tax_amount_raw":46.75,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$981.75","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$46.75","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":981.75,"tax_amount_raw":46.75,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$981.75","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$46.75","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":981.75,"tax_amount_raw":46.75,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$981.75","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$46.75","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":981.75,"tax_amount_raw":46.75,"product_cost_raw":0}],"reminder1_sent":"","reminder2_sent":"","reminder3_sent":"","reminder_last_sent":"","paid_to_date":"$4,889.50","auto_bill_enabled":false,"client":{"name":"Jakubowski Group","balance":"28296.170000","payment_balance":"0.000000","credit_balance":"1084.840000"},"payments":[{"status":"Partially Refunded","badge":"
Partially Refunded<\/span><\/h6>","amount":"$4,955.50","applied":"$4,955.50","balance":"-$66.00","refunded":"$66.00","amount_raw":"4955.500000","applied_raw":"4955.500000","refunded_raw":"66.000000","balance_raw":-66,"date":"30\/Sep\/2023","method":"Maestro","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0004","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"Jakubowski Group","balance":"28296.170000","payment_balance":"0.000000","credit_balance":"1084.840000"},"paymentables":[{"invoice":"0022","amount_raw":"4955.5000","refunded_raw":"66.0000","net_raw":4889.5,"amount":"$4,955.50","refunded":"$66.00","net":"$4,889.50","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696150843}]}],"total_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}],"line_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}]},{"amount":"$2,290.75","balance":"$34.00","balance_raw":"34.000000","number":"0023","discount":0,"po_number":"","date":"30\/Aug\/2023","last_sent_date":"","next_send_date":"","due_date":"","terms":"","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"","tax_rate1":0,"tax_name2":"VAT","tax_rate2":17.5,"tax_name3":"CA Sales Tax","tax_rate3":5,"total_taxes":"$420.75","total_taxes_raw":"420.750000","is_amount_discount":true,"footer":"","partial":"0.000000","partial_due_date":"","custom_value1":"2015-12-15","custom_value2":"no","custom_value3":"","custom_value4":"","custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"exchange_rate":1,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$935.00","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$0.00","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":935,"tax_amount_raw":0,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$935.00","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$0.00","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":935,"tax_amount_raw":0,"product_cost_raw":0}],"reminder1_sent":"","reminder2_sent":"","reminder3_sent":"","reminder_last_sent":"","paid_to_date":"$2,256.75","auto_bill_enabled":false,"client":{"name":"Jakubowski Group","balance":"28296.170000","payment_balance":"0.000000","credit_balance":"1084.840000"},"payments":[{"status":"Partially Refunded","badge":"
Partially Refunded<\/span><\/h6>","amount":"$2,290.75","applied":"$2,290.75","balance":"-$34.00","refunded":"$34.00","amount_raw":"2290.750000","applied_raw":"2290.750000","refunded_raw":"34.000000","balance_raw":-34,"date":"30\/Sep\/2023","method":"Diners Card","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0005","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"Jakubowski Group","balance":"28296.170000","payment_balance":"0.000000","credit_balance":"1084.840000"},"paymentables":[{"invoice":"0023","amount_raw":"2290.7500","refunded_raw":"34.0000","net_raw":2256.75,"amount":"$2,290.75","refunded":"$34.00","net":"$2,256.75","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696150843}]}],"total_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}],"line_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}]},{"amount":"$6,802.13","balance":"$444.00","balance_raw":"444.000000","number":"0024","discount":0,"po_number":"","date":"25\/Jul\/2023","last_sent_date":"","next_send_date":"","due_date":"","terms":"","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"GST","tax_rate1":10,"tax_name2":"","tax_rate2":0,"tax_name3":"CA Sales Tax","tax_rate3":5,"total_taxes":"$1,192.13","total_taxes_raw":"1192.130000","is_amount_discount":true,"footer":"","partial":"0.000000","partial_due_date":"","custom_value1":"1990-07-11","custom_value2":"no","custom_value3":"","custom_value4":"","custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"exchange_rate":1,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"VAT","tax_rate1":17.5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,098.63","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$163.63","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1098.63,"tax_amount_raw":163.63,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$935.00","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$0.00","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":935,"tax_amount_raw":0,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$981.75","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$46.75","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":981.75,"tax_amount_raw":46.75,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$935.00","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$0.00","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":935,"tax_amount_raw":0,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$981.75","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$46.75","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":981.75,"tax_amount_raw":46.75,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"GST","tax_rate1":10,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,028.50","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$93.50","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1028.5,"tax_amount_raw":93.5,"product_cost_raw":0}],"reminder1_sent":"","reminder2_sent":"","reminder3_sent":"","reminder_last_sent":"","paid_to_date":"$6,358.13","auto_bill_enabled":false,"client":{"name":"Jakubowski Group","balance":"28296.170000","payment_balance":"0.000000","credit_balance":"1084.840000"},"payments":[{"status":"Partially Refunded","badge":"
Partially Refunded<\/span><\/h6>","amount":"$6,802.13","applied":"$6,802.13","balance":"-$444.00","refunded":"$444.00","amount_raw":"6802.130000","applied_raw":"6802.130000","refunded_raw":"444.000000","balance_raw":-444,"date":"30\/Sep\/2023","method":"Maestro","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0006","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"Jakubowski Group","balance":"28296.170000","payment_balance":"0.000000","credit_balance":"1084.840000"},"paymentables":[{"invoice":"0024","amount_raw":"6802.1300","refunded_raw":"444.0000","net_raw":6358.13,"amount":"$6,802.13","refunded":"$444.00","net":"$6,358.13","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696150843}]}],"total_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}],"line_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}]},{"amount":"$10,986.26","balance":"$146.26","balance_raw":"146.260000","number":"0025","discount":0,"po_number":"","date":"23\/Jul\/2023","last_sent_date":"","next_send_date":"","due_date":"","terms":"","public_notes":"","private_notes":"","uses_inclusive_taxes":false,"tax_name1":"GST","tax_rate1":10,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"total_taxes":"$1,636.26","total_taxes_raw":"1636.260000","is_amount_discount":true,"footer":"","partial":"0.000000","partial_due_date":"","custom_value1":"1975-02-18","custom_value2":"no","custom_value3":"","custom_value4":"","custom_surcharge1":0,"custom_surcharge2":0,"custom_surcharge3":0,"custom_surcharge4":0,"exchange_rate":1,"custom_surcharge_tax1":false,"custom_surcharge_tax2":false,"custom_surcharge_tax3":false,"custom_surcharge_tax4":false,"line_items":[{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"VAT","tax_rate1":17.5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,098.63","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$163.63","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1098.63,"tax_amount_raw":163.63,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"GST","tax_rate1":10,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,028.50","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$93.50","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1028.5,"tax_amount_raw":93.5,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$935.00","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$0.00","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":935,"tax_amount_raw":0,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$935.00","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$0.00","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":935,"tax_amount_raw":0,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"VAT","tax_rate1":17.5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,098.63","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$163.63","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1098.63,"tax_amount_raw":163.63,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$981.75","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$46.75","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":981.75,"tax_amount_raw":46.75,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$981.75","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$46.75","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":981.75,"tax_amount_raw":46.75,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$981.75","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$46.75","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":981.75,"tax_amount_raw":46.75,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$981.75","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$46.75","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":981.75,"tax_amount_raw":46.75,"product_cost_raw":0},{"quantity":1,"cost":"$935.00","product_key":"Et.","notes":"Illum similique.","discount":"$0.00","is_amount_discount":true,"tax_name1":"GST","tax_rate1":10,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"$935.00","gross_line_total":"$1,028.50","custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"78","custom_value3":"Itaque laudantium.","custom_value4":"Qui voluptatem ea a.","type_id":"1","product_cost":"$0.00","tax_amount":"$93.50","date":"","tax_id":"","task_id":"","expense_id":"","cost_raw":935,"discount_raw":0,"line_total_raw":935,"gross_line_total_raw":1028.5,"tax_amount_raw":93.5,"product_cost_raw":0}],"reminder1_sent":"","reminder2_sent":"","reminder3_sent":"","reminder_last_sent":"","paid_to_date":"$10,840.00","auto_bill_enabled":false,"client":{"name":"Jakubowski Group","balance":"28296.170000","payment_balance":"0.000000","credit_balance":"1084.840000"},"payments":[{"status":"Partially Refunded","badge":"
Partially Refunded<\/span><\/h6>","amount":"$10,986.26","applied":"$10,986.26","balance":"-$146.26","refunded":"$146.26","amount_raw":"10986.260000","applied_raw":"10986.260000","refunded_raw":"146.260000","balance_raw":-146.26000000000022,"date":"30\/Sep\/2023","method":"UnionPay","currency":"USD","exchange_rate":1,"transaction_reference":"Manual entry","is_manual":1,"number":"0007","custom_value1":"","custom_value2":"","custom_value3":"","custom_value4":"","client":{"name":"Jakubowski Group","balance":"28296.170000","payment_balance":"0.000000","credit_balance":"1084.840000"},"paymentables":[{"invoice":"0025","amount_raw":"10986.2600","refunded_raw":"146.2600","net_raw":10840,"amount":"$10,986.26","refunded":"$146.26","net":"$10,840.00","is_credit":false,"created_at":"01\/Oct\/2023","updated_at":"01\/Oct\/2023","timestamp":1696150843}]}],"total_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}],"line_tax_map":[{"name":"CA Sales Tax 5%","total":"$141.90","total_raw":141.9}]}]'; + + public string $quote_data = '[{"id":1,"client_id":1,"user_id":1,"assigned_user_id":null,"company_id":1,"status_id":2,"project_id":null,"vendor_id":null,"recurring_id":null,"design_id":2,"invoice_id":null,"number":"0001","discount":1,"is_amount_discount":false,"po_number":"Molestias.","date":"1986-08-02","last_sent_date":null,"due_date":null,"next_send_date":null,"is_deleted":false,"line_items":[{"quantity":1,"cost":637,"product_key":"Nisi.","notes":"Ut ipsa rerum.","discount":0,"is_amount_discount":false,"tax_name1":"VAT","tax_rate1":17.5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"637.00","gross_line_total":747.36,"custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"42","custom_value3":"Maxime dolores.","custom_value4":"Odio consequatur.","type_id":"1","product_cost":0,"tax_amount":110.36,"date":"","tax_id":"","task_id":"","expense_id":""},{"quantity":1,"cost":637,"product_key":"Nisi.","notes":"Ut ipsa rerum.","discount":0,"is_amount_discount":false,"tax_name1":"GST","tax_rate1":10,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"637.00","gross_line_total":700.06,"custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"42","custom_value3":"Maxime dolores.","custom_value4":"Odio consequatur.","type_id":"1","product_cost":0,"tax_amount":63.06,"date":"","tax_id":"","task_id":"","expense_id":""},{"quantity":1,"cost":637,"product_key":"Nisi.","notes":"Ut ipsa rerum.","discount":0,"is_amount_discount":false,"tax_name1":"","tax_rate1":0,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"637.00","gross_line_total":637,"custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"42","custom_value3":"Maxime dolores.","custom_value4":"Odio consequatur.","type_id":"1","product_cost":0,"tax_amount":0,"date":"","tax_id":"","task_id":"","expense_id":""},{"quantity":1,"cost":637,"product_key":"Nisi.","notes":"Ut ipsa rerum.","discount":0,"is_amount_discount":false,"tax_name1":"GST","tax_rate1":10,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"637.00","gross_line_total":700.06,"custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"42","custom_value3":"Maxime dolores.","custom_value4":"Odio consequatur.","type_id":"1","product_cost":0,"tax_amount":63.06,"date":"","tax_id":"","task_id":"","expense_id":""},{"quantity":1,"cost":637,"product_key":"Nisi.","notes":"Ut ipsa rerum.","discount":0,"is_amount_discount":false,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"637.00","gross_line_total":668.53,"custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"42","custom_value3":"Maxime dolores.","custom_value4":"Odio consequatur.","type_id":"1","product_cost":0,"tax_amount":31.53,"date":"","tax_id":"","task_id":"","expense_id":""},{"quantity":1,"cost":637,"product_key":"Nisi.","notes":"Ut ipsa rerum.","discount":0,"is_amount_discount":false,"tax_name1":"GST","tax_rate1":10,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"637.00","gross_line_total":700.06,"custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"42","custom_value3":"Maxime dolores.","custom_value4":"Odio consequatur.","type_id":"1","product_cost":0,"tax_amount":63.06,"date":"","tax_id":"","task_id":"","expense_id":""},{"quantity":1,"cost":637,"product_key":"Nisi.","notes":"Ut ipsa rerum.","discount":0,"is_amount_discount":false,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":"637.00","gross_line_total":668.53,"custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"42","custom_value3":"Maxime dolores.","custom_value4":"Odio consequatur.","type_id":"1","product_cost":0,"tax_amount":31.53,"date":"","tax_id":"","task_id":"","expense_id":""}],"backup":null,"footer":null,"public_notes":null,"private_notes":null,"terms":null,"tax_name1":"GST","tax_rate1":"10.000000","tax_name2":"VAT","tax_rate2":"17.500000","tax_name3":"THIRDTAX","tax_rate3":"5.000000","total_taxes":"1797.280000","uses_inclusive_taxes":0,"custom_value1":null,"custom_value2":null,"custom_value3":null,"custom_value4":null,"custom_surcharge1":null,"custom_surcharge2":null,"custom_surcharge3":null,"custom_surcharge4":null,"custom_surcharge_tax1":0,"custom_surcharge_tax2":0,"custom_surcharge_tax3":0,"custom_surcharge_tax4":0,"exchange_rate":"1.000000","amount":"6211.690000","balance":"0.000000","partial":null,"partial_due_date":null,"last_viewed":null,"created_at":1695943654,"updated_at":1695943658,"deleted_at":null,"reminder1_sent":null,"reminder2_sent":null,"reminder3_sent":null,"reminder_last_sent":null,"paid_to_date":"0.000000","subscription_id":null,"hashed_id":"VolejRejNm"},{"id":2,"client_id":1,"user_id":1,"assigned_user_id":null,"company_id":1,"status_id":2,"project_id":null,"vendor_id":null,"recurring_id":null,"design_id":2,"invoice_id":null,"number":"0002","discount":9,"is_amount_discount":true,"po_number":"Omnis.","date":"1988-11-22","last_sent_date":null,"due_date":null,"next_send_date":null,"is_deleted":false,"line_items":[{"quantity":1,"cost":637,"product_key":"Nisi.","notes":"Ut ipsa rerum.","discount":0,"is_amount_discount":true,"tax_name1":"VAT","tax_rate1":17.5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":637,"gross_line_total":738.44,"custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"42","custom_value3":"Maxime dolores.","custom_value4":"Odio consequatur.","type_id":"1","product_cost":0,"tax_amount":101.44,"date":"","tax_id":"","task_id":"","expense_id":""},{"quantity":1,"cost":637,"product_key":"Nisi.","notes":"Ut ipsa rerum.","discount":0,"is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":637,"gross_line_total":665.98,"custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"42","custom_value3":"Maxime dolores.","custom_value4":"Odio consequatur.","type_id":"1","product_cost":0,"tax_amount":28.98,"date":"","tax_id":"","task_id":"","expense_id":""},{"quantity":1,"cost":637,"product_key":"Nisi.","notes":"Ut ipsa rerum.","discount":0,"is_amount_discount":true,"tax_name1":"Sales Tax","tax_rate1":5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":637,"gross_line_total":665.98,"custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"42","custom_value3":"Maxime dolores.","custom_value4":"Odio consequatur.","type_id":"1","product_cost":0,"tax_amount":28.98,"date":"","tax_id":"","task_id":"","expense_id":""},{"quantity":1,"cost":637,"product_key":"Nisi.","notes":"Ut ipsa rerum.","discount":0,"is_amount_discount":true,"tax_name1":"GST","tax_rate1":10,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":637,"gross_line_total":694.97,"custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"42","custom_value3":"Maxime dolores.","custom_value4":"Odio consequatur.","type_id":"1","product_cost":0,"tax_amount":57.97,"date":"","tax_id":"","task_id":"","expense_id":""},{"quantity":1,"cost":637,"product_key":"Nisi.","notes":"Ut ipsa rerum.","discount":0,"is_amount_discount":true,"tax_name1":"VAT","tax_rate1":17.5,"tax_name2":"","tax_rate2":0,"tax_name3":"","tax_rate3":0,"sort_id":0,"line_total":637,"gross_line_total":738.44,"custom_value1":"https:\/\/picsum.photos\/200","custom_value2":"42","custom_value3":"Maxime dolores.","custom_value4":"Odio consequatur.","type_id":"1","product_cost":0,"tax_amount":101.44,"date":"","tax_id":"","task_id":"","expense_id":""}],"backup":null,"footer":null,"public_notes":null,"private_notes":null,"terms":null,"tax_name1":"GST","tax_rate1":"10.000000","tax_name2":"VAT","tax_rate2":"17.500000","tax_name3":"THIRDTAX","tax_rate3":"5.000000","total_taxes":"1381.560000","uses_inclusive_taxes":0,"custom_value1":null,"custom_value2":null,"custom_value3":null,"custom_value4":null,"custom_surcharge1":null,"custom_surcharge2":null,"custom_surcharge3":null,"custom_surcharge4":null,"custom_surcharge_tax1":0,"custom_surcharge_tax2":0,"custom_surcharge_tax3":0,"custom_surcharge_tax4":0,"exchange_rate":"1.000000","amount":"4557.560000","balance":"0.000000","partial":null,"partial_due_date":null,"last_viewed":null,"created_at":1695943654,"updated_at":1696118643,"deleted_at":null,"reminder1_sent":null,"reminder2_sent":null,"reminder3_sent":null,"reminder_last_sent":null,"paid_to_date":"0.000000","subscription_id":null,"hashed_id":"Wpmbk5ezJn"}]'; + + public function __construct(public Company $company) + { + } + + public function init(): self + { + + $this->variables = collect(['invoices', 'quotes', 'credits', 'purchase_orders'])->map(function ($type) { + return $this->createVariables($type); + })->toArray(); + + $this->engines['invoices'] = json_decode($this->invoice_data, true); + // $this->engines['quotes'] = json_decode($this->quote_data, true); + // $this->engines['credits'] = json_decode($this->credit_data, true); + // $this->engines['tasks'] = json_decode($this->task_data, true); + // $this->engines['projects'] = json_decode($this->project_data, true); + $this->engines['payments'] = json_decode($this->payment_data, true); + // $this->engines['purchase_orders'] = json_decode($this->purchase_order_data, true); + + // nlog("engines"); + // nlog($this->engines); + + return $this; + + } + + /** + * ['entity_type','design','settings_type','settings'] + * + * @param string $type + * @return array + */ + private function createVariables(string $type): array + { + $data = [ + 'entity_type' => rtrim($type, "s"), + 'design' => '', + 'settings_type' => 'company', + 'settings' => $this->company->settings, + ]; + + $mock = (new PdfMock($data, $this->company)); + $mock->settings = $this->company->settings; + $mock->build(); + + return [$type => $mock->getStubVariables()]; + } + + +} diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php new file mode 100644 index 0000000000..6376533001 --- /dev/null +++ b/app/Services/Template/TemplateService.php @@ -0,0 +1,782 @@ +template = $template; + $this->init(); + } + + /** + * Boot Dom Document + * + * @return self + */ + private function init(): self + { + $this->document = new \DOMDocument(); + $this->document->validateOnParse = true; + $loader = new FilesystemLoader(storage_path()); + $this->twig = new Environment($loader, [ + 'debug' => true, + ]); + + $string_extension = new StringLoaderExtension(); + $this->twig->addExtension($string_extension); + $this->twig->addExtension(new IntlExtension()); + $this->twig->addExtension(new DebugExtension()); + + $function = new TwigFunction('img', function ($string, $style = '') { + return ''; + }); + $this->twig->addFunction($function); + + $filter = new TwigFilter('sum', function (array $array, string $column) { + return array_sum(array_column($array, $column)); + }); + + $this->twig->addFilter($filter); + + return $this; + } + + /** + * Iterate through all of the + * ninja nodes + * + * @param array $data - the payload to be passed into the template + * @return self + */ + public function build(array $data): self + { + $this->compose() + ->processData($data) + ->parseNinjaBlocks() + ->processVariables($data) + ->parseVariables(); + + return $this; + } + + private function processVariables($data): self + { + $this->variables = $this->resolveHtmlEngine($data); + + return $this; + } + + public function mock(): self + { + $tm = new TemplateMock($this->company); + $tm->init(); + + $this->data = $tm->engines; + $this->variables = $tm->variables[0]; + + + $this->parseNinjaBlocks() + ->parseVariables(); + + return $this; + } + + /** + * Returns the HTML as string + * + * @return string + */ + public function getHtml(): string + { + return $this->compiled_html; + } + + public function getPdf(): mixed + { + + if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { + $pdf = (new NinjaPdf())->build($this->compiled_html); + } else { + $pdf = $this->makePdf(null, null, $this->compiled_html); + } + + return $pdf; + + } + + public function getData(): array + { + return $this->data; + } + + public function processData($data): self + { + + $this->data = $this->preProcessDataBlocks($data); + + return $this; + } + + /** + * Parses all Ninja tags in the document + * + * @return self + */ + private function parseNinjaBlocks(): self + { + $replacements = []; + + $contents = $this->document->getElementsByTagName('ninja'); + + foreach ($contents as $content) { + + $template = $content->ownerDocument->saveHTML($content); + + try { + $template = $this->twig->createTemplate(html_entity_decode($template)); + } catch(SyntaxError $e) { + nlog($e->getMessage()); + continue; + } catch(Error $e) { + nlog("error = " .$e->getMessage()); + continue; + } catch(RuntimeError $e) { + nlog("runtime = " .$e->getMessage()); + continue; + } catch(LoaderError $e) { + nlog("loader = " . $e->getMessage()); + continue; + } catch(SecurityError $e) { + nlog("security = " . $e->getMessage()); + continue; + } + + $template = $template->render($this->data); + + $f = $this->document->createDocumentFragment(); + $f->appendXML(html_entity_decode($template)); + + $replacements[] = $f; + + } + + foreach($contents as $key => $content) { + $content->parentNode->replaceChild($replacements[$key], $content); + } + + $this->save(); + + return $this; + + } + + /** + * Parses all variables in the document + * + * @return self + */ + private function parseVariables(): self + { + + $html = $this->getHtml(); + + foreach($this->variables as $key => $variable) { + + if(isset($variable['labels']) && isset($variable['values'])) { + $html = strtr($html, $variable['labels']); + $html = strtr($html, $variable['values']); + } + } + + @$this->document->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); + $this->save(); + + return $this; + } + + /** + * Saves the document and updates the compiled string. + * + * @return self + */ + private function save(): self + { + $this->compiled_html = str_replace('%24', '$', $this->document->saveHTML()); + + return $this; + } + + /** + * compose + * + * @return self + */ + private function compose(): self + { + if(!$this->template) { + return $this; + } + + $html = ''; + $html .= $this->template->design->includes; + $html .= $this->template->design->header; + $html .= $this->template->design->body; + $html .= $this->template->design->footer; + + @$this->document->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); + + return $this; + + } + + /** + * Inject the template components + * manually + * + * @return self + */ + public function setTemplate(array $partials): self + { + + $html = ''; + $html .= $partials['design']['includes']; + $html .= $partials['design']['header']; + $html .= $partials['design']['body']; + $html .= $partials['design']['footer']; + + @$this->document->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); + + return $this; + + } + + /** + * Resolves the labels and values needed to replace the string + * holders in the template. + * + * @return array + */ + private function resolveHtmlEngine(array $data): array + { + return collect($data)->map(function ($value, $key) { + + $processed = []; + + if(in_array($key, ['tasks','projects','aging']) || !$value->first()) { + return $processed; + } + + match ($key) { + 'variables' => $processed = $value->first() ?? [], + 'invoices' => $processed = (new HtmlEngine($value->first()->invitations()->first()))->generateLabelsAndValues() ?? [], + 'quotes' => $processed = (new HtmlEngine($value->first()->invitations()->first()))->generateLabelsAndValues() ?? [], + 'credits' => $processed = (new HtmlEngine($value->first()->invitations()->first()))->generateLabelsAndValues() ?? [], + 'payments' => $processed = (new PaymentHtmlEngine($value->first(), $value->first()->client->contacts()->first()))->generateLabelsAndValues() ?? [], + 'tasks' => $processed = [], + 'projects' => $processed = [], + 'purchase_orders' => (new VendorHtmlEngine($value->first()->invitations()->first()))->generateLabelsAndValues() ?? [], + 'aging' => $processed = [], + default => $processed = [], + }; + + return $processed; + + })->toArray(); + + } + + private function preProcessDataBlocks($data): array + { + return collect($data)->map(function ($value, $key) { + + $processed = []; + + match ($key) { + 'invoices' => $processed = $this->processInvoices($value), + 'quotes' => $processed = $this->processQuotes($value), + 'credits' => $processed = $this->processCredits($value), + 'payments' => $processed = $this->processPayments($value), + 'tasks' => $processed = $this->processTasks($value), + 'projects' => $processed = $this->processProjects($value), + 'purchase_orders' => $processed = $this->processPurchaseOrders($value), + 'aging' => $processed = $value, + default => $processed = [], + }; + + return $processed; + + })->toArray(); + } + + public function processInvoices($invoices): array + { + $invoices = collect($invoices) + ->map(function ($invoice) { + + $payments = []; + + if($invoice->payments ?? false) { + $payments = $invoice->payments->map(function ($payment) { + return $this->transformPayment($payment); + })->toArray(); + } + + return [ + 'amount' => Number::formatMoney($invoice->amount, $invoice->client), + 'balance' => Number::formatMoney($invoice->balance, $invoice->client), + 'balance_raw' => $invoice->balance, + 'number' => $invoice->number ?: '', + 'discount' => $invoice->discount, + 'po_number' => $invoice->po_number ?: '', + 'date' => $this->translateDate($invoice->date, $invoice->client->date_format(), $invoice->client->locale()), + 'last_sent_date' => $this->translateDate($invoice->last_sent_date, $invoice->client->date_format(), $invoice->client->locale()), + 'next_send_date' => $this->translateDate($invoice->next_send_date, $invoice->client->date_format(), $invoice->client->locale()), + 'due_date' => $this->translateDate($invoice->due_date, $invoice->client->date_format(), $invoice->client->locale()), + 'terms' => $invoice->terms ?: '', + 'public_notes' => $invoice->public_notes ?: '', + 'private_notes' => $invoice->private_notes ?: '', + 'uses_inclusive_taxes' => (bool) $invoice->uses_inclusive_taxes, + 'tax_name1' => $invoice->tax_name1 ?? '', + 'tax_rate1' => (float) $invoice->tax_rate1, + 'tax_name2' => $invoice->tax_name2 ?? '', + 'tax_rate2' => (float) $invoice->tax_rate2, + 'tax_name3' => $invoice->tax_name3 ?? '', + 'tax_rate3' => (float) $invoice->tax_rate3, + 'total_taxes' => Number::formatMoney($invoice->total_taxes, $invoice->client), + 'total_taxes_raw' => $invoice->total_taxes, + 'is_amount_discount' => (bool) $invoice->is_amount_discount ?? false, + 'footer' => $invoice->footer ?? '', + 'partial' => $invoice->partial ?? 0, + 'partial_due_date' => $this->translateDate($invoice->partial_due_date, $invoice->client->date_format(), $invoice->client->locale()), + 'custom_value1' => (string) $invoice->custom_value1 ?: '', + 'custom_value2' => (string) $invoice->custom_value2 ?: '', + 'custom_value3' => (string) $invoice->custom_value3 ?: '', + 'custom_value4' => (string) $invoice->custom_value4 ?: '', + 'custom_surcharge1' => (float) $invoice->custom_surcharge1, + 'custom_surcharge2' => (float) $invoice->custom_surcharge2, + 'custom_surcharge3' => (float) $invoice->custom_surcharge3, + 'custom_surcharge4' => (float) $invoice->custom_surcharge4, + 'exchange_rate' => (float) $invoice->exchange_rate, + 'custom_surcharge_tax1' => (bool) $invoice->custom_surcharge_tax1, + 'custom_surcharge_tax2' => (bool) $invoice->custom_surcharge_tax2, + 'custom_surcharge_tax3' => (bool) $invoice->custom_surcharge_tax3, + 'custom_surcharge_tax4' => (bool) $invoice->custom_surcharge_tax4, + 'line_items' => $invoice->line_items ? $this->padLineItems($invoice->line_items, $invoice->client): (array) [], + 'reminder1_sent' => $this->translateDate($invoice->reminder1_sent, $invoice->client->date_format(), $invoice->client->locale()), + 'reminder2_sent' => $this->translateDate($invoice->reminder2_sent, $invoice->client->date_format(), $invoice->client->locale()), + 'reminder3_sent' => $this->translateDate($invoice->reminder3_sent, $invoice->client->date_format(), $invoice->client->locale()), + 'reminder_last_sent' => $this->translateDate($invoice->reminder_last_sent, $invoice->client->date_format(), $invoice->client->locale()), + 'paid_to_date' => Number::formatMoney($invoice->paid_to_date, $invoice->client), + 'auto_bill_enabled' => (bool) $invoice->auto_bill_enabled, + 'client' => [ + 'name' => $invoice->client->present()->name(), + 'balance' => $invoice->client->balance, + 'payment_balance' => $invoice->client->payment_balance, + 'credit_balance' => $invoice->client->credit_balance, + ], + 'payments' => $payments, + 'total_tax_map' => $invoice->calc()->getTotalTaxMap(), + 'line_tax_map' => $invoice->calc()->getTaxMap(), + ]; + + }); + + return $invoices->toArray(); + + } + + public function padLineItems(array $items, Client $client): array + { + return collect($items)->map(function ($item) use ($client) { + + $item->cost_raw = $item->cost ?? 0; + $item->discount_raw = $item->discount ?? 0; + $item->line_total_raw = $item->line_total ?? 0; + $item->gross_line_total_raw = $item->gross_line_total ?? 0; + $item->tax_amount_raw = $item->tax_amount ?? 0; + $item->product_cost_raw = $item->product_cost ?? 0; + + $item->cost = Number::formatMoney($item->cost_raw, $client); + + if($item->is_amount_discount) { + $item->discount = Number::formatMoney($item->discount_raw, $client); + } + + $item->line_total = Number::formatMoney($item->line_total_raw, $client); + $item->gross_line_total = Number::formatMoney($item->gross_line_total_raw, $client); + $item->tax_amount = Number::formatMoney($item->tax_amount_raw, $client); + $item->product_cost = Number::formatMoney($item->product_cost_raw, $client); + + return $item; + + })->toArray(); + } + + private function transformPayment(Payment $payment): array + { + + $data = []; + + $credits = $payment->credits->map(function ($credit) use ($payment) { + return [ + 'credit' => $credit->number, + 'amount_raw' => $credit->pivot->amount, + 'refunded_raw' => $credit->pivot->refunded, + 'net_raw' => $credit->pivot->amount - $credit->pivot->refunded, + 'amount' => Number::formatMoney($credit->pivot->amount, $payment->client), + 'refunded' => Number::formatMoney($credit->pivot->refunded, $payment->client), + 'net' => Number::formatMoney($credit->pivot->amount - $credit->pivot->refunded, $payment->client), + 'is_credit' => true, + 'date' => $this->translateDate($credit->date, $payment->client->date_format(), $payment->client->locale()), + 'created_at' => $this->translateDate($credit->pivot->created_at, $payment->client->date_format(), $payment->client->locale()), + 'updated_at' => $this->translateDate($credit->pivot->updated_at, $payment->client->date_format(), $payment->client->locale()), + 'timestamp' => $credit->pivot->created_at->timestamp, + ]; + }); + + $pivot = $payment->invoices->map(function ($invoice) use ($payment) { + return [ + 'invoice' => $invoice->number, + 'amount_raw' => $invoice->pivot->amount, + 'refunded_raw' => $invoice->pivot->refunded, + 'net_raw' => $invoice->pivot->amount - $invoice->pivot->refunded, + 'amount' => Number::formatMoney($invoice->pivot->amount, $payment->client), + 'refunded' => Number::formatMoney($invoice->pivot->refunded, $payment->client), + 'net' => Number::formatMoney($invoice->pivot->amount - $invoice->pivot->refunded, $payment->client), + 'is_credit' => false, + 'date' => $this->translateDate($invoice->date, $payment->client->date_format(), $payment->client->locale()), + 'created_at' => $this->translateDate($invoice->pivot->created_at, $payment->client->date_format(), $payment->client->locale()), + 'updated_at' => $this->translateDate($invoice->pivot->updated_at, $payment->client->date_format(), $payment->client->locale()), + 'timestamp' => $invoice->pivot->created_at->timestamp, + ]; + })->merge($credits)->sortBy('timestamp')->toArray(); + + return [ + 'status' => $payment->stringStatus($payment->status_id), + 'badge' => $payment->badgeForStatus($payment->status_id), + 'amount' => Number::formatMoney($payment->amount, $payment->client), + 'applied' => Number::formatMoney($payment->applied, $payment->client), + 'balance' => Number::formatMoney(($payment->amount - $payment->refunded - $payment->applied), $payment->client), + 'refunded' => Number::formatMoney($payment->refunded, $payment->client), + 'amount_raw' => $payment->amount, + 'applied_raw' => $payment->applied, + 'refunded_raw' => $payment->refunded, + 'balance_raw' => ($payment->amount - $payment->refunded - $payment->applied), + 'date' => $this->translateDate($payment->date, $payment->client->date_format(), $payment->client->locale()), + 'method' => $payment->translatedType(), + 'currency' => $payment->currency->code, + 'exchange_rate' => $payment->exchange_rate, + 'transaction_reference' => $payment->transaction_reference, + 'is_manual' => $payment->is_manual, + 'number' => $payment->number, + 'custom_value1' => $payment->custom_value1 ?? '', + 'custom_value2' => $payment->custom_value2 ?? '', + 'custom_value3' => $payment->custom_value3 ?? '', + 'custom_value4' => $payment->custom_value4 ?? '', + 'created_at' => $this->translateDate($payment->created_at, $payment->client->date_format(), $payment->client->locale()), + 'updated_at' => $this->translateDate($payment->updated_at, $payment->client->date_format(), $payment->client->locale()), + 'client' => [ + 'name' => $payment->client->present()->name(), + 'balance' => $payment->client->balance, + 'payment_balance' => $payment->client->payment_balance, + 'credit_balance' => $payment->client->credit_balance, + ], + 'paymentables' => $pivot, + 'refund_activity' => $this->getPaymentRefundActivity($payment), + ]; + + nlog($data); + + return $data; + + } + + /** + * [ + "id" => 12, + "date" => "2023-10-08", + "invoices" => [ + [ + "amount" => 1, + "invoice_id" => 23, + "id" => null, + ], + ], + "q" => "/api/v1/payments/refund", + "email_receipt" => "true", + "gateway_refund" => false, + "send_email" => false, + ], + * + * @param Payment $payment + * @return array + */ + private function getPaymentRefundActivity(Payment $payment): array + { + + return collect($payment->refund_meta ?? []) + ->map(function ($refund) use ($payment) { + + $date = \Carbon\Carbon::parse($refund['date'])->addSeconds($payment->client->timezone_offset()); + $date = $this->translateDate($date, $payment->client->date_format(), $payment->client->locale()); + $entity = ctrans('texts.invoice'); + + $map = []; + + foreach($refund['invoices'] as $refunded_invoice) { + $invoice = Invoice::withTrashed()->find($refunded_invoice['invoice_id']); + $amount = Number::formatMoney($refunded_invoice['amount'], $payment->client); + $notes = ctrans('texts.status_partially_refunded_amount', ['amount' => $amount]); + + array_push($map, "{$date} {$entity} #{$invoice->number} {$notes}\n"); + + } + + return $map; + + })->flatten()->toArray(); + + } + + public function processQuotes($quotes): array + { + $it = new QuoteTransformer(); + $it->setDefaultIncludes(['client']); + $manager = new Manager(); + $manager->parseIncludes(['client']); + $resource = new \League\Fractal\Resource\Collection($quotes, $it, null); + $resources = $manager->createData($resource)->toArray(); + + foreach($resources['data'] as $key => $resource) { + + $resources['data'][$key]['client'] = $resource['client']['data'] ?? []; + $resources['data'][$key]['client']['contacts'] = $resource['client']['data']['contacts']['data'] ?? []; + + } + + return $resources['data']; + + } + + /** + * Pushes credits through the appropriate transformer + * and builds any required relationships + * + * @param mixed $credits + * @return array + */ + public function processCredits($credits): array + { + $credits = collect($credits) + ->map(function ($credit) { + + return [ + 'amount' => Number::formatMoney($credit->amount, $credit->client), + 'balance' => Number::formatMoney($credit->balance, $credit->client), + 'balance_raw' => $credit->balance, + 'number' => $credit->number ?: '', + 'discount' => $credit->discount, + 'po_number' => $credit->po_number ?: '', + 'date' => $this->translateDate($credit->date, $credit->client->date_format(), $credit->client->locale()), + 'last_sent_date' => $this->translateDate($credit->last_sent_date, $credit->client->date_format(), $credit->client->locale()), + 'next_send_date' => $this->translateDate($credit->next_send_date, $credit->client->date_format(), $credit->client->locale()), + 'due_date' => $this->translateDate($credit->due_date, $credit->client->date_format(), $credit->client->locale()), + 'terms' => $credit->terms ?: '', + 'public_notes' => $credit->public_notes ?: '', + 'private_notes' => $credit->private_notes ?: '', + 'uses_inclusive_taxes' => (bool) $credit->uses_inclusive_taxes, + 'tax_name1' => $credit->tax_name1 ?? '', + 'tax_rate1' => (float) $credit->tax_rate1, + 'tax_name2' => $credit->tax_name2 ?? '', + 'tax_rate2' => (float) $credit->tax_rate2, + 'tax_name3' => $credit->tax_name3 ?? '', + 'tax_rate3' => (float) $credit->tax_rate3, + 'total_taxes' => Number::formatMoney($credit->total_taxes, $credit->client), + 'total_taxes_raw' => $credit->total_taxes, + 'is_amount_discount' => (bool) $credit->is_amount_discount ?? false, + 'footer' => $credit->footer ?? '', + 'partial' => $credit->partial ?? 0, + 'partial_due_date' => $this->translateDate($credit->partial_due_date, $credit->client->date_format(), $credit->client->locale()), + 'custom_value1' => (string) $credit->custom_value1 ?: '', + 'custom_value2' => (string) $credit->custom_value2 ?: '', + 'custom_value3' => (string) $credit->custom_value3 ?: '', + 'custom_value4' => (string) $credit->custom_value4 ?: '', + 'custom_surcharge1' => (float) $credit->custom_surcharge1, + 'custom_surcharge2' => (float) $credit->custom_surcharge2, + 'custom_surcharge3' => (float) $credit->custom_surcharge3, + 'custom_surcharge4' => (float) $credit->custom_surcharge4, + 'exchange_rate' => (float) $credit->exchange_rate, + 'custom_surcharge_tax1' => (bool) $credit->custom_surcharge_tax1, + 'custom_surcharge_tax2' => (bool) $credit->custom_surcharge_tax2, + 'custom_surcharge_tax3' => (bool) $credit->custom_surcharge_tax3, + 'custom_surcharge_tax4' => (bool) $credit->custom_surcharge_tax4, + 'line_items' => $credit->line_items ? $this->padLineItems($credit->line_items, $credit->client): (array) [], + 'reminder1_sent' => $this->translateDate($credit->reminder1_sent, $credit->client->date_format(), $credit->client->locale()), + 'reminder2_sent' => $this->translateDate($credit->reminder2_sent, $credit->client->date_format(), $credit->client->locale()), + 'reminder3_sent' => $this->translateDate($credit->reminder3_sent, $credit->client->date_format(), $credit->client->locale()), + 'reminder_last_sent' => $this->translateDate($credit->reminder_last_sent, $credit->client->date_format(), $credit->client->locale()), + 'paid_to_date' => Number::formatMoney($credit->paid_to_date, $credit->client), + 'auto_bill_enabled' => (bool) $credit->auto_bill_enabled, + 'client' => [ + 'name' => $credit->client->present()->name(), + 'balance' => $credit->client->balance, + 'payment_balance' => $credit->client->payment_balance, + 'credit_balance' => $credit->client->credit_balance, + ], + 'payments' => [], + 'total_tax_map' => $credit->calc()->getTotalTaxMap(), + 'line_tax_map' => $credit->calc()->getTaxMap(), + ]; + + }); + + return $credits->toArray(); + + } + + + + /** + * Pushes payments through the appropriate transformer + * + * @param mixed $payments + * @return array + */ + public function processPayments($payments): array + { + + $payments = collect($payments)->map(function ($payment) { + return $this->transformPayment($payment); + })->toArray(); + + return $payments; + + + } + + public function processTasks($tasks): array + { + $it = new TaskTransformer(); + $it->setDefaultIncludes(['client','project','invoice']); + $manager = new Manager(); + $resource = new \League\Fractal\Resource\Collection($tasks, $it, null); + $resources = $manager->createData($resource)->toArray(); + + foreach($resources['data'] as $key => $resource) { + + $resources['data'][$key]['client'] = $resource['client']['data'] ?? []; + $resources['data'][$key]['client']['contacts'] = $resource['client']['data']['contacts']['data'] ?? []; + $resources['data'][$key]['project'] = $resource['project']['data'] ?? []; + $resources['data'][$key]['invoice'] = $resource['invoice'] ?? []; + + } + + return $resources['data']; + + + } + + public function processProjects($projects): array + { + + $it = new ProjectTransformer(); + $it->setDefaultIncludes(['client','tasks']); + $manager = new Manager(); + $manager->setSerializer(new ArraySerializer()); + $resource = new \League\Fractal\Resource\Collection($projects, $it, Project::class); + $i = $manager->createData($resource)->toArray(); + return $i[Project::class]; + + } + + public function processPurchaseOrders($purchase_orders): array + { + + $it = new PurchaseOrderTransformer(); + $it->setDefaultIncludes(['vendor','expense']); + $manager = new Manager(); + $manager->setSerializer(new ArraySerializer()); + $resource = new \League\Fractal\Resource\Collection($purchase_orders, $it, PurchaseOrder::class); + $i = $manager->createData($resource)->toArray(); + return $i[PurchaseOrder::class]; + + } + + public function setCompany(Company $company): self + { + $this->company = $company; + + return $this; + } + + public function getCompany(): Company + { + return $this->company; + } + + public function overrideVariables($variables): self + { + $this->variables = $variables; + + return $this; + } + +} diff --git a/app/Transformers/ActivityTransformer.php b/app/Transformers/ActivityTransformer.php index 9c910daae6..6d8f465f1e 100644 --- a/app/Transformers/ActivityTransformer.php +++ b/app/Transformers/ActivityTransformer.php @@ -11,24 +11,22 @@ namespace App\Transformers; -use App\Models\Task; -use App\Models\User; -use App\Models\Quote; +use App\Models\Activity; use App\Models\Backup; use App\Models\Client; +use App\Models\ClientContact; use App\Models\Credit; -use App\Models\Vendor; use App\Models\Expense; use App\Models\Invoice; use App\Models\Payment; -use App\Models\Activity; -use App\Models\ClientContact; use App\Models\PurchaseOrder; +use App\Models\Quote; +use App\Models\RecurringInvoice; +use App\Models\Task; +use App\Models\User; +use App\Models\Vendor; use App\Models\VendorContact; use App\Utils\Traits\MakesHash; -use App\Models\RecurringInvoice; -use App\Transformers\EntityTransformer; -use App\Transformers\InvoiceHistoryTransformer; class ActivityTransformer extends EntityTransformer { diff --git a/app/Transformers/BankIntegrationTransformer.php b/app/Transformers/BankIntegrationTransformer.php index c414e76f46..7757ee7559 100644 --- a/app/Transformers/BankIntegrationTransformer.php +++ b/app/Transformers/BankIntegrationTransformer.php @@ -12,11 +12,10 @@ namespace App\Transformers; use App\Models\Account; -use App\Models\Company; use App\Models\BankIntegration; use App\Models\BankTransaction; +use App\Models\Company; use App\Utils\Traits\MakesHash; -use App\Transformers\EntityTransformer; /** * Class BankIntegrationTransformer. diff --git a/app/Transformers/BankTransactionRuleTransformer.php b/app/Transformers/BankTransactionRuleTransformer.php index 9b9b71ed65..ca3bf7262a 100644 --- a/app/Transformers/BankTransactionRuleTransformer.php +++ b/app/Transformers/BankTransactionRuleTransformer.php @@ -11,7 +11,6 @@ namespace App\Transformers; -use App\Models\BankTransaction; use App\Models\BankTransactionRule; use App\Models\Client; use App\Models\Company; diff --git a/app/Transformers/BankTransactionTransformer.php b/app/Transformers/BankTransactionTransformer.php index dd9484c135..401d5a0a1e 100644 --- a/app/Transformers/BankTransactionTransformer.php +++ b/app/Transformers/BankTransactionTransformer.php @@ -11,11 +11,11 @@ namespace App\Transformers; -use App\Models\Vendor; +use App\Models\BankTransaction; use App\Models\Company; use App\Models\Expense; use App\Models\Payment; -use App\Models\BankTransaction; +use App\Models\Vendor; use App\Utils\Traits\MakesHash; /** diff --git a/app/Transformers/ClientTransformer.php b/app/Transformers/ClientTransformer.php index 4cfb7742b3..8bbddfa9f2 100644 --- a/app/Transformers/ClientTransformer.php +++ b/app/Transformers/ClientTransformer.php @@ -20,7 +20,6 @@ use App\Models\Document; use App\Models\GroupSetting; use App\Models\SystemLog; use App\Utils\Traits\MakesHash; -use League\Fractal\Resource\Collection; use stdClass; /** @@ -100,8 +99,9 @@ class ClientTransformer extends EntityTransformer public function includeGroupSettings(Client $client) { - if (!$client->group_settings) + if (!$client->group_settings) { return null; + } $transformer = new GroupSettingTransformer($this->serializer); diff --git a/app/Transformers/CompanyGatewayTransformer.php b/app/Transformers/CompanyGatewayTransformer.php index 9a0fd943d1..768e8f903b 100644 --- a/app/Transformers/CompanyGatewayTransformer.php +++ b/app/Transformers/CompanyGatewayTransformer.php @@ -11,12 +11,12 @@ namespace App\Transformers; -use stdClass; +use App\Models\CompanyGateway; use App\Models\Gateway; use App\Models\SystemLog; -use App\Models\CompanyGateway; use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\SoftDeletes; +use stdClass; /** * Class CompanyGatewayTransformer. diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index c3e9f25641..c7d56e27f5 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -11,43 +11,43 @@ namespace App\Transformers; -use stdClass; -use App\Models\Task; -use App\Models\User; -use App\Models\Quote; -use App\Models\Client; -use App\Models\Credit; -use App\Models\Design; -use App\Models\Vendor; use App\Models\Account; -use App\Models\Company; -use App\Models\Expense; -use App\Models\Invoice; -use App\Models\Payment; -use App\Models\Product; -use App\Models\Project; -use App\Models\TaxRate; -use App\Models\Webhook; use App\Models\Activity; -use App\Models\Document; -use App\Models\Scheduler; -use App\Models\SystemLog; -use App\Models\TaskStatus; -use App\Models\CompanyUser; -use App\Models\PaymentTerm; -use App\Models\CompanyToken; -use App\Models\GroupSetting; -use App\Models\Subscription; -use App\Models\CompanyLedger; -use App\Models\PurchaseOrder; -use App\Models\CompanyGateway; use App\Models\BankIntegration; use App\Models\BankTransaction; +use App\Models\BankTransactionRule; +use App\Models\Client; +use App\Models\Company; +use App\Models\CompanyGateway; +use App\Models\CompanyLedger; +use App\Models\CompanyToken; +use App\Models\CompanyUser; +use App\Models\Credit; +use App\Models\Design; +use App\Models\Document; +use App\Models\Expense; use App\Models\ExpenseCategory; -use App\Utils\Traits\MakesHash; +use App\Models\GroupSetting; +use App\Models\Invoice; +use App\Models\Payment; +use App\Models\PaymentTerm; +use App\Models\Product; +use App\Models\Project; +use App\Models\PurchaseOrder; +use App\Models\Quote; use App\Models\RecurringExpense; use App\Models\RecurringInvoice; -use App\Models\BankTransactionRule; +use App\Models\Scheduler; +use App\Models\Subscription; +use App\Models\SystemLog; +use App\Models\Task; +use App\Models\TaskStatus; +use App\Models\TaxRate; +use App\Models\User; +use App\Models\Vendor; +use App\Models\Webhook; +use App\Utils\Traits\MakesHash; +use stdClass; /** * Class CompanyTransformer. @@ -213,8 +213,7 @@ class CompanyTransformer extends EntityTransformer $user = auth()->user(); //if the user is attached to more than one company AND they are not an admin across all companies - if ($company->is_large || ($user->company_users()->count() > 1 && ($user->company_users()->where('is_admin', 1)->count() != $user->company_users()->count()))) - { + if ($company->is_large || ($user->company_users()->count() > 1 && ($user->company_users()->where('is_admin', 1)->count() != $user->company_users()->count()))) { return true; } diff --git a/app/Transformers/CreditTransformer.php b/app/Transformers/CreditTransformer.php index c6e2963160..9a720f9fb4 100644 --- a/app/Transformers/CreditTransformer.php +++ b/app/Transformers/CreditTransformer.php @@ -11,13 +11,13 @@ namespace App\Transformers; +use App\Models\Activity; use App\Models\Backup; use App\Models\Client; use App\Models\Credit; -use App\Models\Activity; +use App\Models\CreditInvitation; use App\Models\Document; use App\Utils\Traits\MakesHash; -use App\Models\CreditInvitation; use League\Fractal\Resource\Item; class CreditTransformer extends EntityTransformer diff --git a/app/Transformers/DesignTransformer.php b/app/Transformers/DesignTransformer.php index 953f5f1648..371733ce85 100644 --- a/app/Transformers/DesignTransformer.php +++ b/app/Transformers/DesignTransformer.php @@ -47,12 +47,15 @@ class DesignTransformer extends EntityTransformer 'name' => (string) $design->name, 'is_custom' => (bool) $design->is_custom, 'is_active' => (bool) $design->is_active, + 'is_template' => (bool) $design->is_template, 'design' => $design->design, 'updated_at' => (int) $design->updated_at, 'archived_at' => (int) $design->deleted_at, 'created_at' => (int) $design->created_at, 'is_deleted' => (bool) $design->is_deleted, 'is_free' => ($design->id <= 4) ? true : false, + 'is_template' => (bool) $design->is_template, + 'entities' => (string) $design->entities ?: '', ]; } } diff --git a/app/Transformers/ExpenseTransformer.php b/app/Transformers/ExpenseTransformer.php index 760f35909c..aa13889a92 100644 --- a/app/Transformers/ExpenseTransformer.php +++ b/app/Transformers/ExpenseTransformer.php @@ -12,15 +12,14 @@ namespace App\Transformers; use App\Models\Client; -use App\Models\Vendor; -use App\Models\Expense; -use App\Models\Invoice; use App\Models\Document; +use App\Models\Expense; use App\Models\ExpenseCategory; +use App\Models\Invoice; +use App\Models\Vendor; use App\Utils\Traits\MakesHash; -use League\Fractal\Resource\Item; use Illuminate\Database\Eloquent\SoftDeletes; -use App\Transformers\ExpenseCategoryTransformer; +use League\Fractal\Resource\Item; /** * class ExpenseTransformer. diff --git a/app/Transformers/InvoiceTransformer.php b/app/Transformers/InvoiceTransformer.php index cc271bebe4..85605ed374 100644 --- a/app/Transformers/InvoiceTransformer.php +++ b/app/Transformers/InvoiceTransformer.php @@ -14,6 +14,7 @@ namespace App\Transformers; use App\Models\Activity; use App\Models\Backup; use App\Models\Client; +use App\Models\Credit; use App\Models\Document; use App\Models\Invoice; use App\Models\InvoiceInvitation; @@ -63,6 +64,13 @@ class InvoiceTransformer extends EntityTransformer return $this->includeCollection($invoice->payments, $transformer, Payment::class); } + public function includeCredits(Invoice $invoice) + { + $transformer = new CreditTransformer($this->serializer); + + return $this->includeCollection($invoice->credits, $transformer, Credit::class); + } + /* public function includeExpenses(Invoice $invoice) { diff --git a/app/Transformers/PaymentTransformer.php b/app/Transformers/PaymentTransformer.php index 2c9df5a407..0f37438bce 100644 --- a/app/Transformers/PaymentTransformer.php +++ b/app/Transformers/PaymentTransformer.php @@ -12,10 +12,12 @@ namespace App\Transformers; use App\Models\Client; +use App\Models\Credit; use App\Models\Document; use App\Models\Invoice; use App\Models\Payment; use App\Models\Paymentable; +use App\Models\PaymentType; use App\Utils\Traits\MakesHash; class PaymentTransformer extends EntityTransformer @@ -32,6 +34,8 @@ class PaymentTransformer extends EntityTransformer protected array $availableIncludes = [ 'client', 'invoices', + 'type', + 'credits', ]; public function __construct($serializer = null) @@ -48,6 +52,13 @@ class PaymentTransformer extends EntityTransformer return $this->includeCollection($payment->invoices, $transformer, Invoice::class); } + public function includeCredits(Payment $payment) + { + $transformer = new CreditTransformer($this->serializer); + + return $this->includeCollection($payment->credits, $transformer, Credit::class); + } + public function includeClient(Payment $payment) { $transformer = new ClientTransformer($this->serializer); @@ -69,6 +80,11 @@ class PaymentTransformer extends EntityTransformer return $this->includeCollection($payment->documents, $transformer, Document::class); } + public function includeType(Payment $payment) + { + return $this->includeItem($payment, new PaymentTypeTransformer, PaymentType::class); + } + public function transform(Payment $payment) { return [ diff --git a/app/Transformers/PaymentTypeTransformer.php b/app/Transformers/PaymentTypeTransformer.php new file mode 100644 index 0000000000..3d9aae3da6 --- /dev/null +++ b/app/Transformers/PaymentTypeTransformer.php @@ -0,0 +1,25 @@ + $payment->translatedType() + ]; + } +} diff --git a/app/Transformers/ProductTransformer.php b/app/Transformers/ProductTransformer.php index bab47b6f79..cc28edc2ed 100644 --- a/app/Transformers/ProductTransformer.php +++ b/app/Transformers/ProductTransformer.php @@ -16,7 +16,6 @@ use App\Models\Document; use App\Models\Product; use App\Models\User; use App\Utils\Traits\MakesHash; -use League\Fractal\Resource\Collection; class ProductTransformer extends EntityTransformer { @@ -95,7 +94,7 @@ class ProductTransformer extends EntityTransformer 'stock_notification_threshold' => (int) $product->stock_notification_threshold, 'max_quantity' => (int) $product->max_quantity, 'product_image' => (string) $product->product_image ?: '', - 'tax_id' => (string) $product->tax_id ?: '1', + 'tax_id' => (string) $product->tax_id ?: '1', ]; } } diff --git a/app/Transformers/PurchaseOrderTransformer.php b/app/Transformers/PurchaseOrderTransformer.php index b960def789..8404dd56d7 100644 --- a/app/Transformers/PurchaseOrderTransformer.php +++ b/app/Transformers/PurchaseOrderTransformer.php @@ -11,14 +11,14 @@ namespace App\Transformers; -use App\Models\Backup; -use App\Models\Vendor; -use App\Models\Expense; use App\Models\Activity; +use App\Models\Backup; use App\Models\Document; +use App\Models\Expense; use App\Models\PurchaseOrder; -use App\Utils\Traits\MakesHash; use App\Models\PurchaseOrderInvitation; +use App\Models\Vendor; +use App\Utils\Traits\MakesHash; class PurchaseOrderTransformer extends EntityTransformer { diff --git a/app/Transformers/QuoteTransformer.php b/app/Transformers/QuoteTransformer.php index 4d4cf0ced3..649b9995e2 100644 --- a/app/Transformers/QuoteTransformer.php +++ b/app/Transformers/QuoteTransformer.php @@ -11,11 +11,11 @@ namespace App\Transformers; -use App\Models\Quote; +use App\Models\Activity; use App\Models\Backup; use App\Models\Client; -use App\Models\Activity; use App\Models\Document; +use App\Models\Quote; use App\Models\QuoteInvitation; use App\Utils\Traits\MakesHash; use League\Fractal\Resource\Item; diff --git a/app/Transformers/RecurringExpenseTransformer.php b/app/Transformers/RecurringExpenseTransformer.php index 4b48e8654e..2988115b38 100644 --- a/app/Transformers/RecurringExpenseTransformer.php +++ b/app/Transformers/RecurringExpenseTransformer.php @@ -12,12 +12,12 @@ namespace App\Transformers; use App\Models\Client; -use App\Models\Vendor; use App\Models\Document; -use App\Utils\Traits\MakesHash; use App\Models\RecurringExpense; -use League\Fractal\Resource\Item; +use App\Models\Vendor; +use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\SoftDeletes; +use League\Fractal\Resource\Item; /** * class RecurringExpenseTransformer. diff --git a/app/Transformers/TaskTransformer.php b/app/Transformers/TaskTransformer.php index b4d50535f5..131789b32f 100644 --- a/app/Transformers/TaskTransformer.php +++ b/app/Transformers/TaskTransformer.php @@ -11,13 +11,13 @@ namespace App\Transformers; -use App\Models\Task; -use App\Models\User; use App\Models\Client; +use App\Models\Document; use App\Models\Invoice; use App\Models\Project; -use App\Models\Document; +use App\Models\Task; use App\Models\TaskStatus; +use App\Models\User; use App\Utils\Traits\MakesHash; use League\Fractal\Resource\Item; @@ -55,7 +55,7 @@ class TaskTransformer extends EntityTransformer { $transformer = new InvoiceTransformer($this->serializer); - if (!$task->user) { + if (!$task->invoice) { return null; } diff --git a/app/Transformers/UserTransformer.php b/app/Transformers/UserTransformer.php index 0971c7dc2b..c0274a3e81 100644 --- a/app/Transformers/UserTransformer.php +++ b/app/Transformers/UserTransformer.php @@ -97,7 +97,7 @@ class UserTransformer extends EntityTransformer } /** - * + * * @param User $user */ public function includeCompanyUser(User $user) diff --git a/app/Transformers/VendorContactTransformer.php b/app/Transformers/VendorContactTransformer.php index ca344634ce..7a584e18ca 100644 --- a/app/Transformers/VendorContactTransformer.php +++ b/app/Transformers/VendorContactTransformer.php @@ -45,6 +45,7 @@ class VendorContactTransformer extends EntityTransformer 'custom_value4' => $vendor->custom_value4 ?: '', 'link' => $vendor->getLoginLink(), 'last_login' => (int)$vendor->last_login, + 'password' => empty($vendor->password) ? '' : '**********', ]; } } diff --git a/app/Transformers/VendorTransformer.php b/app/Transformers/VendorTransformer.php index 29aeafa703..aa3a286692 100644 --- a/app/Transformers/VendorTransformer.php +++ b/app/Transformers/VendorTransformer.php @@ -16,7 +16,6 @@ use App\Models\Document; use App\Models\Vendor; use App\Models\VendorContact; use App\Utils\Traits\MakesHash; -use League\Fractal\Resource\Collection; /** * class VendorTransformer. @@ -105,6 +104,7 @@ class VendorTransformer extends EntityTransformer 'number' => (string) $vendor->number ?: '', 'language_id' => (string) $vendor->language_id ?: '', 'classification' => (string) $vendor->classification ?: '', + 'display_name' => (string) $vendor->present()->name(), ]; } } diff --git a/app/Utils/Helpers.php b/app/Utils/Helpers.php index 0debc7da42..0bbbca13d2 100644 --- a/app/Utils/Helpers.php +++ b/app/Utils/Helpers.php @@ -68,7 +68,7 @@ class Helpers $quote_or_credit_field = true; - }elseif($custom_fields && stripos($field, 'credit') !== false && property_exists($custom_fields, $field)) { + } elseif($custom_fields && stripos($field, 'credit') !== false && property_exists($custom_fields, $field)) { $custom_field = $custom_fields->{$field}; $custom_field_parts = explode('|', $custom_field); @@ -78,9 +78,9 @@ class Helpers $quote_or_credit_field = true; - }elseif($custom_fields && stripos($field, 'credit') !== false) { + } elseif($custom_fields && stripos($field, 'credit') !== false) { $field = str_replace("credit", "invoice", $field); - }elseif($custom_fields && stripos($field, 'quote') !== false) { + } elseif($custom_fields && stripos($field, 'quote') !== false) { $field = str_replace("quote", "invoice", $field); } diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index 01d2d7d6fe..e896780c1b 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -12,22 +12,22 @@ namespace App\Utils; -use Exception; +use App\Helpers\Epc\EpcQrGenerator; +use App\Helpers\SwissQr\SwissQrGenerator; use App\Models\Account; use App\Models\Country; -use App\Models\GatewayType; -use App\Utils\Traits\AppSetup; -use App\Models\QuoteInvitation; -use App\Utils\Traits\MakesHash; use App\Models\CreditInvitation; -use App\Utils\Traits\MakesDates; +use App\Models\GatewayType; use App\Models\InvoiceInvitation; -use App\Helpers\Epc\EpcQrGenerator; +use App\Models\QuoteInvitation; +use App\Models\RecurringInvoiceInvitation; +use App\Utils\Traits\AppSetup; +use App\Utils\Traits\DesignCalculator; +use App\Utils\Traits\MakesDates; +use App\Utils\Traits\MakesHash; +use Exception; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Cache; -use App\Utils\Traits\DesignCalculator; -use App\Helpers\SwissQr\SwissQrGenerator; -use App\Models\RecurringInvoiceInvitation; class HtmlEngine { @@ -460,7 +460,7 @@ class HtmlEngine $data['$client.postal_city'] = &$data['$postal_city']; $data['$client.country'] = &$data['$country']; $data['$client.email'] = &$data['$email']; - + $data['$client.classification'] = ['value' => isset($this->client->classification) ? ctrans("texts.{$this->client->classification}") : ' ', 'label' => ctrans('texts.classification')]; $data['$client.billing_address'] = &$data['$client_address']; $data['$client.billing_address1'] = &$data['$client.address1']; $data['$client.billing_address2'] = &$data['$client.address2']; @@ -516,6 +516,7 @@ class HtmlEngine $data['$company.postal_city_state'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city_state')]; $data['$company.postal_city'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, null, $this->settings->postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city')]; $data['$company.name'] = ['value' => $this->settings->name ?: ctrans('texts.untitled_account'), 'label' => ctrans('texts.company_name')]; + $data['$company.classification'] = ['value' => ($this->settings->classification ?? false) ? ctrans("texts.{$this->settings->classification}") : ' ', 'label' => ctrans('texts.classification')]; $data['$account'] = &$data['$company.name']; $data['$company.address1'] = ['value' => $this->settings->address1 ?: ' ', 'label' => ctrans('texts.address1')]; @@ -716,7 +717,7 @@ class HtmlEngine $tax_label .= ctrans('texts.reverse_tax_info') . "
"; } - if((int)$this->client->country_id !== (int)$this->company->settings->country_id){ + if((int)$this->client->country_id !== (int)$this->company->settings->country_id) { $tax_label .= ctrans('texts.intracommunity_tax_info') . "
"; } @@ -725,7 +726,7 @@ class HtmlEngine private function getBalance() { - if($this->entity->status_id == 1){ + if($this->entity->status_id == 1) { return $this->entity->amount; } @@ -745,6 +746,20 @@ class HtmlEngine return $data; } + public function makeValuesNoPrefix() :array + { + $data = []; + + $values = $this->buildEntityDataArray(); + + foreach ($values as $key => $value) { + $data[str_replace(["$","."], ["_","_"], $key)] = $value['value']; + } + + return $data; + } + + public function generateLabelsAndValues() { $data = []; @@ -1023,8 +1038,8 @@ html { $container = $dom->createElement('div'); $container->setAttribute('style', 'display:grid; grid-auto-flow: row; grid-template-columns: repeat(2, 1fr); grid-template-rows: repeat(2, 1fr);justify-items: center;'); - foreach ($this->entity->documents()->where('is_public',true)->get() as $document) { - if (!$document->isImage()) { + foreach ($this->entity->documents()->where('is_public', true)->get() as $document) { + if (!$document->isImage()) { continue; } diff --git a/app/Utils/Ninja.php b/app/Utils/Ninja.php index ce03dfefc3..d25f23ed4a 100644 --- a/app/Utils/Ninja.php +++ b/app/Utils/Ninja.php @@ -164,8 +164,7 @@ class Ninja ]); nlog($x->body()); - } - catch (\Exception $e) { + } catch (\Exception $e) { nlog("Attempt forwarding for {$email} - {$company_key} Failed"); } } diff --git a/app/Utils/PaymentHtmlEngine.php b/app/Utils/PaymentHtmlEngine.php index e3b0be94c0..eaa8e902e4 100644 --- a/app/Utils/PaymentHtmlEngine.php +++ b/app/Utils/PaymentHtmlEngine.php @@ -64,7 +64,15 @@ class PaymentHtmlEngine $data['$amount'] = &$data['$payment.amount']; $data['$payment.date'] = ['value' => $this->translateDate($this->payment->date, $this->client->date_format(), $this->client->locale()), 'label' => ctrans('texts.payment_date')]; $data['$transaction_reference'] = ['value' => $this->payment->transaction_reference, 'label' => ctrans('texts.transaction_reference')]; - // $data['$public_notes'] = ['value' => $this->payment->public_notes, 'label' => ctrans('texts.notes')]; + + $data['$font_size'] = ['value' => $this->settings->font_size . 'px !important;', 'label' => '']; + $data['$font_name'] = ['value' => Helpers::resolveFont($this->settings->primary_font)['name'], 'label' => '']; + $data['$font_url'] = ['value' => Helpers::resolveFont($this->settings->primary_font)['url'], 'label' => '']; + $data['$secondary_font_name'] = ['value' => Helpers::resolveFont($this->settings->secondary_font)['name'], 'label' => '']; + $data['$secondary_font_url'] = ['value' => Helpers::resolveFont($this->settings->secondary_font)['url'], 'label' => '']; + $data['$invoiceninja.whitelabel'] = ['value' => 'https://invoicing.co/images/new_logo.png', 'label' => '']; + $data['$primary_color'] = ['value' => $this->settings->primary_color, 'label' => '']; + $data['$secondary_color'] = ['value' => $this->settings->secondary_color, 'label' => '']; $data['$payment1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'payment1', $this->payment->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'payment1')]; $data['$payment2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'payment2', $this->payment->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'payment2')]; diff --git a/app/Utils/PhantomJS/Phantom.php b/app/Utils/PhantomJS/Phantom.php index 8080f5c669..d0acd053b5 100644 --- a/app/Utils/PhantomJS/Phantom.php +++ b/app/Utils/PhantomJS/Phantom.php @@ -214,6 +214,8 @@ class Phantom 'options' => [ 'all_pages_header' => $entity_obj->client->getSetting('all_pages_header'), 'all_pages_footer' => $entity_obj->client->getSetting('all_pages_footer'), + 'client' => $entity_obj->client, + 'entity' => $entity_obj, ], 'process_markdown' => $entity_obj->client->company->markdown_enabled, ]; @@ -230,4 +232,5 @@ class Phantom return view('pdf.html', $data); } + } diff --git a/app/Utils/TemplateEngine.php b/app/Utils/TemplateEngine.php index 4bd3b8310a..b653921187 100644 --- a/app/Utils/TemplateEngine.php +++ b/app/Utils/TemplateEngine.php @@ -12,29 +12,27 @@ namespace App\Utils; -use DB; -use App\Models\Quote; -use App\Models\Client; -use App\Models\Credit; -use App\Models\Vendor; -use App\Models\Invoice; -use App\Models\Payment; -use Illuminate\Support\Str; -use App\Models\ClientContact; -use App\Models\PurchaseOrder; -use App\Models\VendorContact; -use App\Models\QuoteInvitation; -use App\Utils\Traits\MakesHash; -use App\Models\RecurringInvoice; -use App\Models\InvoiceInvitation; -use Illuminate\Support\Facades\App; -use App\Utils\Traits\MakesInvoiceHtml; -use App\Mail\Engine\PaymentEmailEngine; -use App\Models\PurchaseOrderInvitation; -use App\Utils\Traits\MakesTemplateData; use App\DataMapper\EmailTemplateDefaults; -use League\CommonMark\CommonMarkConverter; +use App\Mail\Engine\PaymentEmailEngine; +use App\Models\Client; +use App\Models\ClientContact; +use App\Models\Invoice; +use App\Models\InvoiceInvitation; +use App\Models\Payment; +use App\Models\PurchaseOrder; +use App\Models\PurchaseOrderInvitation; +use App\Models\Quote; +use App\Models\QuoteInvitation; +use App\Models\Vendor; +use App\Models\VendorContact; use App\Services\PdfMaker\Designs\Utilities\DesignHelpers; +use App\Utils\Traits\MakesHash; +use App\Utils\Traits\MakesInvoiceHtml; +use App\Utils\Traits\MakesTemplateData; +use DB; +use Illuminate\Support\Facades\App; +use Illuminate\Support\Str; +use League\CommonMark\CommonMarkConverter; class TemplateEngine { @@ -106,11 +104,10 @@ class TemplateEngine } elseif (stripos($this->template, 'purchase') !== false && $purchase_order = PurchaseOrder::query()->whereHas('invitations')->withTrashed()->company()->first()) { $this->entity = 'purchase_order'; $this->entity_obj = $purchase_order; - }elseif (stripos($this->template, 'payment') !== false && $payment = Payment::query()->withTrashed()->company()->first()) { + } elseif (stripos($this->template, 'payment') !== false && $payment = Payment::query()->withTrashed()->company()->first()) { $this->entity = 'payment'; $this->entity_obj = $payment; - } - elseif ($invoice = Invoice::query()->whereHas('invitations')->withTrashed()->company()->first()) { + } elseif ($invoice = Invoice::query()->whereHas('invitations')->withTrashed()->company()->first()) { /** @var \App\Models\Invoice $invoice */ $this->entity_obj = $invoice; } else { diff --git a/app/Utils/Traits/CleanLineItems.php b/app/Utils/Traits/CleanLineItems.php index 8c6a79b854..8ccb07d029 100644 --- a/app/Utils/Traits/CleanLineItems.php +++ b/app/Utils/Traits/CleanLineItems.php @@ -64,13 +64,13 @@ trait CleanLineItems if (! array_key_exists('tax_id', $item)) { $item['tax_id'] = '1'; - } - elseif(array_key_exists('tax_id', $item) && $item['tax_id'] == '') { + } elseif(array_key_exists('tax_id', $item) && $item['tax_id'] == '') { - if($item['type_id'] == '2') + if($item['type_id'] == '2') { $item['tax_id'] = '2'; - else + } else { $item['tax_id'] = '1'; + } } diff --git a/app/Utils/Traits/ClientGroupSettingsSaver.php b/app/Utils/Traits/ClientGroupSettingsSaver.php index abfb409891..2d32b34684 100644 --- a/app/Utils/Traits/ClientGroupSettingsSaver.php +++ b/app/Utils/Traits/ClientGroupSettingsSaver.php @@ -86,7 +86,7 @@ trait ClientGroupSettingsSaver unset($settings->translations); } - foreach(['translations','pdf_variables'] as $key){ + foreach(['translations','pdf_variables'] as $key) { if (property_exists($settings, $key)) { unset($settings->{$key}); } diff --git a/app/Utils/Traits/CompanyGatewayFeesAndLimitsSaver.php b/app/Utils/Traits/CompanyGatewayFeesAndLimitsSaver.php index 2b4a894094..d5b6648248 100644 --- a/app/Utils/Traits/CompanyGatewayFeesAndLimitsSaver.php +++ b/app/Utils/Traits/CompanyGatewayFeesAndLimitsSaver.php @@ -63,7 +63,7 @@ trait CompanyGatewayFeesAndLimitsSaver case 'float': case 'double': return ! is_string($value) && (is_float($value) || is_numeric(strval($value))); - // return is_float($value) || is_numeric(strval($value)); + // return is_float($value) || is_numeric(strval($value)); case 'string': return (is_string($value) && method_exists($value, '__toString')) || is_null($value) || is_string($value); case 'bool': diff --git a/app/Utils/Traits/CompanySettingsSaver.php b/app/Utils/Traits/CompanySettingsSaver.php index 915b2e88be..043d8db5c9 100644 --- a/app/Utils/Traits/CompanySettingsSaver.php +++ b/app/Utils/Traits/CompanySettingsSaver.php @@ -11,11 +11,10 @@ namespace App\Utils\Traits; -use stdClass; -use App\Utils\Ninja; -use App\Models\Company; use App\DataMapper\CompanySettings; use App\Jobs\Company\CompanyTaxRate; +use App\Models\Company; +use stdClass; /** * Class CompanySettingsSaver. @@ -28,6 +27,15 @@ use App\Jobs\Company\CompanyTaxRate; */ trait CompanySettingsSaver { + + private array $string_ids = [ + 'payment_refund_design_id', + 'payment_receipt_design_id', + 'delivery_note_design_id', + 'statement_design_id', + 'besr_id', + 'gmail_sending_user_id', + ]; /** * Saves a setting object. * @@ -79,18 +87,16 @@ trait CompanySettingsSaver $entity->settings = $company_settings; - if($entity?->calculate_taxes && $company_settings->country_id == "840" && array_key_exists('settings', $entity->getDirty()) && !$entity?->account->isFreeHostedClient()) - { + if($entity?->calculate_taxes && $company_settings->country_id == "840" && array_key_exists('settings', $entity->getDirty()) && !$entity?->account->isFreeHostedClient()) { $old_settings = $entity->getOriginal()['settings']; /** Monitor changes of the Postal code */ - if($old_settings->postal_code != $company_settings->postal_code) + if($old_settings->postal_code != $company_settings->postal_code) { CompanyTaxRate::dispatch($entity); + } - } - elseif( $entity?->calculate_taxes && $company_settings->country_id == "840" && array_key_exists('calculate_taxes', $entity->getDirty()) && $entity->getOriginal('calculate_taxes') == 0 && !$entity?->account->isFreeHostedClient()) - { + } elseif($entity?->calculate_taxes && $company_settings->country_id == "840" && array_key_exists('calculate_taxes', $entity->getDirty()) && $entity->getOriginal('calculate_taxes') == 0 && !$entity?->account->isFreeHostedClient()) { CompanyTaxRate::dispatch($entity); } @@ -131,7 +137,8 @@ trait CompanySettingsSaver elseif (substr($key, -3) == '_id' || substr($key, -14) == 'number_counter') { $value = 'integer'; - if ($key == 'besr_id') { + if(in_array($key, $this->string_ids)) { + // if ($key == 'besr_id') { $value = 'string'; } @@ -199,13 +206,17 @@ trait CompanySettingsSaver if (substr($key, -3) == '_id' || substr($key, -14) == 'number_counter') { $value = 'integer'; - if ($key == 'gmail_sending_user_id') { - $value = 'string'; + if(in_array($key, $this->string_ids)) { + $value ='string'; } - if ($key == 'besr_id') { - $value = 'string'; - } + // if ($key == 'gmail_sending_user_id') { + // $value = 'string'; + // } + + // if ($key == 'besr_id') { + // $value = 'string'; + // } if (! property_exists($settings, $key)) { continue; @@ -266,7 +277,7 @@ trait CompanySettingsSaver case 'float': case 'double': return ! is_string($value) && (is_float($value) || is_numeric(strval($value))); -// return is_float($value) || is_numeric(strval($value)); + // return is_float($value) || is_numeric(strval($value)); case 'string': return (is_string($value) && method_exists($value, '__toString')) || is_null($value) || is_string($value); case 'bool': diff --git a/app/Utils/Traits/GeneratesCounter.php b/app/Utils/Traits/GeneratesCounter.php index ab5355a329..89c69b1073 100644 --- a/app/Utils/Traits/GeneratesCounter.php +++ b/app/Utils/Traits/GeneratesCounter.php @@ -794,16 +794,16 @@ trait GeneratesCounter $replace = []; $search[] = '{$user_custom1}'; - $replace[] = $entity->user->custom_value1; + $replace[] = $entity->user->custom_value1 ?? ''; $search[] = '{$user_custom2}'; - $replace[] = $entity->user->custom_value2; + $replace[] = $entity->user->custom_value2 ?? ''; $search[] = '{$user_custom3}'; - $replace[] = $entity->user->custom_value3; + $replace[] = $entity->user->custom_value3 ?? ''; $search[] = '{$user_custom4}'; - $replace[] = $entity->user->custom_value4; + $replace[] = $entity->user->custom_value4 ?? ''; return str_replace($search, $replace, $pattern); } diff --git a/app/Utils/Traits/MakesDates.php b/app/Utils/Traits/MakesDates.php index 69ccb7a8b3..a33afbd19f 100644 --- a/app/Utils/Traits/MakesDates.php +++ b/app/Utils/Traits/MakesDates.php @@ -11,11 +11,11 @@ namespace App\Utils\Traits; +use App\DataMapper\Schedule\EmailStatement; +use App\Models\Company; +use Carbon\Carbon; use DateTime; use DateTimeZone; -use Carbon\Carbon; -use App\Models\Company; -use App\DataMapper\Schedule\EmailStatement; /** * Class MakesDates. @@ -127,8 +127,9 @@ trait MakesDates $first_month_of_year = $company ? $company?->first_month_of_year : 1; $fin_year_start = now()->createFromDate(now()->year, $first_month_of_year, 1); - if(now()->lt($fin_year_start)) + if(now()->lt($fin_year_start)) { $fin_year_start->subYearNoOverflow(); + } } @@ -161,4 +162,4 @@ trait MakesDates }; } -} \ No newline at end of file +} diff --git a/app/Utils/Traits/MakesInvoiceValues.php b/app/Utils/Traits/MakesInvoiceValues.php index 69c44fff9f..764be09690 100644 --- a/app/Utils/Traits/MakesInvoiceValues.php +++ b/app/Utils/Traits/MakesInvoiceValues.php @@ -578,7 +578,7 @@ html { '; $css .= 'font-size:'.$settings->font_size.'px;'; -// $css .= 'font-size:14px;'; + // $css .= 'font-size:14px;'; $css .= '}'; diff --git a/app/Utils/Traits/MakesReminders.php b/app/Utils/Traits/MakesReminders.php index 753fb1d986..4ef6a73683 100644 --- a/app/Utils/Traits/MakesReminders.php +++ b/app/Utils/Traits/MakesReminders.php @@ -16,7 +16,7 @@ use Illuminate\Support\Carbon; /** * Class MakesReminders. - * + * */ trait MakesReminders { @@ -34,9 +34,11 @@ trait MakesReminders case 'after_invoice_date': return Carbon::parse($this->date)->addDays($num_days_reminder)->startOfDay()->addSeconds($offset)->isSameDay(Carbon::now()); case 'before_due_date': - return Carbon::parse($this->due_date)->subDays($num_days_reminder)->startOfDay()->addSeconds($offset)->isSameDay(Carbon::now()); + $partial_or_due_date = ($this->partial > 0 && isset($this->partial_due_date)) ? $this->partial_due_date : $this->due_date; + return Carbon::parse($partial_or_due_date)->subDays($num_days_reminder)->startOfDay()->addSeconds($offset)->isSameDay(Carbon::now()); case 'after_due_date': - return Carbon::parse($this->due_date)->addDays($num_days_reminder)->startOfDay()->addSeconds($offset)->isSameDay(Carbon::now()); + $partial_or_due_date = ($this->partial > 0 && isset($this->partial_due_date)) ? $this->partial_due_date : $this->due_date; + return Carbon::parse($partial_or_due_date)->addDays($num_days_reminder)->startOfDay()->addSeconds($offset)->isSameDay(Carbon::now()); default: return null; } diff --git a/app/Utils/Traits/SettingsSaver.php b/app/Utils/Traits/SettingsSaver.php index e53a764318..71877bad31 100644 --- a/app/Utils/Traits/SettingsSaver.php +++ b/app/Utils/Traits/SettingsSaver.php @@ -18,6 +18,15 @@ use App\DataMapper\CompanySettings; */ trait SettingsSaver { + private array $string_ids = [ + 'payment_refund_design_id', + 'payment_receipt_design_id', + 'delivery_note_design_id', + 'statement_design_id', + 'besr_id', + 'gmail_sending_user_id', + ]; + /** * Used for custom validation of inbound * settings request. @@ -54,7 +63,8 @@ trait SettingsSaver elseif (substr($key, -3) == '_id' || substr($key, -14) == 'number_counter' || ($key == 'payment_terms' && property_exists($settings, $key) && strlen($settings->{$key}) >= 1) || ($key == 'valid_until' && property_exists($settings, $key) && strlen($settings->{$key}) >= 1)) { $value = 'integer'; - if ($key == 'gmail_sending_user_id' || $key == 'besr_id') { + if(in_array($key, $this->string_ids)) { + // if ($key == 'gmail_sending_user_id' || $key == 'besr_id') { $value = 'string'; } diff --git a/app/Utils/TranslationHelper.php b/app/Utils/TranslationHelper.php index 15850f62e4..20ffbd984e 100644 --- a/app/Utils/TranslationHelper.php +++ b/app/Utils/TranslationHelper.php @@ -11,8 +11,8 @@ namespace App\Utils; -use App\Models\PaymentTerm; use \Illuminate\Support\Facades\Cache; +use App\Models\PaymentTerm; use Illuminate\Support\Str; class TranslationHelper diff --git a/app/Utils/VendorHtmlEngine.php b/app/Utils/VendorHtmlEngine.php index 7bd05d7592..8a7fddfbbe 100644 --- a/app/Utils/VendorHtmlEngine.php +++ b/app/Utils/VendorHtmlEngine.php @@ -12,19 +12,19 @@ namespace App\Utils; -use Exception; use App\Models\Account; use App\Models\Country; -use App\Utils\Traits\AppSetup; -use App\Models\QuoteInvitation; use App\Models\CreditInvitation; -use App\Utils\Traits\MakesDates; use App\Models\InvoiceInvitation; +use App\Models\PurchaseOrderInvitation; +use App\Models\QuoteInvitation; +use App\Models\RecurringInvoiceInvitation; +use App\Utils\Traits\AppSetup; +use App\Utils\Traits\DesignCalculator; +use App\Utils\Traits\MakesDates; +use Exception; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Cache; -use App\Utils\Traits\DesignCalculator; -use App\Models\PurchaseOrderInvitation; -use App\Models\RecurringInvoiceInvitation; /** * Note the premise used here is that any currencies will be formatted back to the company currency and not @@ -604,54 +604,54 @@ class VendorHtmlEngine * aggregate data */ - /* + /* private function makeLineTaxes() :string { - $tax_map = $this->entity_calc->getTaxMap(); + $tax_map = $this->entity_calc->getTaxMap(); - $data = ''; + $data = ''; - foreach ($tax_map as $tax) { - $data .= ''; - $data .= ''.$tax['name'].''; - $data .= ''.Number::formatMoney($tax['total'], $this->company).''; - } + foreach ($tax_map as $tax) { + $data .= ''; + $data .= ''.$tax['name'].''; + $data .= ''.Number::formatMoney($tax['total'], $this->company).''; + } - return $data; + return $data; } private function makeTotalTaxes() :string { - $data = ''; + $data = ''; - if (! $this->entity_calc->getTotalTaxMap()) { - return $data; - } + if (! $this->entity_calc->getTotalTaxMap()) { + return $data; + } - foreach ($this->entity_calc->getTotalTaxMap() as $tax) { - $data .= ''; - $data .= ''; - $data .= ''.$tax['name'].''; - $data .= ''.Number::formatMoney($tax['total'], $this->company).''; - } + foreach ($this->entity_calc->getTotalTaxMap() as $tax) { + $data .= ''; + $data .= ''; + $data .= ''.$tax['name'].''; + $data .= ''.Number::formatMoney($tax['total'], $this->company).''; + } - return $data; + return $data; } private function parseLabelsAndValues($labels, $values, $section) :string { - $section = strtr($section, $labels); + $section = strtr($section, $labels); - return strtr($section, $values); + return strtr($section, $values); } - */ + */ /** * Builds CSS to assist with the generation * of Repeating headers and footers on the PDF. * @return string The css string - + private function generateCustomCSS() :string { $header_and_footer = ' @@ -786,7 +786,7 @@ html { $container = $dom->createElement('div'); $container->setAttribute('style', 'display:grid; grid-auto-flow: row; grid-template-columns: repeat(2, 1fr); grid-template-rows: repeat(2, 1fr);justify-items: center;'); - foreach ($this->entity->documents()->where('is_public',true)->get() as $document) { + foreach ($this->entity->documents()->where('is_public', true)->get() as $document) { if (!$document->isImage()) { continue; } @@ -852,7 +852,7 @@ html { // return ' // // - // // diff --git a/composer.json b/composer.json index b13446fdbe..50a2a41150 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ ], "type": "project", "require": { - "php": "^8.1", + "php": "^8.1|^8.2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -76,6 +76,7 @@ "omnipay/paypal": "^3.0", "payfast/payfast-php-sdk": "^1.1", "pragmarx/google2fa": "^8.0", + "predis/predis": "^2", "psr/http-message": "^1.0", "pusher/pusher-php-server": "^7.2", "razorpay/razorpay": "2.*", @@ -93,14 +94,15 @@ "symfony/mailgun-mailer": "^6.1", "symfony/postmark-mailer": "^6.1", "turbo124/beacon": "^1.5", - "predis/predis": "^2", + "twig/intl-extra": "^3.7", + "twig/twig": "^3", "twilio/sdk": "^6.40", "webpatser/laravel-countries": "dev-master#75992ad", "wepay/php-sdk": "^0.3", "wildbit/postmark-php": "^4.0" }, "require-dev": { - "php": "^8.1", + "php": "^8.1|^8.2", "barryvdh/laravel-debugbar": "^3.6", "barryvdh/laravel-ide-helper": "^2.13", "brianium/paratest": "^7", diff --git a/composer.lock b/composer.lock index cae23d0ecc..e1e0f74679 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f0ad0b9b101d54a8530ab494539e9590", + "content-hash": "ccb40a561924dd86196ff48a3c6f803d", "packages": [ { "name": "afosto/yaac", @@ -431,16 +431,16 @@ }, { "name": "aws/aws-crt-php", - "version": "v1.2.2", + "version": "v1.2.3", "source": { "type": "git", "url": "https://github.com/awslabs/aws-crt-php.git", - "reference": "2f1dc7b7eda080498be96a4a6d683a41583030e9" + "reference": "5545a4fa310aec39f54279fdacebcce33b3ff382" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/2f1dc7b7eda080498be96a4a6d683a41583030e9", - "reference": "2f1dc7b7eda080498be96a4a6d683a41583030e9", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/5545a4fa310aec39f54279fdacebcce33b3ff382", + "reference": "5545a4fa310aec39f54279fdacebcce33b3ff382", "shasum": "" }, "require": { @@ -479,26 +479,26 @@ ], "support": { "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.2" + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.3" }, - "time": "2023-07-20T16:49:55+00:00" + "time": "2023-10-16T20:10:06+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.283.0", + "version": "3.283.11", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "5084c03431ecda0003e35d7fc7a12eeca4242685" + "reference": "348b68edcc83062c329cf7540c4c92d061d27d9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5084c03431ecda0003e35d7fc7a12eeca4242685", - "reference": "5084c03431ecda0003e35d7fc7a12eeca4242685", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/348b68edcc83062c329cf7540c4c92d061d27d9c", + "reference": "348b68edcc83062c329cf7540c4c92d061d27d9c", "shasum": "" }, "require": { - "aws/aws-crt-php": "^1.0.4", + "aws/aws-crt-php": "^1.2.3", "ext-json": "*", "ext-pcre": "*", "ext-simplexml": "*", @@ -574,9 +574,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.283.0" + "source": "https://github.com/aws/aws-sdk-php/tree/3.283.11" }, - "time": "2023-10-04T18:08:32+00:00" + "time": "2023-10-24T18:10:38+00:00" }, { "name": "bacon/bacon-qr-code", @@ -686,16 +686,16 @@ }, { "name": "braintree/braintree_php", - "version": "6.13.0", + "version": "6.14.0", "source": { "type": "git", "url": "https://github.com/braintree/braintree_php.git", - "reference": "c7bdef50c9692f1f9f9bf982e452aec0d137f5ec" + "reference": "084ed5bb728bc32ad444c6d043c87b409cca72d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/braintree/braintree_php/zipball/c7bdef50c9692f1f9f9bf982e452aec0d137f5ec", - "reference": "c7bdef50c9692f1f9f9bf982e452aec0d137f5ec", + "url": "https://api.github.com/repos/braintree/braintree_php/zipball/084ed5bb728bc32ad444c6d043c87b409cca72d7", + "reference": "084ed5bb728bc32ad444c6d043c87b409cca72d7", "shasum": "" }, "require": { @@ -729,9 +729,9 @@ "description": "Braintree PHP Client Library", "support": { "issues": "https://github.com/braintree/braintree_php/issues", - "source": "https://github.com/braintree/braintree_php/tree/6.13.0" + "source": "https://github.com/braintree/braintree_php/tree/6.14.0" }, - "time": "2023-08-30T21:46:13+00:00" + "time": "2023-10-18T22:00:15+00:00" }, { "name": "brick/math", @@ -790,16 +790,16 @@ }, { "name": "checkout/checkout-sdk-php", - "version": "3.0.15", + "version": "3.0.17", "source": { "type": "git", "url": "https://github.com/checkout/checkout-sdk-php.git", - "reference": "18a2278eb28cb1141b1cb189d4a2ee86b1837350" + "reference": "dabb6dd37ad80aaa9c34e60f48f9bf8b651bdc27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/checkout/checkout-sdk-php/zipball/18a2278eb28cb1141b1cb189d4a2ee86b1837350", - "reference": "18a2278eb28cb1141b1cb189d4a2ee86b1837350", + "url": "https://api.github.com/repos/checkout/checkout-sdk-php/zipball/dabb6dd37ad80aaa9c34e60f48f9bf8b651bdc27", + "reference": "dabb6dd37ad80aaa9c34e60f48f9bf8b651bdc27", "shasum": "" }, "require": { @@ -852,9 +852,9 @@ ], "support": { "issues": "https://github.com/checkout/checkout-sdk-php/issues", - "source": "https://github.com/checkout/checkout-sdk-php/tree/3.0.15" + "source": "https://github.com/checkout/checkout-sdk-php/tree/3.0.17" }, - "time": "2023-09-19T14:42:51+00:00" + "time": "2023-10-20T22:35:30+00:00" }, { "name": "cleverit/ubl_invoice", @@ -1353,16 +1353,16 @@ }, { "name": "doctrine/dbal", - "version": "3.7.0", + "version": "3.7.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "00d03067f07482f025d41ab55e4ba0db5eca2cdf" + "reference": "5b7bd66c9ff58c04c5474ab85edce442f8081cb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/00d03067f07482f025d41ab55e4ba0db5eca2cdf", - "reference": "00d03067f07482f025d41ab55e4ba0db5eca2cdf", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/5b7bd66c9ff58c04c5474ab85edce442f8081cb2", + "reference": "5b7bd66c9ff58c04c5474ab85edce442f8081cb2", "shasum": "" }, "require": { @@ -1446,7 +1446,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.7.0" + "source": "https://github.com/doctrine/dbal/tree/3.7.1" }, "funding": [ { @@ -1462,7 +1462,7 @@ "type": "tidelift" } ], - "time": "2023-09-26T20:56:55+00:00" + "time": "2023-10-06T05:06:20+00:00" }, { "name": "doctrine/deprecations", @@ -1903,16 +1903,16 @@ }, { "name": "egulias/email-validator", - "version": "4.0.1", + "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff" + "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/3a85486b709bc384dae8eb78fb2eec649bdb64ff", - "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ebaaf5be6c0286928352e054f2d5125608e5405e", + "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e", "shasum": "" }, "require": { @@ -1921,8 +1921,8 @@ "symfony/polyfill-intl-idn": "^1.26" }, "require-dev": { - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^4.30" + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" @@ -1958,7 +1958,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/4.0.1" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.2" }, "funding": [ { @@ -1966,7 +1966,7 @@ "type": "github" } ], - "time": "2023-01-14T14:17:03+00:00" + "time": "2023-10-06T06:47:41+00:00" }, { "name": "endroid/qr-code", @@ -2232,21 +2232,21 @@ }, { "name": "fruitcake/php-cors", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/fruitcake/php-cors.git", - "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e" + "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/58571acbaa5f9f462c9c77e911700ac66f446d4e", - "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/3d158f36e7875e2f040f37bc0573956240a5a38b", + "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "symfony/http-foundation": "^4.4|^5.4|^6" + "symfony/http-foundation": "^4.4|^5.4|^6|^7" }, "require-dev": { "phpstan/phpstan": "^1.4", @@ -2256,7 +2256,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.1-dev" + "dev-master": "1.2-dev" } }, "autoload": { @@ -2287,7 +2287,7 @@ ], "support": { "issues": "https://github.com/fruitcake/php-cors/issues", - "source": "https://github.com/fruitcake/php-cors/tree/v1.2.0" + "source": "https://github.com/fruitcake/php-cors/tree/v1.3.0" }, "funding": [ { @@ -2299,7 +2299,7 @@ "type": "github" } ], - "time": "2022-02-20T15:07:15+00:00" + "time": "2023-10-12T05:21:21+00:00" }, { "name": "gocardless/gocardless-pro", @@ -2487,16 +2487,16 @@ }, { "name": "google/apiclient-services", - "version": "v0.318.0", + "version": "v0.321.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", - "reference": "908a866797b9731352e650997112c8c3a0347ac5" + "reference": "bf0aa8b1cb272f45c1f73270ad113047625c5937" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/908a866797b9731352e650997112c8c3a0347ac5", - "reference": "908a866797b9731352e650997112c8c3a0347ac5", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/bf0aa8b1cb272f45c1f73270ad113047625c5937", + "reference": "bf0aa8b1cb272f45c1f73270ad113047625c5937", "shasum": "" }, "require": { @@ -2525,22 +2525,22 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client-services/issues", - "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.318.0" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.321.0" }, - "time": "2023-10-02T01:10:14+00:00" + "time": "2023-10-23T01:08:38+00:00" }, { "name": "google/auth", - "version": "v1.30.0", + "version": "v1.32.1", "source": { "type": "git", "url": "https://github.com/googleapis/google-auth-library-php.git", - "reference": "6028b072aa444d7edecbed603431322026704627" + "reference": "999e9ce8b9d17914f04e1718271a0a46da4de2f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/6028b072aa444d7edecbed603431322026704627", - "reference": "6028b072aa444d7edecbed603431322026704627", + "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/999e9ce8b9d17914f04e1718271a0a46da4de2f3", + "reference": "999e9ce8b9d17914f04e1718271a0a46da4de2f3", "shasum": "" }, "require": { @@ -2583,9 +2583,9 @@ "support": { "docs": "https://googleapis.github.io/google-auth-library-php/main/", "issues": "https://github.com/googleapis/google-auth-library-php/issues", - "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.30.0" + "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.32.1" }, - "time": "2023-09-07T19:13:44+00:00" + "time": "2023-10-17T21:13:22+00:00" }, { "name": "graham-campbell/result-type", @@ -3397,16 +3397,16 @@ }, { "name": "horstoeko/zugferd", - "version": "v1.0.30", + "version": "v1.0.31", "source": { "type": "git", "url": "https://github.com/horstoeko/zugferd.git", - "reference": "b5e85651fe2e53eef82aa086c9245b7f5229433f" + "reference": "d1cdb2100aed1cec67b25a660da5e4cc31cf7e09" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/horstoeko/zugferd/zipball/b5e85651fe2e53eef82aa086c9245b7f5229433f", - "reference": "b5e85651fe2e53eef82aa086c9245b7f5229433f", + "url": "https://api.github.com/repos/horstoeko/zugferd/zipball/d1cdb2100aed1cec67b25a660da5e4cc31cf7e09", + "reference": "d1cdb2100aed1cec67b25a660da5e4cc31cf7e09", "shasum": "" }, "require": { @@ -3464,9 +3464,9 @@ ], "support": { "issues": "https://github.com/horstoeko/zugferd/issues", - "source": "https://github.com/horstoeko/zugferd/tree/v1.0.30" + "source": "https://github.com/horstoeko/zugferd/tree/v1.0.31" }, - "time": "2023-09-30T13:42:02+00:00" + "time": "2023-10-12T16:05:51+00:00" }, { "name": "http-interop/http-factory-guzzle", @@ -4287,16 +4287,16 @@ }, { "name": "laravel/framework", - "version": "v10.26.2", + "version": "v10.29.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "6e5440f7c518f26b4495e5d7e4796ec239e26df9" + "reference": "2d002849a16ad131110a50cbea4d64dbb78515a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/6e5440f7c518f26b4495e5d7e4796ec239e26df9", - "reference": "6e5440f7c518f26b4495e5d7e4796ec239e26df9", + "url": "https://api.github.com/repos/laravel/framework/zipball/2d002849a16ad131110a50cbea4d64dbb78515a3", + "reference": "2d002849a16ad131110a50cbea4d64dbb78515a3", "shasum": "" }, "require": { @@ -4329,7 +4329,7 @@ "symfony/console": "^6.2", "symfony/error-handler": "^6.2", "symfony/finder": "^6.2", - "symfony/http-foundation": "^6.2", + "symfony/http-foundation": "^6.3", "symfony/http-kernel": "^6.2", "symfony/mailer": "^6.2", "symfony/mime": "^6.2", @@ -4396,13 +4396,15 @@ "league/flysystem-read-only": "^3.3", "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", + "nyholm/psr7": "^1.2", "orchestra/testbench-core": "^8.12", "pda/pheanstalk": "^4.0", "phpstan/phpstan": "^1.4.7", "phpunit/phpunit": "^10.0.7", "predis/predis": "^2.0.2", "symfony/cache": "^6.2", - "symfony/http-client": "^6.2.4" + "symfony/http-client": "^6.2.4", + "symfony/psr-http-message-bridge": "^2.0" }, "suggest": { "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", @@ -4483,27 +4485,27 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-10-03T14:24:20+00:00" + "time": "2023-10-24T13:48:53+00:00" }, { "name": "laravel/prompts", - "version": "v0.1.11", + "version": "v0.1.12", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "cce65a90e64712909ea1adc033e1d88de8455ffd" + "reference": "b35f249028c22016e45e48626e19e5d42fd827ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/cce65a90e64712909ea1adc033e1d88de8455ffd", - "reference": "cce65a90e64712909ea1adc033e1d88de8455ffd", + "url": "https://api.github.com/repos/laravel/prompts/zipball/b35f249028c22016e45e48626e19e5d42fd827ff", + "reference": "b35f249028c22016e45e48626e19e5d42fd827ff", "shasum": "" }, "require": { "ext-mbstring": "*", "illuminate/collections": "^10.0|^11.0", "php": "^8.1", - "symfony/console": "^6.2" + "symfony/console": "^6.2|^7.0" }, "conflict": { "illuminate/console": ">=10.17.0 <10.25.0", @@ -4538,22 +4540,22 @@ ], "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.1.11" + "source": "https://github.com/laravel/prompts/tree/v0.1.12" }, - "time": "2023-10-03T01:07:35+00:00" + "time": "2023-10-18T14:18:57+00:00" }, { "name": "laravel/serializable-closure", - "version": "v1.3.1", + "version": "v1.3.2", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "e5a3057a5591e1cfe8183034b0203921abe2c902" + "reference": "076fe2cf128bd54b4341cdc6d49b95b34e101e4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/e5a3057a5591e1cfe8183034b0203921abe2c902", - "reference": "e5a3057a5591e1cfe8183034b0203921abe2c902", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/076fe2cf128bd54b4341cdc6d49b95b34e101e4c", + "reference": "076fe2cf128bd54b4341cdc6d49b95b34e101e4c", "shasum": "" }, "require": { @@ -4600,7 +4602,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2023-07-14T13:56:28+00:00" + "time": "2023-10-17T13:38:16+00:00" }, { "name": "laravel/slack-notification-channel", @@ -5280,16 +5282,16 @@ }, { "name": "league/flysystem", - "version": "3.16.0", + "version": "3.18.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "4fdf372ca6b63c6e281b1c01a624349ccb757729" + "reference": "015633a05aee22490495159237a5944091d8281e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/4fdf372ca6b63c6e281b1c01a624349ccb757729", - "reference": "4fdf372ca6b63c6e281b1c01a624349ccb757729", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/015633a05aee22490495159237a5944091d8281e", + "reference": "015633a05aee22490495159237a5944091d8281e", "shasum": "" }, "require": { @@ -5307,8 +5309,8 @@ "symfony/http-client": "<5.2" }, "require-dev": { - "async-aws/s3": "^1.5", - "async-aws/simple-s3": "^1.1", + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", "aws/aws-sdk-php": "^3.220.0", "composer/semver": "^3.0", "ext-fileinfo": "*", @@ -5318,7 +5320,7 @@ "google/cloud-storage": "^1.23", "microsoft/azure-storage-blob": "^1.1", "phpseclib/phpseclib": "^3.0.14", - "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.5.11|^10.0", "sabre/dav": "^4.3.1" }, @@ -5354,7 +5356,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.16.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.18.0" }, "funding": [ { @@ -5366,7 +5368,7 @@ "type": "github" } ], - "time": "2023-09-07T19:22:17+00:00" + "time": "2023-10-20T17:59:40+00:00" }, { "name": "league/flysystem-aws-s3-v3", @@ -5436,16 +5438,16 @@ }, { "name": "league/flysystem-local", - "version": "3.16.0", + "version": "3.18.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "ec7383f25642e6fd4bb0c9554fc2311245391781" + "reference": "e7381ef7643f658b87efb7dbe98fe538fb1bbf32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/ec7383f25642e6fd4bb0c9554fc2311245391781", - "reference": "ec7383f25642e6fd4bb0c9554fc2311245391781", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e7381ef7643f658b87efb7dbe98fe538fb1bbf32", + "reference": "e7381ef7643f658b87efb7dbe98fe538fb1bbf32", "shasum": "" }, "require": { @@ -5480,7 +5482,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem-local/issues", - "source": "https://github.com/thephpleague/flysystem-local/tree/3.16.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.18.0" }, "funding": [ { @@ -5492,7 +5494,7 @@ "type": "github" } ], - "time": "2023-08-30T10:23:59+00:00" + "time": "2023-10-19T20:07:13+00:00" }, { "name": "league/fractal", @@ -5566,16 +5568,16 @@ }, { "name": "league/mime-type-detection", - "version": "1.13.0", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "a6dfb1194a2946fcdc1f38219445234f65b35c96" + "reference": "b6a5854368533df0295c5761a0253656a2e52d9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/a6dfb1194a2946fcdc1f38219445234f65b35c96", - "reference": "a6dfb1194a2946fcdc1f38219445234f65b35c96", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/b6a5854368533df0295c5761a0253656a2e52d9e", + "reference": "b6a5854368533df0295c5761a0253656a2e52d9e", "shasum": "" }, "require": { @@ -5606,7 +5608,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.13.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.14.0" }, "funding": [ { @@ -5618,7 +5620,7 @@ "type": "tidelift" } ], - "time": "2023-08-05T12:09:49+00:00" + "time": "2023-10-17T14:13:20+00:00" }, { "name": "league/oauth1-client", @@ -5834,16 +5836,16 @@ }, { "name": "microsoft/microsoft-graph", - "version": "1.107.0", + "version": "1.108.0", "source": { "type": "git", "url": "https://github.com/microsoftgraph/msgraph-sdk-php.git", - "reference": "63fed05d4d9c348db094f8d8a1d44ff9ce6887c7" + "reference": "96b940ab530c693832648bdf5ffbb975e29d2233" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/zipball/63fed05d4d9c348db094f8d8a1d44ff9ce6887c7", - "reference": "63fed05d4d9c348db094f8d8a1d44ff9ce6887c7", + "url": "https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/zipball/96b940ab530c693832648bdf5ffbb975e29d2233", + "reference": "96b940ab530c693832648bdf5ffbb975e29d2233", "shasum": "" }, "require": { @@ -5880,22 +5882,22 @@ "homepage": "https://developer.microsoft.com/en-us/graph", "support": { "issues": "https://github.com/microsoftgraph/msgraph-sdk-php/issues", - "source": "https://github.com/microsoftgraph/msgraph-sdk-php/tree/1.107.0" + "source": "https://github.com/microsoftgraph/msgraph-sdk-php/tree/1.108.0" }, - "time": "2023-09-27T06:43:40+00:00" + "time": "2023-10-11T11:00:38+00:00" }, { "name": "mollie/mollie-api-php", - "version": "v2.61.0", + "version": "v2.62.0", "source": { "type": "git", "url": "https://github.com/mollie/mollie-api-php.git", - "reference": "d3ec7a191985aa57bec9b4425a665e95b4ba346a" + "reference": "feb6d52859ed1ea7a65b25bb6cbfaadb04b33827" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mollie/mollie-api-php/zipball/d3ec7a191985aa57bec9b4425a665e95b4ba346a", - "reference": "d3ec7a191985aa57bec9b4425a665e95b4ba346a", + "url": "https://api.github.com/repos/mollie/mollie-api-php/zipball/feb6d52859ed1ea7a65b25bb6cbfaadb04b33827", + "reference": "feb6d52859ed1ea7a65b25bb6cbfaadb04b33827", "shasum": "" }, "require": { @@ -5972,9 +5974,9 @@ ], "support": { "issues": "https://github.com/mollie/mollie-api-php/issues", - "source": "https://github.com/mollie/mollie-api-php/tree/v2.61.0" + "source": "https://github.com/mollie/mollie-api-php/tree/v2.62.0" }, - "time": "2023-07-31T15:37:46+00:00" + "time": "2023-10-23T11:22:58+00:00" }, { "name": "moneyphp/money", @@ -6476,16 +6478,16 @@ }, { "name": "nette/schema", - "version": "v1.2.4", + "version": "v1.2.5", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab" + "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/c9ff517a53903b3d4e29ec547fb20feecb05b8ab", - "reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab", + "url": "https://api.github.com/repos/nette/schema/zipball/0462f0166e823aad657c9224d0f849ecac1ba10a", + "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a", "shasum": "" }, "require": { @@ -6532,9 +6534,9 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.4" + "source": "https://github.com/nette/schema/tree/v1.2.5" }, - "time": "2023-08-05T18:56:25+00:00" + "time": "2023-10-05T20:37:59+00:00" }, { "name": "nette/utils", @@ -6766,16 +6768,16 @@ }, { "name": "nwidart/laravel-modules", - "version": "v10.0.0", + "version": "10.0.2", "source": { "type": "git", "url": "https://github.com/nWidart/laravel-modules.git", - "reference": "35e514f13cb8ae8dce093e9794785fea27319d81" + "reference": "ef67a7367223ab96539136116d69405d1c21c321" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nWidart/laravel-modules/zipball/35e514f13cb8ae8dce093e9794785fea27319d81", - "reference": "35e514f13cb8ae8dce093e9794785fea27319d81", + "url": "https://api.github.com/repos/nWidart/laravel-modules/zipball/ef67a7367223ab96539136116d69405d1c21c321", + "reference": "ef67a7367223ab96539136116d69405d1c21c321", "shasum": "" }, "require": { @@ -6835,7 +6837,7 @@ ], "support": { "issues": "https://github.com/nWidart/laravel-modules/issues", - "source": "https://github.com/nWidart/laravel-modules/tree/v10.0.0" + "source": "https://github.com/nWidart/laravel-modules/tree/10.0.2" }, "funding": [ { @@ -6843,7 +6845,7 @@ "type": "github" } ], - "time": "2023-02-16T11:08:15+00:00" + "time": "2023-10-18T19:14:29+00:00" }, { "name": "nyholm/psr7", @@ -7285,22 +7287,22 @@ }, { "name": "payfast/payfast-php-sdk", - "version": "v1.1.4", + "version": "v1.1.5", "source": { "type": "git", - "url": "https://github.com/PayFast/payfast-php-sdk.git", - "reference": "897e88dabc99283e891d6f8dbad25ccca7a787b9" + "url": "https://github.com/Payfast/payfast-php-sdk.git", + "reference": "902b2cfa7318ad947ed0eba953eea4a3831c526a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PayFast/payfast-php-sdk/zipball/897e88dabc99283e891d6f8dbad25ccca7a787b9", - "reference": "897e88dabc99283e891d6f8dbad25ccca7a787b9", + "url": "https://api.github.com/repos/Payfast/payfast-php-sdk/zipball/902b2cfa7318ad947ed0eba953eea4a3831c526a", + "reference": "902b2cfa7318ad947ed0eba953eea4a3831c526a", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/guzzle": ">=6.0.0", - "php": ">=7.2.5" + "php": ">=8.1" }, "require-dev": { "phpunit/phpunit": "^9" @@ -7308,7 +7310,7 @@ "type": "library", "autoload": { "psr-4": { - "PayFast\\": "lib/" + "Payfast\\": "lib/" } }, "notification-url": "https://packagist.org/downloads/", @@ -7317,11 +7319,11 @@ ], "authors": [ { - "name": "Claire Grant", - "email": "claire.grant@payfast.co.za" + "name": "Payfast", + "email": "support@payfast.help" } ], - "description": "PayFast PHP Library", + "description": "Payfast PHP Library", "keywords": [ "api", "onsite", @@ -7329,11 +7331,11 @@ "php" ], "support": { - "issues": "https://github.com/PayFast/payfast-php-sdk/issues", - "source": "https://github.com/PayFast/payfast-php-sdk/tree/v1.1.4" + "issues": "https://github.com/Payfast/payfast-php-sdk/issues", + "source": "https://github.com/Payfast/payfast-php-sdk/tree/v1.1.5" }, "abandoned": true, - "time": "2022-12-20T10:39:51+00:00" + "time": "2023-10-11T09:57:01+00:00" }, { "name": "php-http/client-common", @@ -7727,31 +7729,26 @@ }, { "name": "php-http/promise", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/php-http/promise.git", - "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88" + "reference": "ef4905bfb492ff389eb7f12e26925a0f20073050" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/promise/zipball/4c4c1f9b7289a2ec57cde7f1e9762a5789506f88", - "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88", + "url": "https://api.github.com/repos/php-http/promise/zipball/ef4905bfb492ff389eb7f12e26925a0f20073050", + "reference": "ef4905bfb492ff389eb7f12e26925a0f20073050", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "friends-of-phpspec/phpspec-code-coverage": "^4.3.2", - "phpspec/phpspec": "^5.1.2 || ^6.2" + "friends-of-phpspec/phpspec-code-coverage": "^4.3.2 || ^6.3", + "phpspec/phpspec": "^5.1.2 || ^6.2 || ^7.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, "autoload": { "psr-4": { "Http\\Promise\\": "src/" @@ -7778,9 +7775,9 @@ ], "support": { "issues": "https://github.com/php-http/promise/issues", - "source": "https://github.com/php-http/promise/tree/1.1.0" + "source": "https://github.com/php-http/promise/tree/1.2.0" }, - "time": "2020-07-07T09:29:14+00:00" + "time": "2023-10-24T09:20:26+00:00" }, { "name": "php-jsonpointer/php-jsonpointer", @@ -8026,16 +8023,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.23", + "version": "3.0.33", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "866cc78fbd82462ffd880e3f65692afe928bed50" + "reference": "33fa69b2514a61138dd48e7a49f99445711e0ad0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/866cc78fbd82462ffd880e3f65692afe928bed50", - "reference": "866cc78fbd82462ffd880e3f65692afe928bed50", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/33fa69b2514a61138dd48e7a49f99445711e0ad0", + "reference": "33fa69b2514a61138dd48e7a49f99445711e0ad0", "shasum": "" }, "require": { @@ -8116,7 +8113,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.23" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.33" }, "funding": [ { @@ -8132,7 +8129,7 @@ "type": "tidelift" } ], - "time": "2023-09-18T17:22:01+00:00" + "time": "2023-10-21T14:00:39+00:00" }, { "name": "phpstan/phpdoc-parser", @@ -8757,16 +8754,16 @@ }, { "name": "psy/psysh", - "version": "v0.11.21", + "version": "v0.11.22", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "bcb22101107f3bf770523b65630c9d547f60c540" + "reference": "128fa1b608be651999ed9789c95e6e2a31b5802b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/bcb22101107f3bf770523b65630c9d547f60c540", - "reference": "bcb22101107f3bf770523b65630c9d547f60c540", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/128fa1b608be651999ed9789c95e6e2a31b5802b", + "reference": "128fa1b608be651999ed9789c95e6e2a31b5802b", "shasum": "" }, "require": { @@ -8795,7 +8792,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "0.11.x-dev" + "dev-0.11": "0.11.x-dev" }, "bamarni-bin": { "bin-links": false, @@ -8831,9 +8828,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.21" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.22" }, - "time": "2023-09-17T21:15:54+00:00" + "time": "2023-10-14T21:56:36+00:00" }, { "name": "pusher/pusher-php-server", @@ -9460,16 +9457,16 @@ }, { "name": "sentry/sentry", - "version": "3.21.0", + "version": "3.22.0", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-php.git", - "reference": "624aafc22b84b089ffa43b71fb01e0096505ec4f" + "reference": "c0e3df5a5c1d133cd9461e7672568ff07042c19d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/624aafc22b84b089ffa43b71fb01e0096505ec4f", - "reference": "624aafc22b84b089ffa43b71fb01e0096505ec4f", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/c0e3df5a5c1d133cd9461e7672568ff07042c19d", + "reference": "c0e3df5a5c1d133cd9461e7672568ff07042c19d", "shasum": "" }, "require": { @@ -9487,7 +9484,7 @@ "psr/http-factory": "^1.0", "psr/http-factory-implementation": "^1.0", "psr/log": "^1.0|^2.0|^3.0", - "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0", + "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0|^7.0", "symfony/polyfill-php80": "^1.17" }, "conflict": { @@ -9544,7 +9541,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-php/issues", - "source": "https://github.com/getsentry/sentry-php/tree/3.21.0" + "source": "https://github.com/getsentry/sentry-php/tree/3.22.0" }, "funding": [ { @@ -9556,20 +9553,20 @@ "type": "custom" } ], - "time": "2023-07-31T15:31:24+00:00" + "time": "2023-10-23T20:34:53+00:00" }, { "name": "sentry/sentry-laravel", - "version": "3.8.1", + "version": "3.8.2", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-laravel.git", - "reference": "b6142a80fa9360a10b786d2da032339602d0e362" + "reference": "1293e5732f8405e12f000cdf5dee78c927a18de0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/b6142a80fa9360a10b786d2da032339602d0e362", - "reference": "b6142a80fa9360a10b786d2da032339602d0e362", + "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/1293e5732f8405e12f000cdf5dee78c927a18de0", + "reference": "1293e5732f8405e12f000cdf5dee78c927a18de0", "shasum": "" }, "require": { @@ -9636,7 +9633,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-laravel/issues", - "source": "https://github.com/getsentry/sentry-laravel/tree/3.8.1" + "source": "https://github.com/getsentry/sentry-laravel/tree/3.8.2" }, "funding": [ { @@ -9648,7 +9645,7 @@ "type": "custom" } ], - "time": "2023-10-04T10:21:16+00:00" + "time": "2023-10-12T14:38:46+00:00" }, { "name": "setasign/fpdf", @@ -10080,16 +10077,16 @@ }, { "name": "spatie/laravel-data", - "version": "3.9.0", + "version": "3.9.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-data.git", - "reference": "21bad55113a1e1e5180a0f89b695f02ce1732aef" + "reference": "dea1e755549a9b54f5895097cc56b9f244f97867" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-data/zipball/21bad55113a1e1e5180a0f89b695f02ce1732aef", - "reference": "21bad55113a1e1e5180a0f89b695f02ce1732aef", + "url": "https://api.github.com/repos/spatie/laravel-data/zipball/dea1e755549a9b54f5895097cc56b9f244f97867", + "reference": "dea1e755549a9b54f5895097cc56b9f244f97867", "shasum": "" }, "require": { @@ -10151,7 +10148,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-data/issues", - "source": "https://github.com/spatie/laravel-data/tree/3.9.0" + "source": "https://github.com/spatie/laravel-data/tree/3.9.2" }, "funding": [ { @@ -10159,7 +10156,7 @@ "type": "github" } ], - "time": "2023-09-15T12:04:39+00:00" + "time": "2023-10-20T10:18:36+00:00" }, { "name": "spatie/laravel-package-tools", @@ -10339,16 +10336,16 @@ }, { "name": "stripe/stripe-php", - "version": "v12.5.0", + "version": "v12.8.0", "source": { "type": "git", "url": "https://github.com/stripe/stripe-php.git", - "reference": "a4249b4a90437844f6c35e8701f8c68acd206f56" + "reference": "6b6f4a775ad46fee4b1df2df4fdfa574365b1621" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stripe/stripe-php/zipball/a4249b4a90437844f6c35e8701f8c68acd206f56", - "reference": "a4249b4a90437844f6c35e8701f8c68acd206f56", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/6b6f4a775ad46fee4b1df2df4fdfa574365b1621", + "reference": "6b6f4a775ad46fee4b1df2df4fdfa574365b1621", "shasum": "" }, "require": { @@ -10359,7 +10356,6 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "3.5.0", - "php-coveralls/php-coveralls": "^2.5", "phpstan/phpstan": "^1.2", "phpunit/phpunit": "^5.7 || ^9.0" }, @@ -10393,9 +10389,9 @@ ], "support": { "issues": "https://github.com/stripe/stripe-php/issues", - "source": "https://github.com/stripe/stripe-php/tree/v12.5.0" + "source": "https://github.com/stripe/stripe-php/tree/v12.8.0" }, - "time": "2023-09-28T23:06:27+00:00" + "time": "2023-10-16T18:04:12+00:00" }, { "name": "symfony/console", @@ -10978,16 +10974,16 @@ }, { "name": "symfony/http-client", - "version": "v6.3.5", + "version": "v6.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "213e564da4cbf61acc9728d97e666bcdb868c10d" + "reference": "ab8446f997efb9913627e9da10fa784d2182fe92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/213e564da4cbf61acc9728d97e666bcdb868c10d", - "reference": "213e564da4cbf61acc9728d97e666bcdb868c10d", + "url": "https://api.github.com/repos/symfony/http-client/zipball/ab8446f997efb9913627e9da10fa784d2182fe92", + "reference": "ab8446f997efb9913627e9da10fa784d2182fe92", "shasum": "" }, "require": { @@ -11050,7 +11046,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.3.5" + "source": "https://github.com/symfony/http-client/tree/v6.3.6" }, "funding": [ { @@ -11066,7 +11062,7 @@ "type": "tidelift" } ], - "time": "2023-09-29T15:57:12+00:00" + "time": "2023-10-06T10:08:56+00:00" }, { "name": "symfony/http-client-contracts", @@ -11148,16 +11144,16 @@ }, { "name": "symfony/http-foundation", - "version": "v6.3.5", + "version": "v6.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "b50f5e281d722cb0f4c296f908bacc3e2b721957" + "reference": "c186627f52febe09c6d5270b04f8462687a250a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/b50f5e281d722cb0f4c296f908bacc3e2b721957", - "reference": "b50f5e281d722cb0f4c296f908bacc3e2b721957", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/c186627f52febe09c6d5270b04f8462687a250a6", + "reference": "c186627f52febe09c6d5270b04f8462687a250a6", "shasum": "" }, "require": { @@ -11167,12 +11163,12 @@ "symfony/polyfill-php83": "^1.27" }, "conflict": { - "symfony/cache": "<6.2" + "symfony/cache": "<6.3" }, "require-dev": { - "doctrine/dbal": "^2.13.1|^3.0", + "doctrine/dbal": "^2.13.1|^3|^4", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^5.4|^6.0", + "symfony/cache": "^6.3", "symfony/dependency-injection": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", @@ -11205,7 +11201,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.3.5" + "source": "https://github.com/symfony/http-foundation/tree/v6.3.6" }, "funding": [ { @@ -11221,20 +11217,20 @@ "type": "tidelift" } ], - "time": "2023-09-04T21:33:54+00:00" + "time": "2023-10-17T11:32:53+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.3.5", + "version": "v6.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "9f991a964368bee8d883e8d57ced4fe9fff04dfc" + "reference": "4945f5001b06ff9080cd3d8f1f9f069094c0d156" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/9f991a964368bee8d883e8d57ced4fe9fff04dfc", - "reference": "9f991a964368bee8d883e8d57ced4fe9fff04dfc", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/4945f5001b06ff9080cd3d8f1f9f069094c0d156", + "reference": "4945f5001b06ff9080cd3d8f1f9f069094c0d156", "shasum": "" }, "require": { @@ -11318,7 +11314,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.3.5" + "source": "https://github.com/symfony/http-kernel/tree/v6.3.6" }, "funding": [ { @@ -11334,7 +11330,7 @@ "type": "tidelift" } ], - "time": "2023-09-30T06:37:04+00:00" + "time": "2023-10-21T13:12:51+00:00" }, { "name": "symfony/intl", @@ -11500,16 +11496,16 @@ }, { "name": "symfony/mailgun-mailer", - "version": "v6.3.5", + "version": "v6.3.6", "source": { "type": "git", "url": "https://github.com/symfony/mailgun-mailer.git", - "reference": "b467aba49c8240a71f7027c213d9d140ba1abce7" + "reference": "8d9741467c53750dc8ccda23a1cdb91cda732571" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/b467aba49c8240a71f7027c213d9d140ba1abce7", - "reference": "b467aba49c8240a71f7027c213d9d140ba1abce7", + "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/8d9741467c53750dc8ccda23a1cdb91cda732571", + "reference": "8d9741467c53750dc8ccda23a1cdb91cda732571", "shasum": "" }, "require": { @@ -11549,7 +11545,7 @@ "description": "Symfony Mailgun Mailer Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailgun-mailer/tree/v6.3.5" + "source": "https://github.com/symfony/mailgun-mailer/tree/v6.3.6" }, "funding": [ { @@ -11565,7 +11561,7 @@ "type": "tidelift" } ], - "time": "2023-09-29T17:30:10+00:00" + "time": "2023-10-12T13:32:47+00:00" }, { "name": "symfony/mime", @@ -13261,16 +13257,16 @@ }, { "name": "symfony/translation", - "version": "v6.3.3", + "version": "v6.3.6", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "3ed078c54bc98bbe4414e1e9b2d5e85ed5a5c8bd" + "reference": "869b26c7a9d4b8a48afdd77ab36031909c87e3a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/3ed078c54bc98bbe4414e1e9b2d5e85ed5a5c8bd", - "reference": "3ed078c54bc98bbe4414e1e9b2d5e85ed5a5c8bd", + "url": "https://api.github.com/repos/symfony/translation/zipball/869b26c7a9d4b8a48afdd77ab36031909c87e3a2", + "reference": "869b26c7a9d4b8a48afdd77ab36031909c87e3a2", "shasum": "" }, "require": { @@ -13336,7 +13332,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.3.3" + "source": "https://github.com/symfony/translation/tree/v6.3.6" }, "funding": [ { @@ -13352,7 +13348,7 @@ "type": "tidelift" } ], - "time": "2023-07-31T07:08:24+00:00" + "time": "2023-10-17T11:32:53+00:00" }, { "name": "symfony/translation-contracts", @@ -13508,16 +13504,16 @@ }, { "name": "symfony/validator", - "version": "v6.3.5", + "version": "v6.3.6", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "48e815ba3b5eb72e632588dbf7ea2dc4e608ee47" + "reference": "254290aa13d591883eb36327cbe80689cee38ffb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/48e815ba3b5eb72e632588dbf7ea2dc4e608ee47", - "reference": "48e815ba3b5eb72e632588dbf7ea2dc4e608ee47", + "url": "https://api.github.com/repos/symfony/validator/zipball/254290aa13d591883eb36327cbe80689cee38ffb", + "reference": "254290aa13d591883eb36327cbe80689cee38ffb", "shasum": "" }, "require": { @@ -13584,7 +13580,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v6.3.5" + "source": "https://github.com/symfony/validator/tree/v6.3.6" }, "funding": [ { @@ -13600,20 +13596,20 @@ "type": "tidelift" } ], - "time": "2023-09-29T07:41:15+00:00" + "time": "2023-10-20T16:20:17+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.3.5", + "version": "v6.3.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "3d9999376be5fea8de47752837a3e1d1c5f69ef5" + "reference": "999ede244507c32b8e43aebaa10e9fce20de7c97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3d9999376be5fea8de47752837a3e1d1c5f69ef5", - "reference": "3d9999376be5fea8de47752837a3e1d1c5f69ef5", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/999ede244507c32b8e43aebaa10e9fce20de7c97", + "reference": "999ede244507c32b8e43aebaa10e9fce20de7c97", "shasum": "" }, "require": { @@ -13668,7 +13664,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.3.5" + "source": "https://github.com/symfony/var-dumper/tree/v6.3.6" }, "funding": [ { @@ -13684,7 +13680,7 @@ "type": "tidelift" } ], - "time": "2023-09-12T10:11:35+00:00" + "time": "2023-10-12T18:45:56+00:00" }, { "name": "symfony/yaml", @@ -13873,6 +13869,141 @@ }, "time": "2023-10-01T07:13:02+00:00" }, + { + "name": "twig/intl-extra", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/twigphp/intl-extra.git", + "reference": "4f4fe572f635534649cc069e1dafe4a8ad63774d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/4f4fe572f635534649cc069e1dafe4a8ad63774d", + "reference": "4f4fe572f635534649cc069e1dafe4a8ad63774d", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/intl": "^5.4|^6.0", + "twig/twig": "^2.7|^3.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5.4|^6.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Twig\\Extra\\Intl\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Twig extension for Intl", + "homepage": "https://twig.symfony.com", + "keywords": [ + "intl", + "twig" + ], + "support": { + "source": "https://github.com/twigphp/intl-extra/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2023-07-29T15:34:56+00:00" + }, + { + "name": "twig/twig", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", + "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2023-08-28T11:09:02+00:00" + }, { "name": "twilio/sdk", "version": "6.44.4", @@ -14528,16 +14659,16 @@ }, { "name": "brianium/paratest", - "version": "v7.2.8", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "882b02d197328138686bb06ce7d8cbb98fc0a16c" + "reference": "2951d3f773ea91451c7440f48122287778634b0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/882b02d197328138686bb06ce7d8cbb98fc0a16c", - "reference": "882b02d197328138686bb06ce7d8cbb98fc0a16c", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/2951d3f773ea91451c7440f48122287778634b0d", + "reference": "2951d3f773ea91451c7440f48122287778634b0d", "shasum": "" }, "require": { @@ -14545,28 +14676,28 @@ "ext-pcre": "*", "ext-reflection": "*", "ext-simplexml": "*", - "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1", + "fidry/cpu-core-counter": "^0.5.1", "jean85/pretty-package-versions": "^2.0.5", "php": "~8.1.0 || ~8.2.0 || ~8.3.0", - "phpunit/php-code-coverage": "^10.1.3", - "phpunit/php-file-iterator": "^4.0.2", + "phpunit/php-code-coverage": "^10.1.7", + "phpunit/php-file-iterator": "^4.1.0", "phpunit/php-timer": "^6.0", - "phpunit/phpunit": "^10.3.2", + "phpunit/phpunit": "^10.4.1", "sebastian/environment": "^6.0.1", - "symfony/console": "^6.3.4", - "symfony/process": "^6.3.4" + "symfony/console": "^6.3.4 || ^7.0.0", + "symfony/process": "^6.3.4 || ^7.0.0" }, "require-dev": { "doctrine/coding-standard": "^12.0.0", "ext-pcov": "*", "ext-posix": "*", - "infection/infection": "^0.27.0", - "phpstan/phpstan": "^1.10.32", + "infection/infection": "^0.27.4", + "phpstan/phpstan": "^1.10.38", "phpstan/phpstan-deprecation-rules": "^1.1.4", - "phpstan/phpstan-phpunit": "^1.3.14", + "phpstan/phpstan-phpunit": "^1.3.15", "phpstan/phpstan-strict-rules": "^1.5.1", "squizlabs/php_codesniffer": "^3.7.2", - "symfony/filesystem": "^6.3.1" + "symfony/filesystem": "^6.3.1 || ^7.0.0" }, "bin": [ "bin/paratest", @@ -14607,7 +14738,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.2.8" + "source": "https://github.com/paratestphp/paratest/tree/v7.3.0" }, "funding": [ { @@ -14619,7 +14750,7 @@ "type": "paypal" } ], - "time": "2023-10-04T13:38:04+00:00" + "time": "2023-10-10T15:11:25+00:00" }, { "name": "composer/class-map-generator", @@ -14696,16 +14827,16 @@ }, { "name": "composer/pcre", - "version": "3.1.0", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2" + "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", - "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", + "url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9", + "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9", "shasum": "" }, "require": { @@ -14747,7 +14878,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.0" + "source": "https://github.com/composer/pcre/tree/3.1.1" }, "funding": [ { @@ -14763,7 +14894,7 @@ "type": "tidelift" } ], - "time": "2022-11-17T09:50:14+00:00" + "time": "2023-10-11T07:11:09+00:00" }, { "name": "composer/semver", @@ -15046,16 +15177,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.34.1", + "version": "v3.35.1", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "98bf1b1068b4ceddbbc2a2b70b67a5e380add9e3" + "reference": "ec1ccc264994b6764882669973ca435cf05bab08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/98bf1b1068b4ceddbbc2a2b70b67a5e380add9e3", - "reference": "98bf1b1068b4ceddbbc2a2b70b67a5e380add9e3", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/ec1ccc264994b6764882669973ca435cf05bab08", + "reference": "ec1ccc264994b6764882669973ca435cf05bab08", "shasum": "" }, "require": { @@ -15088,8 +15219,6 @@ "phpspec/prophecy": "^1.16", "phpspec/prophecy-phpunit": "^2.0", "phpunit/phpunit": "^9.5", - "phpunitgoodpractices/polyfill": "^1.6", - "phpunitgoodpractices/traits": "^1.9.2", "symfony/phpunit-bridge": "^6.2.3", "symfony/yaml": "^5.4 || ^6.0" }, @@ -15129,7 +15258,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.34.1" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.35.1" }, "funding": [ { @@ -15137,7 +15266,7 @@ "type": "github" } ], - "time": "2023-10-03T23:51:05+00:00" + "time": "2023-10-12T13:47:26+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -15251,16 +15380,16 @@ }, { "name": "maximebf/debugbar", - "version": "v1.19.0", + "version": "v1.19.1", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "30f65f18f7ac086255a77a079f8e0dcdd35e828e" + "reference": "03dd40a1826f4d585ef93ef83afa2a9874a00523" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/30f65f18f7ac086255a77a079f8e0dcdd35e828e", - "reference": "30f65f18f7ac086255a77a079f8e0dcdd35e828e", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/03dd40a1826f4d585ef93ef83afa2a9874a00523", + "reference": "03dd40a1826f4d585ef93ef83afa2a9874a00523", "shasum": "" }, "require": { @@ -15311,9 +15440,9 @@ ], "support": { "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.19.0" + "source": "https://github.com/maximebf/php-debugbar/tree/v1.19.1" }, - "time": "2023-09-19T19:53:10+00:00" + "time": "2023-10-12T08:10:52+00:00" }, { "name": "mockery/mockery", @@ -15461,16 +15590,16 @@ }, { "name": "nunomaduro/collision", - "version": "v7.9.0", + "version": "v7.10.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "296d0cf9fe462837ac0da8a568b56fc026b132da" + "reference": "49ec67fa7b002712da8526678abd651c09f375b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/296d0cf9fe462837ac0da8a568b56fc026b132da", - "reference": "296d0cf9fe462837ac0da8a568b56fc026b132da", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/49ec67fa7b002712da8526678abd651c09f375b2", + "reference": "49ec67fa7b002712da8526678abd651c09f375b2", "shasum": "" }, "require": { @@ -15479,19 +15608,22 @@ "php": "^8.1.0", "symfony/console": "^6.3.4" }, + "conflict": { + "laravel/framework": ">=11.0.0" + }, "require-dev": { - "brianium/paratest": "^7.2.7", - "laravel/framework": "^10.23.1", - "laravel/pint": "^1.13.1", + "brianium/paratest": "^7.3.0", + "laravel/framework": "^10.28.0", + "laravel/pint": "^1.13.3", "laravel/sail": "^1.25.0", "laravel/sanctum": "^3.3.1", "laravel/tinker": "^2.8.2", "nunomaduro/larastan": "^2.6.4", - "orchestra/testbench-core": "^8.11.0", - "pestphp/pest": "^2.19.1", - "phpunit/phpunit": "^10.3.5", + "orchestra/testbench-core": "^8.13.0", + "pestphp/pest": "^2.23.2", + "phpunit/phpunit": "^10.4.1", "sebastian/environment": "^6.0.1", - "spatie/laravel-ignition": "^2.3.0" + "spatie/laravel-ignition": "^2.3.1" }, "type": "library", "extra": { @@ -15550,7 +15682,7 @@ "type": "patreon" } ], - "time": "2023-09-19T10:45:09+00:00" + "time": "2023-10-11T15:45:01+00:00" }, { "name": "nunomaduro/larastan", @@ -15848,16 +15980,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.37", + "version": "1.10.39", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "058ba07e92f744d4dcf6061ae75283d0c6456f2e" + "reference": "d9dedb0413f678b4d03cbc2279a48f91592c97c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/058ba07e92f744d4dcf6061ae75283d0c6456f2e", - "reference": "058ba07e92f744d4dcf6061ae75283d0c6456f2e", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d9dedb0413f678b4d03cbc2279a48f91592c97c4", + "reference": "d9dedb0413f678b4d03cbc2279a48f91592c97c4", "shasum": "" }, "require": { @@ -15906,7 +16038,7 @@ "type": "tidelift" } ], - "time": "2023-10-02T16:18:37+00:00" + "time": "2023-10-17T15:46:26+00:00" }, { "name": "phpunit/php-code-coverage", @@ -16231,16 +16363,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.3.5", + "version": "10.4.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "747c3b2038f1139e3dcd9886a3f5a948648b7503" + "reference": "62bd7af13d282deeb95650077d28ba3600ca321c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/747c3b2038f1139e3dcd9886a3f5a948648b7503", - "reference": "747c3b2038f1139e3dcd9886a3f5a948648b7503", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/62bd7af13d282deeb95650077d28ba3600ca321c", + "reference": "62bd7af13d282deeb95650077d28ba3600ca321c", "shasum": "" }, "require": { @@ -16280,7 +16412,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.3-dev" + "dev-main": "10.4-dev" } }, "autoload": { @@ -16312,7 +16444,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.5" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.4.1" }, "funding": [ { @@ -16328,7 +16460,7 @@ "type": "tidelift" } ], - "time": "2023-09-19T05:42:37+00:00" + "time": "2023-10-08T05:01:11+00:00" }, { "name": "sebastian/cli-parser", @@ -17309,35 +17441,35 @@ }, { "name": "spatie/flare-client-php", - "version": "1.4.2", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "5f2c6a7a0d2c1d90c12559dc7828fd942911a544" + "reference": "5db2fdd743c3ede33f2a5367d89ec1a7c9c1d1ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/5f2c6a7a0d2c1d90c12559dc7828fd942911a544", - "reference": "5f2c6a7a0d2c1d90c12559dc7828fd942911a544", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/5db2fdd743c3ede33f2a5367d89ec1a7c9c1d1ec", + "reference": "5db2fdd743c3ede33f2a5367d89ec1a7c9c1d1ec", "shasum": "" }, "require": { - "illuminate/pipeline": "^8.0|^9.0|^10.0", + "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0", "nesbot/carbon": "^2.62.1", "php": "^8.0", "spatie/backtrace": "^1.5.2", - "symfony/http-foundation": "^5.0|^6.0", - "symfony/mime": "^5.2|^6.0", - "symfony/process": "^5.2|^6.0", - "symfony/var-dumper": "^5.2|^6.0" + "symfony/http-foundation": "^5.2|^6.0|^7.0", + "symfony/mime": "^5.2|^6.0|^7.0", + "symfony/process": "^5.2|^6.0|^7.0", + "symfony/var-dumper": "^5.2|^6.0|^7.0" }, "require-dev": { - "dms/phpunit-arraysubset-asserts": "^0.3.0", - "pestphp/pest": "^1.20", + "dms/phpunit-arraysubset-asserts": "^0.5.0", + "pestphp/pest": "^1.20|^2.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "spatie/phpunit-snapshot-assertions": "^4.0" + "spatie/phpunit-snapshot-assertions": "^4.0|^5.0" }, "type": "library", "extra": { @@ -17367,7 +17499,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.4.2" + "source": "https://github.com/spatie/flare-client-php/tree/1.4.3" }, "funding": [ { @@ -17375,20 +17507,20 @@ "type": "github" } ], - "time": "2023-07-28T08:07:24+00:00" + "time": "2023-10-17T15:54:07+00:00" }, { "name": "spatie/ignition", - "version": "1.11.2", + "version": "1.11.3", "source": { "type": "git", "url": "https://github.com/spatie/ignition.git", - "reference": "48b23411ca4bfbc75c75dfc638b6b36159c375aa" + "reference": "3d886de644ff7a5b42e4d27c1e1f67c8b5f00044" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ignition/zipball/48b23411ca4bfbc75c75dfc638b6b36159c375aa", - "reference": "48b23411ca4bfbc75c75dfc638b6b36159c375aa", + "url": "https://api.github.com/repos/spatie/ignition/zipball/3d886de644ff7a5b42e4d27c1e1f67c8b5f00044", + "reference": "3d886de644ff7a5b42e4d27c1e1f67c8b5f00044", "shasum": "" }, "require": { @@ -17397,19 +17529,19 @@ "php": "^8.0", "spatie/backtrace": "^1.5.3", "spatie/flare-client-php": "^1.4.0", - "symfony/console": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "require-dev": { - "illuminate/cache": "^9.52", + "illuminate/cache": "^9.52|^10.0|^11.0", "mockery/mockery": "^1.4", - "pestphp/pest": "^1.20", + "pestphp/pest": "^1.20|^2.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", "psr/simple-cache-implementation": "*", - "symfony/cache": "^6.0", - "symfony/process": "^5.4|^6.0", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", "vlucas/phpdotenv": "^5.5" }, "suggest": { @@ -17458,20 +17590,20 @@ "type": "github" } ], - "time": "2023-09-19T15:29:52+00:00" + "time": "2023-10-18T14:09:40+00:00" }, { "name": "spatie/laravel-ignition", - "version": "2.3.0", + "version": "2.3.1", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "4ed813d16edb5a1ab0d7f4b1d116c37ee8cdf3c0" + "reference": "bf21cd15aa47fa4ec5d73bbc932005c70261efc8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/4ed813d16edb5a1ab0d7f4b1d116c37ee8cdf3c0", - "reference": "4ed813d16edb5a1ab0d7f4b1d116c37ee8cdf3c0", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/bf21cd15aa47fa4ec5d73bbc932005c70261efc8", + "reference": "bf21cd15aa47fa4ec5d73bbc932005c70261efc8", "shasum": "" }, "require": { @@ -17550,7 +17682,7 @@ "type": "github" } ], - "time": "2023-08-23T06:24:34+00:00" + "time": "2023-10-09T12:55:26+00:00" }, { "name": "spaze/phpstan-stripe", @@ -17814,13 +17946,13 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^8.1", + "php": "^8.1|^8.2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*" }, "platform-dev": { - "php": "^8.1" + "php": "^8.1|^8.2" }, "plugin-api-version": "2.3.0" } diff --git a/config/ninja.php b/config/ninja.php index d5f584da4e..b6801e5f61 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -15,8 +15,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => env('APP_VERSION','5.7.33'), - 'app_tag' => env('APP_TAG','5.7.33'), + 'app_version' => env('APP_VERSION','5.7.34'), + 'app_tag' => env('APP_TAG','5.7.34'), 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), diff --git a/database/factories/PaymentFactory.php b/database/factories/PaymentFactory.php index a0b528a9c8..0dfa29a545 100644 --- a/database/factories/PaymentFactory.php +++ b/database/factories/PaymentFactory.php @@ -30,6 +30,7 @@ class PaymentFactory extends Factory 'transaction_reference' => $this->faker->text(10), 'type_id' => Payment::TYPE_CREDIT_CARD, 'status_id' => Payment::STATUS_COMPLETED, + 'currency_id' => 1, ]; } } diff --git a/database/migrations/2023_09_21_042010_add_template_flag_to_designs_table.php b/database/migrations/2023_09_21_042010_add_template_flag_to_designs_table.php new file mode 100644 index 0000000000..f8639253a0 --- /dev/null +++ b/database/migrations/2023_09_21_042010_add_template_flag_to_designs_table.php @@ -0,0 +1,26 @@ +boolean('is_template')->default(false); + $table->text('entities')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + } +}; diff --git a/database/migrations/2023_10_08_092508_add_refund_meta_and_category_to_payments_table.php b/database/migrations/2023_10_08_092508_add_refund_meta_and_category_to_payments_table.php new file mode 100644 index 0000000000..b738ff1561 --- /dev/null +++ b/database/migrations/2023_10_08_092508_add_refund_meta_and_category_to_payments_table.php @@ -0,0 +1,26 @@ +text('refund_meta')->nullable(); + $table->unsignedInteger('category_id')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + } +}; diff --git a/lang/en/texts.php b/lang/en/texts.php index 48cfe30beb..a608b2b91b 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5184,7 +5184,11 @@ $LANG = array( 'client_contact' => 'Client Contact', 'uncategorized' => 'Uncategorized', 'login_notification' => 'Login Notification', - 'login_notification_help' => 'Sends an email notifying that a login has taken place.' + 'login_notification_help' => 'Sends an email notifying that a login has taken place.', + 'payment_refund_receipt' => 'Payment Refund Receipt # :number', + 'payment_receipt' => 'Payment Receipt # :number', + 'load_template_description' => 'The template will be applied to following:', + 'run_template' => 'Run template', ); return $LANG; diff --git a/openapi/api-docs.yaml b/openapi/api-docs.yaml index f5d9ac5a92..600f56788a 100644 --- a/openapi/api-docs.yaml +++ b/openapi/api-docs.yaml @@ -8236,392 +8236,6 @@ paths: default: $ref: "#/components/responses/default" - /api/v1/vendors: - get: - tags: - - vendors - summary: "List vendors" - description: "Lists vendors, search and filters allow fine grained lists to be generated.\n\n Query parameters can be added to performed more fine grained filtering of the vendors, these are handled by the VendorFilters class which defines the methods available" - operationId: getVendors - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - $ref: "#/components/parameters/index" - responses: - 200: - description: "A list of vendors" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/Vendor' - meta: - type: object - $ref: '#/components/schemas/Meta' - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - post: - tags: - - vendors - summary: "Create vendor" - description: "Adds a vendor to a company" - operationId: storeVendor - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - responses: - 200: - description: "Returns the saved clivendorent object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Vendor" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - "/api/v1/vendors/{id}": - get: - tags: - - vendors - summary: "Show vendor" - description: "Displays a vendor by id" - operationId: showVendor - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The vendor Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the vendor object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Vendor" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - put: - tags: - - vendors - summary: "Update vendor" - description: "Handles the updating of a vendor by id" - operationId: updateVendor - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The Vendor Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the vendor object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Vendor" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - delete: - tags: - - vendors - summary: "Delete vendor" - description: "Handles the deletion of a vendor by id" - operationId: deleteVendor - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The Vendor Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns a HTTP status" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - "/api/v1/vendors/{id}/edit": - get: - tags: - - vendors - summary: "Edit vendor" - description: "Displays a vendor by id" - operationId: editVendor - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The Vendor Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the vendor object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Vendor" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - /api/v1/vendors/create: - get: - tags: - - vendors - summary: "Blank vendor" - description: "Returns a blank vendor with default values" - operationId: getVendorsCreate - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - responses: - 200: - description: "A blank vendor object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Vendor" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - /api/v1/vendors/bulk: - post: - tags: - - vendors - summary: "Bulk vendor actions" - description: "" - operationId: bulkVendors - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/index" - requestBody: - description: "User credentials" - required: true - content: - application/json: - schema: - type: array - items: - description: "Array of hashed IDs to be bulk 'actioned" - type: integer - example: "[0,1,2,3]" - responses: - 200: - description: "The Vendor User response" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Vendor" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - "/api/v1/vendors/{id}/upload": - put: - tags: - - vendors - summary: "Uploads a vendor document" - description: "Handles the uploading of a document to a vendor" - operationId: uploadVendor - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The Vendor Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the Vendor object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Vendor" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" /api/v1/products: get: tags: @@ -9065,59 +8679,21 @@ paths: default: $ref: '#/components/responses/default' - /api/v1/recurring_invoices: + /api/v1/tasks: get: tags: - - Recurring Invoices - summary: "List recurring invoices" - description: | - Lists invoices with the option to chain multiple query parameters allowing fine grained filtering of the list. - - operationId: getRecurringInvoices + - tasks + summary: "List tasks" + description: "Lists tasks, search and filters allow fine grained lists to be generated.\n *\n * Query parameters can be added to performed more fine grained filtering of the tasks, these are handled by the TaskFilters class which defines the methods available" + operationId: getTasks parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - - $ref: "#/components/parameters/client_id" - - $ref: "#/components/parameters/created_at" - - $ref: "#/components/parameters/updated_at" - - $ref: "#/components/parameters/is_deleted" - - $ref: "#/components/parameters/filter_deleted_clients" - - $ref: "#/components/parameters/vendor_id" - - name: filter - in: query - description: | - Searches across a range of columns including: - - custom_value1 - - custom_value2 - - custom_value3 - - custom_value4 - required: false - schema: - type: string - example: ?filter=bob - - name: client_status - in: query - description: | - A comma separated list of invoice status strings. Valid options include: - - all - - active - - paused - - completed - required: false - schema: - type: string - example: ?client_status=active,paused - - name: sort - in: query - description: Returns the list sorted by column in ascending or descending order. - required: false - schema: - type: string - example: id|desc number|desc balance|asc + - $ref: "#/components/parameters/index" responses: 200: - description: "A list of recurring_invoices" + description: "A list of tasks" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -9133,7 +8709,7 @@ paths: data: type: array items: - $ref: '#/components/schemas/RecurringInvoice' + $ref: '#/components/schemas/Task' meta: type: object $ref: '#/components/schemas/Meta' @@ -9151,17 +8727,17 @@ paths: $ref: "#/components/responses/default" post: tags: - - Recurring Invoices - summary: "Create recurring invoice" - description: "Adds a Recurring Invoice to the system" - operationId: storeRecurringInvoice + - tasks + summary: "Create task" + description: "Adds an task to a company" + operationId: storeTask parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" responses: 200: - description: "Returns the saved RecurringInvoice object" + description: "Returns the saved task object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -9172,7 +8748,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/RecurringInvoice" + $ref: "#/components/schemas/Task" 401: $ref: "#/components/responses/401" 403: @@ -9185,21 +8761,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - - "/api/v1/recurring_invoices/{id}": + "/api/v1/tasks/{id}": get: tags: - - Recurring Invoices - summary: "Show recurring invoice" - description: "Displays an RecurringInvoice by id" - operationId: showRecurringInvoice + - tasks + summary: "Show task" + description: "Displays a task by id" + operationId: showTask parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The RecurringInvoice Hashed ID" + description: "The Task Hashed ID" required: true schema: type: string @@ -9207,7 +8782,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the RecurringInvoice object" + description: "Returns the task object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -9218,7 +8793,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/RecurringInvoice" + $ref: "#/components/schemas/Task" 401: $ref: "#/components/responses/401" 403: @@ -9233,17 +8808,17 @@ paths: $ref: "#/components/responses/default" put: tags: - - Recurring Invoices - summary: "Update recurring invoice" - description: "Handles the updating of an RecurringInvoice by id" - operationId: updateRecurringInvoice + - tasks + summary: "Update task" + description: "Handles the updating of a task by id" + operationId: updateTask parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The RecurringInvoice Hashed ID" + description: "The task Hashed ID" required: true schema: type: string @@ -9251,7 +8826,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the RecurringInvoice object" + description: "Returns the task object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -9262,7 +8837,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/RecurringInvoice" + $ref: "#/components/schemas/Task" 401: $ref: "#/components/responses/401" 403: @@ -9277,17 +8852,17 @@ paths: $ref: "#/components/responses/default" delete: tags: - - Recurring Invoices - summary: "Delete recurring invoice" - description: "Handles the deletion of an RecurringInvoice by id" - operationId: deleteRecurringInvoice + - tasks + summary: "Delete task" + description: "Handles the deletion of a task by id" + operationId: deleteTask parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The RecurringInvoice Hashed ID" + description: "The Task Hashed ID" required: true schema: type: string @@ -9315,20 +8890,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/recurring_invoices/{id}/edit": + "/api/v1/tasks/{id}/edit": get: tags: - - Recurring Invoices - summary: "Edit recurring invoice" - description: "Displays an RecurringInvoice by id" - operationId: editRecurringInvoice + - tasks + summary: "Edit task" + description: "Displays a task by id" + operationId: editTask parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The RecurringInvoice Hashed ID" + description: "The Task Hashed ID" required: true schema: type: string @@ -9336,7 +8911,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the RecurringInvoice object" + description: "Returns the client object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -9347,7 +8922,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/RecurringInvoice" + $ref: "#/components/schemas/Task" 401: $ref: "#/components/responses/401" 403: @@ -9360,21 +8935,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - - /api/v1/recurring_invoices/create: + /api/v1/tasks/create: get: tags: - - Recurring Invoices - summary: "Blank recurring invoice" - description: "Returns a blank object with default values" - operationId: getRecurringInvoicesCreate + - tasks + summary: "Blank task" + description: "Returns a blank task with default values" + operationId: getTasksCreate parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" responses: 200: - description: "A blank RecurringInvoice object" + description: "A blank task object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -9385,7 +8959,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/RecurringInvoice" + $ref: "#/components/schemas/Task" 401: $ref: "#/components/responses/401" 403: @@ -9398,55 +8972,31 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/recurring_invoices/bulk: + /api/v1/tasks/bulk: post: tags: - - Recurring Invoices - summary: "Bulk recurring invoice actions" - description: | - There are multiple actions that are available including: - - operationId: bulkRecurringInvoices + - tasks + summary: "Bulk task actions" + description: "" + operationId: bulkTasks parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/index" requestBody: - description: "Bulk action details" + description: "User credentials" required: true content: application/json: schema: - type: object - properties: - action: - type: string - description: | - The action to be performed, options include: - - `start` - Starts (or restarts) the recurring invoice. **note** if the recurring invoice has been stopped for a long time, it will attempt to catch back up firing a new Invoice every hour per interval that has been missed. - If you do not wish to have the recurring invoice catch up, you should set the next_send_date to the correct date you wish the recurring invoice to commence from. - - `stop` - Stops the recurring invoice. - - `send_now` - Force sends the recurring invoice - this option is only available when the recurring invoice is in a draft state. - - `restore` - Restores the recurring invoice from an archived or deleted state. - - `archive` - Archives the recurring invoice. The recurring invoice will not fire in this state. - - `delete` - Deletes a recurring invoice. - ids: - type: array - items: - description: "Array of hashed IDs to be bulk 'actioned - ['D2J234DFA','D2J234DFA','D2J234DFA']" - type: string - example: - action: start - ids: "['D2J234DFA','D2J234DFA','D2J234DFA']" + type: array + items: + description: "Array of hashed IDs to be bulk 'actioned" + type: integer + example: "[0,1,2,3]" responses: 200: - description: "The RecurringInvoice response" + description: "The Task User response" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -9457,7 +9007,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/RecurringInvoice" + $ref: "#/components/schemas/Task" 401: $ref: "#/components/responses/401" 403: @@ -9470,115 +9020,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/recurring_invoices/{id}/{action}": - get: - deprecated: true - tags: - - Recurring Invoices - summary: "Custom recurring invoice action" - description: "Performs a custom action on an RecurringInvoice.\n\n The current range of actions are as follows\n - clone_to_RecurringInvoice\n - clone_to_quote\n - history\n - delivery_note\n - mark_paid\n - download\n - archive\n - delete\n - email" - operationId: actionRecurringInvoice - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The RecurringInvoice Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - - name: action - in: path - description: "The action string to be performed" - required: true - schema: - type: string - format: string - example: clone_to_quote - responses: - 200: - description: "Returns the RecurringInvoice object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/RecurringInvoice" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - "/api/v1/recurring_invoice/{invitation_key}/download": - get: - tags: - - Recurring Invoices - summary: "Download recurring invoice PDF" - description: "Downloads a specific invoice" - operationId: downloadRecurringInvoice - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: invitation_key - in: path - description: "The Recurring Invoice Invitation Key" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the recurring invoice pdf" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - "/api/v1/recurring_invoices/{id}/upload": + "/api/v1/tasks/{id}/upload": put: tags: - - Recurring Invoices - summary: "Add recurring invoice document" - description: "Handles the uploading of a document to a recurring_invoice" - operationId: uploadRecurringInvoice + - tasks + summary: "Uploads a task document" + description: "Handles the uploading of a document to a task" + operationId: uploadTask parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The RecurringInvoice Hashed ID" + description: "The Task Hashed ID" required: true schema: type: string @@ -9586,7 +9041,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the RecurringInvoice object" + description: "Returns the Task object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -9597,7 +9052,426 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/RecurringInvoice" + $ref: "#/components/schemas/Task" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + /api/v1/tasks/sort: + post: + tags: + - tasks + summary: "Sort tasks on KanBan" + description: "Sorts tasks after drag and drop on the KanBan." + operationId: sortTasks + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + responses: + 200: + description: "Returns an Ok, 200 HTTP status" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + /api/v1/projects: + get: + tags: + - projects + summary: "List projects" + description: "Lists projects" + operationId: getProjects + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - $ref: "#/components/parameters/index" + responses: + 200: + description: "A list of projects" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Project' + meta: + type: object + $ref: '#/components/schemas/Meta' + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + post: + tags: + - projects + summary: "Create project" + description: "Adds an project to a company" + operationId: storeProject + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + responses: + 200: + description: "Returns the saved project object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Project" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + "/api/v1/projects/{id}": + get: + tags: + - projects + summary: "Show project" + description: "Displays a project by id" + operationId: showProject + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Project Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the expense object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Project" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + put: + tags: + - projects + summary: "Update project" + description: "Handles the updating of a project by id" + operationId: updateProject + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Project Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the project object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Project" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + delete: + tags: + - projects + summary: "Delete project" + description: "Handles the deletion of a project by id" + operationId: deleteProject + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Project Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns a HTTP status" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + "/api/v1/projects/{id}/edit": + get: + tags: + - projects + summary: "Edit project" + description: "Displays a project by id" + operationId: editProject + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Project Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the project object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Project" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + /api/v1/projects/create: + get: + tags: + - projects + summary: "Blank project" + description: "Returns a blank object with default values" + operationId: getProjectsCreate + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + responses: + 200: + description: "A blank project object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Project" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + /api/v1/projects/bulk: + post: + tags: + - projects + summary: "Bulk project actions" + description: "" + operationId: bulkProjects + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/index" + requestBody: + description: "User credentials" + required: true + content: + application/json: + schema: + type: array + items: + description: "Array of hashed IDs to be bulk 'actioned" + type: integer + example: "[0,1,2,3]" + responses: + 200: + description: "The Project User response" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Project" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + "/api/v1/projects/{id}/upload": + put: + tags: + - projects + summary: "Uploads a project document" + description: "Handles the uploading of a document to a project" + operationId: uploadProject + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Project Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the Project object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Project" 401: $ref: "#/components/responses/401" 403: @@ -10784,21 +10658,56 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/projects: + /api/v1/payments: get: tags: - - projects - summary: "List projects" - description: "Lists projects" - operationId: getProjects + - payments + summary: "List payments" + description: "Lists payments, search and filters allow fine grained lists to be generated.\n\n Query parameters can be added to performed more fine grained filtering of the payments, these are handled by the PaymentFilters class which defines the methods available" + operationId: getPayments parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - - $ref: "#/components/parameters/index" + - $ref: "#/components/parameters/status" + - $ref: "#/components/parameters/client_id" + - $ref: "#/components/parameters/created_at" + - $ref: "#/components/parameters/updated_at" + - $ref: "#/components/parameters/is_deleted" + - $ref: "#/components/parameters/filter_deleted_clients" + - $ref: "#/components/parameters/vendor_id" + - name: filter + in: query + description: | + Searches across a range of columns including: + - amount + - date + - custom_value1 + - custom_value2 + - custom_value3 + - custom_value4 + required: false + schema: + type: string + example: ?filter=10 + - name: number + in: query + description: | + Search payments by payment number + required: false + schema: + type: string + example: ?number=0001 + - name: sort + in: query + description: Returns the list sorted by column in ascending or descending order. + required: false + schema: + type: string + example: id|desc number|desc balance|asc responses: 200: - description: "A list of projects" + description: "A list of payments" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -10814,7 +10723,7 @@ paths: data: type: array items: - $ref: '#/components/schemas/Project' + $ref: '#/components/schemas/Payment' meta: type: object $ref: '#/components/schemas/Meta' @@ -10832,17 +10741,24 @@ paths: $ref: "#/components/responses/default" post: tags: - - projects - summary: "Create project" - description: "Adds an project to a company" - operationId: storeProject + - payments + summary: "Create payment" + description: "Adds an Payment to the system" + operationId: storePayment parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" + requestBody: + description: "The payment request" + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Payment" responses: 200: - description: "Returns the saved project object" + description: "Returns the saved Payment object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -10853,7 +10769,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Project" + $ref: "#/components/schemas/Payment" 401: $ref: "#/components/responses/401" 403: @@ -10866,20 +10782,21 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/projects/{id}": + + "/api/v1/payments/{id}": get: tags: - - projects - summary: "Show project" - description: "Displays a project by id" - operationId: showProject + - payments + summary: "Show payment" + description: "Displays an Payment by id" + operationId: showPayment parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Project Hashed ID" + description: "The Payment Hashed ID" required: true schema: type: string @@ -10887,7 +10804,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the expense object" + description: "Returns the Payment object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -10898,7 +10815,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Project" + $ref: "#/components/schemas/Payment" 401: $ref: "#/components/responses/401" 403: @@ -10913,17 +10830,17 @@ paths: $ref: "#/components/responses/default" put: tags: - - projects - summary: "Update project" - description: "Handles the updating of a project by id" - operationId: updateProject + - payments + summary: "Update payment" + description: "Handles the updating of an Payment by id" + operationId: updatePayment parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Project Hashed ID" + description: "The Payment Hashed ID" required: true schema: type: string @@ -10931,7 +10848,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the project object" + description: "Returns the Payment object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -10942,7 +10859,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Project" + $ref: "#/components/schemas/Payment" 401: $ref: "#/components/responses/401" 403: @@ -10957,17 +10874,17 @@ paths: $ref: "#/components/responses/default" delete: tags: - - projects - summary: "Delete project" - description: "Handles the deletion of a project by id" - operationId: deleteProject + - payments + summary: "Delete payment" + description: "Handles the deletion of an Payment by id" + operationId: deletePayment parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Project Hashed ID" + description: "The Payment Hashed ID" required: true schema: type: string @@ -10995,20 +10912,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/projects/{id}/edit": + "/api/v1/payments/{id}/edit": get: tags: - - projects - summary: "Edit project" - description: "Displays a project by id" - operationId: editProject + - payments + summary: "Edit payment" + description: "Displays an Payment by id" + operationId: editPayment parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Project Hashed ID" + description: "The Payment Hashed ID" required: true schema: type: string @@ -11016,7 +10933,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the project object" + description: "Returns the Payment object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -11027,7 +10944,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Project" + $ref: "#/components/schemas/Payment" 401: $ref: "#/components/responses/401" 403: @@ -11040,20 +10957,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/projects/create: + /api/v1/payments/create: get: tags: - - projects - summary: "Blank project" + - payments + summary: "Blank payment" description: "Returns a blank object with default values" - operationId: getProjectsCreate + operationId: getPaymentsCreate parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" responses: 200: - description: "A blank project object" + description: "A blank Payment object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -11064,7 +10981,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Project" + $ref: "#/components/schemas/Payment" 401: $ref: "#/components/responses/401" 403: @@ -11077,13 +10994,57 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/projects/bulk: + /api/v1/payments/refund: post: tags: - - projects - summary: "Bulk project actions" + - payments + summary: "Refund payment" + description: "Adds an Refund to the system" + operationId: storeRefund + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + requestBody: + description: "The refund request" + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Payment" + responses: + 200: + description: "Returns the saved Payment object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Payment" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + /api/v1/payments/bulk: + post: + tags: + - payments + summary: "Bulk payment actions" description: "" - operationId: bulkProjects + operationId: bulkPayments parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" @@ -11101,7 +11062,7 @@ paths: example: "[0,1,2,3]" responses: 200: - description: "The Project User response" + description: "The Payment response" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -11112,7 +11073,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Project" + $ref: "#/components/schemas/Payment" 401: $ref: "#/components/responses/401" 403: @@ -11125,455 +11086,21 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/projects/{id}/upload": - put: - tags: - - projects - summary: "Uploads a project document" - description: "Handles the uploading of a document to a project" - operationId: uploadProject - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The Project Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the Project object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Project" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - /api/v1/quotes: - get: - tags: - - quotes - summary: "List quotes" - description: "Lists quotes, search and filters allow fine grained lists to be generated.\n *\n * Query parameters can be added to performed more fine grained filtering of the quotes, these are handled by the QuoteFilters class which defines the methods available" - operationId: getQuotes - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - $ref: "#/components/parameters/status" - - $ref: "#/components/parameters/client_id" - - $ref: "#/components/parameters/created_at" - - $ref: "#/components/parameters/updated_at" - - $ref: "#/components/parameters/is_deleted" - - $ref: "#/components/parameters/filter_deleted_clients" - - $ref: "#/components/parameters/vendor_id" - - name: filter - in: query - description: | - Searches across a range of columns including: - - number - - custom_value1 - - custom_value2 - - custom_value3 - - custom_value4 - required: false - schema: - type: string - example: ?filter=bob - - name: client_status - in: query - description: | - A comma separated list of quote status strings. Valid options include: - - all - - draft - - sent - - approved - - expired - - upcoming - required: false - schema: - type: string - example: ?client_status=paid,unpaid - - name: number - in: query - description: | - Search quote by quote number - required: false - schema: - type: string - example: ?number=Q-001 - - name: sort - in: query - description: Returns the list sorted by column in ascending or descending order. - required: false - schema: - type: string - example: id|desc number|desc balance|asc - responses: - 200: - description: "A list of quotes" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/Quote' - meta: - type: object - $ref: '#/components/schemas/Meta' - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - post: - tags: - - quotes - summary: "Create quote" - description: "Adds an Quote to the system" - operationId: storeQuote - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - responses: - 200: - description: "Returns the saved Quote object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Quote" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - "/api/v1/quotes/{id}": - get: - tags: - - quotes - summary: "Show quote" - description: "Displays an Quote by id" - operationId: showQuote - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The Quote Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the Quote object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Quote" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - put: - tags: - - quotes - summary: "Update quote" - description: "Handles the updating of an Quote by id" - operationId: updateQuote - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The Quote Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the Quote object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Quote" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - delete: - tags: - - quotes - summary: "Delete quote" - description: "Handles the deletion of an Quote by id" - operationId: deleteQuote - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The Quote Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns a HTTP status" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - "/api/v1/quotes/{id}/edit": - get: - tags: - - quotes - summary: "Edit quote" - description: "Displays an Quote by id" - operationId: editQuote - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The Quote Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the Quote object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Quote" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - /api/v1/quotes/create: - get: - tags: - - quotes - summary: "Blank quote" - description: "Returns a blank object with default values" - operationId: getQuotesCreate - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - responses: - 200: - description: "A blank Quote object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Quote" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - /api/v1/quotes/bulk: - post: - tags: - - quotes - summary: "Bulk quote actions" - description: "" - operationId: bulkQuotes - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/index" - requestBody: - description: "Hashed ids" - required: true - content: - application/json: - schema: - type: array - items: - description: "Array of hashed IDs to be bulk 'actioned" - type: integer - example: "[0,1,2,3]" - responses: - 200: - description: "The Quote response" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Quote" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - "/api/v1/quotes/{id}/{action}": + "/api/v1/payments/{id}/{action}": get: deprecated: true tags: - - quotes - summary: "Performs a custom action on an Quote" - description: "Performs a custom action on an Quote.\n\n The current range of actions are as follows\n - clone_to_quote\n - history\n - delivery_note\n - mark_paid\n - download\n - archive\n - delete\n - convert\n - convert_to_invoice\n - email" - operationId: actionQuote + - payments + summary: "Custom payment actions" + description: "Performs a custom action on an Payment.\n\n The current range of actions are as follows\n - clone_to_Payment\n - clone_to_quote\n - history\n - delivery_note\n - mark_paid\n - download\n - archive\n - delete\n - email" + operationId: actionPayment parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Quote Hashed ID" + description: "The Payment Hashed ID" required: true schema: type: string @@ -11589,7 +11116,7 @@ paths: example: clone_to_quote responses: 200: - description: "Returns the Quote object" + description: "Returns the Payment object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -11600,7 +11127,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Quote" + $ref: "#/components/schemas/Payment" 401: $ref: "#/components/responses/401" 403: @@ -11613,61 +11140,21 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/quote/{invitation_key}/download": - get: - tags: - - quotes - summary: "Download quote PDF" - description: "Downloads a specific quote" - operationId: downloadQuote - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: invitation_key - in: path - description: "The Quote Invitation Key" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the quote pdf" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - "/api/v1/quotes/{id}/upload": + + "/api/v1/payments/{id}/upload": put: tags: - - quotes - summary: "Upload a quote document" - description: "Handles the uploading of a document to a quote" - operationId: uploadQuote + - payments + summary: "Upload a payment document" + description: "Handles the uploading of a document to a payment" + operationId: uploadPayment parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Quote Hashed ID" + description: "The Payment Hashed ID" required: true schema: type: string @@ -11675,7 +11162,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the Quote object" + description: "Returns the Payment object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -11686,7 +11173,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Quote" + $ref: "#/components/schemas/Payment" 401: $ref: "#/components/responses/401" 403: @@ -12412,18 +11899,19 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/payments: + /api/v1/recurring_invoices: get: tags: - - payments - summary: "List payments" - description: "Lists payments, search and filters allow fine grained lists to be generated.\n\n Query parameters can be added to performed more fine grained filtering of the payments, these are handled by the PaymentFilters class which defines the methods available" - operationId: getPayments + - Recurring Invoices + summary: "List recurring invoices" + description: | + Lists invoices with the option to chain multiple query parameters allowing fine grained filtering of the list. + + operationId: getRecurringInvoices parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - - $ref: "#/components/parameters/status" - $ref: "#/components/parameters/client_id" - $ref: "#/components/parameters/created_at" - $ref: "#/components/parameters/updated_at" @@ -12434,8 +11922,6 @@ paths: in: query description: | Searches across a range of columns including: - - amount - - date - custom_value1 - custom_value2 - custom_value3 @@ -12443,15 +11929,19 @@ paths: required: false schema: type: string - example: ?filter=10 - - name: number + example: ?filter=bob + - name: client_status in: query description: | - Search payments by payment number + A comma separated list of invoice status strings. Valid options include: + - all + - active + - paused + - completed required: false schema: type: string - example: ?number=0001 + example: ?client_status=active,paused - name: sort in: query description: Returns the list sorted by column in ascending or descending order. @@ -12461,7 +11951,7 @@ paths: example: id|desc number|desc balance|asc responses: 200: - description: "A list of payments" + description: "A list of recurring_invoices" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -12477,7 +11967,7 @@ paths: data: type: array items: - $ref: '#/components/schemas/Payment' + $ref: '#/components/schemas/RecurringInvoice' meta: type: object $ref: '#/components/schemas/Meta' @@ -12495,24 +11985,17 @@ paths: $ref: "#/components/responses/default" post: tags: - - payments - summary: "Create payment" - description: "Adds an Payment to the system" - operationId: storePayment + - Recurring Invoices + summary: "Create recurring invoice" + description: "Adds a Recurring Invoice to the system" + operationId: storeRecurringInvoice parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - requestBody: - description: "The payment request" - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/Payment" responses: 200: - description: "Returns the saved Payment object" + description: "Returns the saved RecurringInvoice object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -12523,7 +12006,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Payment" + $ref: "#/components/schemas/RecurringInvoice" 401: $ref: "#/components/responses/401" 403: @@ -12537,20 +12020,20 @@ paths: default: $ref: "#/components/responses/default" - "/api/v1/payments/{id}": + "/api/v1/recurring_invoices/{id}": get: tags: - - payments - summary: "Show payment" - description: "Displays an Payment by id" - operationId: showPayment + - Recurring Invoices + summary: "Show recurring invoice" + description: "Displays an RecurringInvoice by id" + operationId: showRecurringInvoice parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Payment Hashed ID" + description: "The RecurringInvoice Hashed ID" required: true schema: type: string @@ -12558,7 +12041,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the Payment object" + description: "Returns the RecurringInvoice object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -12569,7 +12052,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Payment" + $ref: "#/components/schemas/RecurringInvoice" 401: $ref: "#/components/responses/401" 403: @@ -12584,17 +12067,17 @@ paths: $ref: "#/components/responses/default" put: tags: - - payments - summary: "Update payment" - description: "Handles the updating of an Payment by id" - operationId: updatePayment + - Recurring Invoices + summary: "Update recurring invoice" + description: "Handles the updating of an RecurringInvoice by id" + operationId: updateRecurringInvoice parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Payment Hashed ID" + description: "The RecurringInvoice Hashed ID" required: true schema: type: string @@ -12602,7 +12085,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the Payment object" + description: "Returns the RecurringInvoice object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -12613,7 +12096,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Payment" + $ref: "#/components/schemas/RecurringInvoice" 401: $ref: "#/components/responses/401" 403: @@ -12628,17 +12111,17 @@ paths: $ref: "#/components/responses/default" delete: tags: - - payments - summary: "Delete payment" - description: "Handles the deletion of an Payment by id" - operationId: deletePayment + - Recurring Invoices + summary: "Delete recurring invoice" + description: "Handles the deletion of an RecurringInvoice by id" + operationId: deleteRecurringInvoice parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Payment Hashed ID" + description: "The RecurringInvoice Hashed ID" required: true schema: type: string @@ -12666,20 +12149,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/payments/{id}/edit": + "/api/v1/recurring_invoices/{id}/edit": get: tags: - - payments - summary: "Edit payment" - description: "Displays an Payment by id" - operationId: editPayment + - Recurring Invoices + summary: "Edit recurring invoice" + description: "Displays an RecurringInvoice by id" + operationId: editRecurringInvoice parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Payment Hashed ID" + description: "The RecurringInvoice Hashed ID" required: true schema: type: string @@ -12687,7 +12170,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the Payment object" + description: "Returns the RecurringInvoice object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -12698,7 +12181,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Payment" + $ref: "#/components/schemas/RecurringInvoice" 401: $ref: "#/components/responses/401" 403: @@ -12711,20 +12194,21 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/payments/create: + + /api/v1/recurring_invoices/create: get: tags: - - payments - summary: "Blank payment" + - Recurring Invoices + summary: "Blank recurring invoice" description: "Returns a blank object with default values" - operationId: getPaymentsCreate + operationId: getRecurringInvoicesCreate parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" responses: 200: - description: "A blank Payment object" + description: "A blank RecurringInvoice object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -12735,7 +12219,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Payment" + $ref: "#/components/schemas/RecurringInvoice" 401: $ref: "#/components/responses/401" 403: @@ -12748,75 +12232,55 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/payments/refund: + /api/v1/recurring_invoices/bulk: post: tags: - - payments - summary: "Refund payment" - description: "Adds an Refund to the system" - operationId: storeRefund - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - requestBody: - description: "The refund request" - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/Payment" - responses: - 200: - description: "Returns the saved Payment object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Payment" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - /api/v1/payments/bulk: - post: - tags: - - payments - summary: "Bulk payment actions" - description: "" - operationId: bulkPayments + - Recurring Invoices + summary: "Bulk recurring invoice actions" + description: | + There are multiple actions that are available including: + + operationId: bulkRecurringInvoices parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/index" requestBody: - description: "User credentials" + description: "Bulk action details" required: true content: application/json: schema: - type: array - items: - description: "Array of hashed IDs to be bulk 'actioned" - type: integer - example: "[0,1,2,3]" + type: object + properties: + action: + type: string + description: | + The action to be performed, options include: + - `start` + Starts (or restarts) the recurring invoice. **note** if the recurring invoice has been stopped for a long time, it will attempt to catch back up firing a new Invoice every hour per interval that has been missed. + If you do not wish to have the recurring invoice catch up, you should set the next_send_date to the correct date you wish the recurring invoice to commence from. + - `stop` + Stops the recurring invoice. + - `send_now` + Force sends the recurring invoice - this option is only available when the recurring invoice is in a draft state. + - `restore` + Restores the recurring invoice from an archived or deleted state. + - `archive` + Archives the recurring invoice. The recurring invoice will not fire in this state. + - `delete` + Deletes a recurring invoice. + ids: + type: array + items: + description: "Array of hashed IDs to be bulk 'actioned - ['D2J234DFA','D2J234DFA','D2J234DFA']" + type: string + example: + action: start + ids: "['D2J234DFA','D2J234DFA','D2J234DFA']" responses: 200: - description: "The Payment response" + description: "The RecurringInvoice response" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -12827,7 +12291,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Payment" + $ref: "#/components/schemas/RecurringInvoice" 401: $ref: "#/components/responses/401" 403: @@ -12840,21 +12304,21 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/payments/{id}/{action}": + "/api/v1/recurring_invoices/{id}/{action}": get: deprecated: true tags: - - payments - summary: "Custom payment actions" - description: "Performs a custom action on an Payment.\n\n The current range of actions are as follows\n - clone_to_Payment\n - clone_to_quote\n - history\n - delivery_note\n - mark_paid\n - download\n - archive\n - delete\n - email" - operationId: actionPayment + - Recurring Invoices + summary: "Custom recurring invoice action" + description: "Performs a custom action on an RecurringInvoice.\n\n The current range of actions are as follows\n - clone_to_RecurringInvoice\n - clone_to_quote\n - history\n - delivery_note\n - mark_paid\n - download\n - archive\n - delete\n - email" + operationId: actionRecurringInvoice parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Payment Hashed ID" + description: "The RecurringInvoice Hashed ID" required: true schema: type: string @@ -12870,7 +12334,7 @@ paths: example: clone_to_quote responses: 200: - description: "Returns the Payment object" + description: "Returns the RecurringInvoice object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -12881,7 +12345,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Payment" + $ref: "#/components/schemas/RecurringInvoice" 401: $ref: "#/components/responses/401" 403: @@ -12894,21 +12358,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - - "/api/v1/payments/{id}/upload": - put: + "/api/v1/recurring_invoice/{invitation_key}/download": + get: tags: - - payments - summary: "Upload a payment document" - description: "Handles the uploading of a document to a payment" - operationId: uploadPayment + - Recurring Invoices + summary: "Download recurring invoice PDF" + description: "Downloads a specific invoice" + operationId: downloadRecurringInvoice parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - - name: id + - name: invitation_key in: path - description: "The Payment Hashed ID" + description: "The Recurring Invoice Invitation Key" required: true schema: type: string @@ -12916,7 +12379,48 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the Payment object" + description: "Returns the recurring invoice pdf" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + "/api/v1/recurring_invoices/{id}/upload": + put: + tags: + - Recurring Invoices + summary: "Add recurring invoice document" + description: "Handles the uploading of a document to a recurring_invoice" + operationId: uploadRecurringInvoice + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The RecurringInvoice Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the RecurringInvoice object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -12927,7 +12431,536 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Payment" + $ref: "#/components/schemas/RecurringInvoice" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + /api/v1/quotes: + get: + tags: + - quotes + summary: "List quotes" + description: "Lists quotes, search and filters allow fine grained lists to be generated.\n *\n * Query parameters can be added to performed more fine grained filtering of the quotes, these are handled by the QuoteFilters class which defines the methods available" + operationId: getQuotes + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - $ref: "#/components/parameters/status" + - $ref: "#/components/parameters/client_id" + - $ref: "#/components/parameters/created_at" + - $ref: "#/components/parameters/updated_at" + - $ref: "#/components/parameters/is_deleted" + - $ref: "#/components/parameters/filter_deleted_clients" + - $ref: "#/components/parameters/vendor_id" + - name: filter + in: query + description: | + Searches across a range of columns including: + - number + - custom_value1 + - custom_value2 + - custom_value3 + - custom_value4 + required: false + schema: + type: string + example: ?filter=bob + - name: client_status + in: query + description: | + A comma separated list of quote status strings. Valid options include: + - all + - draft + - sent + - approved + - expired + - upcoming + required: false + schema: + type: string + example: ?client_status=paid,unpaid + - name: number + in: query + description: | + Search quote by quote number + required: false + schema: + type: string + example: ?number=Q-001 + - name: sort + in: query + description: Returns the list sorted by column in ascending or descending order. + required: false + schema: + type: string + example: id|desc number|desc balance|asc + responses: + 200: + description: "A list of quotes" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Quote' + meta: + type: object + $ref: '#/components/schemas/Meta' + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + post: + tags: + - quotes + summary: "Create quote" + description: "Adds an Quote to the system" + operationId: storeQuote + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + responses: + 200: + description: "Returns the saved Quote object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Quote" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + "/api/v1/quotes/{id}": + get: + tags: + - quotes + summary: "Show quote" + description: "Displays an Quote by id" + operationId: showQuote + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Quote Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the Quote object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Quote" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + put: + tags: + - quotes + summary: "Update quote" + description: "Handles the updating of an Quote by id" + operationId: updateQuote + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Quote Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the Quote object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Quote" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + delete: + tags: + - quotes + summary: "Delete quote" + description: "Handles the deletion of an Quote by id" + operationId: deleteQuote + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Quote Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns a HTTP status" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + "/api/v1/quotes/{id}/edit": + get: + tags: + - quotes + summary: "Edit quote" + description: "Displays an Quote by id" + operationId: editQuote + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Quote Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the Quote object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Quote" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + /api/v1/quotes/create: + get: + tags: + - quotes + summary: "Blank quote" + description: "Returns a blank object with default values" + operationId: getQuotesCreate + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + responses: + 200: + description: "A blank Quote object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Quote" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + /api/v1/quotes/bulk: + post: + tags: + - quotes + summary: "Bulk quote actions" + description: "" + operationId: bulkQuotes + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/index" + requestBody: + description: "Hashed ids" + required: true + content: + application/json: + schema: + type: array + items: + description: "Array of hashed IDs to be bulk 'actioned" + type: integer + example: "[0,1,2,3]" + responses: + 200: + description: "The Quote response" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Quote" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + "/api/v1/quotes/{id}/{action}": + get: + deprecated: true + tags: + - quotes + summary: "Performs a custom action on an Quote" + description: "Performs a custom action on an Quote.\n\n The current range of actions are as follows\n - clone_to_quote\n - history\n - delivery_note\n - mark_paid\n - download\n - archive\n - delete\n - convert\n - convert_to_invoice\n - email" + operationId: actionQuote + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Quote Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + - name: action + in: path + description: "The action string to be performed" + required: true + schema: + type: string + format: string + example: clone_to_quote + responses: + 200: + description: "Returns the Quote object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Quote" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + "/api/v1/quote/{invitation_key}/download": + get: + tags: + - quotes + summary: "Download quote PDF" + description: "Downloads a specific quote" + operationId: downloadQuote + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: invitation_key + in: path + description: "The Quote Invitation Key" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the quote pdf" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + "/api/v1/quotes/{id}/upload": + put: + tags: + - quotes + summary: "Upload a quote document" + description: "Handles the uploading of a document to a quote" + operationId: uploadQuote + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Quote Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the Quote object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Quote" 401: $ref: "#/components/responses/401" 403: @@ -13419,13 +13452,13 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/tasks: + /api/v1/vendors: get: tags: - - tasks - summary: "List tasks" - description: "Lists tasks, search and filters allow fine grained lists to be generated.\n *\n * Query parameters can be added to performed more fine grained filtering of the tasks, these are handled by the TaskFilters class which defines the methods available" - operationId: getTasks + - vendors + summary: "List vendors" + description: "Lists vendors, search and filters allow fine grained lists to be generated.\n\n Query parameters can be added to performed more fine grained filtering of the vendors, these are handled by the VendorFilters class which defines the methods available" + operationId: getVendors parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" @@ -13433,7 +13466,7 @@ paths: - $ref: "#/components/parameters/index" responses: 200: - description: "A list of tasks" + description: "A list of vendors" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -13449,7 +13482,7 @@ paths: data: type: array items: - $ref: '#/components/schemas/Task' + $ref: '#/components/schemas/Vendor' meta: type: object $ref: '#/components/schemas/Meta' @@ -13467,17 +13500,17 @@ paths: $ref: "#/components/responses/default" post: tags: - - tasks - summary: "Create task" - description: "Adds an task to a company" - operationId: storeTask + - vendors + summary: "Create vendor" + description: "Adds a vendor to a company" + operationId: storeVendor parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" responses: 200: - description: "Returns the saved task object" + description: "Returns the saved clivendorent object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -13488,7 +13521,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Task" + $ref: "#/components/schemas/Vendor" 401: $ref: "#/components/responses/401" 403: @@ -13501,20 +13534,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/tasks/{id}": + "/api/v1/vendors/{id}": get: tags: - - tasks - summary: "Show task" - description: "Displays a task by id" - operationId: showTask + - vendors + summary: "Show vendor" + description: "Displays a vendor by id" + operationId: showVendor parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Task Hashed ID" + description: "The vendor Hashed ID" required: true schema: type: string @@ -13522,7 +13555,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the task object" + description: "Returns the vendor object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -13533,7 +13566,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Task" + $ref: "#/components/schemas/Vendor" 401: $ref: "#/components/responses/401" 403: @@ -13548,17 +13581,17 @@ paths: $ref: "#/components/responses/default" put: tags: - - tasks - summary: "Update task" - description: "Handles the updating of a task by id" - operationId: updateTask + - vendors + summary: "Update vendor" + description: "Handles the updating of a vendor by id" + operationId: updateVendor parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The task Hashed ID" + description: "The Vendor Hashed ID" required: true schema: type: string @@ -13566,7 +13599,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the task object" + description: "Returns the vendor object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -13577,7 +13610,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Task" + $ref: "#/components/schemas/Vendor" 401: $ref: "#/components/responses/401" 403: @@ -13592,17 +13625,17 @@ paths: $ref: "#/components/responses/default" delete: tags: - - tasks - summary: "Delete task" - description: "Handles the deletion of a task by id" - operationId: deleteTask + - vendors + summary: "Delete vendor" + description: "Handles the deletion of a vendor by id" + operationId: deleteVendor parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Task Hashed ID" + description: "The Vendor Hashed ID" required: true schema: type: string @@ -13630,20 +13663,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/tasks/{id}/edit": + "/api/v1/vendors/{id}/edit": get: tags: - - tasks - summary: "Edit task" - description: "Displays a task by id" - operationId: editTask + - vendors + summary: "Edit vendor" + description: "Displays a vendor by id" + operationId: editVendor parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Task Hashed ID" + description: "The Vendor Hashed ID" required: true schema: type: string @@ -13651,7 +13684,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the client object" + description: "Returns the vendor object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -13662,7 +13695,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Task" + $ref: "#/components/schemas/Vendor" 401: $ref: "#/components/responses/401" 403: @@ -13675,20 +13708,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/tasks/create: + /api/v1/vendors/create: get: tags: - - tasks - summary: "Blank task" - description: "Returns a blank task with default values" - operationId: getTasksCreate + - vendors + summary: "Blank vendor" + description: "Returns a blank vendor with default values" + operationId: getVendorsCreate parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" responses: 200: - description: "A blank task object" + description: "A blank vendor object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -13699,7 +13732,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Task" + $ref: "#/components/schemas/Vendor" 401: $ref: "#/components/responses/401" 403: @@ -13712,13 +13745,13 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/tasks/bulk: + /api/v1/vendors/bulk: post: tags: - - tasks - summary: "Bulk task actions" + - vendors + summary: "Bulk vendor actions" description: "" - operationId: bulkTasks + operationId: bulkVendors parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" @@ -13736,7 +13769,7 @@ paths: example: "[0,1,2,3]" responses: 200: - description: "The Task User response" + description: "The Vendor User response" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -13747,7 +13780,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Task" + $ref: "#/components/schemas/Vendor" 401: $ref: "#/components/responses/401" 403: @@ -13760,20 +13793,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/tasks/{id}/upload": + "/api/v1/vendors/{id}/upload": put: tags: - - tasks - summary: "Uploads a task document" - description: "Handles the uploading of a document to a task" - operationId: uploadTask + - vendors + summary: "Uploads a vendor document" + description: "Handles the uploading of a document to a vendor" + operationId: uploadVendor parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Task Hashed ID" + description: "The Vendor Hashed ID" required: true schema: type: string @@ -13781,7 +13814,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the Task object" + description: "Returns the Vendor object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -13792,40 +13825,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Task" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - /api/v1/tasks/sort: - post: - tags: - - tasks - summary: "Sort tasks on KanBan" - description: "Sorts tasks after drag and drop on the KanBan." - operationId: sortTasks - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - responses: - 200: - description: "Returns an Ok, 200 HTTP status" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" + $ref: "#/components/schemas/Vendor" 401: $ref: "#/components/responses/401" 403: @@ -13931,43 +13931,43 @@ components: - last: https://invoicing.co/api/v1/invoices?page=1 - prev: null - next: null - 403: - description: 'Authorization error' - content: - application/json: - schema: - $ref: '#components/schemas/AuthorizationError' - - default: - description: 'Unexpected Error' - content: - application/json: - schema: - $ref: '#/components/schemas/Error' 429: description: 'Rate Limit Exceeded' content: application/json: schema: $ref: '#/components/schemas/RateLimiterError' + + 403: + description: 'Authorization error' + content: + application/json: + schema: + $ref: '#components/schemas/AuthorizationError' 422: description: 'Validation error' content: application/json: schema: $ref: '#/components/schemas/ValidationError' - 401: - description: 'Authentication error' + default: + description: 'Unexpected Error' content: application/json: schema: - $ref: '#components/schemas/AuthenticationError' + $ref: '#/components/schemas/Error' 400: description: 'Invalid user input' content: application/json: schema: $ref: '#components/schemas/InvalidInputError' + 401: + description: 'Authentication error' + content: + application/json: + schema: + $ref: '#components/schemas/AuthenticationError' parameters: X-API-SECRET: name: X-API-SECRET @@ -14543,243 +14543,194 @@ components: type: string example: JSON type: object - Credit: + Product: + type: object properties: id: - description: "The unique hashed ID of the credit" + type: string + description: 'The hashed product ID.' + example: eP01N + readOnly: true + company_id: + type: string + description: 'The hashed ID of the company that owns this product.' + example: eP01N + readOnly: true + user_id: + type: string + description: 'The hashed ID of the user that created this product.' + example: n30m4 + readOnly: true + assigned_user_id: + type: string + description: 'The hashed ID of the user assigned to this product.' + example: pR0j3 + project_id: + type: string + description: 'The hashed ID of the project that this product is associated with.' + example: pR0j3 + vendor_id: + type: string + description: 'The hashed ID of the vendor that this product is associated with.' + example: pR0j3 + custom_value1: + type: string + description: 'Custom value field 1.' + example: 'Custom value 1' + custom_value2: + type: string + description: 'Custom value field 2.' + example: 'Custom value 2' + custom_value3: + type: string + description: 'Custom value field 3.' + example: 'Custom value 3' + custom_value4: + type: string + description: 'Custom value field 4.' + example: 'Custom value 4' + product_key: + type: string + description: 'The product key.' + example: '1234' + notes: + type: string + description: 'Notes about the product.' + example: 'These are some notes about the product.' + cost: + type: number + format: double + description: 'The cost of the product. (Your purchase price for this product)' + example: 10.0 + price: + type: number + format: double + description: 'The price of the product that you are charging.' + example: 20.0 + quantity: + type: number + format: double + description: 'The quantity of the product. (used as a default)' + example: 5.0 + tax_name1: + type: string + description: 'The name of tax 1.' + example: 'Tax 1' + tax_rate1: + type: number + format: double + description: 'The rate of tax 1.' + example: 10.0 + tax_name2: + type: string + description: 'The name of tax 2.' + example: 'Tax 2' + tax_rate2: + type: number + format: double + description: 'The rate of tax 2.' + example: 5.0 + tax_name3: + type: string + description: 'The name of tax 3.' + example: 'Tax 3' + tax_rate3: + type: number + format: double + description: 'The rate of tax 3.' + example: 0.0 + archived_at: + type: integer + format: timestamp + description: 'The timestamp when the product was archived.' + example: '2022-03-18T15:00:00Z' + readOnly: true + created_at: + type: integer + format: timestamp + description: 'The timestamp when the product was created.' + example: '2022-03-18T15:00:00Z' + readOnly: true + updated_at: + description: Timestamp + type: integer + format: timestamp + example: '2022-03-18T12:34:56.789Z' + readOnly: true + is_deleted: + type: boolean + description: 'Boolean flag determining if the product has been deleted' + example: false + readOnly: true + in_stock_quantity: + type: integer + format: int32 + description: The quantity of the product that is currently in stock + default: 0 + stock_notification: + type: boolean + description: Indicates whether stock notifications are enabled for this product + default: true + stock_notification_threshold: + type: integer + format: int32 + description: The minimum quantity threshold for which stock notifications will be triggered + default: 0 + max_quantity: + type: integer + format: int32 + description: The maximum quantity that can be ordered for this product + product_image: + type: string + description: The URL of the product image + format: uri-reference + tax_id: + type: string + default: '1' + description: | + The tax category id for this product.' + + The following constants are available (default = '1') + + ``` + PRODUCT_TYPE_PHYSICAL = '1' + PRODUCT_TYPE_SERVICE = '2' + PRODUCT_TYPE_DIGITAL = '3' + PRODUCT_TYPE_SHIPPING = '4' + PRODUCT_TYPE_EXEMPT = '5' + PRODUCT_TYPE_REDUCED_TAX = '6' + PRODUCT_TYPE_OVERRIDE_TAX = '7' + PRODUCT_TYPE_ZERO_RATED = '8' + PRODUCT_TYPE_REVERSE_TAX = '9' + ``` + example: '1' + + ExpenseCategory: + properties: + id: + description: 'The expense hashed id' type: string example: Opnel5aKBz + name: + description: 'The expense category name' + type: string + example: Accounting user_id: - description: "The unique hashed ID of the user associated with the credit" + description: 'The user hashed id' type: string - example: 1a2b3c4d5e - assigned_user_id: - description: "The unique hashed ID of the assigned user responsible for the credit" - type: string - example: 6f7g8h9i0j - company_id: - description: "The unique hashed ID of the company associated with the credit" - type: string - example: k1l2m3n4o5 - client_id: - description: "The unique hashed ID of the client associated with the credit" - type: string - example: p1q2r3s4t5 - status_id: - description: "The ID representing the current status of the credit" - type: string - example: 3 - invoice_id: - description: "The unique hashed ID of the linked invoice to which the credit is applied" - type: string - example: u1v2w3x4y5 - number: - description: "The unique alphanumeric credit number per company" - type: string - example: QUOTE_101 - po_number: - description: "The purchase order number referred to by the credit" - type: string - example: PO_12345 - terms: - description: "The terms associated with the credit" - type: string - example: "Net 30" - public_notes: - description: "Public notes for the credit" - type: string - example: "Thank you for your business." - private_notes: - description: "Private notes for internal use, not visible to the client" - type: string - example: "Client is requesting a discount." - footer: - description: "The footer text for the credit" - type: string - example: "Footer text goes here." - custom_value1: - description: "Custom value 1 for additional credit information" - type: string - example: "Custom data 1" - custom_value2: - description: "Custom value 2 for additional credit information" - type: string - example: "Custom data 2" - custom_value3: - description: "Custom value 3 for additional credit information" - type: string - example: "Custom data 3" - custom_value4: - description: "Custom value 4 for additional credit information" - type: string - example: "Custom data 4" - tax_name1: - description: "The name of the first tax applied to the credit" - type: string - example: "VAT" - tax_name2: - description: "The name of the second tax applied to the credit" - type: string - example: "GST" - tax_rate1: - description: "The rate of the first tax applied to the credit" - type: number - format: float - example: 10.00 - tax_rate2: - description: "The rate of the second tax applied to the credit" - type: number - format: float - example: 5.00 - tax_name3: - description: "The name of the third tax applied to the credit" - type: string - example: "PST" - tax_rate3: - description: "The rate of the third tax applied to the credit" - type: number - format: float - example: 8.00 - total_taxes: - description: "The total amount of taxes for the credit" - type: number - format: float - example: 23.00 - line_items: - type: array - description: 'An array of objects which define the line items of the credit' - items: - $ref: '#/components/schemas/InvoiceItem' - amount: - description: "The total amount of the credit" - type: number - format: float - example: 100.00 - balance: - description: "The outstanding balance of the credit" - type: number - format: float - example: 50.00 - paid_to_date: - description: "The total amount paid to date for the credit" - type: number - format: float - example: 50.00 - discount: - description: "The discount applied to the credit" - type: number - format: float - example: 10.00 - partial: - description: "The partial amount applied to the credit" - type: number - format: float - example: 20.00 - is_amount_discount: - description: "Indicates whether the discount applied is a fixed amount or a percentage" - type: boolean - example: true + example: XS987sD is_deleted: - description: "Indicates whether the credit has been deleted" - type: boolean - example: false - uses_inclusive_taxes: - description: "Indicates whether the tax rates applied to the credit are inclusive or exclusive" + description: 'Flag determining whether the expense category has been deleted' type: boolean example: true - date: - description: "The date the credit was issued" - type: string - format: date - example: "1994-07-30" - last_sent_date: - description: "The date the credit was last sent out" - type: string - format: date - example: "1994-07-30" - next_send_date: - description: "The next scheduled date for sending a credit reminder" - type: string - format: date - example: "1994-07-30" - partial_due_date: - description: "The due date for the partial amount of the credit" - type: string - format: date - example: "1994-07-30" - due_date: - description: "The due date for the total amount of the credit" - type: string - format: date - example: "1994-07-30" - settings: - $ref: "#/components/schemas/CompanySettings" - last_viewed: - description: "The timestamp of the last time the credit was viewed" - type: number - format: integer - example: 1434342123 updated_at: - description: "The timestamp of the last time the credit was updated" - type: number - format: integer - example: 1434342123 - archived_at: - description: "The timestamp of the last time the credit was archived" - type: number - format: integer - example: 1434342123 - custom_surcharge1: - description: "First custom surcharge amount" - type: number - format: float - example: 10.00 - custom_surcharge2: - description: 'Second Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge3: - description: 'Third Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge4: - description: 'Fourth Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge_tax1: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax2: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax3: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax4: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - type: object - - GenericBulkAction: - properties: - action: - type: string - example: archive - description: 'The action to perform ie. archive / restore / delete' - ids: - type: array - items: - format: string - type: string - example: 2J234DFA,D2J234DFA,D2J234DFA - description: string array of client hashed ids + description: 'The updated at timestamp' + type: integer + example: '2' + created_at: + description: 'The created at timestamp' + type: integer + example: '2' type: object ProductRequest: type: object @@ -14941,1411 +14892,228 @@ components: ``` example: '1' - CompanyLedger: + CompanyToken: properties: - entity_id: - description: 'This field will reference one of the following entity hashed ID payment_id, invoice_id or credit_id' + name: + description: 'The token name' + type: string + example: 'Token Name' + token: + description: 'The token value' + type: string + example: AS3df3jUUH765fhfd9KJuidj3JShjA + is_system: + description: 'Determines whether the token is created by the system rather than a user' + type: boolean + example: 'true' + type: object + Document: + properties: + id: + description: 'The document hashed id' type: string example: AS3df3A - notes: - description: 'The notes which reference this entry of the ledger' + user_id: + description: 'The user hashed id' type: string - example: 'Credit note for invoice #3212' - balance: - description: 'The client balance' - type: number - format: float - example: '10.00' - adjustment: - description: 'The amount the client balance is adjusted by' - type: number - format: float - example: '10.00' - updated_at: - description: Timestamp - type: number - format: integer - example: '1434342123' + example: '' + assigned_user_id: + description: 'The assigned user hashed id' + type: string + example: '' + project_id: + description: 'The project associated with this document' + type: string + example: '' + vendor_id: + description: 'The vendor associated with this documents' + type: string + example: '' + name: + description: 'The document name' + type: string + example: Beauty + url: + description: 'The document url' + type: string + example: Beauty + preview: + description: 'The document preview url' + type: string + example: Beauty + type: + description: 'The document type' + type: string + example: Beauty + disk: + description: 'The document disk' + type: string + example: Beauty + hash: + description: 'The document hashed' + type: string + example: Beauty + is_deleted: + description: 'Flag to determine if the document is deleted' + type: boolean + example: true + is_default: + description: 'Flag to determine if the document is a default doc' + type: boolean + example: true created_at: description: Timestamp type: number format: integer - example: '1434342123' - type: object - Invoice: - properties: - id: - description: 'The invoice hashed id' - type: string - example: Opnel5aKBz - readOnly: true - user_id: - description: 'The user hashed id' - type: string - example: Opnel5aKBz - readOnly: true - assigned_user_id: - description: 'The assigned user hashed id' - type: string - example: Opnel5aKBz - company_id: - description: 'The company hashed id' - type: string - example: Opnel5aKBz - readOnly: true - client_id: - description: 'The client hashed id' - type: string - example: Opnel5aKBz - status_id: - description: 'The invoice status variable' - type: string - example: '4' - number: - description: 'The invoice number - is a unique alpha numeric number per invoice per company' - type: string - example: INV_101 - po_number: - description: 'The purchase order associated with this invoice' - type: string - example: PO-1234 - terms: - description: 'The invoice terms' - type: string - example: 'These are invoice terms' - public_notes: - description: 'The public notes of the invoice' - type: string - example: 'These are some public notes' - private_notes: - description: 'The private notes of the invoice' - type: string - example: 'These are some private notes' - footer: - description: 'The invoice footer notes' - type: string - example: '' - custom_value1: - description: 'A custom field value' - type: string - example: '2022-10-01' - custom_value2: - description: 'A custom field value' - type: string - example: 'Something custom' - custom_value3: - description: 'A custom field value' - type: string - example: '' - custom_value4: - description: 'A custom field value' - type: string - example: '' - tax_name1: - description: 'The tax name' - type: string - example: '' - tax_name2: - description: 'The tax name' - type: string - example: '' - tax_rate1: - description: 'The tax rate' - type: number - format: float - example: '10.00' - tax_rate2: - description: 'The tax rate' - type: number - format: float - example: '10.00' - tax_name3: - description: 'The tax name' - type: string - example: '' - tax_rate3: - description: 'The tax rate' - type: number - format: float - example: '10.00' - total_taxes: - description: 'The total taxes for the invoice' - type: number - format: float - example: '10.00' - line_items: - type: array - description: 'An array of objects which define the line items of the invoice' - items: - $ref: '#/components/schemas/InvoiceItem' - invitations: - type: array - description: 'An array of objects which define the invitations of the invoice' - items: - $ref: '#/components/schemas/InvoiceInvitation' - amount: - description: 'The invoice amount' - type: number - format: float - example: '10.00' - balance: - description: 'The invoice balance' - type: number - format: float - example: '10.00' - paid_to_date: - description: 'The amount paid on the invoice to date' - type: number - format: float - example: '10.00' - discount: - description: 'The invoice discount, can be an amount or a percentage' - type: number - format: float - example: '10.00' - partial: - description: 'The deposit/partial amount' - type: number - format: float - example: '10.00' - is_amount_discount: - description: 'Flag determining if the discount is an amount or a percentage' - type: boolean - example: true - is_deleted: - description: 'Defines if the invoice has been deleted' - type: boolean - example: true - uses_inclusive_taxes: - description: 'Defines the type of taxes used as either inclusive or exclusive' - type: boolean - example: true - date: - description: 'The Invoice Date' - type: string - format: date - example: '1994-07-30' - last_sent_date: - description: 'The last date the invoice was sent out' - type: string - format: date - example: '1994-07-30' - next_send_date: - description: 'The Next date for a reminder to be sent' - type: string - format: date - example: '1994-07-30' - partial_due_date: - description: 'The due date for the deposit/partial amount' - type: string - format: date - example: '1994-07-30' - due_date: - description: 'The due date of the invoice' - type: string - format: date - example: '1994-07-30' - last_viewed: - description: Timestamp - type: number - format: integer - example: '1434342123' + example: '134341234234' updated_at: description: Timestamp type: number format: integer - example: '1434342123' - archived_at: + example: '134341234234' + deleted_at: description: Timestamp type: number format: integer - example: '1434342123' - custom_surcharge1: - description: 'First Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge2: - description: 'Second Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge3: - description: 'Third Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge4: - description: 'Fourth Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge_tax1: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax2: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax3: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax4: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - project_id: - description: 'The project associated with this invoice' - type: string - example: Opnel5aKBz - auto_bill_tries: - description: 'The number of times the invoice has attempted to be auto billed' - type: integer - example: '1' - readOnly: true - auto_bill_enabled: - description: 'Boolean flag determining if the invoice is set to auto bill' - type: boolean - example: true - subscription_id: - description: 'The subscription associated with this invoice' - type: string - example: Opnel5aKBz - + example: '134341234234' type: object - Company: + Payment: properties: id: - description: "The unique hashed identifier for the company" - type: string - example: WJxbojagwO - size_id: - description: "The unique identifier representing the company's size category" - type: string - example: '2' - industry_id: - description: "The unique identifier representing the company's industry category" - type: string - example: '5' - slack_webhook_url: - description: "The URL for the company's Slack webhook notifications" - type: string - example: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' - google_analytics_key: - description: "The company's Google Analytics tracking ID" - type: string - example: 'UA-123456789-1' - portal_mode: - description: "The mode determining how client-facing URLs are structured (e.g., subdomain, domain, or iframe)" - type: string - example: subdomain - subdomain: - description: "The subdomain prefix for the company's domain (e.g., 'acme' in acme.domain.com)" - type: string - example: acme - portal_domain: - description: "The fully qualified domain used for client-facing URLs" - type: string - example: 'https://subdomain.invoicing.co' - enabled_tax_rates: - description: "The number of tax rates used per entity" - type: integer - example: '2' - fill_products: - description: "A flag determining whether to auto-fill product descriptions based on the product key" - type: boolean - example: true - convert_products: - description: "A flag determining whether to convert products between different types or units" - type: boolean - example: true - update_products: - description: "A flag determining whether to update product descriptions when the description changes" - type: boolean - example: true - show_product_details: - description: "A flag determining whether to display product details in the user interface" - type: boolean - example: true - show_product_cost: - description: "A flag determining whether to display product cost is shown in the user interface" - type: boolean - example: true - custom_fields: - description: "A mapping of custom fields for various objects within the company" - type: object - enable_product_cost: - description: "A flag determining whether to show or hide the product cost field in the user interface" - type: boolean - example: true - enable_product_quantity: - description: "A flag determining whether to show or hide the product quantity field in the user interface" - type: boolean - example: true - default_quantity: - description: "A flag determining whether to use a default quantity for products" - type: boolean - example: true - custom_surcharge_taxes1: - description: "A flag determining whether to apply taxes on custom surcharge amounts for the first custom surcharge field" - type: boolean - example: true - custom_surcharge_taxes2: - description: "A flag determining whether to apply taxes on custom surcharge amounts for the second custom surcharge field" - type: boolean - example: true - custom_surcharge_taxes3: - description: "A flag determining whether to apply taxes on custom surcharge amounts for the third custom surcharge field" - type: boolean - example: true - custom_surcharge_taxes4: - description: "A flag determining whether to apply taxes on custom surcharge amounts for the fourth custom" - logo: - description: "The company logo file in binary format" - type: string - format: binary - example: logo.png - company_key: - description: "The static company key hash used to identify the Company" - readOnly: true - type: string - example: "Vnb14bRlwiFjc5ckte6cfbygTRkn5IMQ" - client_can_register: - description: "A flag determining whether clients can register for the client portal" - type: boolean - example: true - enabled_modules: - type: integer - description: | - Bitmask representation of the modules that are enabled in the application - - ``` - self::ENTITY_RECURRING_INVOICE => 1, - self::ENTITY_CREDIT => 2, - self::ENTITY_QUOTE => 4, - self::ENTITY_TASK => 8, - self::ENTITY_EXPENSE => 16, - self::ENTITY_PROJECT => 32, - self::ENTITY_VENDOR => 64, - self::ENTITY_TICKET => 128, - self::ENTITY_PROPOSAL => 256, - self::ENTITY_RECURRING_EXPENSE => 512, - self::ENTITY_RECURRING_TASK => 1024, - self::ENTITY_RECURRING_QUOTE => 2048, - ``` - - The default per_page value is 20. - - example: 2048 - db: - readOnly: true - type: string - example: 'db-ninja-01' - first_day_of_week: - description: "The first day of the week for the company" - type: string - example: '1' - first_month_of_year: - description: "The first month for the company financial year" - type: string - example: '1' - enabled_item_tax_rates: - description: "The number of tax rates used per item" - type: integer - example: 2 - is_large: - description: "A flag determining whether the company is considered large" - type: boolean - example: true - default_auto_bill: - type: enum - example: 'always' - description: | - A flag determining whether to auto-bill clients by default - - values: - - - always - Always auto bill - - disabled - Never auto bill - - optin - Allow the client to select their auto bill status with the default being disabled - - optout -Allow the client to select their auto bill status with the default being enabled - mark_expenses_invoiceable: - description: "A flag determining whether to mark expenses as invoiceable by default" - type: boolean - example: true - mark_expenses_paid: - description: "A flag determining whether to mark expenses as paid by default" - type: boolean - example: true - invoice_expense_documents: - description: "A flag determining whether to include expense documents on invoices by default" - type: boolean - example: true - auto_start_tasks: - description: "A flag determining whether to auto-start tasks by default" - type: boolean - example: true - invoice_task_timelog: - description: "A flag determining whether to include task time logs on invoices by default" - type: boolean - example: true - invoice_task_documents: - description: "A flag determining whether to include task documents on invoices by default" - type: boolean - example: true - show_tasks_table: - description: "A flag determining whether to show the tasks table on invoices by default" - type: boolean - example: true - is_disabled: - description: "A flag determining whether the company is disabled" - type: boolean - example: true - default_task_is_date_based: - description: "A flag determining whether to default tasks to be date-based" - type: boolean - example: true - enable_product_discount: - description: "A flag determining whether to show or hide the product discount field in the user interface" - type: boolean - example: true - calculate_expense_tax_by_amount: - description: "A flag determining whether to calculate expense taxes by amount" - type: boolean - example: true - expense_inclusive_taxes: - description: "A flag determining whether to include taxes in the expense amount" - type: boolean - example: true - session_timeout: - description: "The session timeout for the company" - type: integer - example: 60 - oauth_password_required: - description: "A flag determining whether to require a password for `dangerous` actions when using OAuth" - type: boolean - example: true - invoice_task_datelog: - description: "A flag determining whether to include task date logs on invoices by default" - type: boolean - example: true - default_password_timeout: - description: "The default password timeout for the company" - type: integer - example: 60 - show_task_end_date: - description: "A flag determining whether to show the task end date on invoices by default" - type: boolean - example: true - markdown_enabled: - description: "A flag determining whether markdown is enabled for the company" - type: boolean - example: true - report_include_drafts: - description: "A flag determining whether to include draft invoices in reports" - type: boolean - example: true - client_registration_fields: - description: "The client registration fields for the company" - type: object - stop_on_unpaid_recurring: - description: "A flag determining whether to stop recurring invoices when they are unpaid" - type: boolean - example: true - use_quote_terms_on_conversion: - description: "A flag determining whether to use quote terms on conversion to an invoice" - type: boolean - example: true - enable_applying_payments: - description: "A flag determining whether to enable applying payments to invoices" - type: boolean - example: true - track_inventory: - description: "A flag determining whether to track inventory for the company" - type: boolean - example: true - inventory_notification_threshold: - description: "The inventory notification threshold for the company" - type: integer - example: 60 - stock_notification: - description: "A flag determining whether to send stock notifications for the company" - type: boolean - example: true - matomo_url: - description: "The Matomo URL for the company" - type: string - example: 'https://matomo.example.com' - matomo_id: - description: "The Matomo ID for the company" - type: string - example: '1' - enabled_expense_tax_rates: - description: "The number of tax rates used per expense" - type: integer - example: 2 - invoice_task_project: - description: "A flag determining whether to include the project on invoices by default" - type: boolean - example: true - report_include_deleted: - description: "A flag determining whether to include deleted invoices in reports" - type: boolean - example: true - invoice_task_lock: - description: "A flag determining whether to lock tasks when invoiced" - type: boolean - example: true - convert_payment_currency: - description: "A flag determining whether to convert the payment currency" - type: boolean - example: true - convert_expense_currency: - description: "A flag determining whether to convert the expense currency" - type: boolean - example: true - notify_vendor_when_paid: - description: "A flag determining whether to notify the vendor when an expense is paid" - type: boolean - example: true - invoice_task_hours: - description: "A flag determining whether to include the task hours on invoices by default" - type: boolean - example: true - calculate_taxes: - description: "A flag determining whether to calculate taxes for the company" - type: boolean - example: true - tax_data: - description: "The tax data for the company" - type: object - e_invoice_certificate: - description: "The e-invoice certificate for the company" - type: string - example: '-----BEGIN CERTIFICATE-----' - e_invoice_certificate_passphrase: - description: "The e-invoice certificate passphrase for the company" - type: string - example: 'secret' - origin_tax_data: - description: "The origin tax data for the company" - type: object - invoice_task_project_header: - description: "A flag determining whether to include the project header on invoices by default" - type: boolean - example: true - invoice_task_item_description: - description: "A flag determining whether to include the item description on invoices by default" - type: boolean - example: true - - settings: - $ref: '#/components/schemas/CompanySettings' - type: object - InvoiceRequest: - required: - - client_id - properties: - id: - description: 'The invoice hashed id' + description: 'The payment hashed id' type: string example: Opnel5aKBz - readOnly: true - user_id: - description: 'The user hashed id' - type: string - example: Opnel5aKBz - assigned_user_id: - description: 'The assigned user hashed id' - type: string - example: Opnel5aKBz - company_id: - description: 'The company hashed id' - type: string - example: Opnel5aKBz - readOnly: true client_id: description: 'The client hashed id' type: string example: Opnel5aKBz - status_id: - description: 'The invoice status variable' - type: string - example: '4' - readOnly: true - number: - description: 'The invoice number - is a unique alpha numeric number per invoice per company' - type: string - example: INV_101 - po_number: - description: 'The purchase order associated with this invoice' - type: string - example: PO-1234 - terms: - description: 'The invoice terms' - type: string - example: 'These are invoice terms' - public_notes: - description: 'The public notes of the invoice' - type: string - example: 'These are some public notes' - private_notes: - description: 'The private notes of the invoice' - type: string - example: 'These are some private notes' - footer: - description: 'The invoice footer notes' - type: string - example: '' - custom_value1: - description: 'A custom field value' - type: string - example: '2022-10-01' - custom_value2: - description: 'A custom field value' - type: string - example: 'Something custom' - custom_value3: - description: 'A custom field value' - type: string - example: '' - custom_value4: - description: 'A custom field value' - type: string - example: '' - tax_name1: - description: 'The tax name' - type: string - example: '' - tax_name2: - description: 'The tax name' - type: string - example: '' - tax_rate1: - description: 'The tax rate' - type: number - format: float - example: '10.00' - tax_rate2: - description: 'The tax rate' - type: number - format: float - example: '10.00' - tax_name3: - description: 'The tax name' - type: string - example: '' - tax_rate3: - description: 'The tax rate' - type: number - format: float - example: '10.00' - total_taxes: - description: 'The total taxes for the invoice' - type: number - format: float - example: '10.00' - readOnly: - line_items: - type: array - description: 'An array of objects which define the line items of the invoice' - items: - $ref: '#/components/schemas/InvoiceItem' - invitations: - type: array - description: 'An array of objects which define the invitations of the invoice' - items: - $ref: '#/components/schemas/InvoiceInvitationRequest' - amount: - description: 'The invoice amount' - type: number - format: float - example: '10.00' - readOnly: true - balance: - description: 'The invoice balance' - type: number - format: float - example: '10.00' - readOnly: true - paid_to_date: - description: 'The amount paid on the invoice to date' - type: number - format: float - example: '10.00' - readOnly: true - discount: - description: 'The invoice discount, can be an amount or a percentage' - type: number - format: float - example: '10.00' - partial: - description: 'The deposit/partial amount' - type: number - format: float - example: '10.00' - is_amount_discount: - description: 'Flag determining if the discount is an amount or a percentage' - type: boolean - example: true - is_deleted: - description: 'Defines if the invoice has been deleted' - type: boolean - example: true - readOnly: true - uses_inclusive_taxes: - description: 'Defines the type of taxes used as either inclusive or exclusive' - type: boolean - example: true - date: - description: 'The Invoice Date' - type: string - format: date - example: '1994-07-30' - last_sent_date: - description: 'The last date the invoice was sent out' - type: string - format: date - example: '1994-07-30' - readOnly: true - next_send_date: - description: 'The Next date for a reminder to be sent' - type: string - format: date - example: '1994-07-30' - readOnly: true - partial_due_date: - description: 'The due date for the deposit/partial amount' - type: string - format: date - example: '1994-07-30' - due_date: - description: 'The due date of the invoice' - type: string - format: date - example: '1994-07-30' - last_viewed: - description: Timestamp - type: number - format: integer - example: '1434342123' - readOnly: true - updated_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - readOnly: true - archived_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - readOnly: true - custom_surcharge1: - description: 'First Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge2: - description: 'Second Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge3: - description: 'Third Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge4: - description: 'Fourth Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge_tax1: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax2: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax3: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax4: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - project_id: - description: 'The project associated with this invoice' + invitation_id: + description: 'The invitation hashed id' type: string example: Opnel5aKBz - type: object - SystemLog: - properties: - id: - description: 'The account hashed id' + client_contact_id: + description: 'The client contact hashed id' type: string - example: AS3df3A - company_id: - description: 'The company hashed id' - type: string - example: AS3df3A + example: Opnel5aKBz user_id: - description: 'The user_id hashed id' + description: 'The user hashed id' type: string - example: AS3df3A - client_id: - description: 'The client_id hashed id' - type: string - example: AS3df3A - event_id: - description: 'The Log Type ID' - type: integer - example: 1 - category_id: - description: 'The Category Type ID' - type: integer - example: 1 + example: Opnel5aKBz type_id: - description: 'The Type Type ID' - type: integer - example: 1 - log: - description: 'The json object of the error' - type: object - example: '{''key'':''value''}' - updated_at: - description: Timestamp + description: 'The Payment Type ID' type: string - example: '2' - created_at: - description: Timestamp + example: '1' + date: + description: 'The Payment date' type: string - example: '2' - type: object - Expense: - properties: - id: - description: 'The expense hashed id' + example: 1-1-2014 + transaction_reference: + description: 'The transaction reference as defined by the payment gateway' type: string - example: Opnel5aKBz - user_id: - description: 'The user hashed id' - type: string - example: '' + example: xcsSxcs124asd assigned_user_id: description: 'The assigned user hashed id' type: string - example: '' - company_id: - description: 'The company hashed id' - type: string - example: '' - client_id: - description: 'The client hashed id' - type: string - example: '' - invoice_id: - description: 'The related invoice hashed id' - type: string - example: '' - bank_id: - description: 'The bank id related to this expense' - type: string - example: '' - invoice_currency_id: - description: 'The currency id of the related invoice' - type: string - example: '' - expense_currency_id: - description: 'The currency id of the expense' - type: string - example: '' - invoice_category_id: - description: 'The invoice category id' - type: string - example: '' - payment_type_id: - description: 'The payment type id' - type: string - example: '' - recurring_expense_id: - description: 'The related recurring expense this expense was created from' - type: string - example: '' + example: Opnel5aKBz private_notes: - description: 'The private notes of the expense' + description: 'The private notes of the payment' type: string - example: '' - public_notes: - description: 'The public notes of the expense' - type: string - example: '' - transaction_reference: - description: 'The transaction references of the expense' - type: string - example: '' - transcation_id: - description: 'The transaction id of the expense' - type: string - example: '' - custom_value1: - description: 'A custom value' - type: string - example: '' - custom_value2: - description: 'A custom value' - type: string - example: '' - custom_value3: - description: 'A custom value' - type: string - example: '' - custom_value4: - description: 'A custom value' - type: string - example: '' - tax_name1: - description: 'Tax name' - type: string - example: '' - tax_name2: - description: 'Tax name' - type: string - example: '' - tax_rate1: - description: 'Tax rate' - type: number - format: float - example: '10.00' - tax_rate2: - description: 'Tax rate' - type: number - format: float - example: '10.00' - tax_name3: - description: 'Tax name' - type: string - example: '' - tax_rate3: - description: 'Tax rate' - type: number - format: float - example: '10.00' - amount: - description: 'The total expense amont' - type: number - format: float - example: '10.00' - foreign_amount: - description: 'The total foreign amount of the expense' - type: number - format: float - example: '10.00' - exchange_rate: - description: 'The exchange rate at the time of the expense' - type: number - format: float - example: '0.80' - date: - description: 'The expense date formate Y-m-d' - type: string - example: '2022-12-01' - payment_date: - description: 'The date of payment for the expense, format Y-m-d' - type: string - example: '' - should_be_invoiced: - description: 'Flag whether the expense should be invoiced' + example: 'The payment was refunded due to error' + is_manual: + description: 'Flags whether the payment was made manually or processed via a gateway' type: boolean example: true is_deleted: - description: 'Boolean determining whether the expense has been deleted' + description: 'Defines if the payment has been deleted' type: boolean example: true - invoice_documents: - description: 'Passing the expense documents over to the invoice' - type: boolean - example: true - updated_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - archived_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - type: object - BankTransaction: - properties: - id: - description: 'The bank integration hashed id' - type: string - example: AS3df3A - company_id: - description: 'The company hashed id' - type: string - example: AS3df3A - user_id: - description: 'The user hashed id' - type: string - example: AS3df3A - transaction_id: - description: 'The id of the transaction rule' - type: integer - example: 343434 amount: - description: 'The transaction amount' + description: 'The amount of this payment' type: number example: 10 - currency_id: - description: 'The currency ID of the currency' + refunded: + description: 'The refunded amount of this payment' + type: number + example: 10 + updated_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + archived_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + company_gateway_id: + description: 'The company gateway id' type: string - example: '1' - account_type: - description: 'The account type' + example: '3' + paymentables: + $ref: '#/components/schemas/Paymentable' + invoices: + description: '' + type: array + items: + $ref: '#/components/schemas/InvoicePaymentable' + credits: + description: '' + type: array + items: + $ref: '#/components/schemas/CreditPaymentable' + number: + description: 'The payment number - is a unique alpha numeric number per payment per company' type: string - example: creditCard - description: - description: 'The description of the transaction' + example: PAY_101 + type: object + + BankTransactionRule: + properties: + id: + description: 'The bank transaction rules hashed id' type: string - example: 'Potato purchases for kevin' - category_id: - description: 'The category id' - type: integer - example: 1 - category_type: - description: 'The category description' + example: AS3df3A + company_id: + description: 'The company hashed id' type: string - example: Expenses - base_type: - description: 'Either CREDIT or DEBIT' + example: AS3df3A + user_id: + description: 'The user hashed id' + type: string + example: AS3df3A + name: + description: 'The name of the transaction' + type: string + example: 'Rule 1' + rules: + description: 'A mapped collection of the sub rules for the BankTransactionRule' + type: array + items: + $ref: '#/components/schemas/BTRules' + auto_convert: + description: 'Flags whether the rule converts the transaction automatically' + type: boolean + example: true + matches_on_all: + description: 'Flags whether all subrules are required for the match' + type: boolean + example: true + applies_to: + description: 'Flags whether the rule applies to a CREDIT or DEBIT' type: string example: CREDIT - date: - description: 'The date of the transaction' - type: string - example: '2022-09-01' - bank_account_id: - description: 'The ID number of the bank account' - type: integer - example: '1' - type: object - ExpenseCategory: - properties: - id: - description: 'The expense hashed id' - type: string - example: Opnel5aKBz - name: - description: 'The expense category name' - type: string - example: Accounting - user_id: - description: 'The user hashed id' - type: string - example: XS987sD - is_deleted: - description: 'Flag determining whether the expense category has been deleted' - type: boolean - example: true - updated_at: - description: 'The updated at timestamp' - type: integer - example: '2' - created_at: - description: 'The created at timestamp' - type: integer - example: '2' - type: object - BankIntegration: - properties: - id: - description: 'The bank integration hashed id' - type: string - example: AS3df3A - company_id: - description: 'The company hashed id' - type: string - example: AS3df3A - user_id: - description: 'The user hashed id' - type: string - example: AS3df3A - provider_bank_name: - description: 'The providers bank name' - type: string - example: 'Chase Bank' - bank_account_id: - description: 'The bank account id' - type: integer - example: '1233434' - bank_account_name: - description: 'The name of the account' - type: string - example: 'My Checking Acc' - bank_account_number: - description: 'The account number' - type: string - example: '111 234 2332' - bank_account_status: - description: 'The status of the bank account' - type: string - example: ACTIVE - bank_account_type: - description: 'The type of account' - type: string - example: CREDITCARD - balance: - description: 'The current bank balance if available' - type: number - example: '1000000' - currency: - description: 'iso_3166_3 code' - type: string - example: USD - type: object - Subscription: - properties: - id: - description: Unique identifier for the subscription - type: string - example: Opnel5aKBz - user_id: - description: Unique identifier for the user associated with the subscription - type: string - example: Ua6Rw4pVbS - product_id: - description: Unique identifier for the product associated with the subscription - type: string - example: Pr5Ft7yBmC - company_id: - description: Unique identifier for the company associated with the subscription - type: string - example: Co7Vn3yLmW - recurring_invoice_id: - description: Unique identifier for the recurring invoice associated with the subscription - type: string - example: Ri2Yt8zJkP - is_recurring: - description: Indicates whether the subscription is recurring - type: boolean - example: 'true' - frequency_id: - description: 'integer const representation of the frequency' - type: string - example: '1' - auto_bill: - description: 'enum setting' - type: string - example: always - promo_code: - description: Promotional code applied to the subscription - type: string - example: PROMOCODE4U - promo_discount: - description: Discount percentage or amount applied to the subscription - type: number - example: 10 - is_amount_discount: - description: Indicates whether the discount is a fixed amount - type: boolean - example: 'true' - allow_cancellation: - description: Indicates whether the subscription can be cancelled - type: boolean - example: 'true' - per_seat_enabled: - description: Indicates whether the subscription pricing is per seat - type: boolean - example: 'true' - currency_id: - description: Unique identifier for the currency used in the subscription - type: integer - example: '1' - max_seats_limit: - description: Maximum number of seats allowed for the subscription - type: integer - example: '100' - trial_enabled: - description: Indicates whether the subscription has a trial period - type: boolean - example: 'true' - trial_duration: - description: Duration of the trial period in days - type: integer - example: '14' - allow_query_overrides: - description: Indicates whether query overrides are allowed for the subscription - type: boolean - example: 'true' - allow_plan_changes: - description: Indicates whether plan changes are allowed for the subscription - type: boolean - example: 'true' - refund_period: - description: Number of days within which refunds can be requested - type: integer - example: '30' - webhook_configuration: - description: Webhook configuration for the subscription - type: string - example: 'expand reference for this' - is_deleted: - description: Indicates whether the subscription has been deleted - type: boolean - example: 'false' - archived_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - created_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - updated_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - type: object - BulkAction: - type: array - items: - type: integer - example: '[0,1,2,3,]' - FillableInvoice: - properties: - assigned_user_id: - description: "The assigned user's hashed ID" - type: string - example: 'a1b2c3d4' client_id: - description: "The client's hashed ID" + description: 'The client hashed id' type: string - example: 'x1y2z3a4' - number: - description: "The unique alphanumeric invoice number for each invoice per company" + example: AS3df3A + vendor_id: + description: 'The vendor hashed id' type: string - example: INV_101 - po_number: - description: "The purchase order number associated with the invoice" + example: AS3df3A + category_id: + description: 'The category hashed id' type: string - example: 'PO12345' - terms: - description: "The terms and conditions for the invoice" - type: string - example: 'Net 30' - public_notes: - description: "Public notes visible to the client on the invoice" - type: string - example: 'Thank you for your business.' - private_notes: - description: "Private notes for internal use only" - type: string - example: 'Client is a slow payer.' - footer: - description: "The footer text displayed on the invoice" - type: string - example: 'Authorized Signature' - custom_value1: - description: "First custom value for additional information" - type: string - example: 'Project ABC' - custom_value2: - description: "Second custom value for additional information" - type: string - example: 'Department XYZ' - custom_value3: - description: "Third custom value for additional information" - type: string - example: 'Location 123' - custom_value4: - description: "Fourth custom value for additional information" - type: string - example: 'Currency USD' - tax_name1: - description: "Name of the first tax applied to the invoice" - type: string - example: 'VAT' - tax_name2: - description: "Name of the second tax applied to the invoice" - type: string - example: 'GST' - tax_rate1: - description: "Rate of the first tax applied to the invoice" - type: number - example: 10.00 - tax_rate2: - description: "Rate of the second tax applied to the invoice" - type: number - example: 5.00 - tax_name3: - description: "Name of the third tax applied to the invoice" - type: string - example: 'PST' - tax_rate3: - description: "Rate of the third tax applied to the invoice" - type: number - example: 8.00 - line_items: - type: array - description: 'An array of objects which define the line items of the invoice' - items: - $ref: '#/components/schemas/InvoiceItem' - discount: - description: "The discount applied to the invoice" - type: number - example: 10.00 - partial: - description: "The partial amount applied to the invoice" - type: number - example: 20.00 - is_amount_discount: - description: "Indicates whether the discount applied is a fixed amount or a percentage" - type: boolean - example: true - uses_inclusive_taxes: - description: "Indicates whether the tax rates applied to the invoice are inclusive or exclusive" - type: boolean - example: true - date: - description: "The date the invoice was issued" - type: string - example: '1994-07-30' - partial_due_date: - description: "The due date for the partial payment" - type: string - example: '1994-08-15' - due_date: - description: "The due date for the invoice" - type: string - example: '1994-08-30' - custom_surcharge1: - description: "First custom surcharge applied to the invoice" - type: number - example: 10.00 - custom_surcharge2: - description: "Second custom surcharge applied to the invoice" - type: number - example: 15.00 - custom_surcharge3: - description: "Third custom surcharge applied to the invoice" - type: number - example: 5.00 - custom_surcharge4: - description: "Fourth custom surcharge applied to the invoice" - type: number - example: 20.00 + example: AS3df3A type: object RecurringQuote: properties: @@ -16573,1813 +15341,6 @@ components: type: boolean example: true type: object - Paymentable: - properties: - id: - description: 'The paymentable hashed id' - type: string - example: AS3df3A - invoice_id: - description: 'The invoice hashed id' - type: string - example: AS3df3A - credit_id: - description: 'The credit hashed id' - type: string - example: AS3df3A - refunded: - description: 'The amount that has been refunded for this payment' - type: number - format: float - example: '10.00' - amount: - description: 'The amount that has been applied to the payment' - type: number - format: float - example: '10.00' - updated_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - created_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - type: object - Meta: - properties: - pagination: - $ref: '#/components/schemas/Pagination' - Pagination: - type: object - properties: - total: - type: integer - description: 'The total number of items' - example: 1 - readOnly: true - count: - type: integer - description: 'The number of items per page' - example: 1 - readOnly: true - per_page: - type: integer - description: 'The number of items per page' - example: 1 - readOnly: true - current_page: - type: integer - description: 'The current page number' - example: 1 - readOnly: true - total_pages: - type: integer - description: 'The total number of pages' - example: 1 - readOnly: true - links: - type: array - description: 'The pagination links' - readOnly: true - Project: - type: object - properties: - id: - description: 'The project hashed id' - type: string - example: Opnel5aKBz - user_id: - description: 'The user hashed id' - type: string - example: Opnel5aKBz - assigned_user_id: - description: The assigned user identifier associated with the project - type: string - example: Opnel5aKBz - client_id: - type: string - example: Opnel5aKBz - description: The client identifier associated with the project - name: - type: string - description: The name of the project - example: 'New Project' - task_rate: - type: number - format: float - example: 10 - description: The default rate per task for the project - due_date: - type: string - format: date - example: '2019-01-01' - description: The due date for the project - private_notes: - type: string - description: Private notes associated with the project - budgeted_hours: - type: number - format: float - description: The number of budgeted hours for the project - custom_value1: - type: string - description: Custom value field 1 - custom_value2: - type: string - description: Custom value field 2 - custom_value3: - type: string - description: Custom value field 3 - custom_value4: - type: string - description: Custom value field 4 - created_at: - type: number - format: integer - example: 134341234234 - description: The timestamp of the project creation - updated_at: - type: number - format: integer - example: 134341234234 - description: The timestamp of the last project update - archived_at: - type: number - format: integer - example: 134341234234 - description: The timestamp of the project deletion - public_notes: - type: string - description: Public notes associated with the project - is_deleted: - type: boolean - description: A flag indicating if the project is deleted - number: - type: string - description: The project number - color: - type: string - description: The color associated with the project - required: - - id - - user_id - - company_id - - name - - task_rate - - budgeted_hours - - is_deleted - - color - - Client: - properties: - id: - description: 'The unique identifier of the client' - type: string - example: Opnel5aKBz - readOnly: true - contacts: - type: array - items: - $ref: '#/components/schemas/ClientContact' - user_id: - description: 'The unique identifier of the user who created the client' - type: string - example: Ua6Rw4pVbS - readOnly: true - assigned_user_id: - description: 'The unique identifier of the user who has been assigned the client' - type: string - example: Ua6Rw4pVbS - company_id: - description: 'The unique identifier of the company the client belongs to' - type: string - example: Co7Vn3yLmW - readOnly: true - name: - description: 'The name of the client company or organization' - type: string - example: "Jim's Housekeeping" - website: - description: 'The website URL of the client company or organization' - type: string - example: 'https://www.jims-housekeeping.com' - private_notes: - description: 'Notes that are only visible to the user who created the client' - type: string - example: 'Client prefers email communication over phone calls' - client_hash: - description: 'A unique hash value for the client' - type: string - example: asdfkjhk342hjhbfdvmnfb1 - readOnly: true - industry_id: - description: 'The unique identifier of the industry the client operates in' - type: number - example: '5' - size_id: - description: 'The unique identifier for the size category of the client company or organization' - type: number - example: '2' - address1: - description: "First line of the client's address" - type: string - example: '123 Main St' - address2: - description: "Second line of the client's address, if needed" - type: string - example: 'Apt 4B' - city: - description: 'The city the client is located in' - type: string - example: 'Beverly Hills' - state: - description: 'The state, province, or locality the client is located in' - type: string - example: 'California' - postal_code: - description: 'The postal code or ZIP code of the client' - type: string - example: '90210' - phone: - description: "The client's phone number" - type: string - example: '555-3434-3434' - country_id: - description: "The unique identifier of the client's country" - type: number - format: integer - example: '1' - custom_value1: - description: 'A custom field for storing additional information' - type: string - example: 'Preferred contact: Email' - custom_value2: - description: 'A custom field for storing additional information' - type: string - example: 'Account manager: John Doe' - custom_value3: - description: 'A custom field for storing additional information' - type: string - example: 'VIP client: Yes' - custom_value4: - description: 'A custom field for storing additional information' - type: string - example: 'Annual contract value: $50,000' - vat_number: - description: "The client's VAT (Value Added Tax) number, if applicable" - type: string - example: 'VAT123456' - id_number: - description: 'A unique identification number for the client, such as a tax ID or business registration number' - type: string - number: - description: 'A system-assigned unique number for the client, typically used for invoicing purposes' - type: string - example: 'CL-0001' - shipping_address1: - description: "First line of the client's shipping address" - type: string - example: '5 Wallaby Way' - shipping_address2: - description: "Second line of the client's shipping address, if needed" - type: string - example: 'Suite 5' - shipping_city: - description: "The city of the client's shipping address" - type: string - example: 'Perth' - shipping_state: - description: "The state, province, or locality of the client's shipping address" - type: string - example: 'Western Australia' - shipping_postal_code: - description: "The postal code or ZIP code of the client's shipping address" - type: string - example: '6110' - shipping_country_id: - description: "The unique identifier of the country for the client's shipping address" - type: number - format: integer - example: '4' - is_deleted: - description: 'A boolean value indicating whether the client has been deleted or not' - type: boolean - example: false - readOnly: true - balance: - description: 'The outstanding balance the client owes' - type: number - format: float - example: '500.00' - readOnly: true - paid_to_date: - description: 'The total amount the client has paid to date' - type: number - format: float - example: '2000.00' - readOnly: true - credit_balance: - description: 'The available credit balance for the client to use on future purchases' - type: number - format: float - example: '100.00' - readOnly: true - last_login: - description: "The timestamp of the client's last login" - type: number - format: integer - example: '1628686031' - readOnly: true - created_at: - description: 'The timestamp when the client was created' - type: number - format: integer - example: '1617629031' - readOnly: true - updated_at: - description: 'The timestamp when the client was last updated' - type: number - format: integer - example: '1628445631' - readOnly: true - group_settings_id: - description: 'The group settings assigned to the client' - type: string - example: Opnel5aKBz - routing_id: - description: 'The routing address id for e-invoicing for this client' - type: string - example: Opnel5aKBz3489-dfkiu-2239-sdsd - is_tax_exempt: - description: 'Flag which defines if the client is exempt from taxes' - type: boolean - example: false - has_valid_vat_number: - description: 'Flag which defines if the client has a valid VAT number' - type: boolean - example: false - readOnly: true - payment_balance: - description: 'Defines the payment balance the client has on file (pre payments / over payments / unapplied amounts)' - type: number - example: 100 - readOnly: true - settings: - $ref: '#/components/schemas/ClientSettings' - type: object - Vendor: - properties: - id: - description: 'The hashed id of the vendor. This is a unique identifier for the vendor.' - type: string - example: Opnel5aKBz - readOnly: true - user_id: - description: 'The hashed id of the user who created the vendor. This is a unique identifier for the user.' - type: string - example: Opnel5aKBz - assigned_user_id: - description: 'The hashed id of the assigned user to this vendor. This is a unique identifier for the user.' - type: string - example: Opnel5aKBz - company_id: - description: 'The hashed id of the company. This is a unique identifier for the company.' - type: string - example: Opnel5aKBz - client_id: - description: 'The hashed id of the client. This is a unique identifier for the client.' - type: string - example: Opnel5aKBz - contacts: - type: array - items: - $ref: '#/components/schemas/VendorContact' - description: 'An array of contacts associated with the vendor.' - name: - description: 'The name of the vendor.' - type: string - example: 'Harry cafe de wheels' - classification: - description: 'The classification of the vendor.' - type: string - example: 'individual' - website: - description: 'The website of the vendor.' - type: string - example: www.harry.com - private_notes: - description: 'The private notes of the vendor. These notes are only visible to users with appropriate permissions.' - type: string - example: 'Shhh, do not tell the vendor' - industry_id: - description: 'The industry id of the vendor. This is a unique identifier for the industry.' - type: string - example: '1' - size_id: - description: 'The size id of the vendor. This is a unique identifier for the size of the vendor.' - type: string - example: '' - address1: - description: 'The first line of the vendor''s address.' - type: string - example: '' - address2: - description: 'The second line of the vendor''s address.' - type: string - example: '' - city: - description: 'The city of the vendor''s address.' - type: string - example: '' - state: - description: 'The state of the vendor''s address.' - type: string - example: '' - postal_code: - description: 'The postal code of the vendor''s address.' - type: string - example: '' - phone: - description: 'The phone number of the vendor.' - type: string - example: 555-3434-3434 - country_id: - description: 'The country id of the vendor. This is a unique identifier for the country.' - type: string - example: '' - currency_id: - description: 'The currency id of the vendor. This is a unique identifier for the currency.' - type: string - example: '4' - custom_value1: - description: 'The value of the first custom field for the vendor.' - type: string - example: '' - custom_value2: - description: 'The value of the second custom field for the vendor.' - type: string - example: '' - custom_value3: - description: 'The value of the third custom field for the vendor.' - type: string - example: '' - custom_value4: - description: 'The value of the fourth custom field for the vendor.' - type: string - example: '' - vat_number: - description: 'The VAT number of the vendor.' - type: string - example: '' - id_number: - description: 'The ID number of the vendor.' - type: string - example: '' - number: - description: 'The number of the vendor' - type: string - example: '11234' - is_deleted: - description: 'Boolean flag determining if the vendor has been deleted' - type: boolean - example: true - language_id: - description: 'The language id of the vendor. This is a unique identifier for the language.' - type: string - example: '1' - vendor_hash: - description: 'The vendor hash of the vendor. This is a unique identifier for the vendor.' - type: string - example: 'aaa-sss-www' - readOnly: true - transaction_name: - description: 'The transaction name of the vendor.' - type: string - example: 'aaa-sss-www' - last_login: - description: Timestamp - type: number - format: integer - example: '134341234234' - readOnly: true - created_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - readOnly: true - updated_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - readOnly: true - settings: - $ref: '#/components/schemas/CompanySettings' - type: object - VendorContact: - properties: - id: - description: 'The hashed id of the vendor contact' - type: string - example: Opnel5aKBz - readOnly: true - user_id: - description: 'The hashed id of the user id' - type: string - example: Opnel5aKBz - company_id: - description: 'The hashed id of the company' - type: string - example: Opnel5aKBz - vendor_id: - description: 'The hashed id of the vendor' - type: string - example: Opnel5aKBz - first_name: - description: 'The first name of the contact' - type: string - example: Harry - last_name: - description: 'The last name of the contact' - type: string - example: Windsor - phone: - description: 'The contacts phone number' - type: string - example: 555-123-1234 - custom_value1: - description: 'A custom value' - type: string - example: '2022-10-10' - custom_value2: - description: 'A custom value' - type: string - example: $1000 - custom_value3: - description: 'A custom value' - type: string - example: '' - custom_value4: - description: 'A custom value' - type: string - example: '' - email: - description: 'The contact email address' - type: string - example: harry@windsor.com - is_primary: - description: 'Boolean flag determining if the contact is the primary contact for the vendor' - type: boolean - example: true - created_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - readOnly: true - updated_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - readOnly: true - deleted_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - readOnly: true - type: object - Quote: - properties: - id: - description: 'The unique hashed identifier for the quote' - type: string - example: Opnel5aKBz - user_id: - description: 'The unique hashed identifier for the user who created the quote' - type: string - example: '' - assigned_user_id: - description: 'The unique hashed identifier for the user assigned to the quote' - type: string - example: '' - company_id: - description: 'The unique hashed identifier for the company associated with the quote' - type: string - example: '' - client_id: - description: 'The unique hashed identifier for the client associated with the quote' - type: string - example: '' - status_id: - description: 'The status of the quote represented by a unique identifier' - type: string - example: '' - number: - description: 'The unique alpha-numeric quote number for the quote per company' - type: string - example: QUOTE_101 - po_number: - description: 'The purchase order number associated with the quote' - type: string - example: PO-1234 - terms: - description: 'The terms and conditions for the quote' - type: string - example: 'These are some quote terms. Valid for 14 days.' - public_notes: - description: 'Publicly visible notes associated with the quote' - type: string - example: 'These are public notes which the client may see' - private_notes: - description: 'Privately visible notes associated with the quote, not disclosed to the client' - type: string - example: 'These are private notes, not to be disclosed to the client' - footer: - description: 'The footer text of the quote' - type: string - example: 'The text goes in the footer of the quote' - custom_value1: - description: 'First custom value field for additional information' - type: string - example: 'A custom value' - custom_value2: - description: 'Second custom value field for additional information' - type: string - example: 'A custom value' - custom_value3: - description: 'Third custom value field for additional information' - type: string - example: 'A custom value' - custom_value4: - description: 'Fourth custom value field for additional information' - type: string - example: 'A custom value' - tax_name1: - description: 'The name of the first tax applied to the quote' - type: string - example: GST - tax_name2: - description: 'The name of the second tax applied to the quote' - type: string - example: VAT - tax_rate1: - description: 'The rate of the first tax applied to the quote' - type: number - format: float - example: 10.00 - tax_rate2: - description: 'The rate of the second tax applied to the quote' - type: number - format: float - example: 10.00 - tax_name3: - description: 'The name of the third tax applied to the quote' - type: string - example: '' - tax_rate3: - description: 'The rate of the third tax applied to the quote' - type: number - format: float - example: 10.00 - total_taxes: - description: 'The total amount of taxes for the quote' - type: number - format: float - example: 10.00 - line_items: - type: array - description: 'An array of objects which define the line items of the quote' - items: - $ref: '#/components/schemas/InvoiceItem' - amount: - description: 'The total amount of the quote before taxes and discounts' - type: number - format: float - example: 10.00 - balance: - description: 'The balance due for the quote after accounting for payments' - type: number - format: float - example: 10.00 - paid_to_date: - description: 'The total amount paid on the quote so far' - type: number - format: float - example: 10.00 - discount: - description: 'The discount amount or percentage applied to the quote' - type: number - format: float - example: 10.00 - partial: - description: 'The partial or deposit amount for the quote' - type: number - format: float - example: 10.00 - is_amount_discount: - description: 'Boolean flag indicating if the discount is a fixed amount or a percentage' - type: boolean - example: true - is_deleted: - description: 'Boolean flag indicating if the quote has been deleted' - type: boolean - example: false - uses_inclusive_taxes: - description: 'Boolean flag indicating if the taxes used are inclusive or exclusive' - type: boolean - example: true - date: - description: 'The date the quote was created' - type: string - format: date - example: '1994-07-30' - last_sent_date: - description: 'The last date the quote was sent to the client' - type: string - format: date - example: '1994-07-30' - next_send_date: - description: 'The next scheduled date for sending a reminder for the quote' - type: string - format: date - example: '1994-07-30' - partial_due_date: - description: 'The due date for the partial or deposit amount' - type: string - format: date - example: '1994-07-30' - due_date: - description: 'The due date for the total amount of the quote' - type: string - format: date - example: '1994-07-30' - settings: - $ref: '#/components/schemas/CompanySettings' - last_viewed: - description: 'The timestamp of the last time the quote was viewed' - type: number - format: integer - example: 1434342123 - updated_at: - description: 'The timestamp of the last update to the quote' - type: number - format: integer - example: 1434342123 - archived_at: - description: 'The timestamp of when the quote was archived' - type: number - format: integer - example: 1434342123 - custom_surcharge1: - description: 'First custom surcharge amount for the quote' - type: number - format: float - example: 10.00 - custom_surcharge2: - description: 'Second custom surcharge amount for the quote' - type: number - format: float - example: 10.00 - custom_surcharge3: - description: 'Third custom surcharge amount for the quote' - type: number - format: float - example: 10.00 - custom_surcharge4: - description: 'Fourth custom surcharge amount for the quote' - type: number - format: float - example: 10.00 - custom_surcharge_tax1: - description: 'Boolean flag indicating if taxes are charged on the first custom surcharge amount' - type: boolean - example: true - custom_surcharge_tax2: - description: 'Boolean flag indicating if taxes are charged on the second custom surcharge amount' - type: boolean - example: true - custom_surcharge_tax3: - description: 'Boolean flag indicating if taxes are charged on the third custom surcharge amount' - type: boolean - example: true - custom_surcharge_tax4: - description: 'Boolean flag indicating if taxes are charged on the fourth custom surcharge amount' - type: boolean - example: true - type: object - Payment: - properties: - id: - description: 'The payment hashed id' - type: string - example: Opnel5aKBz - client_id: - description: 'The client hashed id' - type: string - example: Opnel5aKBz - invitation_id: - description: 'The invitation hashed id' - type: string - example: Opnel5aKBz - client_contact_id: - description: 'The client contact hashed id' - type: string - example: Opnel5aKBz - user_id: - description: 'The user hashed id' - type: string - example: Opnel5aKBz - type_id: - description: 'The Payment Type ID' - type: string - example: '1' - date: - description: 'The Payment date' - type: string - example: 1-1-2014 - transaction_reference: - description: 'The transaction reference as defined by the payment gateway' - type: string - example: xcsSxcs124asd - assigned_user_id: - description: 'The assigned user hashed id' - type: string - example: Opnel5aKBz - private_notes: - description: 'The private notes of the payment' - type: string - example: 'The payment was refunded due to error' - is_manual: - description: 'Flags whether the payment was made manually or processed via a gateway' - type: boolean - example: true - is_deleted: - description: 'Defines if the payment has been deleted' - type: boolean - example: true - amount: - description: 'The amount of this payment' - type: number - example: 10 - refunded: - description: 'The refunded amount of this payment' - type: number - example: 10 - updated_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - archived_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - company_gateway_id: - description: 'The company gateway id' - type: string - example: '3' - paymentables: - $ref: '#/components/schemas/Paymentable' - invoices: - description: '' - type: array - items: - $ref: '#/components/schemas/InvoicePaymentable' - credits: - description: '' - type: array - items: - $ref: '#/components/schemas/CreditPaymentable' - number: - description: 'The payment number - is a unique alpha numeric number per payment per company' - type: string - example: PAY_101 - type: object - - Task: - properties: - id: - description: 'The hashed id of the task' - type: string - example: Opnel5aKBz - user_id: - description: 'The hashed id of the user who created the task' - type: string - example: Opnel5aKBz - assigned_user_id: - description: 'The assigned user of the task' - type: string - example: Opnel5aKBz - company_id: - description: 'The hashed id of the company' - type: string - example: Opnel5aKBz - client_id: - description: 'The hashed if of the client' - type: string - example: Opnel5aKBz - invoice_id: - description: 'The hashed id of the invoice associated with the task' - type: string - example: Opnel5aKBz - project_id: - description: 'The hashed id of the project associated with the task' - type: string - example: Opnel5aKBz - number: - description: 'The number of the task' - type: string - example: TASK-123 - time_log: - description: 'An array of unix time stamps defining the start and end times of the task' - type: string - example: '[[1,2],[3,4]]' - is_running: - description: 'Determines if the task is still running' - type: boolean - example: true - is_deleted: - description: 'Boolean flag determining if the task has been deleted' - type: boolean - example: true - task_status_id: - description: 'The hashed id of the task status' - type: string - example: Opnel5aKBz - description: - description: 'The task description' - type: string - example: 'A wonder task to work on' - duration: - description: 'The task duration in seconds' - type: integer - example: '3600' - task_status_order: - description: 'The order of the task' - type: integer - example: '4' - rate: - description: 'The task rate' - type: number - example: 10.00 - custom_value1: - description: 'A custom value' - type: string - example: '2022-10-10' - custom_value2: - description: 'A custom value' - type: string - example: $1100 - custom_value3: - description: 'A custom value' - type: string - example: 'I need help' - custom_value4: - description: 'A custom value' - type: string - example: INV-3343 - is_date_based: - description: 'Boolean flag determining if the task is date based' - type: boolean - example: true - calculated_start_date: - description: 'The calculated start date of the task' - type: string - example: '2022-10-10' - readOnly: true - invoice_documents: - description: "Boolean flags which determines whether to include the task documents on the invoice" - type: boolean - example: true - created_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - readOnly: true - updated_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - readOnly: true - archived_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - readOnly: true - type: object - RecurringInvoice: - properties: - id: - description: 'The hashed id of the recurring invoice' - type: string - example: Opnel5aKBz - user_id: - description: 'The user hashed id' - type: string - example: Opnel5aKBz - assigned_user_id: - description: 'The assigned user hashed id' - type: string - example: Opnel5aKBz - company_id: - description: 'The company hashed id' - type: string - example: Opnel5aKBz - client_id: - description: 'The client hashed id' - type: string - example: Opnel5aKBz - status_id: - description: 'The invoice status variable' - type: string - example: '4' - frequency_id: - description: 'The recurring invoice frequency' - type: number - example: '4' - remaining_cycles: - description: 'The number of invoices left to be generated' - type: number - example: '4' - number: - description: 'The recurringinvoice number - is a unique alpha numeric number per invoice per company' - type: string - example: INV_101 - po_number: - description: 'The purchase order associated with this recurring invoice' - type: string - example: PO-1234 - terms: - description: 'The invoice terms' - type: string - example: 'These are invoice terms' - public_notes: - description: 'The public notes of the invoice' - type: string - example: 'These are some public notes' - private_notes: - description: 'The private notes of the invoice' - type: string - example: 'These are some private notes' - footer: - description: 'The invoice footer notes' - type: string - example: '' - custom_value1: - description: 'A custom field value' - type: string - example: '2022-10-01' - custom_value2: - description: 'A custom field value' - type: string - example: 'Something custom' - custom_value3: - description: 'A custom field value' - type: string - example: '' - custom_value4: - description: 'A custom field value' - type: string - example: '' - tax_name1: - description: 'The tax name' - type: string - example: '' - tax_name2: - description: 'The tax name' - type: string - example: '' - tax_rate1: - description: 'The tax rate' - type: number - format: float - example: '10.00' - tax_rate2: - description: 'The tax rate' - type: number - format: float - example: '10.00' - tax_name3: - description: 'The tax name' - type: string - example: '' - tax_rate3: - description: 'The tax rate' - type: number - format: float - example: '10.00' - total_taxes: - description: 'The total taxes for the invoice' - type: number - format: float - example: '10.00' - line_items: - description: 'An array of objects which define the line items of the invoice' - type: object - example: '' - amount: - description: 'The invoice amount' - type: number - format: float - example: '10.00' - balance: - description: 'The invoice balance' - type: number - format: float - example: '10.00' - paid_to_date: - description: 'The amount paid on the invoice to date' - type: number - format: float - example: '10.00' - discount: - description: 'The invoice discount, can be an amount or a percentage' - type: number - format: float - example: '10.00' - partial: - description: 'The deposit/partial amount' - type: number - format: float - example: '10.00' - is_amount_discount: - description: 'Flag determining if the discount is an amount or a percentage' - type: boolean - example: true - is_deleted: - description: 'Defines if the invoice has been deleted' - type: boolean - example: true - uses_inclusive_taxes: - description: 'Defines the type of taxes used as either inclusive or exclusive' - type: boolean - example: true - date: - description: 'The Invoice Date' - type: string - format: date - example: '1994-07-30' - last_sent_date: - description: 'The last date the invoice was sent out' - type: string - format: date - example: '1994-07-30' - next_send_date: - description: 'The Next date for a reminder to be sent' - type: string - format: date - example: '1994-07-30' - partial_due_date: - description: 'The due date for the deposit/partial amount' - type: string - format: date - example: '1994-07-30' - due_date: - description: 'The due date of the invoice' - type: string - format: date - example: '1994-07-30' - settings: - $ref: '#/components/schemas/CompanySettings' - last_viewed: - description: Timestamp - type: number - format: integer - example: '1434342123' - updated_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - archived_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - custom_surcharge1: - description: 'First Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge2: - description: 'Second Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge3: - description: 'Third Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge4: - description: 'Fourth Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge_tax1: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax2: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax3: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax4: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - type: object - - InvoiceItem: - type: object - properties: - quantity: - type: integer - example: 1 - description: 'The quantity of the product offered for this line item' - cost: - type: number - format: float - example: 10.00 - description: 'The cost of the product offered for this line item' - product_key: - type: string - example: 'Product key' - description: 'The product key of the product offered for this line item (Referred to as Product in the product tab)' - product_cost: - type: number - format: float - example: 10.00 - description: 'The cost of the product offered for this line item (Referred to as Cost in the product tab)' - notes: - type: string - example: 'Item notes' - description: 'The notes/description for the product offered for this line item' - discount: - type: number - format: float - example: 5.00 - description: 'The discount applied to the product offered for this line item' - is_amount_discount: - type: boolean - example: false - description: 'Indicates whether the discount applied to the product offered for this line item is a fixed amount or a percentage' - tax_name1: - type: string - example: 'GST' - description: 'The name of the first tax applied to the product offered for this line item' - tax_rate1: - type: number - format: float - example: 10.00 - description: 'The rate of the first tax applied to the product offered for this line item' - tax_name2: - type: string - example: 'VAT' - description: 'The name of the second tax applied to the product offered for this line item' - tax_rate2: - type: number - format: float - example: 5.00 - description: 'The rate of the second tax applied to the product offered for this line item' - tax_name3: - type: string - example: 'CA Sales Tax' - description: 'The name of the third tax applied to the product offered for this line item' - tax_rate3: - type: number - format: float - example: 3.00 - description: 'The rate of the third tax applied to the product offered for this line item' - sort_id: - type: string - example: '0' - description: 'Deprecated' - deprecated: true - line_total: - type: number - format: float - example: 10.00 - description: 'The total amount of the product offered for this line item' - readOnly: true - gross_line_total: - type: number - format: float - example: 15.00 - description: 'The total amount of the product offered for this line item before discounts' - readOnly: true - tax_amount: - type: number - format: float - example: 1.00 - description: 'The total amount of tax applied to the product offered for this line item' - readOnly: true - date: - type: string - format: date-time - example: '2023-03-19T00:00:00Z' - description: 'Deprecated' - deprecated: true - custom_value1: - type: string - example: 'Custom value 1' - description: 'The first custom value of the product offered for this line item' - custom_value2: - type: string - example: 'Custom value 2' - description: 'The second custom value of the product offered for this line item' - custom_value3: - type: string - example: 'Custom value 3' - description: 'The third custom value of the product offered for this line item' - custom_value4: - type: string - example: 'Custom value 4' - description: 'The fourth custom value of the product offered for this line item' - type_id: - type: string - example: '1' - description: '1 = product, 2 = service, 3 unpaid gateway fee, 4 paid gateway fee, 5 late fee, 6 expense' - default: '1' - tax_id: - type: string - example: '1' - default: '1' - description: 'The tax ID of the product: 1 product, 2 service, 3 digital, 4 shipping, 5 exempt, 5 reduced tax, 7 override, 8 zero rate, 9 reverse tax' - Activity: - properties: - id: - description: 'The id field of the activity' - type: string - example: Opnel5aKBz - activity_type_id: - description: 'The activity type id' - type: string - example: Opnel5aKBz - client_id: - description: 'The client hashed id' - type: string - example: Opnel5aKBz - company_id: - description: 'The company hashed id' - type: string - example: Opnel5aKBz - user_id: - description: 'The user hashed id' - type: string - example: Opnel5aKBz - invoice_id: - description: 'The invoice hashed id' - type: string - example: Opnel5aKBz - payment_id: - description: 'The payment hashed id' - type: string - example: Opnel5aKBz - credit_id: - description: 'The credit hashed id' - type: string - example: Opnel5aKBz - updated_at: - description: 'Unixtimestamp the last time the record was updated' - type: integer - example: '343421434' - expense_id: - description: 'The expense hashed id' - type: string - example: Opnel5aKBz - is_system: - description: 'Defines is the activity was performed by the system' - type: boolean - example: true - contact_id: - description: 'The contact hashed id' - type: string - example: Opnel5aKBz - task_id: - description: 'The task hashed id' - type: string - example: Opnel5aKBz - notes: - description: 'Activity Notes' - type: string - example: Opnel5aKBz - token_id: - description: 'The hashed ID of the token who performed the action' - type: string - example: Opnel5aKBz - ip: - description: 'The IP Address of the user who performed the action' - type: string - example: 192.168.1.252 - user: - $ref: '#/components/schemas/User' - client: - $ref: '#/components/schemas/Client' - contact: - $ref: '#/components/schemas/ClientContact' - recurring_invoice: - $ref: '#/components/schemas/RecurringInvoice' - invoice: - $ref: '#/components/schemas/Invoice' - credit: - $ref: '#/components/schemas/Credit' - quote: - $ref: '#/components/schemas/Quote' - payment: - $ref: '#/components/schemas/Payment' - expense: - $ref: '#/components/schemas/Expense' - task: - $ref: '#/components/schemas/Task' - purchase_order: - $ref: '#/components/schemas/PurchaseOrder' - vendor: - $ref: '#/components/schemas/Vendor' - vendor_contact: - $ref: '#/components/schemas/VendorContact' - type: object - - ProductBulkAction: - required: - - action - - ids - properties: - action: - type: string - example: archive - description: 'The action to perform ie. archive / restore / delete / set_tax_id' - ids: - type: array - items: - format: string - type: string - example: 2J234DFA,D2J234DFA,D2J234DFA - description: string array of client hashed ids - tax_id: - type: string - example: '1' - description: | - The tax rate id to set on the list of products - - The following constants are available (default = '1') - - ``` - PRODUCT_TYPE_PHYSICAL = '1' - PRODUCT_TYPE_SERVICE = '2' - PRODUCT_TYPE_DIGITAL = '3' - PRODUCT_TYPE_SHIPPING = '4' - PRODUCT_TYPE_EXEMPT = '5' - PRODUCT_TYPE_REDUCED_TAX = '6' - PRODUCT_TYPE_OVERRIDE_TAX = '7' - PRODUCT_TYPE_ZERO_RATED = '8' - PRODUCT_TYPE_REVERSE_TAX = '9' - ``` - type: object - RecurringExpense: - properties: - id: - description: 'The hashed id of the recurring expense' - type: string - example: Opnel5aKBz - user_id: - description: 'The hashed id of the user who created the recurring expense' - type: string - example: Opnel5aKBz - assigned_user_id: - description: 'The hashed id of the user assigned to this recurring expense' - type: string - example: Opnel5aKBz - company_id: - description: 'The hashed id of the company' - type: string - example: Opnel5aKBz - client_id: - description: 'The hashed id of the client' - type: string - example: Opnel5aKBz - invoice_id: - description: 'The hashed id of the invoice' - type: string - example: Opnel5aKBz - bank_id: - description: 'The id of the bank associated with this recurring expense' - type: string - example: '22' - invoice_currency_id: - description: 'The currency id of the invoice associated with this recurring expense' - type: string - example: '1' - expense_currency_id: - description: 'The currency id of the expense associated with this recurring expense' - type: string - example: '1' - invoice_category_id: - description: 'The category id of the invoice' - type: string - example: '1' - payment_type_id: - description: 'The payment type id' - type: string - example: '1' - private_notes: - description: 'The recurring expense private notes' - type: string - example: 'Private and confidential' - public_notes: - description: 'The recurring expense public notes' - type: string - example: 'This is the best client in the world' - transaction_reference: - description: 'The recurring expense transaction reference' - type: string - example: EXP-1223-2333 - transcation_id: - description: 'The transaction id of the recurring expense' - type: string - example: '1233312312' - custom_value1: - description: 'Custom value field' - type: string - example: $1000 - custom_value2: - description: 'Custom value field' - type: string - example: '2022-10-10' - custom_value3: - description: 'Custom value field' - type: string - example: 'short text' - custom_value4: - description: 'Custom value field' - type: string - example: 'very long text' - tax_name1: - description: 'The tax name' - type: string - example: GST - tax_name2: - description: 'The tax name' - type: string - example: VAT - tax_rate1: - description: 'The tax rate' - type: number - format: float - example: '10.00' - tax_rate2: - description: 'The tax rate' - type: number - format: float - example: '10.00' - tax_name3: - description: 'The tax name' - type: string - example: '' - tax_rate3: - description: 'The tax rate' - type: number - format: float - example: '10.00' - amount: - description: 'The total amount of the recurring expense' - type: number - format: float - example: '10.00' - frequency_id: - description: 'The frequency this recurring expense fires' - type: number - format: int - example: '1' - remaining_cycles: - description: 'The number of remaining cycles for this recurring expense' - type: number - format: int - example: '1' - foreign_amount: - description: 'The foreign currency amount of the recurring expense' - type: number - format: float - example: '10.00' - exchange_rate: - description: 'The exchange rate for the expernse' - type: number - format: float - example: '0.80' - date: - description: 'The date of the expense' - type: string - example: '' - payment_date: - description: 'The date the expense was paid' - type: string - example: '' - should_be_invoiced: - description: 'Boolean flag determining if the expense should be invoiced' - type: boolean - example: true - is_deleted: - description: 'Boolean flag determining if the recurring expense is deleted' - type: boolean - example: true - last_sent_date: - description: 'The Date it was sent last' - type: string - format: date - example: '1994-07-30' - next_send_date: - description: 'The next send date' - type: string - format: date - example: '1994-07-30' - invoice_documents: - description: 'Boolean flag determining if the documents associated with this expense should be passed onto the invoice if it is converted to an invoice' - type: boolean - example: true - updated_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - archived_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - type: object - PaymentTerm: - properties: - num_days: - description: 'The payment term length in days' - type: integer - example: '1' - name: - description: 'The payment term length in string format' - type: string - example: 'NET 1' - created_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - updated_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - archived_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - type: object - ClientGatewayToken: - properties: - id: - description: 'The hashed id of the client gateway token' - type: string - example: Opnel5aKBz - company_id: - description: 'The hashed id of the company' - type: string - example: '2' - client_id: - description: 'The hashed_id of the client' - type: string - example: '2' - token: - description: 'The payment token' - type: string - example: '2' - routing_number: - description: 'THe bank account routing number' - type: string - example: '2' - company_gateway_id: - description: 'The hashed id of the company gateway' - type: string - example: '2' - is_default: - description: 'Flag determining if the token is the default payment method' - type: boolean - example: 'true' - type: object - User: - properties: - id: - description: 'The hashed id of the user' - type: string - example: Opnel5aKBz - readOnly: true - first_name: - description: 'The first name of the user' - type: string - example: Brad - last_name: - description: 'The last name of the user' - type: string - example: Pitt - email: - description: 'The users email address' - type: string - example: brad@pitt.com - phone: - description: 'The users phone number' - type: string - example: 555-1233-23232 - signature: - description: 'The users sign off signature' - type: string - example: 'Have a nice day!' - avatar: - description: 'The users avatar' - type: string - example: 'https://url.to.your/avatar.png' - accepted_terms_version: - description: 'The version of the invoice ninja terms that has been accepted by the user' - type: string - example: 1.0.1 - readOnly: true - oauth_user_id: - description: 'The provider id of the oauth entity' - type: string - example: jkhasdf789as6f675sdf768sdfs - readOnly: true - oauth_provider_id: - description: 'The oauth entity id' - type: string - example: google - readOnly: true - language_id: - description: 'The language id of the user' - type: string - example: 1 - verified_phone_number: - description: 'Boolean flag if the user has their phone verified. Required to settings up 2FA' - type: boolean - example: true - readOnly: true - sms_verification_code: - description: 'The sms verification code for the user. Required to settings up 2FA' - type: string - example: '123456' - readOnly: true - oauth_user_token_expiry: - description: 'The expiry date of the oauth token' - type: string - example: '2022-10-10' - readOnly: true - has_password: - description: 'Boolean flag determining if the user has a password' - type: boolean - example: true - readOnly: true - last_confirmed_email_address: - description: 'The last confirmed email address of the user' - type: string - example: 'bob@gmail.com' - readOnly: true - custom_value1: - description: 'A custom value' - type: string - example: 'Custom value 1' - custom_value2: - description: 'A custom value' - type: string - example: '$1000' - custom_value3: - description: 'A custom value' - type: string - example: 'Custom value 3' - custom_value4: - description: 'A custom value' - type: string - example: 'Custom value 4' - is_deleted: - description: 'Boolean flag determining if the user has been deleted' - type: boolean - example: true - readOnly: true - google_2fa_secret: - description: 'The google 2fa secret for the user' - type: string - example: '123456' - readOnly: true - type: object CompanyGateway: properties: id: @@ -18422,1512 +15383,6 @@ components: type: object - Document: - properties: - id: - description: 'The document hashed id' - type: string - example: AS3df3A - user_id: - description: 'The user hashed id' - type: string - example: '' - assigned_user_id: - description: 'The assigned user hashed id' - type: string - example: '' - project_id: - description: 'The project associated with this document' - type: string - example: '' - vendor_id: - description: 'The vendor associated with this documents' - type: string - example: '' - name: - description: 'The document name' - type: string - example: Beauty - url: - description: 'The document url' - type: string - example: Beauty - preview: - description: 'The document preview url' - type: string - example: Beauty - type: - description: 'The document type' - type: string - example: Beauty - disk: - description: 'The document disk' - type: string - example: Beauty - hash: - description: 'The document hashed' - type: string - example: Beauty - is_deleted: - description: 'Flag to determine if the document is deleted' - type: boolean - example: true - is_default: - description: 'Flag to determine if the document is a default doc' - type: boolean - example: true - created_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - updated_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - deleted_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - type: object - PurchaseOrder: - properties: - id: - description: 'The unique hashed identifier for the purchase order' - type: string - example: Opnel5aKBz - user_id: - description: 'The unique hashed identifier for the user who created the purchase order' - type: string - example: '' - assigned_user_id: - description: 'The unique hashed identifier for the user assigned to the purchase order' - type: string - example: '' - company_id: - description: 'The unique hashed identifier for the company associated with the purchase order' - type: string - example: '' - vendor_id: - description: 'The unique hashed identifier for the vendor associated with the purchase order' - type: string - example: '' - status_id: - description: 'The status of the purchase order represented by a unique identifier' - type: string - example: '' - number: - description: 'The unique alpha-numeric purchase order number per company' - type: string - example: PO_101 - quote_number: - description: 'The quote number associated with this purchase order' - type: string - example: QUOTE_101 - terms: - description: 'The terms and conditions for the purchase order' - type: string - example: 'These are some purchase order terms. Valid for 14 days.' - public_notes: - description: 'Publicly visible notes associated with the purchase order' - type: string - example: 'These are public notes which the vendor may see' - private_notes: - description: 'Privately visible notes associated with the purchase order, not disclosed to the vendor' - type: string - example: 'These are private notes, not to be disclosed to the vendor' - footer: - description: 'The footer text of the purchase order' - type: string - example: 'The text goes in the footer of the purchase order' - custom_value1: - description: 'First custom value field for additional information' - type: string - example: 'A custom value' - custom_value2: - description: 'Second custom value field for additional information' - type: string - example: 'A custom value' - custom_value3: - description: 'Third custom value field for additional information' - type: string - example: 'A custom value' - custom_value4: - description: 'Fourth custom value field for additional information' - type: string - example: 'A custom value' - tax_name1: - description: 'The name of the first tax applied to the purchase order' - type: string - example: GST - tax_name2: - description: 'The name of the second tax applied to the purchase order' - type: string - example: VAT - tax_rate1: - description: 'The rate of the first tax applied to the purchase order' - type: number - format: float - example: 10.00 - tax_rate2: - description: 'The rate of the second tax applied to the purchase order' - type: number - format: float - example: 10.00 - tax_name3: - description: 'The name of the third tax applied to the purchase order' - type: string - example: '' - tax_rate3: - description: 'The rate of the third tax applied to the purchase order' - type: number - format: float - example: 10.00 - total_taxes: - description: 'The total amount of taxes applied to the purchase order' - type: number - format: float - example: 10.00 - line_items: - type: array - description: 'An array of objects which define the line items of the purchase order' - items: - $ref: '#/components/schemas/InvoiceItem' - amount: - description: 'The total amount of the purchase order before taxes and discounts' - type: number - format: float - example: 10.00 - balance: - description: 'The balance due for the purchase order after accounting for payments' - type: number - format: float - example: 10.00 - paid_to_date: - description: 'The total amount paid on the purchase order so far' - type: number - format: float - example: 10.00 - discount: - description: 'The discount amount or percentage applied to the purchase order' - type: number - format: float - example: 10.00 - partial: - description: 'The partial or deposit amount for the purchase order' - type: number - format: float - example: 10.00 - is_amount_discount: - description: 'Boolean flag indicating if the discount is a fixed amount or a percentage' - type: boolean - example: true - is_deleted: - description: 'Boolean flag indicating if the purchase order has been deleted' - type: boolean - example: false - uses_inclusive_taxes: - description: 'Boolean flag indicating if the taxes used are inclusive or exclusive' - type: boolean - example: true - date: - description: 'The date the purchase order was created' - type: string - format: date - example: '1994-07-30' - last_sent_date: - description: 'The last date the purchase order was sent to the vendor' - type: string - format: date - example: '1994-07-30' - next_send_date: - description: 'The next scheduled date for sending a reminder for the purchase order' - type: string - format: date - example: '1994-07-30' - partial_due_date: - description: 'The due date for the partial or deposit amount' - type: string - format: date - example: '1994-07-30' - due_date: - description: 'The due date for the total amount of the purchase order' - type: string - format: date - example: '1994-07-30' - settings: - $ref: '#/components/schemas/CompanySettings' - last_viewed: - description: Timestamp - type: number - format: integer - example: 1434342123 - updated_at: - description: Timestamp - type: number - format: integer - example: 1434342123 - archived_at: - description: Timestamp - type: number - format: integer - example: 1434342123 - custom_surcharge1: - description: 'First custom surcharge amount for the purchase order' - type: number - format: float - example: 10.00 - custom_surcharge2: - description: 'Second custom surcharge amount for the purchase order' - type: number - format: float - example: 10.00 - custom_surcharge3: - description: 'Third custom surcharge amount for the purchase order' - type: number - format: float - example: 10.00 - custom_surcharge4: - description: 'Fourth custom surcharge amount for the purchase order' - type: number - format: float - example: 10.00 - custom_surcharge_tax1: - description: 'Boolean flag indicating if taxes are charged on the first custom surcharge amount' - type: boolean - example: true - custom_surcharge_tax2: - description: 'Boolean flag indicating if taxes are charged on the second custom surcharge amount' - type: boolean - example: true - custom_surcharge_tax3: - description: 'Boolean flag indicating if taxes are charged on the third custom surcharge amount' - type: boolean - example: true - custom_surcharge_tax4: - description: 'Boolean flag indicating if taxes are charged on the fourth custom surcharge amount' - type: boolean - example: true - type: object - - - ClientSettings: - required: - - currency_id - properties: - currency_id: - description: 'The default currency id' - type: string - example: true - timezone_id: - description: 'The timezone id' - type: string - example: '15' - date_format_id: - description: 'The date format id' - type: string - example: '15' - military_time: - description: 'Toggles 12/24 hour time' - type: boolean - example: true - language_id: - description: 'The language id' - type: string - example: '1' - show_currency_code: - description: 'Toggles whether the currency symbol or code is shown' - type: boolean - example: true - payment_terms: - description: '-1 sets no payment term, 0 sets payment due immediately, positive integers indicates payment terms in days' - type: integer - example: '1' - company_gateway_ids: - description: 'A commad separate list of available gateways' - type: string - example: '1,2,3,4' - custom_value1: - description: 'A Custom Label' - type: string - example: 'Custom Label' - custom_value2: - description: 'A Custom Label' - type: string - example: 'Custom Label' - custom_value3: - description: 'A Custom Label' - type: string - example: 'Custom Label' - custom_value4: - description: 'A Custom Label' - type: string - example: 'Custom Label' - default_task_rate: - description: 'The default task rate' - type: number - format: float - example: '10.00' - send_reminders: - description: 'Toggles whether reminders are sent' - type: boolean - example: true - enable_client_portal_tasks: - description: 'Show/hide the tasks panel in the client portal' - type: boolean - example: true - email_style: - description: 'options include plain,light,dark,custom' - type: string - example: light - reply_to_email: - description: 'The reply to email address' - type: string - example: email@gmail.com - bcc_email: - description: 'A comma separate list of BCC emails' - type: string - example: 'email@gmail.com, contact@gmail.com' - pdf_email_attachment: - description: 'Toggles whether to attach PDF as attachment' - type: boolean - example: true - ubl_email_attachment: - description: 'Toggles whether to attach UBL as attachment' - type: boolean - example: true - email_style_custom: - description: 'The custom template' - type: string - example: '' - counter_number_applied: - description: 'enum when the invoice number counter is set, ie when_saved, when_sent, when_paid' - type: string - example: when_sent - quote_number_applied: - description: 'enum when the quote number counter is set, ie when_saved, when_sent' - type: string - example: when_sent - custom_message_dashboard: - description: 'A custom message which is displayed on the dashboard' - type: string - example: 'Please pay invoices immediately' - custom_message_unpaid_invoice: - description: 'A custom message which is displayed in the client portal when a client is viewing a unpaid invoice.' - type: string - example: 'Please pay invoices immediately' - custom_message_paid_invoice: - description: 'A custom message which is displayed in the client portal when a client is viewing a paid invoice.' - type: string - example: 'Thanks for paying this invoice!' - custom_message_unapproved_quote: - description: 'A custom message which is displayed in the client portal when a client is viewing a unapproved quote.' - type: string - example: 'Please approve quote' - lock_invoices: - description: 'Toggles whether invoices are locked once sent and cannot be modified further' - type: boolean - example: true - auto_archive_invoice: - description: 'Toggles whether a invoice is archived immediately following payment' - type: boolean - example: true - auto_archive_quote: - description: 'Toggles whether a quote is archived after being converted to a invoice' - type: boolean - example: true - auto_convert_quote: - description: 'Toggles whether a quote is converted to a invoice when approved' - type: boolean - example: true - inclusive_taxes: - description: 'Boolean flag determining whether inclusive or exclusive taxes are used' - type: boolean - example: true - task_number_pattern: - description: 'Allows customisation of the task number pattern' - type: string - example: '{$year}-{$counter}' - task_number_counter: - description: 'The incrementing counter for tasks' - type: integer - example: '1' - reminder_send_time: - description: 'Time from UTC +0 when the email will be sent to the client' - type: integer - example: '32400' - expense_number_pattern: - description: 'Allows customisation of the expense number pattern' - type: string - example: '{$year}-{$counter}' - expense_number_counter: - description: 'The incrementing counter for expenses' - type: integer - example: '1' - vendor_number_pattern: - description: 'Allows customisation of the vendor number pattern' - type: string - example: '{$year}-{$counter}' - vendor_number_counter: - description: 'The incrementing counter for vendors' - type: integer - example: '1' - ticket_number_pattern: - description: 'Allows customisation of the ticket number pattern' - type: string - example: '{$year}-{$counter}' - ticket_number_counter: - description: 'The incrementing counter for tickets' - type: integer - example: '1' - payment_number_pattern: - description: 'Allows customisation of the payment number pattern' - type: string - example: '{$year}-{$counter}' - payment_number_counter: - description: 'The incrementing counter for payments' - type: integer - example: '1' - invoice_number_pattern: - description: 'Allows customisation of the invoice number pattern' - type: string - example: '{$year}-{$counter}' - invoice_number_counter: - description: 'The incrementing counter for invoices' - type: integer - example: '1' - quote_number_pattern: - description: 'Allows customisation of the quote number pattern' - type: string - example: '{$year}-{$counter}' - quote_number_counter: - description: 'The incrementing counter for quotes' - type: integer - example: '1' - client_number_pattern: - description: 'Allows customisation of the client number pattern' - type: string - example: '{$year}-{$counter}' - client_number_counter: - description: 'The incrementing counter for clients' - type: integer - example: '1' - credit_number_pattern: - description: 'Allows customisation of the credit number pattern' - type: string - example: '{$year}-{$counter}' - credit_number_counter: - description: 'The incrementing counter for credits' - type: integer - example: '1' - recurring_invoice_number_prefix: - description: 'This string is prepended to the recurring invoice number' - type: string - example: R - reset_counter_frequency_id: - description: 'CONSTANT which is used to apply the frequency which the counters are reset' - type: integer - example: '1' - reset_counter_date: - description: 'The explicit date which is used to reset counters' - type: string - example: '2019-01-01' - counter_padding: - description: 'Pads the counter with leading zeros' - type: integer - example: '1' - shared_invoice_quote_counter: - description: 'Flags whether to share the counter for invoices and quotes' - type: boolean - example: true - update_products: - description: 'Determines if client fields are updated from third party APIs' - type: boolean - example: true - convert_products: - description: '' - type: boolean - example: true - fill_products: - description: 'Automatically fill products based on product_key' - type: boolean - example: true - invoice_terms: - description: 'The default invoice terms' - type: string - example: 'Invoice Terms are...' - quote_terms: - description: 'The default quote terms' - type: string - example: 'Quote Terms are...' - invoice_taxes: - description: 'Taxes can be applied to the invoice' - type: number - example: '1' - invoice_design_id: - description: 'The default design id (invoice, quote etc)' - type: string - example: '1' - quote_design_id: - description: 'The default design id (invoice, quote etc)' - type: string - example: '1' - invoice_footer: - description: 'The default invoice footer' - type: string - example: '1' - invoice_labels: - description: 'JSON string of invoice labels' - type: string - example: '1' - tax_rate1: - description: 'The tax rate (float)' - type: number - example: '10' - tax_name1: - description: 'The tax name' - type: string - example: GST - tax_rate2: - description: 'The tax rate (float)' - type: number - example: '10' - tax_name2: - description: 'The tax name' - type: string - example: GST - tax_rate3: - description: 'The tax rate (float)' - type: number - example: '10' - tax_name3: - description: 'The tax name' - type: string - example: GST - payment_type_id: - description: 'The default payment type id' - type: string - example: '1' - custom_fields: - description: 'JSON string of custom fields' - type: string - example: '{}' - email_footer: - description: 'The default email footer' - type: string - example: 'A default email footer' - email_sending_method: - description: 'The email driver to use to send email, options include default, gmail' - type: string - example: default - gmail_sending_user_id: - description: 'The hashed_id of the user account to send email from' - type: string - example: F76sd34D - email_subject_invoice: - description: '' - type: string - example: 'Your Invoice Subject' - email_subject_quote: - description: '' - type: string - example: 'Your Quote Subject' - email_subject_payment: - description: '' - type: string - example: 'Your Payment Subject' - email_template_invoice: - description: 'The full template for invoice emails' - type: string - example: '' - email_template_quote: - description: 'The full template for quote emails' - type: string - example: '' - email_template_payment: - description: 'The full template for payment emails' - type: string - example: '' - email_subject_reminder1: - description: 'Email subject for Reminder' - type: string - example: '' - email_subject_reminder2: - description: 'Email subject for Reminder' - type: string - example: '' - email_subject_reminder3: - description: 'Email subject for Reminder' - type: string - example: '' - email_subject_reminder_endless: - description: 'Email subject for endless reminders' - type: string - example: '' - email_template_reminder1: - description: 'The full template for Reminder 1' - type: string - example: '' - email_template_reminder2: - description: 'The full template for Reminder 2' - type: string - example: '' - email_template_reminder3: - description: 'The full template for Reminder 3' - type: string - example: '' - email_template_reminder_endless: - description: 'The full template for enless reminders' - type: string - example: '' - enable_portal_password: - description: 'Toggles whether a password is required to log into the client portal' - type: boolean - example: true - show_accept_invoice_terms: - description: 'Toggles whether the terms dialogue is shown to the client' - type: boolean - example: true - show_accept_quote_terms: - description: 'Toggles whether the terms dialogue is shown to the client' - type: boolean - example: true - require_invoice_signature: - description: 'Toggles whether a invoice signature is required' - type: boolean - example: true - require_quote_signature: - description: 'Toggles whether a quote signature is required' - type: boolean - example: true - name: - description: 'The company name' - type: string - example: 'Acme Co' - company_logo: - description: 'The company logo file' - type: object - example: logo.png - website: - description: 'The company website URL' - type: string - example: www.acme.com - address1: - description: 'The company address line 1' - type: string - example: 'Suite 888' - address2: - description: 'The company address line 2' - type: string - example: '5 Jimbo Way' - city: - description: 'The company city' - type: string - example: Sydney - state: - description: 'The company state' - type: string - example: Florisa - postal_code: - description: 'The company zip/postal code' - type: string - example: '90210' - phone: - description: 'The company phone' - type: string - example: 555-213-3948 - email: - description: 'The company email' - type: string - example: joe@acme.co - country_id: - description: 'The country ID' - type: string - example: '1' - vat_number: - description: 'The company VAT/TAX ID number' - type: string - example: '32 120 377 720' - page_size: - description: 'The default page size' - type: string - example: A4 - font_size: - description: 'The font size' - type: number - example: '9' - primary_font: - description: 'The primary font' - type: string - example: roboto - secondary_font: - description: 'The secondary font' - type: string - example: roboto - hide_paid_to_date: - description: 'Flags whether to hide the paid to date field' - type: boolean - example: false - embed_documents: - description: 'Toggled whether to embed documents in the PDF' - type: boolean - example: false - all_pages_header: - description: 'The header for the PDF' - type: boolean - example: false - all_pages_footer: - description: 'The footer for the PDF' - type: boolean - example: false - document_email_attachment: - description: 'Toggles whether to attach documents in the email' - type: boolean - example: false - enable_client_portal_password: - description: 'Toggles password protection of the client portal' - type: boolean - example: false - enable_email_markup: - description: 'Toggles the use of markdown in emails' - type: boolean - example: false - enable_client_portal_dashboard: - description: 'Toggles whether the client dashboard is shown in the client portal' - type: boolean - example: false - enable_client_portal: - description: 'Toggles whether the entire client portal is displayed to the client, or only the context' - type: boolean - example: false - email_template_statement: - description: 'The body of the email for statements' - type: string - example: 'template matter' - email_subject_statement: - description: 'The subject of the email for statements' - type: string - example: 'subject matter' - signature_on_pdf: - description: 'Toggles whether the signature (if available) is displayed on the PDF' - type: boolean - example: false - quote_footer: - description: 'The default quote footer' - type: string - example: 'the quote footer' - email_subject_custom1: - description: 'Custom reminder template subject' - type: string - example: 'Custom Subject 1' - email_subject_custom2: - description: 'Custom reminder template subject' - type: string - example: 'Custom Subject 2' - email_subject_custom3: - description: 'Custom reminder template subject' - type: string - example: 'Custom Subject 3' - email_template_custom1: - description: 'Custom reminder template body' - type: string - example: '' - email_template_custom2: - description: 'Custom reminder template body' - type: string - example: '' - email_template_custom3: - description: 'Custom reminder template body' - type: string - example: '' - enable_reminder1: - description: 'Toggles whether this reminder is enabled' - type: boolean - example: false - enable_reminder2: - description: 'Toggles whether this reminder is enabled' - type: boolean - example: false - enable_reminder3: - description: 'Toggles whether this reminder is enabled' - type: boolean - example: false - num_days_reminder1: - description: 'The Reminder interval' - type: number - example: '9' - num_days_reminder2: - description: 'The Reminder interval' - type: number - example: '9' - num_days_reminder3: - description: 'The Reminder interval' - type: number - example: '9' - schedule_reminder1: - description: '(enum: after_invoice_date, before_due_date, after_due_date)' - type: string - example: after_invoice_date - schedule_reminder2: - description: '(enum: after_invoice_date, before_due_date, after_due_date)' - type: string - example: after_invoice_date - schedule_reminder3: - description: '(enum: after_invoice_date, before_due_date, after_due_date)' - type: string - example: after_invoice_date - late_fee_amount1: - description: 'The late fee amount for reminder 1' - type: number - example: 10 - late_fee_amount2: - description: 'The late fee amount for reminder 2' - type: number - example: 20 - late_fee_amount3: - description: 'The late fee amount for reminder 2' - type: number - example: 100 - endless_reminder_frequency_id: - description: 'The frequency id of the endless reminder' - type: string - example: '1' - client_online_payment_notification: - description: 'Determines if a client should receive the notification for a online payment' - type: boolean - example: false - client_manual_payment_notification: - description: 'Determines if a client should receive the notification for a manually entered payment' - type: boolean - example: false - enable_e_invoice: - description: 'Determines if e-invoicing is enabled' - type: boolean - example: false - default_expense_payment_type_id: - description: 'The default payment type for expenses' - type: string - example: '0' - e_invoice_type: - description: 'The e-invoice type' - type: string - example: 'EN16931' - mailgun_endpoint: - description: 'The mailgun endpoint - used to determine whether US or EU endpoints are used' - type: string - example: 'api.mailgun.net or api.eu.mailgun.net' - client_initiated_payments: - description: 'Determines if clients can initiate payments directly from the client portal' - type: boolean - example: false - client_initiated_payments_minimum: - description: 'The minimum amount a client can pay' - type: number - example: 10 - sync_invoice_quote_columns: - description: 'Determines if invoice and quote columns are synced for the PDF rendering, or if they use their own columns' - type: boolean - example: false - show_task_item_description: - description: 'Determines if the task item description is shown on the invoice' - type: boolean - example: false - allow_billable_task_items: - description: 'Determines if task items can be marked as billable' - type: boolean - example: false - accept_client_input_quote_approval: - description: 'Determines if clients can approve quotes and also pass through a PO Number reference' - type: boolean - example: false - custom_sending_email: - description: 'When using Mailgun or Postmark, the FROM email address can be customized using this setting.' - type: string - example: 'bob@gmail.com' - show_paid_stamp: - description: 'Determines if the PAID stamp is shown on the invoice' - type: boolean - example: false - show_shipping_address: - description: 'Determines if the shipping address is shown on the invoice' - type: boolean - example: false - company_logo_size: - description: 'The size of the company logo on the PDF - percentage value between 0 and 100' - type: number - example: 100 - show_email_footer: - description: 'Determines if the email footer is shown on emails' - type: boolean - example: false - email_alignment: - description: 'The alignment of the email body text, options include left / center / right' - type: string - example: 'left' - auto_bill_standard_invoices: - description: 'Determines if standard invoices are automatically billed when they are created or due' - type: boolean - example: false - postmark_secret: - description: 'The Postmark secret API key' - type: string - example: '123456' - mailgun_secret: - description: 'The Mailgun secret API key' - type: string - example: '123456' - mailgun_domain: - description: 'The Mailgun domain' - type: string - example: 'sandbox123456.mailgun.org' - send_email_on_mark_paid: - description: 'Determines if an email is sent when an invoice is marked as paid' - type: boolean - example: false - vendor_portal_enable_uploads: - description: 'Determines if vendors can upload files to the portal' - type: boolean - example: false - besr_id: - description: 'The BESR ID' - type: string - example: '123456' - qr_iban: - description: 'The IBAN for the QR code' - type: string - example: 'CH123456' - email_subject_purchase_order: - description: 'The email subject for purchase orders' - type: string - example: 'Purchase Order' - email_template_purchase_order: - description: 'The email template for purchase orders' - type: string - example: 'Please see attached your purchase order.' - require_purchase_order_signature: - description: 'Determines if a signature is required on purchase orders' - type: boolean - example: false - purchase_order_public_notes: - description: 'The public notes for purchase orders' - type: string - example: 'Please see attached your purchase order.' - purchase_order_terms: - description: 'The terms for purchase orders' - type: string - example: 'Please see attached your purchase order.' - purchase_order_footer: - description: 'The footer for purchase orders' - type: string - example: 'Please see attached your purchase order.' - purchase_order_design_id: - description: 'The design id for purchase orders' - type: string - example: 'hd677df' - purchase_order_number_pattern: - description: 'The pattern for purchase order numbers' - type: string - example: 'PO-000000' - purchase_order_number_counter: - description: 'The counter for purchase order numbers' - type: number - example: 1 - page_numbering_alignment: - description: 'The alignment for page numbering: options include left / center / right' - type: string - example: 'left' - page_numbering: - description: 'Determines if page numbering is enabled on Document PDFs' - type: boolean - example: false - auto_archive_invoice_cancelled: - description: 'Determines if invoices are automatically archived when they are cancelled' - type: boolean - example: false - email_from_name: - description: 'The FROM name for emails when using Custom emailers' - type: string - example: 'Bob Smith' - show_all_tasks_client_portal: - description: 'Determines if all tasks are shown on the client portal' - type: boolean - example: false - entity_send_time: - description: 'The time that emails are sent. The time is localized to the clients locale, integer values from 1 - 24' - type: integer - example: 9 - shared_invoice_credit_counter: - description: 'Determines if the invoice and credit counter are shared' - type: boolean - example: false - reply_to_name: - description: 'The reply to name for emails' - type: string - example: 'Bob Smith' - hide_empty_columns_on_pdf: - description: 'Determines if empty columns are hidden on PDFs' - type: boolean - example: false - enable_reminder_endless: - description: 'Determines if endless reminders are enabled' - type: boolean - example: false - use_credits_payment: - description: 'Determines if credits can be used as a payment method' - type: boolean - example: false - recurring_invoice_number_pattern: - description: 'The pattern for recurring invoice numbers' - type: string - example: 'R-000000' - recurring_invoice_number_counter: - description: 'The counter for recurring invoice numbers' - type: number - example: 1 - client_portal_under_payment_minimum: - description: 'The minimum payment payment' - type: number - example: 10 - auto_bill_date: - description: 'Determines when the invoices are auto billed, options are on_send_date (when the invoice is sent) or on_due_date (when the invoice is due))' - type: string - example: 'on_send_date' - primary_color: - description: 'The primary color for the client portal / document highlights' - type: string - example: '#ffffff' - secondary_color: - description: 'The secondary color for the client portal / document highlights' - type: string - example: '#ffffff' - client_portal_allow_under_payment: - description: 'Determines if clients can pay invoices under the invoice amount due' - type: boolean - example: false - client_portal_allow_over_payment: - description: 'Determines if clients can pay invoices over the invoice amount' - type: boolean - example: false - auto_bill: - description: 'Determines how autobilling is applied for recurring invoices. off (no auto billed), always (always auto bill), optin (The user must opt in to auto billing), optout (The user must opt out of auto billing' - type: string - example: 'off' - client_portal_terms: - description: 'The terms which are displayed on the client portal' - type: string - example: 'Please see attached your invoice.' - client_portal_privacy_policy: - description: 'The privacy policy which is displayed on the client portal' - type: string - example: 'These are the terms of use for using the client portal.' - client_can_register: - description: 'Determines if clients can register on the client portal' - type: boolean - example: false - portal_design_id: - description: 'The design id for the client portal' - type: string - example: 'hd677df' - late_fee_endless_percent: - description: 'The late fee percentage for endless late fees' - type: number - example: 10 - late_fee_endless_amount: - description: 'The late fee amount for endless late fees' - type: number - example: 10 - auto_email_invoice: - description: 'Determines if invoices are automatically emailed when they are created' - type: boolean - example: false - email_signature: - description: 'The email signature for emails' - type: string - example: 'Bob Smith' - type: object - Product: - type: object - properties: - id: - type: string - description: 'The hashed product ID.' - example: eP01N - readOnly: true - company_id: - type: string - description: 'The hashed ID of the company that owns this product.' - example: eP01N - readOnly: true - user_id: - type: string - description: 'The hashed ID of the user that created this product.' - example: n30m4 - readOnly: true - assigned_user_id: - type: string - description: 'The hashed ID of the user assigned to this product.' - example: pR0j3 - project_id: - type: string - description: 'The hashed ID of the project that this product is associated with.' - example: pR0j3 - vendor_id: - type: string - description: 'The hashed ID of the vendor that this product is associated with.' - example: pR0j3 - custom_value1: - type: string - description: 'Custom value field 1.' - example: 'Custom value 1' - custom_value2: - type: string - description: 'Custom value field 2.' - example: 'Custom value 2' - custom_value3: - type: string - description: 'Custom value field 3.' - example: 'Custom value 3' - custom_value4: - type: string - description: 'Custom value field 4.' - example: 'Custom value 4' - product_key: - type: string - description: 'The product key.' - example: '1234' - notes: - type: string - description: 'Notes about the product.' - example: 'These are some notes about the product.' - cost: - type: number - format: double - description: 'The cost of the product. (Your purchase price for this product)' - example: 10.0 - price: - type: number - format: double - description: 'The price of the product that you are charging.' - example: 20.0 - quantity: - type: number - format: double - description: 'The quantity of the product. (used as a default)' - example: 5.0 - tax_name1: - type: string - description: 'The name of tax 1.' - example: 'Tax 1' - tax_rate1: - type: number - format: double - description: 'The rate of tax 1.' - example: 10.0 - tax_name2: - type: string - description: 'The name of tax 2.' - example: 'Tax 2' - tax_rate2: - type: number - format: double - description: 'The rate of tax 2.' - example: 5.0 - tax_name3: - type: string - description: 'The name of tax 3.' - example: 'Tax 3' - tax_rate3: - type: number - format: double - description: 'The rate of tax 3.' - example: 0.0 - archived_at: - type: integer - format: timestamp - description: 'The timestamp when the product was archived.' - example: '2022-03-18T15:00:00Z' - readOnly: true - created_at: - type: integer - format: timestamp - description: 'The timestamp when the product was created.' - example: '2022-03-18T15:00:00Z' - readOnly: true - updated_at: - description: Timestamp - type: integer - format: timestamp - example: '2022-03-18T12:34:56.789Z' - readOnly: true - is_deleted: - type: boolean - description: 'Boolean flag determining if the product has been deleted' - example: false - readOnly: true - in_stock_quantity: - type: integer - format: int32 - description: The quantity of the product that is currently in stock - default: 0 - stock_notification: - type: boolean - description: Indicates whether stock notifications are enabled for this product - default: true - stock_notification_threshold: - type: integer - format: int32 - description: The minimum quantity threshold for which stock notifications will be triggered - default: 0 - max_quantity: - type: integer - format: int32 - description: The maximum quantity that can be ordered for this product - product_image: - type: string - description: The URL of the product image - format: uri-reference - tax_id: - type: string - default: '1' - description: | - The tax category id for this product.' - - The following constants are available (default = '1') - - ``` - PRODUCT_TYPE_PHYSICAL = '1' - PRODUCT_TYPE_SERVICE = '2' - PRODUCT_TYPE_DIGITAL = '3' - PRODUCT_TYPE_SHIPPING = '4' - PRODUCT_TYPE_EXEMPT = '5' - PRODUCT_TYPE_REDUCED_TAX = '6' - PRODUCT_TYPE_OVERRIDE_TAX = '7' - PRODUCT_TYPE_ZERO_RATED = '8' - PRODUCT_TYPE_REVERSE_TAX = '9' - ``` - example: '1' - - ClientContactRequest: - properties: - id: - description: 'The hashed if of the contact' - type: string - example: Opnel5aKBz - readOnly: true - first_name: - description: 'The first name of the contact' - type: string - example: John - last_name: - description: 'The last name of the contact' - type: string - example: Doe - phone: - description: 'The phone number of the contact' - type: string - example: 555-152-4524 - custom_value1: - description: 'A Custom field value' - type: string - example: '' - custom_value2: - description: 'A Custom field value' - type: string - example: '' - custom_value3: - description: 'A Custom field value' - type: string - example: '' - custom_value4: - description: 'A Custom field value' - type: string - example: '' - email: - description: 'The email of the contact' - type: string - example: '' - password: - description: 'The hashed password of the contact' - type: string - example: '*****' - send_email: - description: 'Boolean value determines is this contact should receive emails' - type: boolean - example: true - type: object - ClientRequest: - required: - - contacts - - country_id - properties: - id: - description: 'The unique identifier of the client' - type: string - example: Opnel5aKBz - readOnly: true - contacts: - type: array - description: 'A list of contacts associated with the client' - items: - $ref: '#/components/schemas/ClientContactRequest' - name: - description: 'The name of the client company or organization' - type: string - example: "Jim's Housekeeping" - website: - description: 'The website URL of the client company or organization' - type: string - example: 'https://www.jims-housekeeping.com' - private_notes: - description: 'Notes that are only visible to the user who created the client' - type: string - example: 'Client prefers email communication over phone calls' - industry_id: - description: 'The unique identifier of the industry the client operates in' - type: number - example: '5' - size_id: - description: 'The unique identifier for the size category of the client company or organization' - type: number - example: '2' - address1: - description: "First line of the client's address" - type: string - example: '123 Main St' - address2: - description: "Second line of the client's address, if needed" - type: string - example: 'Apt 4B' - city: - description: 'The city the client is located in' - type: string - example: 'Beverly Hills' - state: - description: 'The state, province, or locality the client is located in' - type: string - example: 'California' - postal_code: - description: 'The postal code or ZIP code of the client' - type: string - example: '90210' - phone: - description: "The client's phone number" - type: string - example: '555-3434-3434' - country_id: - description: "The unique identifier of the client's country" - type: number - format: integer - example: '1' - custom_value1: - description: 'A custom field for storing additional information' - type: string - example: 'Preferred contact: Email' - custom_value2: - description: 'A custom field for storing additional information' - type: string - example: 'Account manager: John Doe' - custom_value3: - description: 'A custom field for storing additional information' - type: string - example: 'VIP client: Yes' - custom_value4: - description: 'A custom field for storing additional information' - type: string - example: 'Annual contract value: $50,000' - vat_number: - description: "The client's VAT (Value Added Tax) number, if applicable" - type: string - example: 'VAT123456' - id_number: - description: 'A unique identification number for the client, such as a tax ID or business registration number' - type: string - number: - description: 'A system-assigned unique number for the client, typically used for invoicing purposes' - type: string - example: 'CL-0001' - shipping_address1: - description: "First line of the client's shipping address" - type: string - example: '5 Wallaby Way' - shipping_address2: - description: "Second line of the client's shipping address, if needed" - type: string - example: 'Suite 5' - shipping_city: - description: "The city of the client's shipping address" - type: string - example: 'Perth' - shipping_state: - description: "The state, province, or locality of the client's shipping address" - type: string - example: 'Western Australia' - shipping_postal_code: - description: "The postal code or ZIP code of the client's shipping address" - type: string - example: '6110' - shipping_country_id: - description: "The unique identifier of the country for the client's shipping address" - type: number - format: integer - example: '4' - is_deleted: - description: 'A boolean value indicating whether the client has been deleted or not' - type: boolean - example: false - readOnly: true - group_settings_id: - description: 'The group settings assigned to the client' - type: string - example: Opnel5aKBz - routing_id: - description: 'The routing address id for e-invoicing for this client' - type: string - example: Opnel5aKBz3489-dfkiu-2239-sdsd - is_tax_exempt: - description: 'Flag which defines if the client is exempt from taxes' - type: boolean - example: false - has_valid_vat_number: - description: 'Flag which defines if the client has a valid VAT number' - type: boolean - example: false - readOnly: true - classification: - description: 'The classification of the client' - type: string - example: 'individual' - settings: - $ref: '#/components/schemas/ClientSettings' - type: object - Error: - properties: - message: - description: 'Something terrible went wrong' - type: string - example: 'Unexpected error' - code: - description: 'The HTTP error code, ie 5xx 4xx' - type: integer - example: '500' - type: object - BTRules: - properties: - data_key: - description: 'The key to search' - type: string - example: 'description,amount' - operator: - description: 'The operator flag of the search' - type: string - example: '>' - value: - description: 'The value to search for' - type: string - example: bob - type: object CompanySettings: required: - currency_id @@ -20770,6 +16225,1630 @@ components: type: string example: 'individual' type: object + SystemLog: + properties: + id: + description: 'The account hashed id' + type: string + example: AS3df3A + company_id: + description: 'The company hashed id' + type: string + example: AS3df3A + user_id: + description: 'The user_id hashed id' + type: string + example: AS3df3A + client_id: + description: 'The client_id hashed id' + type: string + example: AS3df3A + event_id: + description: 'The Log Type ID' + type: integer + example: 1 + category_id: + description: 'The Category Type ID' + type: integer + example: 1 + type_id: + description: 'The Type Type ID' + type: integer + example: 1 + log: + description: 'The json object of the error' + type: object + example: '{''key'':''value''}' + updated_at: + description: Timestamp + type: string + example: '2' + created_at: + description: Timestamp + type: string + example: '2' + type: object + ClientGatewayToken: + properties: + id: + description: 'The hashed id of the client gateway token' + type: string + example: Opnel5aKBz + company_id: + description: 'The hashed id of the company' + type: string + example: '2' + client_id: + description: 'The hashed_id of the client' + type: string + example: '2' + token: + description: 'The payment token' + type: string + example: '2' + routing_number: + description: 'THe bank account routing number' + type: string + example: '2' + company_gateway_id: + description: 'The hashed id of the company gateway' + type: string + example: '2' + is_default: + description: 'Flag determining if the token is the default payment method' + type: boolean + example: 'true' + type: object + + GenericBulkAction: + properties: + action: + type: string + example: archive + description: 'The action to perform ie. archive / restore / delete' + ids: + type: array + items: + format: string + type: string + example: 2J234DFA,D2J234DFA,D2J234DFA + description: string array of client hashed ids + type: object + InvoiceInvitationRequest: + required: + - client_contact_id + properties: + id: + description: 'The entity invitation hashed id' + type: string + example: Opnel5aKBz + readOnly: true + client_contact_id: + description: 'The client contact hashed id' + type: string + example: Opnel5aKBz + key: + description: 'The invitation key' + type: string + example: Opnel5aKBz4343343566236gvbb + readOnly: true + link: + description: 'The invitation link' + type: string + example: 'https://www.example.com/invitations/Opnel5aKBz4343343566236gvbb' + readOnly: true + sent_date: + description: 'The invitation sent date' + type: string + format: date-time + readOnly: true + viewed_date: + description: 'The invitation viewed date' + type: string + format: date-time + readOnly: true + opened_date: + description: 'The invitation opened date' + type: string + format: date-time + readOnly: true + updated_at: + description: 'Timestamp' + type: number + format: integer + example: '1434342123' + readOnly: true + archived_at: + description: 'Timestamp' + type: number + format: integer + example: '1434342123' + readOnly: true + email_error: + description: 'The email error' + type: string + example: 'The email error' + readOnly: true + email_status: + description: 'The email status' + type: string + readOnly: true + + Client: + properties: + id: + description: 'The unique identifier of the client' + type: string + example: Opnel5aKBz + readOnly: true + contacts: + type: array + items: + $ref: '#/components/schemas/ClientContact' + user_id: + description: 'The unique identifier of the user who created the client' + type: string + example: Ua6Rw4pVbS + readOnly: true + assigned_user_id: + description: 'The unique identifier of the user who has been assigned the client' + type: string + example: Ua6Rw4pVbS + company_id: + description: 'The unique identifier of the company the client belongs to' + type: string + example: Co7Vn3yLmW + readOnly: true + name: + description: 'The name of the client company or organization' + type: string + example: "Jim's Housekeeping" + website: + description: 'The website URL of the client company or organization' + type: string + example: 'https://www.jims-housekeeping.com' + private_notes: + description: 'Notes that are only visible to the user who created the client' + type: string + example: 'Client prefers email communication over phone calls' + client_hash: + description: 'A unique hash value for the client' + type: string + example: asdfkjhk342hjhbfdvmnfb1 + readOnly: true + industry_id: + description: 'The unique identifier of the industry the client operates in' + type: number + example: '5' + size_id: + description: 'The unique identifier for the size category of the client company or organization' + type: number + example: '2' + address1: + description: "First line of the client's address" + type: string + example: '123 Main St' + address2: + description: "Second line of the client's address, if needed" + type: string + example: 'Apt 4B' + city: + description: 'The city the client is located in' + type: string + example: 'Beverly Hills' + state: + description: 'The state, province, or locality the client is located in' + type: string + example: 'California' + postal_code: + description: 'The postal code or ZIP code of the client' + type: string + example: '90210' + phone: + description: "The client's phone number" + type: string + example: '555-3434-3434' + country_id: + description: "The unique identifier of the client's country" + type: number + format: integer + example: '1' + custom_value1: + description: 'A custom field for storing additional information' + type: string + example: 'Preferred contact: Email' + custom_value2: + description: 'A custom field for storing additional information' + type: string + example: 'Account manager: John Doe' + custom_value3: + description: 'A custom field for storing additional information' + type: string + example: 'VIP client: Yes' + custom_value4: + description: 'A custom field for storing additional information' + type: string + example: 'Annual contract value: $50,000' + vat_number: + description: "The client's VAT (Value Added Tax) number, if applicable" + type: string + example: 'VAT123456' + id_number: + description: 'A unique identification number for the client, such as a tax ID or business registration number' + type: string + number: + description: 'A system-assigned unique number for the client, typically used for invoicing purposes' + type: string + example: 'CL-0001' + shipping_address1: + description: "First line of the client's shipping address" + type: string + example: '5 Wallaby Way' + shipping_address2: + description: "Second line of the client's shipping address, if needed" + type: string + example: 'Suite 5' + shipping_city: + description: "The city of the client's shipping address" + type: string + example: 'Perth' + shipping_state: + description: "The state, province, or locality of the client's shipping address" + type: string + example: 'Western Australia' + shipping_postal_code: + description: "The postal code or ZIP code of the client's shipping address" + type: string + example: '6110' + shipping_country_id: + description: "The unique identifier of the country for the client's shipping address" + type: number + format: integer + example: '4' + is_deleted: + description: 'A boolean value indicating whether the client has been deleted or not' + type: boolean + example: false + readOnly: true + balance: + description: 'The outstanding balance the client owes' + type: number + format: float + example: '500.00' + readOnly: true + paid_to_date: + description: 'The total amount the client has paid to date' + type: number + format: float + example: '2000.00' + readOnly: true + credit_balance: + description: 'The available credit balance for the client to use on future purchases' + type: number + format: float + example: '100.00' + readOnly: true + last_login: + description: "The timestamp of the client's last login" + type: number + format: integer + example: '1628686031' + readOnly: true + created_at: + description: 'The timestamp when the client was created' + type: number + format: integer + example: '1617629031' + readOnly: true + updated_at: + description: 'The timestamp when the client was last updated' + type: number + format: integer + example: '1628445631' + readOnly: true + group_settings_id: + description: 'The group settings assigned to the client' + type: string + example: Opnel5aKBz + routing_id: + description: 'The routing address id for e-invoicing for this client' + type: string + example: Opnel5aKBz3489-dfkiu-2239-sdsd + is_tax_exempt: + description: 'Flag which defines if the client is exempt from taxes' + type: boolean + example: false + has_valid_vat_number: + description: 'Flag which defines if the client has a valid VAT number' + type: boolean + example: false + readOnly: true + payment_balance: + description: 'Defines the payment balance the client has on file (pre payments / over payments / unapplied amounts)' + type: number + example: 100 + readOnly: true + settings: + $ref: '#/components/schemas/ClientSettings' + type: object + CompanyUser: + properties: + permissions: + description: 'The company user permissions' + type: string + example: '[create_invoice]' + settings: + description: 'Settings that are used for the frontend applications to store user preferences / metadata' + type: object + example: 'json object' + react_settings: + description: 'Dedicated settings object for the react web application' + type: object' + example: 'json object' + is_owner: + description: 'Determines whether the user owns this company' + type: boolean + example: true + is_admin: + description: 'Determines whether the user is the admin of this company' + type: boolean + example: true + is_locked: + description: 'Determines whether the users access to this company has been locked' + type: boolean + example: true + updated_at: + description: 'The last time the record was modified, format Unix Timestamp' + type: integer + example: '1231232312321' + deleted_at: + description: 'Timestamp when the user was archived, format Unix Timestamp' + type: integer + example: '12312312321' + account: + $ref: '#/components/schemas/Account' + company: + $ref: '#/components/schemas/Company' + user: + $ref: '#/components/schemas/User' + token: + $ref: '#/components/schemas/CompanyToken' + type: object + Design: + properties: + id: + description: 'The design hashed id' + type: string + example: AS3df3A + name: + description: 'The design name' + type: string + example: Beauty + design: + description: 'The design HTML' + type: string + example: '' + is_custom: + description: 'Flag to determine if the design is a custom user design' + type: boolean + example: true + is_active: + description: 'Flag to determine if the design is available for use' + type: boolean + example: true + is_deleted: + description: 'Flag to determine if the design is deleted' + type: boolean + example: true + created_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + updated_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + deleted_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + type: object + InvoiceRequest: + required: + - client_id + properties: + id: + description: 'The invoice hashed id' + type: string + example: Opnel5aKBz + readOnly: true + user_id: + description: 'The user hashed id' + type: string + example: Opnel5aKBz + assigned_user_id: + description: 'The assigned user hashed id' + type: string + example: Opnel5aKBz + company_id: + description: 'The company hashed id' + type: string + example: Opnel5aKBz + readOnly: true + client_id: + description: 'The client hashed id' + type: string + example: Opnel5aKBz + status_id: + description: 'The invoice status variable' + type: string + example: '4' + readOnly: true + number: + description: 'The invoice number - is a unique alpha numeric number per invoice per company' + type: string + example: INV_101 + po_number: + description: 'The purchase order associated with this invoice' + type: string + example: PO-1234 + terms: + description: 'The invoice terms' + type: string + example: 'These are invoice terms' + public_notes: + description: 'The public notes of the invoice' + type: string + example: 'These are some public notes' + private_notes: + description: 'The private notes of the invoice' + type: string + example: 'These are some private notes' + footer: + description: 'The invoice footer notes' + type: string + example: '' + custom_value1: + description: 'A custom field value' + type: string + example: '2022-10-01' + custom_value2: + description: 'A custom field value' + type: string + example: 'Something custom' + custom_value3: + description: 'A custom field value' + type: string + example: '' + custom_value4: + description: 'A custom field value' + type: string + example: '' + tax_name1: + description: 'The tax name' + type: string + example: '' + tax_name2: + description: 'The tax name' + type: string + example: '' + tax_rate1: + description: 'The tax rate' + type: number + format: float + example: '10.00' + tax_rate2: + description: 'The tax rate' + type: number + format: float + example: '10.00' + tax_name3: + description: 'The tax name' + type: string + example: '' + tax_rate3: + description: 'The tax rate' + type: number + format: float + example: '10.00' + total_taxes: + description: 'The total taxes for the invoice' + type: number + format: float + example: '10.00' + readOnly: + line_items: + type: array + description: 'An array of objects which define the line items of the invoice' + items: + $ref: '#/components/schemas/InvoiceItem' + invitations: + type: array + description: 'An array of objects which define the invitations of the invoice' + items: + $ref: '#/components/schemas/InvoiceInvitationRequest' + amount: + description: 'The invoice amount' + type: number + format: float + example: '10.00' + readOnly: true + balance: + description: 'The invoice balance' + type: number + format: float + example: '10.00' + readOnly: true + paid_to_date: + description: 'The amount paid on the invoice to date' + type: number + format: float + example: '10.00' + readOnly: true + discount: + description: 'The invoice discount, can be an amount or a percentage' + type: number + format: float + example: '10.00' + partial: + description: 'The deposit/partial amount' + type: number + format: float + example: '10.00' + is_amount_discount: + description: 'Flag determining if the discount is an amount or a percentage' + type: boolean + example: true + is_deleted: + description: 'Defines if the invoice has been deleted' + type: boolean + example: true + readOnly: true + uses_inclusive_taxes: + description: 'Defines the type of taxes used as either inclusive or exclusive' + type: boolean + example: true + date: + description: 'The Invoice Date' + type: string + format: date + example: '1994-07-30' + last_sent_date: + description: 'The last date the invoice was sent out' + type: string + format: date + example: '1994-07-30' + readOnly: true + next_send_date: + description: 'The Next date for a reminder to be sent' + type: string + format: date + example: '1994-07-30' + readOnly: true + partial_due_date: + description: 'The due date for the deposit/partial amount' + type: string + format: date + example: '1994-07-30' + due_date: + description: 'The due date of the invoice' + type: string + format: date + example: '1994-07-30' + last_viewed: + description: Timestamp + type: number + format: integer + example: '1434342123' + readOnly: true + updated_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + readOnly: true + archived_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + readOnly: true + custom_surcharge1: + description: 'First Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge2: + description: 'Second Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge3: + description: 'Third Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge4: + description: 'Fourth Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge_tax1: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax2: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax3: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax4: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + project_id: + description: 'The project associated with this invoice' + type: string + example: Opnel5aKBz + type: object + FillableInvoice: + properties: + assigned_user_id: + description: "The assigned user's hashed ID" + type: string + example: 'a1b2c3d4' + client_id: + description: "The client's hashed ID" + type: string + example: 'x1y2z3a4' + number: + description: "The unique alphanumeric invoice number for each invoice per company" + type: string + example: INV_101 + po_number: + description: "The purchase order number associated with the invoice" + type: string + example: 'PO12345' + terms: + description: "The terms and conditions for the invoice" + type: string + example: 'Net 30' + public_notes: + description: "Public notes visible to the client on the invoice" + type: string + example: 'Thank you for your business.' + private_notes: + description: "Private notes for internal use only" + type: string + example: 'Client is a slow payer.' + footer: + description: "The footer text displayed on the invoice" + type: string + example: 'Authorized Signature' + custom_value1: + description: "First custom value for additional information" + type: string + example: 'Project ABC' + custom_value2: + description: "Second custom value for additional information" + type: string + example: 'Department XYZ' + custom_value3: + description: "Third custom value for additional information" + type: string + example: 'Location 123' + custom_value4: + description: "Fourth custom value for additional information" + type: string + example: 'Currency USD' + tax_name1: + description: "Name of the first tax applied to the invoice" + type: string + example: 'VAT' + tax_name2: + description: "Name of the second tax applied to the invoice" + type: string + example: 'GST' + tax_rate1: + description: "Rate of the first tax applied to the invoice" + type: number + example: 10.00 + tax_rate2: + description: "Rate of the second tax applied to the invoice" + type: number + example: 5.00 + tax_name3: + description: "Name of the third tax applied to the invoice" + type: string + example: 'PST' + tax_rate3: + description: "Rate of the third tax applied to the invoice" + type: number + example: 8.00 + line_items: + type: array + description: 'An array of objects which define the line items of the invoice' + items: + $ref: '#/components/schemas/InvoiceItem' + discount: + description: "The discount applied to the invoice" + type: number + example: 10.00 + partial: + description: "The partial amount applied to the invoice" + type: number + example: 20.00 + is_amount_discount: + description: "Indicates whether the discount applied is a fixed amount or a percentage" + type: boolean + example: true + uses_inclusive_taxes: + description: "Indicates whether the tax rates applied to the invoice are inclusive or exclusive" + type: boolean + example: true + date: + description: "The date the invoice was issued" + type: string + example: '1994-07-30' + partial_due_date: + description: "The due date for the partial payment" + type: string + example: '1994-08-15' + due_date: + description: "The due date for the invoice" + type: string + example: '1994-08-30' + custom_surcharge1: + description: "First custom surcharge applied to the invoice" + type: number + example: 10.00 + custom_surcharge2: + description: "Second custom surcharge applied to the invoice" + type: number + example: 15.00 + custom_surcharge3: + description: "Third custom surcharge applied to the invoice" + type: number + example: 5.00 + custom_surcharge4: + description: "Fourth custom surcharge applied to the invoice" + type: number + example: 20.00 + type: object + Paymentable: + properties: + id: + description: 'The paymentable hashed id' + type: string + example: AS3df3A + invoice_id: + description: 'The invoice hashed id' + type: string + example: AS3df3A + credit_id: + description: 'The credit hashed id' + type: string + example: AS3df3A + refunded: + description: 'The amount that has been refunded for this payment' + type: number + format: float + example: '10.00' + amount: + description: 'The amount that has been applied to the payment' + type: number + format: float + example: '10.00' + updated_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + created_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + type: object + BankTransaction: + properties: + id: + description: 'The bank integration hashed id' + type: string + example: AS3df3A + company_id: + description: 'The company hashed id' + type: string + example: AS3df3A + user_id: + description: 'The user hashed id' + type: string + example: AS3df3A + transaction_id: + description: 'The id of the transaction rule' + type: integer + example: 343434 + amount: + description: 'The transaction amount' + type: number + example: 10 + currency_id: + description: 'The currency ID of the currency' + type: string + example: '1' + account_type: + description: 'The account type' + type: string + example: creditCard + description: + description: 'The description of the transaction' + type: string + example: 'Potato purchases for kevin' + category_id: + description: 'The category id' + type: integer + example: 1 + category_type: + description: 'The category description' + type: string + example: Expenses + base_type: + description: 'Either CREDIT or DEBIT' + type: string + example: CREDIT + date: + description: 'The date of the transaction' + type: string + example: '2022-09-01' + bank_account_id: + description: 'The ID number of the bank account' + type: integer + example: '1' + type: object + Meta: + properties: + pagination: + $ref: '#/components/schemas/Pagination' + Pagination: + type: object + properties: + total: + type: integer + description: 'The total number of items' + example: 1 + readOnly: true + count: + type: integer + description: 'The number of items per page' + example: 1 + readOnly: true + per_page: + type: integer + description: 'The number of items per page' + example: 1 + readOnly: true + current_page: + type: integer + description: 'The current page number' + example: 1 + readOnly: true + total_pages: + type: integer + description: 'The total number of pages' + example: 1 + readOnly: true + links: + type: array + description: 'The pagination links' + readOnly: true + + FeesAndLimits: + properties: + min_limit: + description: 'The minimum amount accepted for this gateway' + type: string + example: '2' + max_limit: + description: 'The maximum amount accepted for this gateway' + type: string + example: '2' + fee_amount: + description: 'The gateway fee amount' + type: number + format: float + example: '2.0' + fee_percent: + description: 'The gateway fee percentage' + type: number + format: float + example: '2.0' + fee_tax_name1: + description: 'Fee tax name' + type: string + example: GST + fee_tax_name2: + description: 'Fee tax name' + type: string + example: VAT + fee_tax_name3: + description: 'Fee tax name' + type: string + example: 'CA Sales Tax' + fee_tax_rate1: + description: 'The tax rate' + type: number + format: float + example: '10.0' + fee_tax_rate2: + description: 'The tax rate' + type: number + format: float + example: '17.5' + fee_tax_rate3: + description: 'The tax rate' + type: number + format: float + example: '25.0' + fee_cap: + description: 'If set the fee amount will be no higher than this amount' + type: number + format: float + example: '2.0' + adjust_fee_percent: + description: 'Adjusts the fee to match the exact gateway fee.' + type: boolean + example: true + type: object + InvoiceItem: + type: object + properties: + quantity: + type: number + example: 1 + description: 'The quantity of the product offered for this line item' + cost: + type: number + format: float + example: 10.00 + description: 'The cost of the product offered for this line item' + product_key: + type: string + example: 'Product key' + description: 'The product key of the product offered for this line item (Referred to as Product in the product tab)' + product_cost: + type: number + format: float + example: 10.00 + description: 'The cost of the product offered for this line item (Referred to as Cost in the product tab)' + notes: + type: string + example: 'Item notes' + description: 'The notes/description for the product offered for this line item' + discount: + type: number + format: float + example: 5.00 + description: 'The discount applied to the product offered for this line item' + is_amount_discount: + type: boolean + example: false + description: 'Indicates whether the discount applied to the product offered for this line item is a fixed amount or a percentage' + tax_name1: + type: string + example: 'GST' + description: 'The name of the first tax applied to the product offered for this line item' + tax_rate1: + type: number + format: float + example: 10.00 + description: 'The rate of the first tax applied to the product offered for this line item' + tax_name2: + type: string + example: 'VAT' + description: 'The name of the second tax applied to the product offered for this line item' + tax_rate2: + type: number + format: float + example: 5.00 + description: 'The rate of the second tax applied to the product offered for this line item' + tax_name3: + type: string + example: 'CA Sales Tax' + description: 'The name of the third tax applied to the product offered for this line item' + tax_rate3: + type: number + format: float + example: 3.00 + description: 'The rate of the third tax applied to the product offered for this line item' + sort_id: + type: string + example: '0' + description: 'Deprecated' + deprecated: true + line_total: + type: number + format: float + example: 10.00 + description: 'The total amount of the product offered for this line item' + readOnly: true + gross_line_total: + type: number + format: float + example: 15.00 + description: 'The total amount of the product offered for this line item before discounts' + readOnly: true + tax_amount: + type: number + format: float + example: 1.00 + description: 'The total amount of tax applied to the product offered for this line item' + readOnly: true + date: + type: string + format: date-time + example: '2023-03-19T00:00:00Z' + description: 'Deprecated' + deprecated: true + custom_value1: + type: string + example: 'Custom value 1' + description: 'The first custom value of the product offered for this line item' + custom_value2: + type: string + example: 'Custom value 2' + description: 'The second custom value of the product offered for this line item' + custom_value3: + type: string + example: 'Custom value 3' + description: 'The third custom value of the product offered for this line item' + custom_value4: + type: string + example: 'Custom value 4' + description: 'The fourth custom value of the product offered for this line item' + type_id: + type: string + example: '1' + description: '1 = product, 2 = service, 3 unpaid gateway fee, 4 paid gateway fee, 5 late fee, 6 expense' + default: '1' + tax_id: + type: string + example: '1' + default: '1' + description: 'The tax ID of the product: 1 product, 2 service, 3 digital, 4 shipping, 5 exempt, 5 reduced tax, 7 override, 8 zero rate, 9 reverse tax' + Project: + type: object + properties: + id: + description: 'The project hashed id' + type: string + example: Opnel5aKBz + user_id: + description: 'The user hashed id' + type: string + example: Opnel5aKBz + assigned_user_id: + description: The assigned user identifier associated with the project + type: string + example: Opnel5aKBz + client_id: + type: string + example: Opnel5aKBz + description: The client identifier associated with the project + name: + type: string + description: The name of the project + example: 'New Project' + task_rate: + type: number + format: float + example: 10 + description: The default rate per task for the project + due_date: + type: string + format: date + example: '2019-01-01' + description: The due date for the project + private_notes: + type: string + description: Private notes associated with the project + budgeted_hours: + type: number + format: float + description: The number of budgeted hours for the project + custom_value1: + type: string + description: Custom value field 1 + custom_value2: + type: string + description: Custom value field 2 + custom_value3: + type: string + description: Custom value field 3 + custom_value4: + type: string + description: Custom value field 4 + created_at: + type: number + format: integer + example: 134341234234 + description: The timestamp of the project creation + updated_at: + type: number + format: integer + example: 134341234234 + description: The timestamp of the last project update + archived_at: + type: number + format: integer + example: 134341234234 + description: The timestamp of the project deletion + public_notes: + type: string + description: Public notes associated with the project + is_deleted: + type: boolean + description: A flag indicating if the project is deleted + number: + type: string + description: The project number + color: + type: string + description: The color associated with the project + required: + - id + - user_id + - company_id + - name + - task_rate + - budgeted_hours + - is_deleted + - color + + PaymentTerm: + properties: + num_days: + description: 'The payment term length in days' + type: integer + example: '1' + name: + description: 'The payment term length in string format' + type: string + example: 'NET 1' + created_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + updated_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + archived_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + type: object + BankIntegration: + properties: + id: + description: 'The bank integration hashed id' + type: string + example: AS3df3A + company_id: + description: 'The company hashed id' + type: string + example: AS3df3A + user_id: + description: 'The user hashed id' + type: string + example: AS3df3A + provider_bank_name: + description: 'The providers bank name' + type: string + example: 'Chase Bank' + bank_account_id: + description: 'The bank account id' + type: integer + example: '1233434' + bank_account_name: + description: 'The name of the account' + type: string + example: 'My Checking Acc' + bank_account_number: + description: 'The account number' + type: string + example: '111 234 2332' + bank_account_status: + description: 'The status of the bank account' + type: string + example: ACTIVE + bank_account_type: + description: 'The type of account' + type: string + example: CREDITCARD + balance: + description: 'The current bank balance if available' + type: number + example: '1000000' + currency: + description: 'iso_3166_3 code' + type: string + example: USD + type: object + PurchaseOrder: + properties: + id: + description: 'The unique hashed identifier for the purchase order' + type: string + example: Opnel5aKBz + user_id: + description: 'The unique hashed identifier for the user who created the purchase order' + type: string + example: '' + assigned_user_id: + description: 'The unique hashed identifier for the user assigned to the purchase order' + type: string + example: '' + company_id: + description: 'The unique hashed identifier for the company associated with the purchase order' + type: string + example: '' + vendor_id: + description: 'The unique hashed identifier for the vendor associated with the purchase order' + type: string + example: '' + status_id: + description: 'The status of the purchase order represented by a unique identifier' + type: string + example: '' + number: + description: 'The unique alpha-numeric purchase order number per company' + type: string + example: PO_101 + quote_number: + description: 'The quote number associated with this purchase order' + type: string + example: QUOTE_101 + terms: + description: 'The terms and conditions for the purchase order' + type: string + example: 'These are some purchase order terms. Valid for 14 days.' + public_notes: + description: 'Publicly visible notes associated with the purchase order' + type: string + example: 'These are public notes which the vendor may see' + private_notes: + description: 'Privately visible notes associated with the purchase order, not disclosed to the vendor' + type: string + example: 'These are private notes, not to be disclosed to the vendor' + footer: + description: 'The footer text of the purchase order' + type: string + example: 'The text goes in the footer of the purchase order' + custom_value1: + description: 'First custom value field for additional information' + type: string + example: 'A custom value' + custom_value2: + description: 'Second custom value field for additional information' + type: string + example: 'A custom value' + custom_value3: + description: 'Third custom value field for additional information' + type: string + example: 'A custom value' + custom_value4: + description: 'Fourth custom value field for additional information' + type: string + example: 'A custom value' + tax_name1: + description: 'The name of the first tax applied to the purchase order' + type: string + example: GST + tax_name2: + description: 'The name of the second tax applied to the purchase order' + type: string + example: VAT + tax_rate1: + description: 'The rate of the first tax applied to the purchase order' + type: number + format: float + example: 10.00 + tax_rate2: + description: 'The rate of the second tax applied to the purchase order' + type: number + format: float + example: 10.00 + tax_name3: + description: 'The name of the third tax applied to the purchase order' + type: string + example: '' + tax_rate3: + description: 'The rate of the third tax applied to the purchase order' + type: number + format: float + example: 10.00 + total_taxes: + description: 'The total amount of taxes applied to the purchase order' + type: number + format: float + example: 10.00 + line_items: + type: array + description: 'An array of objects which define the line items of the purchase order' + items: + $ref: '#/components/schemas/InvoiceItem' + amount: + description: 'The total amount of the purchase order before taxes and discounts' + type: number + format: float + example: 10.00 + balance: + description: 'The balance due for the purchase order after accounting for payments' + type: number + format: float + example: 10.00 + paid_to_date: + description: 'The total amount paid on the purchase order so far' + type: number + format: float + example: 10.00 + discount: + description: 'The discount amount or percentage applied to the purchase order' + type: number + format: float + example: 10.00 + partial: + description: 'The partial or deposit amount for the purchase order' + type: number + format: float + example: 10.00 + is_amount_discount: + description: 'Boolean flag indicating if the discount is a fixed amount or a percentage' + type: boolean + example: true + is_deleted: + description: 'Boolean flag indicating if the purchase order has been deleted' + type: boolean + example: false + uses_inclusive_taxes: + description: 'Boolean flag indicating if the taxes used are inclusive or exclusive' + type: boolean + example: true + date: + description: 'The date the purchase order was created' + type: string + format: date + example: '1994-07-30' + last_sent_date: + description: 'The last date the purchase order was sent to the vendor' + type: string + format: date + example: '1994-07-30' + next_send_date: + description: 'The next scheduled date for sending a reminder for the purchase order' + type: string + format: date + example: '1994-07-30' + partial_due_date: + description: 'The due date for the partial or deposit amount' + type: string + format: date + example: '1994-07-30' + due_date: + description: 'The due date for the total amount of the purchase order' + type: string + format: date + example: '1994-07-30' + settings: + $ref: '#/components/schemas/CompanySettings' + last_viewed: + description: Timestamp + type: number + format: integer + example: 1434342123 + updated_at: + description: Timestamp + type: number + format: integer + example: 1434342123 + archived_at: + description: Timestamp + type: number + format: integer + example: 1434342123 + custom_surcharge1: + description: 'First custom surcharge amount for the purchase order' + type: number + format: float + example: 10.00 + custom_surcharge2: + description: 'Second custom surcharge amount for the purchase order' + type: number + format: float + example: 10.00 + custom_surcharge3: + description: 'Third custom surcharge amount for the purchase order' + type: number + format: float + example: 10.00 + custom_surcharge4: + description: 'Fourth custom surcharge amount for the purchase order' + type: number + format: float + example: 10.00 + custom_surcharge_tax1: + description: 'Boolean flag indicating if taxes are charged on the first custom surcharge amount' + type: boolean + example: true + custom_surcharge_tax2: + description: 'Boolean flag indicating if taxes are charged on the second custom surcharge amount' + type: boolean + example: true + custom_surcharge_tax3: + description: 'Boolean flag indicating if taxes are charged on the third custom surcharge amount' + type: boolean + example: true + custom_surcharge_tax4: + description: 'Boolean flag indicating if taxes are charged on the fourth custom surcharge amount' + type: boolean + example: true + type: object + + + CompanyLedger: + properties: + entity_id: + description: 'This field will reference one of the following entity hashed ID payment_id, invoice_id or credit_id' + type: string + example: AS3df3A + notes: + description: 'The notes which reference this entry of the ledger' + type: string + example: 'Credit note for invoice #3212' + balance: + description: 'The client balance' + type: number + format: float + example: '10.00' + adjustment: + description: 'The amount the client balance is adjusted by' + type: number + format: float + example: '10.00' + updated_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + created_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + type: object + Task: + properties: + id: + description: 'The hashed id of the task' + type: string + example: Opnel5aKBz + user_id: + description: 'The hashed id of the user who created the task' + type: string + example: Opnel5aKBz + assigned_user_id: + description: 'The assigned user of the task' + type: string + example: Opnel5aKBz + company_id: + description: 'The hashed id of the company' + type: string + example: Opnel5aKBz + client_id: + description: 'The hashed if of the client' + type: string + example: Opnel5aKBz + invoice_id: + description: 'The hashed id of the invoice associated with the task' + type: string + example: Opnel5aKBz + project_id: + description: 'The hashed id of the project associated with the task' + type: string + example: Opnel5aKBz + number: + description: 'The number of the task' + type: string + example: TASK-123 + time_log: + description: 'An array of unix time stamps defining the start and end times of the task' + type: string + example: '[[1,2],[3,4]]' + is_running: + description: 'Determines if the task is still running' + type: boolean + example: true + is_deleted: + description: 'Boolean flag determining if the task has been deleted' + type: boolean + example: true + task_status_id: + description: 'The hashed id of the task status' + type: string + example: Opnel5aKBz + description: + description: 'The task description' + type: string + example: 'A wonder task to work on' + duration: + description: 'The task duration in seconds' + type: integer + example: '3600' + task_status_order: + description: 'The order of the task' + type: integer + example: '4' + rate: + description: 'The task rate' + type: number + example: 10.00 + custom_value1: + description: 'A custom value' + type: string + example: '2022-10-10' + custom_value2: + description: 'A custom value' + type: string + example: $1100 + custom_value3: + description: 'A custom value' + type: string + example: 'I need help' + custom_value4: + description: 'A custom value' + type: string + example: INV-3343 + is_date_based: + description: 'Boolean flag determining if the task is date based' + type: boolean + example: true + calculated_start_date: + description: 'The calculated start date of the task' + type: string + example: '2022-10-10' + readOnly: true + invoice_documents: + description: "Boolean flags which determines whether to include the task documents on the invoice" + type: boolean + example: true + created_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + readOnly: true + updated_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + readOnly: true + archived_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + readOnly: true + type: object ClientContact: properties: id: @@ -20883,241 +17962,2836 @@ components: format: integer example: '134341234234' type: object - - FeesAndLimits: + ClientContactRequest: properties: - min_limit: - description: 'The minimum amount accepted for this gateway' + id: + description: 'The hashed if of the contact' type: string - example: '2' - max_limit: - description: 'The maximum amount accepted for this gateway' + example: Opnel5aKBz + readOnly: true + first_name: + description: 'The first name of the contact' type: string - example: '2' - fee_amount: - description: 'The gateway fee amount' - type: number - format: float - example: '2.0' - fee_percent: - description: 'The gateway fee percentage' - type: number - format: float - example: '2.0' - fee_tax_name1: - description: 'Fee tax name' + example: John + last_name: + description: 'The last name of the contact' type: string - example: GST - fee_tax_name2: - description: 'Fee tax name' + example: Doe + phone: + description: 'The phone number of the contact' type: string - example: VAT - fee_tax_name3: - description: 'Fee tax name' + example: 555-152-4524 + custom_value1: + description: 'A Custom field value' type: string - example: 'CA Sales Tax' - fee_tax_rate1: - description: 'The tax rate' - type: number - format: float - example: '10.0' - fee_tax_rate2: - description: 'The tax rate' - type: number - format: float - example: '17.5' - fee_tax_rate3: - description: 'The tax rate' - type: number - format: float - example: '25.0' - fee_cap: - description: 'If set the fee amount will be no higher than this amount' - type: number - format: float - example: '2.0' - adjust_fee_percent: - description: 'Adjusts the fee to match the exact gateway fee.' + example: '' + custom_value2: + description: 'A Custom field value' + type: string + example: '' + custom_value3: + description: 'A Custom field value' + type: string + example: '' + custom_value4: + description: 'A Custom field value' + type: string + example: '' + email: + description: 'The email of the contact' + type: string + example: '' + password: + description: 'The hashed password of the contact' + type: string + example: '*****' + send_email: + description: 'Boolean value determines is this contact should receive emails' type: boolean example: true type: object - Account: + VendorContact: properties: id: - description: 'The account hashed id' + description: 'The hashed id of the vendor contact' type: string - example: AS3df3A - account_sms_verified: - description: 'Boolean flag if the account has been verified by sms' + example: Opnel5aKBz + readOnly: true + user_id: + description: 'The hashed id of the user id' type: string - example: true - type: object - BankTransactionRule: - properties: - id: - description: 'The bank transaction rules hashed id' - type: string - example: AS3df3A + example: Opnel5aKBz company_id: - description: 'The company hashed id' + description: 'The hashed id of the company' type: string - example: AS3df3A + example: Opnel5aKBz + vendor_id: + description: 'The hashed id of the vendor' + type: string + example: Opnel5aKBz + first_name: + description: 'The first name of the contact' + type: string + example: Harry + last_name: + description: 'The last name of the contact' + type: string + example: Windsor + phone: + description: 'The contacts phone number' + type: string + example: 555-123-1234 + custom_value1: + description: 'A custom value' + type: string + example: '2022-10-10' + custom_value2: + description: 'A custom value' + type: string + example: $1000 + custom_value3: + description: 'A custom value' + type: string + example: '' + custom_value4: + description: 'A custom value' + type: string + example: '' + email: + description: 'The contact email address' + type: string + example: harry@windsor.com + is_primary: + description: 'Boolean flag determining if the contact is the primary contact for the vendor' + type: boolean + example: true + created_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + readOnly: true + updated_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + readOnly: true + deleted_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + readOnly: true + type: object + Subscription: + properties: + id: + description: Unique identifier for the subscription + type: string + example: Opnel5aKBz + user_id: + description: Unique identifier for the user associated with the subscription + type: string + example: Ua6Rw4pVbS + product_id: + description: Unique identifier for the product associated with the subscription + type: string + example: Pr5Ft7yBmC + company_id: + description: Unique identifier for the company associated with the subscription + type: string + example: Co7Vn3yLmW + recurring_invoice_id: + description: Unique identifier for the recurring invoice associated with the subscription + type: string + example: Ri2Yt8zJkP + is_recurring: + description: Indicates whether the subscription is recurring + type: boolean + example: 'true' + frequency_id: + description: 'integer const representation of the frequency' + type: string + example: '1' + auto_bill: + description: 'enum setting' + type: string + example: always + promo_code: + description: Promotional code applied to the subscription + type: string + example: PROMOCODE4U + promo_discount: + description: Discount percentage or amount applied to the subscription + type: number + example: 10 + is_amount_discount: + description: Indicates whether the discount is a fixed amount + type: boolean + example: 'true' + allow_cancellation: + description: Indicates whether the subscription can be cancelled + type: boolean + example: 'true' + per_seat_enabled: + description: Indicates whether the subscription pricing is per seat + type: boolean + example: 'true' + currency_id: + description: Unique identifier for the currency used in the subscription + type: integer + example: '1' + max_seats_limit: + description: Maximum number of seats allowed for the subscription + type: integer + example: '100' + trial_enabled: + description: Indicates whether the subscription has a trial period + type: boolean + example: 'true' + trial_duration: + description: Duration of the trial period in days + type: integer + example: '14' + allow_query_overrides: + description: Indicates whether query overrides are allowed for the subscription + type: boolean + example: 'true' + allow_plan_changes: + description: Indicates whether plan changes are allowed for the subscription + type: boolean + example: 'true' + refund_period: + description: Number of days within which refunds can be requested + type: integer + example: '30' + webhook_configuration: + description: Webhook configuration for the subscription + type: string + example: 'expand reference for this' + is_deleted: + description: Indicates whether the subscription has been deleted + type: boolean + example: 'false' + archived_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + created_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + updated_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + type: object + BulkAction: + type: array + items: + type: integer + example: '[0,1,2,3,]' + BTRules: + properties: + data_key: + description: 'The key to search' + type: string + example: 'description,amount' + operator: + description: 'The operator flag of the search' + type: string + example: '>' + value: + description: 'The value to search for' + type: string + example: bob + type: object + Company: + properties: + id: + description: "The unique hashed identifier for the company" + type: string + example: WJxbojagwO + size_id: + description: "The unique identifier representing the company's size category" + type: string + example: '2' + industry_id: + description: "The unique identifier representing the company's industry category" + type: string + example: '5' + slack_webhook_url: + description: "The URL for the company's Slack webhook notifications" + type: string + example: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' + google_analytics_key: + description: "The company's Google Analytics tracking ID" + type: string + example: 'UA-123456789-1' + portal_mode: + description: "The mode determining how client-facing URLs are structured (e.g., subdomain, domain, or iframe)" + type: string + example: subdomain + subdomain: + description: "The subdomain prefix for the company's domain (e.g., 'acme' in acme.domain.com)" + type: string + example: acme + portal_domain: + description: "The fully qualified domain used for client-facing URLs" + type: string + example: 'https://subdomain.invoicing.co' + enabled_tax_rates: + description: "The number of tax rates used per entity" + type: integer + example: '2' + fill_products: + description: "A flag determining whether to auto-fill product descriptions based on the product key" + type: boolean + example: true + convert_products: + description: "A flag determining whether to convert products between different types or units" + type: boolean + example: true + update_products: + description: "A flag determining whether to update product descriptions when the description changes" + type: boolean + example: true + show_product_details: + description: "A flag determining whether to display product details in the user interface" + type: boolean + example: true + show_product_cost: + description: "A flag determining whether to display product cost is shown in the user interface" + type: boolean + example: true + custom_fields: + description: "A mapping of custom fields for various objects within the company" + type: object + enable_product_cost: + description: "A flag determining whether to show or hide the product cost field in the user interface" + type: boolean + example: true + enable_product_quantity: + description: "A flag determining whether to show or hide the product quantity field in the user interface" + type: boolean + example: true + default_quantity: + description: "A flag determining whether to use a default quantity for products" + type: boolean + example: true + custom_surcharge_taxes1: + description: "A flag determining whether to apply taxes on custom surcharge amounts for the first custom surcharge field" + type: boolean + example: true + custom_surcharge_taxes2: + description: "A flag determining whether to apply taxes on custom surcharge amounts for the second custom surcharge field" + type: boolean + example: true + custom_surcharge_taxes3: + description: "A flag determining whether to apply taxes on custom surcharge amounts for the third custom surcharge field" + type: boolean + example: true + custom_surcharge_taxes4: + description: "A flag determining whether to apply taxes on custom surcharge amounts for the fourth custom" + logo: + description: "The company logo file in binary format" + type: string + format: binary + example: logo.png + company_key: + description: "The static company key hash used to identify the Company" + readOnly: true + type: string + example: "Vnb14bRlwiFjc5ckte6cfbygTRkn5IMQ" + client_can_register: + description: "A flag determining whether clients can register for the client portal" + type: boolean + example: true + enabled_modules: + type: integer + description: | + Bitmask representation of the modules that are enabled in the application + + ``` + self::ENTITY_RECURRING_INVOICE => 1, + self::ENTITY_CREDIT => 2, + self::ENTITY_QUOTE => 4, + self::ENTITY_TASK => 8, + self::ENTITY_EXPENSE => 16, + self::ENTITY_PROJECT => 32, + self::ENTITY_VENDOR => 64, + self::ENTITY_TICKET => 128, + self::ENTITY_PROPOSAL => 256, + self::ENTITY_RECURRING_EXPENSE => 512, + self::ENTITY_RECURRING_TASK => 1024, + self::ENTITY_RECURRING_QUOTE => 2048, + ``` + + The default per_page value is 20. + + example: 2048 + db: + readOnly: true + type: string + example: 'db-ninja-01' + first_day_of_week: + description: "The first day of the week for the company" + type: string + example: '1' + first_month_of_year: + description: "The first month for the company financial year" + type: string + example: '1' + enabled_item_tax_rates: + description: "The number of tax rates used per item" + type: integer + example: 2 + is_large: + description: "A flag determining whether the company is considered large" + type: boolean + example: true + default_auto_bill: + type: enum + example: 'always' + description: | + A flag determining whether to auto-bill clients by default + + values: + + - always - Always auto bill + - disabled - Never auto bill + - optin - Allow the client to select their auto bill status with the default being disabled + - optout -Allow the client to select their auto bill status with the default being enabled + mark_expenses_invoiceable: + description: "A flag determining whether to mark expenses as invoiceable by default" + type: boolean + example: true + mark_expenses_paid: + description: "A flag determining whether to mark expenses as paid by default" + type: boolean + example: true + invoice_expense_documents: + description: "A flag determining whether to include expense documents on invoices by default" + type: boolean + example: true + auto_start_tasks: + description: "A flag determining whether to auto-start tasks by default" + type: boolean + example: true + invoice_task_timelog: + description: "A flag determining whether to include task time logs on invoices by default" + type: boolean + example: true + invoice_task_documents: + description: "A flag determining whether to include task documents on invoices by default" + type: boolean + example: true + show_tasks_table: + description: "A flag determining whether to show the tasks table on invoices by default" + type: boolean + example: true + is_disabled: + description: "A flag determining whether the company is disabled" + type: boolean + example: true + default_task_is_date_based: + description: "A flag determining whether to default tasks to be date-based" + type: boolean + example: true + enable_product_discount: + description: "A flag determining whether to show or hide the product discount field in the user interface" + type: boolean + example: true + calculate_expense_tax_by_amount: + description: "A flag determining whether to calculate expense taxes by amount" + type: boolean + example: true + expense_inclusive_taxes: + description: "A flag determining whether to include taxes in the expense amount" + type: boolean + example: true + session_timeout: + description: "The session timeout for the company" + type: integer + example: 60 + oauth_password_required: + description: "A flag determining whether to require a password for `dangerous` actions when using OAuth" + type: boolean + example: true + invoice_task_datelog: + description: "A flag determining whether to include task date logs on invoices by default" + type: boolean + example: true + default_password_timeout: + description: "The default password timeout for the company" + type: integer + example: 60 + show_task_end_date: + description: "A flag determining whether to show the task end date on invoices by default" + type: boolean + example: true + markdown_enabled: + description: "A flag determining whether markdown is enabled for the company" + type: boolean + example: true + report_include_drafts: + description: "A flag determining whether to include draft invoices in reports" + type: boolean + example: true + client_registration_fields: + description: "The client registration fields for the company" + type: object + stop_on_unpaid_recurring: + description: "A flag determining whether to stop recurring invoices when they are unpaid" + type: boolean + example: true + use_quote_terms_on_conversion: + description: "A flag determining whether to use quote terms on conversion to an invoice" + type: boolean + example: true + enable_applying_payments: + description: "A flag determining whether to enable applying payments to invoices" + type: boolean + example: true + track_inventory: + description: "A flag determining whether to track inventory for the company" + type: boolean + example: true + inventory_notification_threshold: + description: "The inventory notification threshold for the company" + type: integer + example: 60 + stock_notification: + description: "A flag determining whether to send stock notifications for the company" + type: boolean + example: true + matomo_url: + description: "The Matomo URL for the company" + type: string + example: 'https://matomo.example.com' + matomo_id: + description: "The Matomo ID for the company" + type: string + example: '1' + enabled_expense_tax_rates: + description: "The number of tax rates used per expense" + type: integer + example: 2 + invoice_task_project: + description: "A flag determining whether to include the project on invoices by default" + type: boolean + example: true + report_include_deleted: + description: "A flag determining whether to include deleted invoices in reports" + type: boolean + example: true + invoice_task_lock: + description: "A flag determining whether to lock tasks when invoiced" + type: boolean + example: true + convert_payment_currency: + description: "A flag determining whether to convert the payment currency" + type: boolean + example: true + convert_expense_currency: + description: "A flag determining whether to convert the expense currency" + type: boolean + example: true + notify_vendor_when_paid: + description: "A flag determining whether to notify the vendor when an expense is paid" + type: boolean + example: true + invoice_task_hours: + description: "A flag determining whether to include the task hours on invoices by default" + type: boolean + example: true + calculate_taxes: + description: "A flag determining whether to calculate taxes for the company" + type: boolean + example: true + tax_data: + description: "The tax data for the company" + type: object + e_invoice_certificate: + description: "The e-invoice certificate for the company" + type: string + example: '-----BEGIN CERTIFICATE-----' + e_invoice_certificate_passphrase: + description: "The e-invoice certificate passphrase for the company" + type: string + example: 'secret' + origin_tax_data: + description: "The origin tax data for the company" + type: object + invoice_task_project_header: + description: "A flag determining whether to include the project header on invoices by default" + type: boolean + example: true + invoice_task_item_description: + description: "A flag determining whether to include the item description on invoices by default" + type: boolean + example: true + + settings: + $ref: '#/components/schemas/CompanySettings' + type: object + RecurringInvoice: + properties: + id: + description: 'The hashed id of the recurring invoice' + type: string + example: Opnel5aKBz user_id: description: 'The user hashed id' type: string - example: AS3df3A - name: - description: 'The name of the transaction' + example: Opnel5aKBz + assigned_user_id: + description: 'The assigned user hashed id' type: string - example: 'Rule 1' - rules: - description: 'A mapped collection of the sub rules for the BankTransactionRule' - type: array - items: - $ref: '#/components/schemas/BTRules' - auto_convert: - description: 'Flags whether the rule converts the transaction automatically' - type: boolean - example: true - matches_on_all: - description: 'Flags whether all subrules are required for the match' - type: boolean - example: true - applies_to: - description: 'Flags whether the rule applies to a CREDIT or DEBIT' + example: Opnel5aKBz + company_id: + description: 'The company hashed id' type: string - example: CREDIT + example: Opnel5aKBz client_id: description: 'The client hashed id' type: string - example: AS3df3A - vendor_id: - description: 'The vendor hashed id' + example: Opnel5aKBz + status_id: + description: 'The invoice status variable' type: string - example: AS3df3A - category_id: - description: 'The category hashed id' + example: '4' + frequency_id: + description: 'The recurring invoice frequency' + type: number + example: '4' + remaining_cycles: + description: 'The number of invoices left to be generated' + type: number + example: '4' + number: + description: 'The recurringinvoice number - is a unique alpha numeric number per invoice per company' type: string - example: AS3df3A - type: object - CompanyToken: - properties: - name: - description: 'The token name' + example: INV_101 + po_number: + description: 'The purchase order associated with this recurring invoice' type: string - example: 'Token Name' - token: - description: 'The token value' + example: PO-1234 + terms: + description: 'The invoice terms' type: string - example: AS3df3jUUH765fhfd9KJuidj3JShjA - is_system: - description: 'Determines whether the token is created by the system rather than a user' - type: boolean - example: 'true' - type: object - CompanyUser: - properties: - permissions: - description: 'The company user permissions' + example: 'These are invoice terms' + public_notes: + description: 'The public notes of the invoice' type: string - example: '[create_invoice]' - settings: - description: 'Settings that are used for the frontend applications to store user preferences / metadata' + example: 'These are some public notes' + private_notes: + description: 'The private notes of the invoice' + type: string + example: 'These are some private notes' + footer: + description: 'The invoice footer notes' + type: string + example: '' + custom_value1: + description: 'A custom field value' + type: string + example: '2022-10-01' + custom_value2: + description: 'A custom field value' + type: string + example: 'Something custom' + custom_value3: + description: 'A custom field value' + type: string + example: '' + custom_value4: + description: 'A custom field value' + type: string + example: '' + tax_name1: + description: 'The tax name' + type: string + example: '' + tax_name2: + description: 'The tax name' + type: string + example: '' + tax_rate1: + description: 'The tax rate' + type: number + format: float + example: '10.00' + tax_rate2: + description: 'The tax rate' + type: number + format: float + example: '10.00' + tax_name3: + description: 'The tax name' + type: string + example: '' + tax_rate3: + description: 'The tax rate' + type: number + format: float + example: '10.00' + total_taxes: + description: 'The total taxes for the invoice' + type: number + format: float + example: '10.00' + line_items: + description: 'An array of objects which define the line items of the invoice' type: object - example: 'json object' - react_settings: - description: 'Dedicated settings object for the react web application' - type: object' - example: 'json object' - is_owner: - description: 'Determines whether the user owns this company' + example: '' + amount: + description: 'The invoice amount' + type: number + format: float + example: '10.00' + balance: + description: 'The invoice balance' + type: number + format: float + example: '10.00' + paid_to_date: + description: 'The amount paid on the invoice to date' + type: number + format: float + example: '10.00' + discount: + description: 'The invoice discount, can be an amount or a percentage' + type: number + format: float + example: '10.00' + partial: + description: 'The deposit/partial amount' + type: number + format: float + example: '10.00' + is_amount_discount: + description: 'Flag determining if the discount is an amount or a percentage' type: boolean example: true - is_admin: - description: 'Determines whether the user is the admin of this company' + is_deleted: + description: 'Defines if the invoice has been deleted' type: boolean example: true - is_locked: - description: 'Determines whether the users access to this company has been locked' + uses_inclusive_taxes: + description: 'Defines the type of taxes used as either inclusive or exclusive' type: boolean example: true + date: + description: 'The Invoice Date' + type: string + format: date + example: '1994-07-30' + last_sent_date: + description: 'The last date the invoice was sent out' + type: string + format: date + example: '1994-07-30' + next_send_date: + description: 'The Next date for a reminder to be sent' + type: string + format: date + example: '1994-07-30' + partial_due_date: + description: 'The due date for the deposit/partial amount' + type: string + format: date + example: '1994-07-30' + due_date: + description: 'The due date of the invoice' + type: string + format: date + example: '1994-07-30' + settings: + $ref: '#/components/schemas/CompanySettings' + last_viewed: + description: Timestamp + type: number + format: integer + example: '1434342123' updated_at: - description: 'The last time the record was modified, format Unix Timestamp' - type: integer - example: '1231232312321' - deleted_at: - description: 'Timestamp when the user was archived, format Unix Timestamp' - type: integer - example: '12312312321' - account: - $ref: '#/components/schemas/Account' - company: - $ref: '#/components/schemas/Company' - user: - $ref: '#/components/schemas/User' - token: - $ref: '#/components/schemas/CompanyToken' + description: Timestamp + type: number + format: integer + example: '1434342123' + archived_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + custom_surcharge1: + description: 'First Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge2: + description: 'Second Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge3: + description: 'Third Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge4: + description: 'Fourth Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge_tax1: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax2: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax3: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax4: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true type: object - InvoiceInvitationRequest: - required: - - client_contact_id + + Quote: properties: id: - description: 'The entity invitation hashed id' + description: 'The unique hashed identifier for the quote' + type: string + example: Opnel5aKBz + user_id: + description: 'The unique hashed identifier for the user who created the quote' + type: string + example: '' + assigned_user_id: + description: 'The unique hashed identifier for the user assigned to the quote' + type: string + example: '' + company_id: + description: 'The unique hashed identifier for the company associated with the quote' + type: string + example: '' + client_id: + description: 'The unique hashed identifier for the client associated with the quote' + type: string + example: '' + status_id: + description: 'The status of the quote represented by a unique identifier' + type: string + example: '' + number: + description: 'The unique alpha-numeric quote number for the quote per company' + type: string + example: QUOTE_101 + po_number: + description: 'The purchase order number associated with the quote' + type: string + example: PO-1234 + terms: + description: 'The terms and conditions for the quote' + type: string + example: 'These are some quote terms. Valid for 14 days.' + public_notes: + description: 'Publicly visible notes associated with the quote' + type: string + example: 'These are public notes which the client may see' + private_notes: + description: 'Privately visible notes associated with the quote, not disclosed to the client' + type: string + example: 'These are private notes, not to be disclosed to the client' + footer: + description: 'The footer text of the quote' + type: string + example: 'The text goes in the footer of the quote' + custom_value1: + description: 'First custom value field for additional information' + type: string + example: 'A custom value' + custom_value2: + description: 'Second custom value field for additional information' + type: string + example: 'A custom value' + custom_value3: + description: 'Third custom value field for additional information' + type: string + example: 'A custom value' + custom_value4: + description: 'Fourth custom value field for additional information' + type: string + example: 'A custom value' + tax_name1: + description: 'The name of the first tax applied to the quote' + type: string + example: GST + tax_name2: + description: 'The name of the second tax applied to the quote' + type: string + example: VAT + tax_rate1: + description: 'The rate of the first tax applied to the quote' + type: number + format: float + example: 10.00 + tax_rate2: + description: 'The rate of the second tax applied to the quote' + type: number + format: float + example: 10.00 + tax_name3: + description: 'The name of the third tax applied to the quote' + type: string + example: '' + tax_rate3: + description: 'The rate of the third tax applied to the quote' + type: number + format: float + example: 10.00 + total_taxes: + description: 'The total amount of taxes for the quote' + type: number + format: float + example: 10.00 + line_items: + type: array + description: 'An array of objects which define the line items of the quote' + items: + $ref: '#/components/schemas/InvoiceItem' + amount: + description: 'The total amount of the quote before taxes and discounts' + type: number + format: float + example: 10.00 + balance: + description: 'The balance due for the quote after accounting for payments' + type: number + format: float + example: 10.00 + paid_to_date: + description: 'The total amount paid on the quote so far' + type: number + format: float + example: 10.00 + discount: + description: 'The discount amount or percentage applied to the quote' + type: number + format: float + example: 10.00 + partial: + description: 'The partial or deposit amount for the quote' + type: number + format: float + example: 10.00 + is_amount_discount: + description: 'Boolean flag indicating if the discount is a fixed amount or a percentage' + type: boolean + example: true + is_deleted: + description: 'Boolean flag indicating if the quote has been deleted' + type: boolean + example: false + uses_inclusive_taxes: + description: 'Boolean flag indicating if the taxes used are inclusive or exclusive' + type: boolean + example: true + date: + description: 'The date the quote was created' + type: string + format: date + example: '1994-07-30' + last_sent_date: + description: 'The last date the quote was sent to the client' + type: string + format: date + example: '1994-07-30' + next_send_date: + description: 'The next scheduled date for sending a reminder for the quote' + type: string + format: date + example: '1994-07-30' + partial_due_date: + description: 'The due date for the partial or deposit amount' + type: string + format: date + example: '1994-07-30' + due_date: + description: 'The due date for the total amount of the quote' + type: string + format: date + example: '1994-07-30' + settings: + $ref: '#/components/schemas/CompanySettings' + last_viewed: + description: 'The timestamp of the last time the quote was viewed' + type: number + format: integer + example: 1434342123 + updated_at: + description: 'The timestamp of the last update to the quote' + type: number + format: integer + example: 1434342123 + archived_at: + description: 'The timestamp of when the quote was archived' + type: number + format: integer + example: 1434342123 + custom_surcharge1: + description: 'First custom surcharge amount for the quote' + type: number + format: float + example: 10.00 + custom_surcharge2: + description: 'Second custom surcharge amount for the quote' + type: number + format: float + example: 10.00 + custom_surcharge3: + description: 'Third custom surcharge amount for the quote' + type: number + format: float + example: 10.00 + custom_surcharge4: + description: 'Fourth custom surcharge amount for the quote' + type: number + format: float + example: 10.00 + custom_surcharge_tax1: + description: 'Boolean flag indicating if taxes are charged on the first custom surcharge amount' + type: boolean + example: true + custom_surcharge_tax2: + description: 'Boolean flag indicating if taxes are charged on the second custom surcharge amount' + type: boolean + example: true + custom_surcharge_tax3: + description: 'Boolean flag indicating if taxes are charged on the third custom surcharge amount' + type: boolean + example: true + custom_surcharge_tax4: + description: 'Boolean flag indicating if taxes are charged on the fourth custom surcharge amount' + type: boolean + example: true + type: object + Invoice: + properties: + id: + description: 'The invoice hashed id' type: string example: Opnel5aKBz readOnly: true - client_contact_id: - description: 'The client contact hashed id' + user_id: + description: 'The user hashed id' type: string example: Opnel5aKBz - key: - description: 'The invitation key' - type: string - example: Opnel5aKBz4343343566236gvbb readOnly: true - link: - description: 'The invitation link' + assigned_user_id: + description: 'The assigned user hashed id' type: string - example: 'https://www.example.com/invitations/Opnel5aKBz4343343566236gvbb' + example: Opnel5aKBz + company_id: + description: 'The company hashed id' + type: string + example: Opnel5aKBz readOnly: true - sent_date: - description: 'The invitation sent date' + client_id: + description: 'The client hashed id' type: string - format: date-time + example: Opnel5aKBz + status_id: + description: 'The invoice status variable' + type: string + example: '4' + number: + description: 'The invoice number - is a unique alpha numeric number per invoice per company' + type: string + example: INV_101 + po_number: + description: 'The purchase order associated with this invoice' + type: string + example: PO-1234 + terms: + description: 'The invoice terms' + type: string + example: 'These are invoice terms' + public_notes: + description: 'The public notes of the invoice' + type: string + example: 'These are some public notes' + private_notes: + description: 'The private notes of the invoice' + type: string + example: 'These are some private notes' + footer: + description: 'The invoice footer notes' + type: string + example: '' + custom_value1: + description: 'A custom field value' + type: string + example: '2022-10-01' + custom_value2: + description: 'A custom field value' + type: string + example: 'Something custom' + custom_value3: + description: 'A custom field value' + type: string + example: '' + custom_value4: + description: 'A custom field value' + type: string + example: '' + tax_name1: + description: 'The tax name' + type: string + example: '' + tax_name2: + description: 'The tax name' + type: string + example: '' + tax_rate1: + description: 'The tax rate' + type: number + format: float + example: '10.00' + tax_rate2: + description: 'The tax rate' + type: number + format: float + example: '10.00' + tax_name3: + description: 'The tax name' + type: string + example: '' + tax_rate3: + description: 'The tax rate' + type: number + format: float + example: '10.00' + total_taxes: + description: 'The total taxes for the invoice' + type: number + format: float + example: '10.00' + line_items: + type: array + description: 'An array of objects which define the line items of the invoice' + items: + $ref: '#/components/schemas/InvoiceItem' + invitations: + type: array + description: 'An array of objects which define the invitations of the invoice' + items: + $ref: '#/components/schemas/InvoiceInvitation' + amount: + description: 'The invoice amount' + type: number + format: float + example: '10.00' + balance: + description: 'The invoice balance' + type: number + format: float + example: '10.00' + paid_to_date: + description: 'The amount paid on the invoice to date' + type: number + format: float + example: '10.00' + discount: + description: 'The invoice discount, can be an amount or a percentage' + type: number + format: float + example: '10.00' + partial: + description: 'The deposit/partial amount' + type: number + format: float + example: '10.00' + is_amount_discount: + description: 'Flag determining if the discount is an amount or a percentage' + type: boolean + example: true + is_deleted: + description: 'Defines if the invoice has been deleted' + type: boolean + example: true + uses_inclusive_taxes: + description: 'Defines the type of taxes used as either inclusive or exclusive' + type: boolean + example: true + date: + description: 'The Invoice Date' + type: string + format: date + example: '1994-07-30' + last_sent_date: + description: 'The last date the invoice was sent out' + type: string + format: date + example: '1994-07-30' + next_send_date: + description: 'The Next date for a reminder to be sent' + type: string + format: date + example: '1994-07-30' + partial_due_date: + description: 'The due date for the deposit/partial amount' + type: string + format: date + example: '1994-07-30' + due_date: + description: 'The due date of the invoice' + type: string + format: date + example: '1994-07-30' + last_viewed: + description: Timestamp + type: number + format: integer + example: '1434342123' + updated_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + archived_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + custom_surcharge1: + description: 'First Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge2: + description: 'Second Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge3: + description: 'Third Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge4: + description: 'Fourth Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge_tax1: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax2: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax3: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax4: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + project_id: + description: 'The project associated with this invoice' + type: string + example: Opnel5aKBz + auto_bill_tries: + description: 'The number of times the invoice has attempted to be auto billed' + type: integer + example: '1' readOnly: true - viewed_date: - description: 'The invitation viewed date' + auto_bill_enabled: + description: 'Boolean flag determining if the invoice is set to auto bill' + type: boolean + example: true + subscription_id: + description: 'The subscription associated with this invoice' type: string - format: date-time + example: Opnel5aKBz + + type: object + ClientRequest: + required: + - contacts + - country_id + properties: + id: + description: 'The unique identifier of the client' + type: string + example: Opnel5aKBz readOnly: true - opened_date: - description: 'The invitation opened date' + contacts: + type: array + description: 'A list of contacts associated with the client' + items: + $ref: '#/components/schemas/ClientContactRequest' + name: + description: 'The name of the client company or organization' type: string - format: date-time + example: "Jim's Housekeeping" + website: + description: 'The website URL of the client company or organization' + type: string + example: 'https://www.jims-housekeeping.com' + private_notes: + description: 'Notes that are only visible to the user who created the client' + type: string + example: 'Client prefers email communication over phone calls' + industry_id: + description: 'The unique identifier of the industry the client operates in' + type: number + example: '5' + size_id: + description: 'The unique identifier for the size category of the client company or organization' + type: number + example: '2' + address1: + description: "First line of the client's address" + type: string + example: '123 Main St' + address2: + description: "Second line of the client's address, if needed" + type: string + example: 'Apt 4B' + city: + description: 'The city the client is located in' + type: string + example: 'Beverly Hills' + state: + description: 'The state, province, or locality the client is located in' + type: string + example: 'California' + postal_code: + description: 'The postal code or ZIP code of the client' + type: string + example: '90210' + phone: + description: "The client's phone number" + type: string + example: '555-3434-3434' + country_id: + description: "The unique identifier of the client's country" + type: number + format: integer + example: '1' + custom_value1: + description: 'A custom field for storing additional information' + type: string + example: 'Preferred contact: Email' + custom_value2: + description: 'A custom field for storing additional information' + type: string + example: 'Account manager: John Doe' + custom_value3: + description: 'A custom field for storing additional information' + type: string + example: 'VIP client: Yes' + custom_value4: + description: 'A custom field for storing additional information' + type: string + example: 'Annual contract value: $50,000' + vat_number: + description: "The client's VAT (Value Added Tax) number, if applicable" + type: string + example: 'VAT123456' + id_number: + description: 'A unique identification number for the client, such as a tax ID or business registration number' + type: string + number: + description: 'A system-assigned unique number for the client, typically used for invoicing purposes' + type: string + example: 'CL-0001' + shipping_address1: + description: "First line of the client's shipping address" + type: string + example: '5 Wallaby Way' + shipping_address2: + description: "Second line of the client's shipping address, if needed" + type: string + example: 'Suite 5' + shipping_city: + description: "The city of the client's shipping address" + type: string + example: 'Perth' + shipping_state: + description: "The state, province, or locality of the client's shipping address" + type: string + example: 'Western Australia' + shipping_postal_code: + description: "The postal code or ZIP code of the client's shipping address" + type: string + example: '6110' + shipping_country_id: + description: "The unique identifier of the country for the client's shipping address" + type: number + format: integer + example: '4' + is_deleted: + description: 'A boolean value indicating whether the client has been deleted or not' + type: boolean + example: false + readOnly: true + group_settings_id: + description: 'The group settings assigned to the client' + type: string + example: Opnel5aKBz + routing_id: + description: 'The routing address id for e-invoicing for this client' + type: string + example: Opnel5aKBz3489-dfkiu-2239-sdsd + is_tax_exempt: + description: 'Flag which defines if the client is exempt from taxes' + type: boolean + example: false + has_valid_vat_number: + description: 'Flag which defines if the client has a valid VAT number' + type: boolean + example: false + readOnly: true + classification: + description: 'The classification of the client' + type: string + example: 'individual' + settings: + $ref: '#/components/schemas/ClientSettings' + type: object + Vendor: + properties: + id: + description: 'The hashed id of the vendor. This is a unique identifier for the vendor.' + type: string + example: Opnel5aKBz + readOnly: true + user_id: + description: 'The hashed id of the user who created the vendor. This is a unique identifier for the user.' + type: string + example: Opnel5aKBz + assigned_user_id: + description: 'The hashed id of the assigned user to this vendor. This is a unique identifier for the user.' + type: string + example: Opnel5aKBz + company_id: + description: 'The hashed id of the company. This is a unique identifier for the company.' + type: string + example: Opnel5aKBz + client_id: + description: 'The hashed id of the client. This is a unique identifier for the client.' + type: string + example: Opnel5aKBz + contacts: + type: array + items: + $ref: '#/components/schemas/VendorContact' + description: 'An array of contacts associated with the vendor.' + name: + description: 'The name of the vendor.' + type: string + example: 'Harry cafe de wheels' + classification: + description: 'The classification of the vendor.' + type: string + example: 'individual' + website: + description: 'The website of the vendor.' + type: string + example: www.harry.com + private_notes: + description: 'The private notes of the vendor. These notes are only visible to users with appropriate permissions.' + type: string + example: 'Shhh, do not tell the vendor' + industry_id: + description: 'The industry id of the vendor. This is a unique identifier for the industry.' + type: string + example: '1' + size_id: + description: 'The size id of the vendor. This is a unique identifier for the size of the vendor.' + type: string + example: '' + address1: + description: 'The first line of the vendor''s address.' + type: string + example: '' + address2: + description: 'The second line of the vendor''s address.' + type: string + example: '' + city: + description: 'The city of the vendor''s address.' + type: string + example: '' + state: + description: 'The state of the vendor''s address.' + type: string + example: '' + postal_code: + description: 'The postal code of the vendor''s address.' + type: string + example: '' + phone: + description: 'The phone number of the vendor.' + type: string + example: 555-3434-3434 + country_id: + description: 'The country id of the vendor. This is a unique identifier for the country.' + type: string + example: '' + currency_id: + description: 'The currency id of the vendor. This is a unique identifier for the currency.' + type: string + example: '4' + custom_value1: + description: 'The value of the first custom field for the vendor.' + type: string + example: '' + custom_value2: + description: 'The value of the second custom field for the vendor.' + type: string + example: '' + custom_value3: + description: 'The value of the third custom field for the vendor.' + type: string + example: '' + custom_value4: + description: 'The value of the fourth custom field for the vendor.' + type: string + example: '' + vat_number: + description: 'The VAT number of the vendor.' + type: string + example: '' + id_number: + description: 'The ID number of the vendor.' + type: string + example: '' + number: + description: 'The number of the vendor' + type: string + example: '11234' + is_deleted: + description: 'Boolean flag determining if the vendor has been deleted' + type: boolean + example: true + language_id: + description: 'The language id of the vendor. This is a unique identifier for the language.' + type: string + example: '1' + vendor_hash: + description: 'The vendor hash of the vendor. This is a unique identifier for the vendor.' + type: string + example: 'aaa-sss-www' + readOnly: true + transaction_name: + description: 'The transaction name of the vendor.' + type: string + example: 'aaa-sss-www' + last_login: + description: Timestamp + type: number + format: integer + example: '134341234234' + readOnly: true + created_at: + description: Timestamp + type: number + format: integer + example: '134341234234' readOnly: true updated_at: - description: 'Timestamp' + description: Timestamp + type: number + format: integer + example: '134341234234' + readOnly: true + display_name: + description: 'The display name of the vendor.' + type: string + example: 'Bob the vendor' + readOnly: true + settings: + $ref: '#/components/schemas/CompanySettings' + type: object + ClientSettings: + required: + - currency_id + properties: + currency_id: + description: 'The default currency id' + type: string + example: true + timezone_id: + description: 'The timezone id' + type: string + example: '15' + date_format_id: + description: 'The date format id' + type: string + example: '15' + military_time: + description: 'Toggles 12/24 hour time' + type: boolean + example: true + language_id: + description: 'The language id' + type: string + example: '1' + show_currency_code: + description: 'Toggles whether the currency symbol or code is shown' + type: boolean + example: true + payment_terms: + description: '-1 sets no payment term, 0 sets payment due immediately, positive integers indicates payment terms in days' + type: integer + example: '1' + company_gateway_ids: + description: 'A commad separate list of available gateways' + type: string + example: '1,2,3,4' + custom_value1: + description: 'A Custom Label' + type: string + example: 'Custom Label' + custom_value2: + description: 'A Custom Label' + type: string + example: 'Custom Label' + custom_value3: + description: 'A Custom Label' + type: string + example: 'Custom Label' + custom_value4: + description: 'A Custom Label' + type: string + example: 'Custom Label' + default_task_rate: + description: 'The default task rate' + type: number + format: float + example: '10.00' + send_reminders: + description: 'Toggles whether reminders are sent' + type: boolean + example: true + enable_client_portal_tasks: + description: 'Show/hide the tasks panel in the client portal' + type: boolean + example: true + email_style: + description: 'options include plain,light,dark,custom' + type: string + example: light + reply_to_email: + description: 'The reply to email address' + type: string + example: email@gmail.com + bcc_email: + description: 'A comma separate list of BCC emails' + type: string + example: 'email@gmail.com, contact@gmail.com' + pdf_email_attachment: + description: 'Toggles whether to attach PDF as attachment' + type: boolean + example: true + ubl_email_attachment: + description: 'Toggles whether to attach UBL as attachment' + type: boolean + example: true + email_style_custom: + description: 'The custom template' + type: string + example: '' + counter_number_applied: + description: 'enum when the invoice number counter is set, ie when_saved, when_sent, when_paid' + type: string + example: when_sent + quote_number_applied: + description: 'enum when the quote number counter is set, ie when_saved, when_sent' + type: string + example: when_sent + custom_message_dashboard: + description: 'A custom message which is displayed on the dashboard' + type: string + example: 'Please pay invoices immediately' + custom_message_unpaid_invoice: + description: 'A custom message which is displayed in the client portal when a client is viewing a unpaid invoice.' + type: string + example: 'Please pay invoices immediately' + custom_message_paid_invoice: + description: 'A custom message which is displayed in the client portal when a client is viewing a paid invoice.' + type: string + example: 'Thanks for paying this invoice!' + custom_message_unapproved_quote: + description: 'A custom message which is displayed in the client portal when a client is viewing a unapproved quote.' + type: string + example: 'Please approve quote' + lock_invoices: + description: 'Toggles whether invoices are locked once sent and cannot be modified further' + type: boolean + example: true + auto_archive_invoice: + description: 'Toggles whether a invoice is archived immediately following payment' + type: boolean + example: true + auto_archive_quote: + description: 'Toggles whether a quote is archived after being converted to a invoice' + type: boolean + example: true + auto_convert_quote: + description: 'Toggles whether a quote is converted to a invoice when approved' + type: boolean + example: true + inclusive_taxes: + description: 'Boolean flag determining whether inclusive or exclusive taxes are used' + type: boolean + example: true + task_number_pattern: + description: 'Allows customisation of the task number pattern' + type: string + example: '{$year}-{$counter}' + task_number_counter: + description: 'The incrementing counter for tasks' + type: integer + example: '1' + reminder_send_time: + description: 'Time from UTC +0 when the email will be sent to the client' + type: integer + example: '32400' + expense_number_pattern: + description: 'Allows customisation of the expense number pattern' + type: string + example: '{$year}-{$counter}' + expense_number_counter: + description: 'The incrementing counter for expenses' + type: integer + example: '1' + vendor_number_pattern: + description: 'Allows customisation of the vendor number pattern' + type: string + example: '{$year}-{$counter}' + vendor_number_counter: + description: 'The incrementing counter for vendors' + type: integer + example: '1' + ticket_number_pattern: + description: 'Allows customisation of the ticket number pattern' + type: string + example: '{$year}-{$counter}' + ticket_number_counter: + description: 'The incrementing counter for tickets' + type: integer + example: '1' + payment_number_pattern: + description: 'Allows customisation of the payment number pattern' + type: string + example: '{$year}-{$counter}' + payment_number_counter: + description: 'The incrementing counter for payments' + type: integer + example: '1' + invoice_number_pattern: + description: 'Allows customisation of the invoice number pattern' + type: string + example: '{$year}-{$counter}' + invoice_number_counter: + description: 'The incrementing counter for invoices' + type: integer + example: '1' + quote_number_pattern: + description: 'Allows customisation of the quote number pattern' + type: string + example: '{$year}-{$counter}' + quote_number_counter: + description: 'The incrementing counter for quotes' + type: integer + example: '1' + client_number_pattern: + description: 'Allows customisation of the client number pattern' + type: string + example: '{$year}-{$counter}' + client_number_counter: + description: 'The incrementing counter for clients' + type: integer + example: '1' + credit_number_pattern: + description: 'Allows customisation of the credit number pattern' + type: string + example: '{$year}-{$counter}' + credit_number_counter: + description: 'The incrementing counter for credits' + type: integer + example: '1' + recurring_invoice_number_prefix: + description: 'This string is prepended to the recurring invoice number' + type: string + example: R + reset_counter_frequency_id: + description: 'CONSTANT which is used to apply the frequency which the counters are reset' + type: integer + example: '1' + reset_counter_date: + description: 'The explicit date which is used to reset counters' + type: string + example: '2019-01-01' + counter_padding: + description: 'Pads the counter with leading zeros' + type: integer + example: '1' + shared_invoice_quote_counter: + description: 'Flags whether to share the counter for invoices and quotes' + type: boolean + example: true + update_products: + description: 'Determines if client fields are updated from third party APIs' + type: boolean + example: true + convert_products: + description: '' + type: boolean + example: true + fill_products: + description: 'Automatically fill products based on product_key' + type: boolean + example: true + invoice_terms: + description: 'The default invoice terms' + type: string + example: 'Invoice Terms are...' + quote_terms: + description: 'The default quote terms' + type: string + example: 'Quote Terms are...' + invoice_taxes: + description: 'Taxes can be applied to the invoice' + type: number + example: '1' + invoice_design_id: + description: 'The default design id (invoice, quote etc)' + type: string + example: '1' + quote_design_id: + description: 'The default design id (invoice, quote etc)' + type: string + example: '1' + invoice_footer: + description: 'The default invoice footer' + type: string + example: '1' + invoice_labels: + description: 'JSON string of invoice labels' + type: string + example: '1' + tax_rate1: + description: 'The tax rate (float)' + type: number + example: '10' + tax_name1: + description: 'The tax name' + type: string + example: GST + tax_rate2: + description: 'The tax rate (float)' + type: number + example: '10' + tax_name2: + description: 'The tax name' + type: string + example: GST + tax_rate3: + description: 'The tax rate (float)' + type: number + example: '10' + tax_name3: + description: 'The tax name' + type: string + example: GST + payment_type_id: + description: 'The default payment type id' + type: string + example: '1' + custom_fields: + description: 'JSON string of custom fields' + type: string + example: '{}' + email_footer: + description: 'The default email footer' + type: string + example: 'A default email footer' + email_sending_method: + description: 'The email driver to use to send email, options include default, gmail' + type: string + example: default + gmail_sending_user_id: + description: 'The hashed_id of the user account to send email from' + type: string + example: F76sd34D + email_subject_invoice: + description: '' + type: string + example: 'Your Invoice Subject' + email_subject_quote: + description: '' + type: string + example: 'Your Quote Subject' + email_subject_payment: + description: '' + type: string + example: 'Your Payment Subject' + email_template_invoice: + description: 'The full template for invoice emails' + type: string + example: '' + email_template_quote: + description: 'The full template for quote emails' + type: string + example: '' + email_template_payment: + description: 'The full template for payment emails' + type: string + example: '' + email_subject_reminder1: + description: 'Email subject for Reminder' + type: string + example: '' + email_subject_reminder2: + description: 'Email subject for Reminder' + type: string + example: '' + email_subject_reminder3: + description: 'Email subject for Reminder' + type: string + example: '' + email_subject_reminder_endless: + description: 'Email subject for endless reminders' + type: string + example: '' + email_template_reminder1: + description: 'The full template for Reminder 1' + type: string + example: '' + email_template_reminder2: + description: 'The full template for Reminder 2' + type: string + example: '' + email_template_reminder3: + description: 'The full template for Reminder 3' + type: string + example: '' + email_template_reminder_endless: + description: 'The full template for enless reminders' + type: string + example: '' + enable_portal_password: + description: 'Toggles whether a password is required to log into the client portal' + type: boolean + example: true + show_accept_invoice_terms: + description: 'Toggles whether the terms dialogue is shown to the client' + type: boolean + example: true + show_accept_quote_terms: + description: 'Toggles whether the terms dialogue is shown to the client' + type: boolean + example: true + require_invoice_signature: + description: 'Toggles whether a invoice signature is required' + type: boolean + example: true + require_quote_signature: + description: 'Toggles whether a quote signature is required' + type: boolean + example: true + name: + description: 'The company name' + type: string + example: 'Acme Co' + company_logo: + description: 'The company logo file' + type: object + example: logo.png + website: + description: 'The company website URL' + type: string + example: www.acme.com + address1: + description: 'The company address line 1' + type: string + example: 'Suite 888' + address2: + description: 'The company address line 2' + type: string + example: '5 Jimbo Way' + city: + description: 'The company city' + type: string + example: Sydney + state: + description: 'The company state' + type: string + example: Florisa + postal_code: + description: 'The company zip/postal code' + type: string + example: '90210' + phone: + description: 'The company phone' + type: string + example: 555-213-3948 + email: + description: 'The company email' + type: string + example: joe@acme.co + country_id: + description: 'The country ID' + type: string + example: '1' + vat_number: + description: 'The company VAT/TAX ID number' + type: string + example: '32 120 377 720' + page_size: + description: 'The default page size' + type: string + example: A4 + font_size: + description: 'The font size' + type: number + example: '9' + primary_font: + description: 'The primary font' + type: string + example: roboto + secondary_font: + description: 'The secondary font' + type: string + example: roboto + hide_paid_to_date: + description: 'Flags whether to hide the paid to date field' + type: boolean + example: false + embed_documents: + description: 'Toggled whether to embed documents in the PDF' + type: boolean + example: false + all_pages_header: + description: 'The header for the PDF' + type: boolean + example: false + all_pages_footer: + description: 'The footer for the PDF' + type: boolean + example: false + document_email_attachment: + description: 'Toggles whether to attach documents in the email' + type: boolean + example: false + enable_client_portal_password: + description: 'Toggles password protection of the client portal' + type: boolean + example: false + enable_email_markup: + description: 'Toggles the use of markdown in emails' + type: boolean + example: false + enable_client_portal_dashboard: + description: 'Toggles whether the client dashboard is shown in the client portal' + type: boolean + example: false + enable_client_portal: + description: 'Toggles whether the entire client portal is displayed to the client, or only the context' + type: boolean + example: false + email_template_statement: + description: 'The body of the email for statements' + type: string + example: 'template matter' + email_subject_statement: + description: 'The subject of the email for statements' + type: string + example: 'subject matter' + signature_on_pdf: + description: 'Toggles whether the signature (if available) is displayed on the PDF' + type: boolean + example: false + quote_footer: + description: 'The default quote footer' + type: string + example: 'the quote footer' + email_subject_custom1: + description: 'Custom reminder template subject' + type: string + example: 'Custom Subject 1' + email_subject_custom2: + description: 'Custom reminder template subject' + type: string + example: 'Custom Subject 2' + email_subject_custom3: + description: 'Custom reminder template subject' + type: string + example: 'Custom Subject 3' + email_template_custom1: + description: 'Custom reminder template body' + type: string + example: '' + email_template_custom2: + description: 'Custom reminder template body' + type: string + example: '' + email_template_custom3: + description: 'Custom reminder template body' + type: string + example: '' + enable_reminder1: + description: 'Toggles whether this reminder is enabled' + type: boolean + example: false + enable_reminder2: + description: 'Toggles whether this reminder is enabled' + type: boolean + example: false + enable_reminder3: + description: 'Toggles whether this reminder is enabled' + type: boolean + example: false + num_days_reminder1: + description: 'The Reminder interval' + type: number + example: '9' + num_days_reminder2: + description: 'The Reminder interval' + type: number + example: '9' + num_days_reminder3: + description: 'The Reminder interval' + type: number + example: '9' + schedule_reminder1: + description: '(enum: after_invoice_date, before_due_date, after_due_date)' + type: string + example: after_invoice_date + schedule_reminder2: + description: '(enum: after_invoice_date, before_due_date, after_due_date)' + type: string + example: after_invoice_date + schedule_reminder3: + description: '(enum: after_invoice_date, before_due_date, after_due_date)' + type: string + example: after_invoice_date + late_fee_amount1: + description: 'The late fee amount for reminder 1' + type: number + example: 10 + late_fee_amount2: + description: 'The late fee amount for reminder 2' + type: number + example: 20 + late_fee_amount3: + description: 'The late fee amount for reminder 2' + type: number + example: 100 + endless_reminder_frequency_id: + description: 'The frequency id of the endless reminder' + type: string + example: '1' + client_online_payment_notification: + description: 'Determines if a client should receive the notification for a online payment' + type: boolean + example: false + client_manual_payment_notification: + description: 'Determines if a client should receive the notification for a manually entered payment' + type: boolean + example: false + enable_e_invoice: + description: 'Determines if e-invoicing is enabled' + type: boolean + example: false + default_expense_payment_type_id: + description: 'The default payment type for expenses' + type: string + example: '0' + e_invoice_type: + description: 'The e-invoice type' + type: string + example: 'EN16931' + mailgun_endpoint: + description: 'The mailgun endpoint - used to determine whether US or EU endpoints are used' + type: string + example: 'api.mailgun.net or api.eu.mailgun.net' + client_initiated_payments: + description: 'Determines if clients can initiate payments directly from the client portal' + type: boolean + example: false + client_initiated_payments_minimum: + description: 'The minimum amount a client can pay' + type: number + example: 10 + sync_invoice_quote_columns: + description: 'Determines if invoice and quote columns are synced for the PDF rendering, or if they use their own columns' + type: boolean + example: false + show_task_item_description: + description: 'Determines if the task item description is shown on the invoice' + type: boolean + example: false + allow_billable_task_items: + description: 'Determines if task items can be marked as billable' + type: boolean + example: false + accept_client_input_quote_approval: + description: 'Determines if clients can approve quotes and also pass through a PO Number reference' + type: boolean + example: false + custom_sending_email: + description: 'When using Mailgun or Postmark, the FROM email address can be customized using this setting.' + type: string + example: 'bob@gmail.com' + show_paid_stamp: + description: 'Determines if the PAID stamp is shown on the invoice' + type: boolean + example: false + show_shipping_address: + description: 'Determines if the shipping address is shown on the invoice' + type: boolean + example: false + company_logo_size: + description: 'The size of the company logo on the PDF - percentage value between 0 and 100' + type: number + example: 100 + show_email_footer: + description: 'Determines if the email footer is shown on emails' + type: boolean + example: false + email_alignment: + description: 'The alignment of the email body text, options include left / center / right' + type: string + example: 'left' + auto_bill_standard_invoices: + description: 'Determines if standard invoices are automatically billed when they are created or due' + type: boolean + example: false + postmark_secret: + description: 'The Postmark secret API key' + type: string + example: '123456' + mailgun_secret: + description: 'The Mailgun secret API key' + type: string + example: '123456' + mailgun_domain: + description: 'The Mailgun domain' + type: string + example: 'sandbox123456.mailgun.org' + send_email_on_mark_paid: + description: 'Determines if an email is sent when an invoice is marked as paid' + type: boolean + example: false + vendor_portal_enable_uploads: + description: 'Determines if vendors can upload files to the portal' + type: boolean + example: false + besr_id: + description: 'The BESR ID' + type: string + example: '123456' + qr_iban: + description: 'The IBAN for the QR code' + type: string + example: 'CH123456' + email_subject_purchase_order: + description: 'The email subject for purchase orders' + type: string + example: 'Purchase Order' + email_template_purchase_order: + description: 'The email template for purchase orders' + type: string + example: 'Please see attached your purchase order.' + require_purchase_order_signature: + description: 'Determines if a signature is required on purchase orders' + type: boolean + example: false + purchase_order_public_notes: + description: 'The public notes for purchase orders' + type: string + example: 'Please see attached your purchase order.' + purchase_order_terms: + description: 'The terms for purchase orders' + type: string + example: 'Please see attached your purchase order.' + purchase_order_footer: + description: 'The footer for purchase orders' + type: string + example: 'Please see attached your purchase order.' + purchase_order_design_id: + description: 'The design id for purchase orders' + type: string + example: 'hd677df' + purchase_order_number_pattern: + description: 'The pattern for purchase order numbers' + type: string + example: 'PO-000000' + purchase_order_number_counter: + description: 'The counter for purchase order numbers' + type: number + example: 1 + page_numbering_alignment: + description: 'The alignment for page numbering: options include left / center / right' + type: string + example: 'left' + page_numbering: + description: 'Determines if page numbering is enabled on Document PDFs' + type: boolean + example: false + auto_archive_invoice_cancelled: + description: 'Determines if invoices are automatically archived when they are cancelled' + type: boolean + example: false + email_from_name: + description: 'The FROM name for emails when using Custom emailers' + type: string + example: 'Bob Smith' + show_all_tasks_client_portal: + description: 'Determines if all tasks are shown on the client portal' + type: boolean + example: false + entity_send_time: + description: 'The time that emails are sent. The time is localized to the clients locale, integer values from 1 - 24' + type: integer + example: 9 + shared_invoice_credit_counter: + description: 'Determines if the invoice and credit counter are shared' + type: boolean + example: false + reply_to_name: + description: 'The reply to name for emails' + type: string + example: 'Bob Smith' + hide_empty_columns_on_pdf: + description: 'Determines if empty columns are hidden on PDFs' + type: boolean + example: false + enable_reminder_endless: + description: 'Determines if endless reminders are enabled' + type: boolean + example: false + use_credits_payment: + description: 'Determines if credits can be used as a payment method' + type: boolean + example: false + recurring_invoice_number_pattern: + description: 'The pattern for recurring invoice numbers' + type: string + example: 'R-000000' + recurring_invoice_number_counter: + description: 'The counter for recurring invoice numbers' + type: number + example: 1 + client_portal_under_payment_minimum: + description: 'The minimum payment payment' + type: number + example: 10 + auto_bill_date: + description: 'Determines when the invoices are auto billed, options are on_send_date (when the invoice is sent) or on_due_date (when the invoice is due))' + type: string + example: 'on_send_date' + primary_color: + description: 'The primary color for the client portal / document highlights' + type: string + example: '#ffffff' + secondary_color: + description: 'The secondary color for the client portal / document highlights' + type: string + example: '#ffffff' + client_portal_allow_under_payment: + description: 'Determines if clients can pay invoices under the invoice amount due' + type: boolean + example: false + client_portal_allow_over_payment: + description: 'Determines if clients can pay invoices over the invoice amount' + type: boolean + example: false + auto_bill: + description: 'Determines how autobilling is applied for recurring invoices. off (no auto billed), always (always auto bill), optin (The user must opt in to auto billing), optout (The user must opt out of auto billing' + type: string + example: 'off' + client_portal_terms: + description: 'The terms which are displayed on the client portal' + type: string + example: 'Please see attached your invoice.' + client_portal_privacy_policy: + description: 'The privacy policy which is displayed on the client portal' + type: string + example: 'These are the terms of use for using the client portal.' + client_can_register: + description: 'Determines if clients can register on the client portal' + type: boolean + example: false + portal_design_id: + description: 'The design id for the client portal' + type: string + example: 'hd677df' + late_fee_endless_percent: + description: 'The late fee percentage for endless late fees' + type: number + example: 10 + late_fee_endless_amount: + description: 'The late fee amount for endless late fees' + type: number + example: 10 + auto_email_invoice: + description: 'Determines if invoices are automatically emailed when they are created' + type: boolean + example: false + email_signature: + description: 'The email signature for emails' + type: string + example: 'Bob Smith' + type: object + RecurringExpense: + properties: + id: + description: 'The hashed id of the recurring expense' + type: string + example: Opnel5aKBz + user_id: + description: 'The hashed id of the user who created the recurring expense' + type: string + example: Opnel5aKBz + assigned_user_id: + description: 'The hashed id of the user assigned to this recurring expense' + type: string + example: Opnel5aKBz + company_id: + description: 'The hashed id of the company' + type: string + example: Opnel5aKBz + client_id: + description: 'The hashed id of the client' + type: string + example: Opnel5aKBz + invoice_id: + description: 'The hashed id of the invoice' + type: string + example: Opnel5aKBz + bank_id: + description: 'The id of the bank associated with this recurring expense' + type: string + example: '22' + invoice_currency_id: + description: 'The currency id of the invoice associated with this recurring expense' + type: string + example: '1' + expense_currency_id: + description: 'The currency id of the expense associated with this recurring expense' + type: string + example: '1' + invoice_category_id: + description: 'The category id of the invoice' + type: string + example: '1' + payment_type_id: + description: 'The payment type id' + type: string + example: '1' + private_notes: + description: 'The recurring expense private notes' + type: string + example: 'Private and confidential' + public_notes: + description: 'The recurring expense public notes' + type: string + example: 'This is the best client in the world' + transaction_reference: + description: 'The recurring expense transaction reference' + type: string + example: EXP-1223-2333 + transcation_id: + description: 'The transaction id of the recurring expense' + type: string + example: '1233312312' + custom_value1: + description: 'Custom value field' + type: string + example: $1000 + custom_value2: + description: 'Custom value field' + type: string + example: '2022-10-10' + custom_value3: + description: 'Custom value field' + type: string + example: 'short text' + custom_value4: + description: 'Custom value field' + type: string + example: 'very long text' + tax_name1: + description: 'The tax name' + type: string + example: GST + tax_name2: + description: 'The tax name' + type: string + example: VAT + tax_rate1: + description: 'The tax rate' + type: number + format: float + example: '10.00' + tax_rate2: + description: 'The tax rate' + type: number + format: float + example: '10.00' + tax_name3: + description: 'The tax name' + type: string + example: '' + tax_rate3: + description: 'The tax rate' + type: number + format: float + example: '10.00' + amount: + description: 'The total amount of the recurring expense' + type: number + format: float + example: '10.00' + frequency_id: + description: 'The frequency this recurring expense fires' + type: number + format: int + example: '1' + remaining_cycles: + description: 'The number of remaining cycles for this recurring expense' + type: number + format: int + example: '1' + foreign_amount: + description: 'The foreign currency amount of the recurring expense' + type: number + format: float + example: '10.00' + exchange_rate: + description: 'The exchange rate for the expernse' + type: number + format: float + example: '0.80' + date: + description: 'The date of the expense' + type: string + example: '' + payment_date: + description: 'The date the expense was paid' + type: string + example: '' + should_be_invoiced: + description: 'Boolean flag determining if the expense should be invoiced' + type: boolean + example: true + is_deleted: + description: 'Boolean flag determining if the recurring expense is deleted' + type: boolean + example: true + last_sent_date: + description: 'The Date it was sent last' + type: string + format: date + example: '1994-07-30' + next_send_date: + description: 'The next send date' + type: string + format: date + example: '1994-07-30' + invoice_documents: + description: 'Boolean flag determining if the documents associated with this expense should be passed onto the invoice if it is converted to an invoice' + type: boolean + example: true + updated_at: + description: Timestamp type: number format: integer example: '1434342123' - readOnly: true archived_at: - description: 'Timestamp' + description: Timestamp type: number format: integer example: '1434342123' - readOnly: true - email_error: - description: 'The email error' + type: object + Activity: + properties: + id: + description: 'The id field of the activity' type: string - example: 'The email error' - readOnly: true - email_status: - description: 'The email status' + example: Opnel5aKBz + activity_type_id: + description: 'The activity type id' type: string - readOnly: true - + example: Opnel5aKBz + client_id: + description: 'The client hashed id' + type: string + example: Opnel5aKBz + company_id: + description: 'The company hashed id' + type: string + example: Opnel5aKBz + user_id: + description: 'The user hashed id' + type: string + example: Opnel5aKBz + invoice_id: + description: 'The invoice hashed id' + type: string + example: Opnel5aKBz + payment_id: + description: 'The payment hashed id' + type: string + example: Opnel5aKBz + credit_id: + description: 'The credit hashed id' + type: string + example: Opnel5aKBz + updated_at: + description: 'Unixtimestamp the last time the record was updated' + type: integer + example: '343421434' + expense_id: + description: 'The expense hashed id' + type: string + example: Opnel5aKBz + is_system: + description: 'Defines is the activity was performed by the system' + type: boolean + example: true + contact_id: + description: 'The contact hashed id' + type: string + example: Opnel5aKBz + task_id: + description: 'The task hashed id' + type: string + example: Opnel5aKBz + notes: + description: 'Activity Notes' + type: string + example: Opnel5aKBz + token_id: + description: 'The hashed ID of the token who performed the action' + type: string + example: Opnel5aKBz + ip: + description: 'The IP Address of the user who performed the action' + type: string + example: 192.168.1.252 + user: + $ref: '#/components/schemas/User' + client: + $ref: '#/components/schemas/Client' + contact: + $ref: '#/components/schemas/ClientContact' + recurring_invoice: + $ref: '#/components/schemas/RecurringInvoice' + invoice: + $ref: '#/components/schemas/Invoice' + credit: + $ref: '#/components/schemas/Credit' + quote: + $ref: '#/components/schemas/Quote' + payment: + $ref: '#/components/schemas/Payment' + expense: + $ref: '#/components/schemas/Expense' + task: + $ref: '#/components/schemas/Task' + purchase_order: + $ref: '#/components/schemas/PurchaseOrder' + vendor: + $ref: '#/components/schemas/Vendor' + vendor_contact: + $ref: '#/components/schemas/VendorContact' + type: object + Expense: + properties: + id: + description: 'The expense hashed id' + type: string + example: Opnel5aKBz + user_id: + description: 'The user hashed id' + type: string + example: '' + assigned_user_id: + description: 'The assigned user hashed id' + type: string + example: '' + company_id: + description: 'The company hashed id' + type: string + example: '' + client_id: + description: 'The client hashed id' + type: string + example: '' + invoice_id: + description: 'The related invoice hashed id' + type: string + example: '' + bank_id: + description: 'The bank id related to this expense' + type: string + example: '' + invoice_currency_id: + description: 'The currency id of the related invoice' + type: string + example: '' + expense_currency_id: + description: 'The currency id of the expense' + type: string + example: '' + invoice_category_id: + description: 'The invoice category id' + type: string + example: '' + payment_type_id: + description: 'The payment type id' + type: string + example: '' + recurring_expense_id: + description: 'The related recurring expense this expense was created from' + type: string + example: '' + private_notes: + description: 'The private notes of the expense' + type: string + example: '' + public_notes: + description: 'The public notes of the expense' + type: string + example: '' + transaction_reference: + description: 'The transaction references of the expense' + type: string + example: '' + transcation_id: + description: 'The transaction id of the expense' + type: string + example: '' + custom_value1: + description: 'A custom value' + type: string + example: '' + custom_value2: + description: 'A custom value' + type: string + example: '' + custom_value3: + description: 'A custom value' + type: string + example: '' + custom_value4: + description: 'A custom value' + type: string + example: '' + tax_name1: + description: 'Tax name' + type: string + example: '' + tax_name2: + description: 'Tax name' + type: string + example: '' + tax_rate1: + description: 'Tax rate' + type: number + format: float + example: '10.00' + tax_rate2: + description: 'Tax rate' + type: number + format: float + example: '10.00' + tax_name3: + description: 'Tax name' + type: string + example: '' + tax_rate3: + description: 'Tax rate' + type: number + format: float + example: '10.00' + amount: + description: 'The total expense amont' + type: number + format: float + example: '10.00' + foreign_amount: + description: 'The total foreign amount of the expense' + type: number + format: float + example: '10.00' + exchange_rate: + description: 'The exchange rate at the time of the expense' + type: number + format: float + example: '0.80' + date: + description: 'The expense date formate Y-m-d' + type: string + example: '2022-12-01' + payment_date: + description: 'The date of payment for the expense, format Y-m-d' + type: string + example: '' + should_be_invoiced: + description: 'Flag whether the expense should be invoiced' + type: boolean + example: true + is_deleted: + description: 'Boolean determining whether the expense has been deleted' + type: boolean + example: true + invoice_documents: + description: 'Passing the expense documents over to the invoice' + type: boolean + example: true + updated_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + archived_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + type: object + Error: + properties: + message: + description: 'Something terrible went wrong' + type: string + example: 'Unexpected error' + code: + description: 'The HTTP error code, ie 5xx 4xx' + type: integer + example: '500' + type: object InvoiceInvitation: properties: id: @@ -21176,47 +20850,378 @@ components: type: string readOnly: true - Design: + + ProductBulkAction: + required: + - action + - ids + properties: + action: + type: string + example: archive + description: 'The action to perform ie. archive / restore / delete / set_tax_id' + ids: + type: array + items: + format: string + type: string + example: 2J234DFA,D2J234DFA,D2J234DFA + description: string array of client hashed ids + tax_id: + type: string + example: '1' + description: | + The tax rate id to set on the list of products + + The following constants are available (default = '1') + + ``` + PRODUCT_TYPE_PHYSICAL = '1' + PRODUCT_TYPE_SERVICE = '2' + PRODUCT_TYPE_DIGITAL = '3' + PRODUCT_TYPE_SHIPPING = '4' + PRODUCT_TYPE_EXEMPT = '5' + PRODUCT_TYPE_REDUCED_TAX = '6' + PRODUCT_TYPE_OVERRIDE_TAX = '7' + PRODUCT_TYPE_ZERO_RATED = '8' + PRODUCT_TYPE_REVERSE_TAX = '9' + ``` + type: object + User: properties: id: - description: 'The design hashed id' + description: 'The hashed id of the user' type: string - example: AS3df3A - name: - description: 'The design name' + example: Opnel5aKBz + readOnly: true + first_name: + description: 'The first name of the user' type: string - example: Beauty - design: - description: 'The design HTML' + example: Brad + last_name: + description: 'The last name of the user' type: string - example: '' - is_custom: - description: 'Flag to determine if the design is a custom user design' + example: Pitt + email: + description: 'The users email address' + type: string + example: brad@pitt.com + phone: + description: 'The users phone number' + type: string + example: 555-1233-23232 + signature: + description: 'The users sign off signature' + type: string + example: 'Have a nice day!' + avatar: + description: 'The users avatar' + type: string + example: 'https://url.to.your/avatar.png' + accepted_terms_version: + description: 'The version of the invoice ninja terms that has been accepted by the user' + type: string + example: 1.0.1 + readOnly: true + oauth_user_id: + description: 'The provider id of the oauth entity' + type: string + example: jkhasdf789as6f675sdf768sdfs + readOnly: true + oauth_provider_id: + description: 'The oauth entity id' + type: string + example: google + readOnly: true + language_id: + description: 'The language id of the user' + type: string + example: 1 + verified_phone_number: + description: 'Boolean flag if the user has their phone verified. Required to settings up 2FA' type: boolean example: true - is_active: - description: 'Flag to determine if the design is available for use' + readOnly: true + sms_verification_code: + description: 'The sms verification code for the user. Required to settings up 2FA' + type: string + example: '123456' + readOnly: true + oauth_user_token_expiry: + description: 'The expiry date of the oauth token' + type: string + example: '2022-10-10' + readOnly: true + has_password: + description: 'Boolean flag determining if the user has a password' + type: boolean + example: true + readOnly: true + last_confirmed_email_address: + description: 'The last confirmed email address of the user' + type: string + example: 'bob@gmail.com' + readOnly: true + custom_value1: + description: 'A custom value' + type: string + example: 'Custom value 1' + custom_value2: + description: 'A custom value' + type: string + example: '$1000' + custom_value3: + description: 'A custom value' + type: string + example: 'Custom value 3' + custom_value4: + description: 'A custom value' + type: string + example: 'Custom value 4' + is_deleted: + description: 'Boolean flag determining if the user has been deleted' + type: boolean + example: true + readOnly: true + google_2fa_secret: + description: 'The google 2fa secret for the user' + type: string + example: '123456' + readOnly: true + type: object + Account: + properties: + id: + description: 'The account hashed id' + type: string + example: AS3df3A + account_sms_verified: + description: 'Boolean flag if the account has been verified by sms' + type: string + example: true + type: object + Credit: + properties: + id: + description: "The unique hashed ID of the credit" + type: string + example: Opnel5aKBz + user_id: + description: "The unique hashed ID of the user associated with the credit" + type: string + example: 1a2b3c4d5e + assigned_user_id: + description: "The unique hashed ID of the assigned user responsible for the credit" + type: string + example: 6f7g8h9i0j + company_id: + description: "The unique hashed ID of the company associated with the credit" + type: string + example: k1l2m3n4o5 + client_id: + description: "The unique hashed ID of the client associated with the credit" + type: string + example: p1q2r3s4t5 + status_id: + description: "The ID representing the current status of the credit" + type: string + example: 3 + invoice_id: + description: "The unique hashed ID of the linked invoice to which the credit is applied" + type: string + example: u1v2w3x4y5 + number: + description: "The unique alphanumeric credit number per company" + type: string + example: QUOTE_101 + po_number: + description: "The purchase order number referred to by the credit" + type: string + example: PO_12345 + terms: + description: "The terms associated with the credit" + type: string + example: "Net 30" + public_notes: + description: "Public notes for the credit" + type: string + example: "Thank you for your business." + private_notes: + description: "Private notes for internal use, not visible to the client" + type: string + example: "Client is requesting a discount." + footer: + description: "The footer text for the credit" + type: string + example: "Footer text goes here." + custom_value1: + description: "Custom value 1 for additional credit information" + type: string + example: "Custom data 1" + custom_value2: + description: "Custom value 2 for additional credit information" + type: string + example: "Custom data 2" + custom_value3: + description: "Custom value 3 for additional credit information" + type: string + example: "Custom data 3" + custom_value4: + description: "Custom value 4 for additional credit information" + type: string + example: "Custom data 4" + tax_name1: + description: "The name of the first tax applied to the credit" + type: string + example: "VAT" + tax_name2: + description: "The name of the second tax applied to the credit" + type: string + example: "GST" + tax_rate1: + description: "The rate of the first tax applied to the credit" + type: number + format: float + example: 10.00 + tax_rate2: + description: "The rate of the second tax applied to the credit" + type: number + format: float + example: 5.00 + tax_name3: + description: "The name of the third tax applied to the credit" + type: string + example: "PST" + tax_rate3: + description: "The rate of the third tax applied to the credit" + type: number + format: float + example: 8.00 + total_taxes: + description: "The total amount of taxes for the credit" + type: number + format: float + example: 23.00 + line_items: + type: array + description: 'An array of objects which define the line items of the credit' + items: + $ref: '#/components/schemas/InvoiceItem' + amount: + description: "The total amount of the credit" + type: number + format: float + example: 100.00 + balance: + description: "The outstanding balance of the credit" + type: number + format: float + example: 50.00 + paid_to_date: + description: "The total amount paid to date for the credit" + type: number + format: float + example: 50.00 + discount: + description: "The discount applied to the credit" + type: number + format: float + example: 10.00 + partial: + description: "The partial amount applied to the credit" + type: number + format: float + example: 20.00 + is_amount_discount: + description: "Indicates whether the discount applied is a fixed amount or a percentage" type: boolean example: true is_deleted: - description: 'Flag to determine if the design is deleted' + description: "Indicates whether the credit has been deleted" + type: boolean + example: false + uses_inclusive_taxes: + description: "Indicates whether the tax rates applied to the credit are inclusive or exclusive" type: boolean example: true - created_at: - description: Timestamp + date: + description: "The date the credit was issued" + type: string + format: date + example: "1994-07-30" + last_sent_date: + description: "The date the credit was last sent out" + type: string + format: date + example: "1994-07-30" + next_send_date: + description: "The next scheduled date for sending a credit reminder" + type: string + format: date + example: "1994-07-30" + partial_due_date: + description: "The due date for the partial amount of the credit" + type: string + format: date + example: "1994-07-30" + due_date: + description: "The due date for the total amount of the credit" + type: string + format: date + example: "1994-07-30" + settings: + $ref: "#/components/schemas/CompanySettings" + last_viewed: + description: "The timestamp of the last time the credit was viewed" type: number format: integer - example: '134341234234' + example: 1434342123 updated_at: - description: Timestamp + description: "The timestamp of the last time the credit was updated" type: number format: integer - example: '134341234234' - deleted_at: - description: Timestamp + example: 1434342123 + archived_at: + description: "The timestamp of the last time the credit was archived" type: number format: integer - example: '134341234234' + example: 1434342123 + custom_surcharge1: + description: "First custom surcharge amount" + type: number + format: float + example: 10.00 + custom_surcharge2: + description: 'Second Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge3: + description: 'Third Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge4: + description: 'Fourth Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge_tax1: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax2: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax3: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax4: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true type: object tags: - name: login diff --git a/openapi/components/schemas/invoice_item.yaml b/openapi/components/schemas/invoice_item.yaml index 2357c127c5..0dcf42204a 100644 --- a/openapi/components/schemas/invoice_item.yaml +++ b/openapi/components/schemas/invoice_item.yaml @@ -2,7 +2,7 @@ type: object properties: quantity: - type: integer + type: number example: 1 description: 'The quantity of the product offered for this line item' cost: diff --git a/openapi/components/schemas/vendor.yaml b/openapi/components/schemas/vendor.yaml index 1a600a20df..26d92809f1 100644 --- a/openapi/components/schemas/vendor.yaml +++ b/openapi/components/schemas/vendor.yaml @@ -145,6 +145,11 @@ format: integer example: '134341234234' readOnly: true + display_name: + description: 'The display name of the vendor.' + type: string + example: 'Bob the vendor' + readOnly: true settings: $ref: '#/components/schemas/CompanySettings' type: object \ No newline at end of file diff --git a/public/storage/.htaccess b/public/storage/.htaccess old mode 100644 new mode 100755 diff --git a/resources/views/pdf-designs/plain.html b/resources/views/pdf-designs/plain.html index a7f8fc2f50..10f3620077 100644 --- a/resources/views/pdf-designs/plain.html +++ b/resources/views/pdf-designs/plain.html @@ -388,7 +388,7 @@ $entity_images ]; tables.forEach((tableIdentifier) => { - document.getElementById(tableIdentifier).childElementCount === 0 + document.getElementById(tableIdentifier) && document.getElementById(tableIdentifier).childElementCount === 0 ? document.getElementById(tableIdentifier).style.display = 'none' : ''; }); diff --git a/resources/views/themes/ninja2020/view_entity/index.blade.php b/resources/views/themes/ninja2020/view_entity/index.blade.php deleted file mode 100644 index d5c1b1db89..0000000000 --- a/resources/views/themes/ninja2020/view_entity/index.blade.php +++ /dev/null @@ -1,58 +0,0 @@ -@extends('portal.ninja2020.layout.clean') - -@push('head') - - - -@endpush - -@section('body') -
-
-
- - -
-
- @if($entity instanceof App\Models\Invoice) - - @elseif($$entity instanceof App\Models\Quote) - - @endif - -
-
- -
- -
-
-
- -
- -
-
-@endsection - -@section('footer') - @vite('resources/js/clients/shared/pdf.js') -@endsection \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index fa22de99e5..e9a685cbf8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -26,6 +26,7 @@ use App\Http\Controllers\ExportController; use App\Http\Controllers\FilterController; use App\Http\Controllers\ImportController; use App\Http\Controllers\LogoutController; +use App\Http\Controllers\SearchController; use App\Http\Controllers\StaticController; use App\Http\Controllers\StripeController; use App\Http\Controllers\TwilioController; @@ -73,6 +74,7 @@ use App\Http\Controllers\BankTransactionController; use App\Http\Controllers\ClientStatementController; use App\Http\Controllers\ExpenseCategoryController; use App\Http\Controllers\HostedMigrationController; +use App\Http\Controllers\TemplatePreviewController; use App\Http\Controllers\ConnectedAccountController; use App\Http\Controllers\RecurringExpenseController; use App\Http\Controllers\RecurringInvoiceController; @@ -87,6 +89,7 @@ use App\Http\Controllers\Auth\PasswordTimeoutController; use App\Http\Controllers\PreviewPurchaseOrderController; use App\Http\Controllers\Reports\ClientReportController; use App\Http\Controllers\Reports\CreditReportController; +use App\Http\Controllers\Reports\ReportExportController; use App\Http\Controllers\Reports\VendorReportController; use App\Http\Controllers\Reports\ExpenseReportController; use App\Http\Controllers\Reports\InvoiceReportController; @@ -111,7 +114,6 @@ use App\Http\Controllers\Reports\ClientContactReportController; use App\Http\Controllers\Reports\PurchaseOrderReportController; use App\Http\Controllers\Reports\RecurringInvoiceReportController; use App\Http\Controllers\Reports\PurchaseOrderItemReportController; -use App\Http\Controllers\SearchController; Route::group(['middleware' => ['throttle:api', 'api_secret_check']], function () { Route::post('api/v1/signup', [AccountController::class, 'store'])->name('signup.submit'); @@ -321,6 +323,9 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::post('reports/tax_summary_report', TaxSummaryReportController::class); Route::post('reports/user_sales_report', UserSalesReportController::class); Route::post('reports/preview/{hash}', ReportPreviewController::class); + Route::post('exports/preview/{hash}', ReportExportController::class); + + Route::post('templates/preview/{hash}', TemplatePreviewController::class); Route::post('search', SearchController::class); Route::resource('task_schedulers', TaskSchedulerController::class); diff --git a/routes/client.php b/routes/client.php index c9fec140ea..7960f0e812 100644 --- a/routes/client.php +++ b/routes/client.php @@ -31,10 +31,7 @@ Route::post('client/password/email', [ContactForgotPasswordController::class, 's Route::get('client/password/reset/{token}', [ContactResetPasswordController::class, 'showResetForm'])->name('client.password.reset')->middleware(['domain_db', 'contact_account','locale', 'throttle:portal']); Route::post('client/password/reset', [ContactResetPasswordController::class, 'reset'])->name('client.password.update')->middleware(['domain_db', 'contact_account','locale', 'throttle:portal']); -Route::get('view/{entity_type}/{invitation_key}', [App\Http\Controllers\ClientPortal\EntityViewController::class, 'index'])->name('client.entity_view'); -Route::get('view/{entity_type}/{invitation_key}/password', [App\Http\Controllers\ClientPortal\EntityViewController::class ,'password'])->name('client.entity_view.password'); -Route::post('view/{entity_type}/{invitation_key}/password', [App\Http\Controllers\ClientPortal\EntityViewController::class, 'handlePassword']); -Route::post('set_password', [App\Http\Controllers\ClientPortal\EntityViewController::class, 'handlePasswordSet'])->name('client.set_password')->middleware('domain_db'); +Route::post('set_password', [App\Http\Controllers\ClientPortal\InvitationController::class, 'handlePasswordSet'])->name('client.set_password')->middleware('domain_db'); Route::get('tmp_pdf/{hash}', [App\Http\Controllers\ClientPortal\TempRouteController::class, 'index'])->name('tmp_pdf'); @@ -141,6 +138,7 @@ Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'clie }); Route::get('phantom/{entity}/{invitation_key}', [Phantom::class, 'displayInvitation'])->middleware(['invite_db', 'phantom_secret'])->name('phantom_view'); +Route::get('blade/', [Phantom::class, 'blade'])->name('blade'); Route::get('.env', function () { })->middleware('throttle:honeypot'); diff --git a/tests/Feature/Client/ClientMergeTest.php b/tests/Feature/Client/ClientMergeTest.php index ef2c4ae62f..d6ff806941 100644 --- a/tests/Feature/Client/ClientMergeTest.php +++ b/tests/Feature/Client/ClientMergeTest.php @@ -37,6 +37,8 @@ class ClientMergeTest extends TestCase private $primary_contact; + public $faker; + protected function setUp(): void { parent::setUp(); diff --git a/tests/Feature/ClientApiTest.php b/tests/Feature/ClientApiTest.php index 75bed1b223..9cb18a2dc2 100644 --- a/tests/Feature/ClientApiTest.php +++ b/tests/Feature/ClientApiTest.php @@ -44,6 +44,8 @@ class ClientApiTest extends TestCase use MockAccountData; use ClientGroupSettingsSaver; + public $faker; + protected function setUp() :void { parent::setUp(); @@ -175,20 +177,16 @@ class ClientApiTest extends TestCase 'status' => 'paid', ]; + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/client_statement', $data); - try { - $response = $this->withHeaders([ - 'X-API-SECRET' => config('ninja.api_secret'), - 'X-API-TOKEN' => $this->token, - ])->postJson('/api/v1/client_statement', $data); - } catch (ValidationException $e) { - $message = json_decode($e->validator->getMessageBag(), 1); - nlog($message); - } + $response->assertStatus(200); $this->assertTrue($response->headers->get('content-type') == 'application/pdf'); - $response->assertStatus(200); + } public function testClientStatementEmail() diff --git a/tests/Feature/DesignApiTest.php b/tests/Feature/DesignApiTest.php index 1fa8bb9c2c..cfa46bfd2b 100644 --- a/tests/Feature/DesignApiTest.php +++ b/tests/Feature/DesignApiTest.php @@ -14,6 +14,7 @@ namespace Tests\Feature; use Tests\TestCase; use App\Models\Design; use Tests\MockAccountData; +use App\Factory\DesignFactory; use App\Utils\Traits\MakesHash; use App\Events\Design\DesignWasCreated; use App\Events\Design\DesignWasDeleted; @@ -36,6 +37,8 @@ class DesignApiTest extends TestCase public $id; + public $faker; + protected function setUp() :void { parent::setUp(); @@ -49,6 +52,136 @@ class DesignApiTest extends TestCase Model::reguard(); } + public function testFindInSetQueries() + { + + $design = DesignFactory::create($this->company->id, $this->user->id); + $design->is_template = true; + $design->name = 'Test Template'; + $design->entities = 'searchable,payment,quote'; + $design->save(); + + $searchable = 'searchable'; + + $q = Design::query() + ->where('is_template', true) + ->whereRaw('FIND_IN_SET( ? ,entities)', [$searchable]); + + $this->assertEquals(1, $q->count()); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/designs?entities=payment'); + + $response->assertStatus(200); + + $arr = $response->json(); + $this->assertCount(1, $arr['data']); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/designs?entities=,,,3,3,3,'); + + $response->assertStatus(200); + + $arr = $response->json(); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/designs?entities=unsearchable'); + + $response->assertStatus(200); + + $arr = $response->json(); + $this->assertCount(0, $arr['data']); + + $design = DesignFactory::create($this->company->id, $this->user->id); + $design->is_template = true; + $design->name = 'Test Template'; + $design->entities = 'searchable,payment,quote'; + $design->save(); + + $searchable = 'unsearchable'; + + $q = Design::query() + ->where('is_template', true) + ->whereRaw('FIND_IN_SET( ? ,entities)', [$searchable]); + + $this->assertEquals(0, $q->count()); + + $design = DesignFactory::create($this->company->id, $this->user->id); + $design->is_template = true; + $design->name = 'Test Template'; + $design->entities = 'searchable,payment,quote'; + $design->save(); + + $searchable = 'searchable,payment'; + + $q = Design::query() + ->where('is_template', true) + ->whereRaw('FIND_IN_SET( ? ,entities)', [$searchable]); + + $this->assertEquals(0, $q->count()); + + + + } + + public function testDesignTemplates() + { + $design = DesignFactory::create($this->company->id, $this->user->id); + $design->is_template = true; + $design->name = 'Test Template'; + $design->save(); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/designs?template=true'); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertCount(1, $arr['data']); + } + + public function testDesignTemplatesExcluded() + { + $design = DesignFactory::create($this->company->id, $this->user->id); + $design->is_template = true; + $design->name = 'Test Template'; + $design->save(); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/designs?template=false'); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertCount(11, $arr['data']); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/designs'); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertCount(12, $arr['data']); + + + } + + public function testDesignPost() { diff --git a/tests/Feature/Export/ArDetailReportTest.php b/tests/Feature/Export/ArDetailReportTest.php index 0c19668529..5b5c599162 100644 --- a/tests/Feature/Export/ArDetailReportTest.php +++ b/tests/Feature/Export/ArDetailReportTest.php @@ -19,6 +19,7 @@ use App\Models\Company; use App\Models\Invoice; use App\Models\User; use App\Services\Report\ARDetailReport; +use App\Utils\Traits\AppSetup; use App\Utils\Traits\MakesHash; use Illuminate\Routing\Middleware\ThrottleRequests; use Tests\MockAccountData; @@ -30,6 +31,7 @@ use Tests\TestCase; class ArDetailReportTest extends TestCase { use MakesHash; + use AppSetup; public $faker; @@ -44,6 +46,8 @@ class ArDetailReportTest extends TestCase ); $this->withoutExceptionHandling(); + + $this->buildCache(true); } @@ -132,7 +136,6 @@ class ArDetailReportTest extends TestCase { $this->buildData(); - $this->payload = [ 'start_date' => '2000-01-01', 'end_date' => '2030-01-11', diff --git a/tests/Feature/Export/ReportCsvGenerationTest.php b/tests/Feature/Export/ReportCsvGenerationTest.php index 90b12ab96a..2df5bf1064 100644 --- a/tests/Feature/Export/ReportCsvGenerationTest.php +++ b/tests/Feature/Export/ReportCsvGenerationTest.php @@ -11,25 +11,26 @@ namespace Tests\Feature\Export; -use Tests\TestCase; -use App\Models\User; -use App\Models\Client; -use App\Models\Credit; -use League\Csv\Reader; -use App\Models\Account; -use App\Models\Company; -use App\Models\Expense; -use App\Models\Invoice; -use App\Models\CompanyToken; -use App\Models\ClientContact; -use App\Export\CSV\TaskExport; -use App\Utils\Traits\MakesHash; -use App\Export\CSV\VendorExport; -use App\Export\CSV\ProductExport; use App\DataMapper\CompanySettings; use App\Export\CSV\PaymentExport; +use App\Export\CSV\ProductExport; +use App\Export\CSV\TaskExport; +use App\Export\CSV\VendorExport; use App\Factory\CompanyUserFactory; +use App\Models\Account; +use App\Models\Client; +use App\Models\ClientContact; +use App\Models\Company; +use App\Models\CompanyToken; +use App\Models\Credit; +use App\Models\Expense; +use App\Models\Invoice; +use App\Models\User; +use App\Utils\Traits\MakesHash; use Illuminate\Routing\Middleware\ThrottleRequests; +use Illuminate\Support\Facades\Http; +use League\Csv\Reader; +use Tests\TestCase; /** * @test @@ -54,6 +55,9 @@ class ReportCsvGenerationTest extends TestCase $this->buildData(); + if (config('ninja.testvars.travis') !== false) + $this->markTestSkipped('Skip test no company gateways installed'); + } @@ -113,7 +117,7 @@ class ReportCsvGenerationTest extends TestCase "contact.custom_value4", ]; - private $all_payment_report_keys = [ + private $all_payment_report_keys = [ 'payment.date', 'payment.amount', 'payment.refunded', @@ -275,9 +279,20 @@ class ReportCsvGenerationTest extends TestCase } + private function poll($hash) + { + $response = Http::retry(50, 1000, throw: false) + ->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post(config('ninja.app_url')."/api/v1/exports/preview/{$hash}"); + + return $response; + } + public function testVendorCsvGeneration() { - + $vendor = \App\Models\Vendor::factory()->create( [ @@ -292,14 +307,13 @@ class ReportCsvGenerationTest extends TestCase 'private_notes' => 'private_notes', 'public_notes' => 'public_notes', 'website' => 'website', - 'number' => '1234', + 'number' => '1234', ] ); $data = [ 'date_range' => 'all', 'report_keys' => [], - // 'report_keys' => ["vendor.name","purchase_order.number","purchase_order.amount", "item.quantity", "item.cost", "item.line_total", "item.discount", "item.notes", "item.product_key", "item.custom_value1", "item.tax_name1", "item.tax_rate1",], 'send_email' => false, ]; @@ -308,7 +322,15 @@ class ReportCsvGenerationTest extends TestCase 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/vendors', $data); - $csv = $response->streamedContent(); + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); $this->assertEquals('Vendor 1', $this->getFirstValueByColumn($csv, 'Vendor Name')); $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Vendor Number')); @@ -334,8 +356,7 @@ class ReportCsvGenerationTest extends TestCase $data = $export->returnJson(); $this->assertNotNull($data); - // nlog($data); - // $this->assertEquals(0, $this->traverseJson($data, 'columns.0.identifier')); + $this->assertEquals('Vendor Name', $this->traverseJson($data, 'columns.9.display_value')); $this->assertEquals('vendor', $this->traverseJson($data, '0.0.entity')); $this->assertEquals('address1', $this->traverseJson($data, '0.0.id')); @@ -345,7 +366,7 @@ class ReportCsvGenerationTest extends TestCase $this->assertEquals('address1', $this->traverseJson($data, '0.0.display_value')); } - public function testVendorCustomColumnCsvGeneration() + public function testVendorCustomColumnCsvGeneration() { \App\Models\Vendor::query()->cursor()->each(function ($t) { @@ -366,7 +387,7 @@ class ReportCsvGenerationTest extends TestCase 'private_notes' => 'private_notes', 'public_notes' => 'public_notes', 'website' => 'website', - 'number' => '1234', + 'number' => '1234', ] ); @@ -381,7 +402,16 @@ class ReportCsvGenerationTest extends TestCase 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/vendors', $data); - $csv = $response->streamedContent(); + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); + $this->assertEquals('Vendor 1', $this->getFirstValueByColumn($csv, 'Vendor Name')); $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Vendor Number')); @@ -405,7 +435,7 @@ class ReportCsvGenerationTest extends TestCase } - public function testTaskCustomColumnsCsvGeneration() + public function testTaskCustomColumnsCsvGeneration() { $invoice = \App\Models\Invoice::factory()->create([ @@ -463,7 +493,16 @@ class ReportCsvGenerationTest extends TestCase 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/tasks', $data); - $csv = $response->streamedContent(); + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); + $this->assertEquals(3600, $this->getFirstValueByColumn($csv, 'Task Duration')); $this->assertEquals('test1', $this->getFirstValueByColumn($csv, 'Task Description')); @@ -515,12 +554,19 @@ class ReportCsvGenerationTest extends TestCase 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/tasks', $data); + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); + } - - - public function testTasksCsvGeneration() { @@ -553,7 +599,15 @@ class ReportCsvGenerationTest extends TestCase 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/tasks', $data); - $csv = $response->streamedContent(); + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); $this->assertEquals(3600, $this->getFirstValueByColumn($csv, 'Task Duration')); $this->assertEquals('test', $this->getFirstValueByColumn($csv, 'Task Description')); @@ -592,8 +646,17 @@ class ReportCsvGenerationTest extends TestCase 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/products', $data); - - $csv = $response->streamedContent(); + + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); + $this->assertEquals('product_key', $this->getFirstValueByColumn($csv, 'Product')); $this->assertEquals('notes', $this->getFirstValueByColumn($csv, 'Notes')); @@ -602,7 +665,7 @@ class ReportCsvGenerationTest extends TestCase $this->assertEquals('Custom 1', $this->getFirstValueByColumn($csv, 'Custom Value 1')); $this->assertEquals('Custom 2', $this->getFirstValueByColumn($csv, 'Custom Value 2')); $this->assertEquals('Custom 3', $this->getFirstValueByColumn($csv, 'Custom Value 3')); - $this->assertEquals('Custom 4', $this->getFirstValueByColumn($csv, 'Custom Value 4')); + $this->assertEquals('Custom 4', $this->getFirstValueByColumn($csv, 'Custom Value 4')); $export = new ProductExport($this->company, $data); $data = $export->returnJson(); @@ -664,8 +727,17 @@ class ReportCsvGenerationTest extends TestCase 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/payments', $data); - - $csv = $response->streamedContent(); + + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); + $this->assertEquals(100, $this->getFirstValueByColumn($csv, 'Payment Amount')); $this->assertEquals(now()->addSeconds($this->company->timezone()->utc_offset)->format('Y-m-d'), $this->getFirstValueByColumn($csv, 'Payment Date')); @@ -712,7 +784,7 @@ class ReportCsvGenerationTest extends TestCase $data = [ 'date_range' => 'all', - 'report_keys' => array_merge(["payment.amount","payment.date"],$this->all_invoice_report_keys), + 'report_keys' => array_merge(["payment.amount","payment.date"], $this->all_invoice_report_keys), 'send_email' => false, ]; @@ -721,6 +793,17 @@ class ReportCsvGenerationTest extends TestCase 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/payments', $data); + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); + + } @@ -746,8 +829,17 @@ class ReportCsvGenerationTest extends TestCase 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/payments', $data); - - $csv = $response->streamedContent(); + + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); + $this->assertEquals(500, $this->getFirstValueByColumn($csv, 'Payment Amount')); $this->assertEquals(0, $this->getFirstValueByColumn($csv, 'Payment Applied')); @@ -771,9 +863,17 @@ class ReportCsvGenerationTest extends TestCase 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/clients', $data); - - $csv = $response->streamedContent(); -// nlog($csv); + + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); + $reader = Reader::createFromString($csv); $reader->setHeaderOffset(0); @@ -802,8 +902,17 @@ class ReportCsvGenerationTest extends TestCase 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/clients', $data); - - $csv = $response->streamedContent(); + + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); + $this->assertEquals('bob', $this->getFirstValueByColumn($csv, 'Name')); $this->assertEquals(100, $this->getFirstValueByColumn($csv, 'Balance')); @@ -848,8 +957,6 @@ class ReportCsvGenerationTest extends TestCase $arr = $response->json(); - // nlog($arr['message']); - $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, @@ -857,16 +964,6 @@ class ReportCsvGenerationTest extends TestCase $response->assertStatus(409); - // sleep(1); - - // $response = $this->withHeaders([ - // 'X-API-SECRET' => config('ninja.api_secret'), - // 'X-API-TOKEN' => $this->token, - // ])->postJson('/api/v1/reports/preview/'.$arr['message']); - - // $response->assertStatus(200); - - // nlog($response->json()); } @@ -899,7 +996,11 @@ class ReportCsvGenerationTest extends TestCase 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/credits', $data); - $csv = $response->streamedContent(); + $response->assertStatus(200); + $arr = $response->json(); + $hash = $arr['message']; + $response = $this->poll($hash); + $csv = $response->body(); $this->assertEquals('bob', $this->getFirstValueByColumn($csv, 'Client Name')); $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Credit Credit Number')); @@ -917,6 +1018,11 @@ class ReportCsvGenerationTest extends TestCase 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/credits', $data)->assertStatus(200); + $arr = $response->json(); + $hash = $arr['message']; + $response = $this->poll($hash); + $csv = $response->body(); + } public function testInvoiceCustomColumnsCsvGeneration() @@ -947,29 +1053,29 @@ class ReportCsvGenerationTest extends TestCase 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/invoices', $data); - - $csv = $response->streamedContent(); -// nlog($csv); + + $response->assertStatus(200); + $arr = $response->json(); + $hash = $arr['message']; + $response = $this->poll($hash); + $csv = $response->body(); + $this->assertEquals('bob', $this->getFirstValueByColumn($csv, 'Client Name')); $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Invoice Invoice Number')); $this->assertEquals('Unpaid', $this->getFirstValueByColumn($csv, 'Payment Amount')); $this->assertEquals('', $this->getFirstValueByColumn($csv, 'Payment Date')); - $data = [ 'date_range' => 'all', 'report_keys' => $this->all_client_report_keys, 'send_email' => false, ]; - $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/invoices', $data)->assertStatus(200); - - $data = [ 'date_range' => 'all', 'report_keys' => $this->all_payment_report_keys, @@ -981,7 +1087,6 @@ class ReportCsvGenerationTest extends TestCase 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/invoices', $data)->assertStatus(200); - } public function testRecurringInvoiceCustomColumnsCsvGeneration() @@ -1013,14 +1118,17 @@ class ReportCsvGenerationTest extends TestCase 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/recurring_invoices', $data); - - $csv = $response->streamedContent(); + + $response->assertStatus(200); + $arr = $response->json(); + $hash = $arr['message']; + $response = $this->poll($hash); + $csv = $response->body(); $this->assertEquals('bob', $this->getFirstValueByColumn($csv, 'Client Name')); $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Recurring Invoice Invoice Number')); $this->assertEquals('Daily', $this->getFirstValueByColumn($csv, 'Recurring Invoice How Often')); - $data = [ 'date_range' => 'all', 'report_keys' => $this->all_client_report_keys, @@ -1066,7 +1174,15 @@ class ReportCsvGenerationTest extends TestCase 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/recurring_invoices', $data); - $csv = $response->streamedContent(); + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Recurring Invoice Invoice Number')); $this->assertEquals('Daily', $this->getFirstValueByColumn($csv, 'Recurring Invoice How Often')); @@ -1121,9 +1237,15 @@ class ReportCsvGenerationTest extends TestCase 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/invoice_items', $data); - - $csv = $response->streamedContent(); -// nlog($csv);// + + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + $response = $this->poll($hash); + $csv = $response->body(); + $this->assertEquals('bob', $this->getFirstValueByColumn($csv, 'Client Name')); $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Invoice Invoice Number')); $this->assertEquals('Unpaid', $this->getFirstValueByColumn($csv, 'Payment Amount')); @@ -1215,7 +1337,16 @@ class ReportCsvGenerationTest extends TestCase 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/quote_items', $data); - $csv = $response->streamedContent(); + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); + $this->assertEquals('bob', $this->getFirstValueByColumn($csv, 'Client Name')); $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Quote Number')); @@ -1283,9 +1414,17 @@ class ReportCsvGenerationTest extends TestCase 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/purchase_orders', $data); + $response->assertStatus(200); - $csv = $response->streamedContent(); + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); + $this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Amount')); $this->assertEquals('50', $this->getFirstValueByColumn($csv, 'Balance')); @@ -1355,7 +1494,16 @@ class ReportCsvGenerationTest extends TestCase 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/purchase_order_items', $data); - $csv = $response->streamedContent(); + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); + $this->assertEquals('Vendor 1', $this->getFirstValueByColumn($csv, 'Vendor Name')); $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Purchase Order Number')); @@ -1400,7 +1548,16 @@ class ReportCsvGenerationTest extends TestCase 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/quotes', $data); - $csv = $response->streamedContent(); + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); + $this->assertEquals('bob', $this->getFirstValueByColumn($csv, 'Client Name')); $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Quote Number')); @@ -1442,7 +1599,7 @@ class ReportCsvGenerationTest extends TestCase 'terms' => 'Terms', ]); - $invoice->service()->markPaid()->save(); + $invoice->service()->markPaid()->save(); $data = [ 'date_range' => 'all', @@ -1455,7 +1612,16 @@ class ReportCsvGenerationTest extends TestCase 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/invoices', $data); - $csv = $response->streamedContent(); + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); + $this->assertEquals('bob', $this->getFirstValueByColumn($csv, 'Client Name')); $this->assertEquals('12345', $this->getFirstValueByColumn($csv, 'Invoice Invoice Number')); @@ -1478,8 +1644,17 @@ class ReportCsvGenerationTest extends TestCase 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/contacts', $data); - - $csv = $response->streamedContent(); + + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); + $reader = Reader::createFromString($csv); $reader->setHeaderOffset(0); @@ -1549,7 +1724,16 @@ class ReportCsvGenerationTest extends TestCase $response->assertStatus(200); - $csv = $response->streamedContent(); + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); + $this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Credit Amount')); $this->assertEquals('50', $this->getFirstValueByColumn($csv, 'Credit Balance')); @@ -1634,7 +1818,16 @@ class ReportCsvGenerationTest extends TestCase $response->assertStatus(200); - $csv = $response->streamedContent(); + $response->assertStatus(200); + + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); + $this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Invoice Amount')); $this->assertEquals('50', $this->getFirstValueByColumn($csv, 'Invoice Balance')); @@ -1709,7 +1902,14 @@ class ReportCsvGenerationTest extends TestCase $response->assertStatus(200); - $csv = $response->streamedContent(); + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); + $this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Recurring Invoice Amount')); $this->assertEquals('50', $this->getFirstValueByColumn($csv, 'Recurring Invoice Balance')); @@ -1785,9 +1985,13 @@ class ReportCsvGenerationTest extends TestCase $response->assertStatus(200); - $csv = $response->streamedContent(); - - //nlog($csv); + $arr = $response->json(); + + $hash = $arr['message']; + + $response = $this->poll($hash); + + $csv = $response->body(); $this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Quote Amount')); $this->assertEquals('50', $this->getFirstValueByColumn($csv, 'Quote Balance')); @@ -1824,7 +2028,7 @@ class ReportCsvGenerationTest extends TestCase 'client_id' => $this->client->id, 'amount' => 100, 'public_notes' => 'Public', - 'private_notes' => 'Private', + 'private_notes' => 'Private', ]); $data = [ @@ -1838,42 +2042,43 @@ class ReportCsvGenerationTest extends TestCase 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/expenses', $data); - $response->assertStatus(200); - - $csv = $response->streamedContent(); + $arr = $response->json(); + $hash = $arr['message']; + $response = $this->poll($hash); + $csv = $response->body(); $this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Expense Amount')); $this->assertEquals('Public', $this->getFirstValueByColumn($csv, 'Expense Public Notes')); $this->assertEquals('Private', $this->getFirstValueByColumn($csv, 'Expense Private Notes')); $this->assertEquals($this->user->present()->name(), $this->getFirstValueByColumn($csv, 'Expense User')); - - + $data = [ 'date_range' => 'all', 'report_keys' => $this->all_client_report_keys, 'send_email' => false, ]; - $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/expenses', $data)->assertStatus(200); - + $arr = $response->json(); + $hash = $arr['message']; + $response = $this->poll($hash); + $csv = $response->body(); } public function testExpenseCustomColumnsCsvGeneration() { - $vendor = + $vendor = \App\Models\Vendor::factory()->create( [ 'user_id' => $this->user->id, 'company_id' => $this->company->id, 'name' => 'Vendor 1', ] - ); Expense::factory()->create([ @@ -1883,8 +2088,8 @@ class ReportCsvGenerationTest extends TestCase 'vendor_id' => $vendor->id, 'amount' => 100, 'public_notes' => 'Public', - 'private_notes' => 'Private', - 'currency_id' => 1, + 'private_notes' => 'Private', + 'currency_id' => 1, ]); $data = [ @@ -1900,7 +2105,10 @@ class ReportCsvGenerationTest extends TestCase $response->assertStatus(200); - $csv = $response->streamedContent(); + $arr = $response->json(); + $hash = $arr['message']; + $response = $this->poll($hash); + $csv = $response->body(); $this->assertEquals('bob', $this->getFirstValueByColumn($csv, 'Client Name')); $this->assertEquals('Vendor 1', $this->getFirstValueByColumn($csv, 'Vendor Name')); @@ -1910,4 +2118,4 @@ class ReportCsvGenerationTest extends TestCase } -} \ No newline at end of file +} diff --git a/tests/Feature/PdfCreatorTest.php b/tests/Feature/PdfCreatorTest.php deleted file mode 100644 index 0eba272e66..0000000000 --- a/tests/Feature/PdfCreatorTest.php +++ /dev/null @@ -1,64 +0,0 @@ -makeTestData(); - - $this->withoutMiddleware( - ThrottleRequests::class - ); - - if(config('ninja.testvars.travis')) - $this->markTestSkipped(); - - } - - // public function testCreditPdfCreated() - // { - // $credit_path = (new CreateEntityPdf($this->credit->invitations->first()))->handle(); - - // $this->assertTrue(Storage::exists($credit_path)); - // } - - public function testInvoicePdfCreated() - { - $invoice_path = (new CreateEntityPdf($this->invoice->invitations->first()))->handle(); - - $this->assertTrue(Storage::exists($invoice_path)); - } - - public function testQuotePdfCreated() - { - $quote_path = (new CreateEntityPdf($this->quote->invitations->first()))->handle(); - - $this->assertTrue(Storage::exists($quote_path)); - } -} diff --git a/tests/Feature/PdfMaker/ExampleIntegrationTest.php b/tests/Feature/PdfMaker/ExampleIntegrationTest.php index 1f923f9f5f..8c567ff58e 100644 --- a/tests/Feature/PdfMaker/ExampleIntegrationTest.php +++ b/tests/Feature/PdfMaker/ExampleIntegrationTest.php @@ -49,6 +49,10 @@ class ExampleIntegrationTest extends TestCase 'pdf_variables' => (array) $invoice->company->settings->pdf_variables, ]), 'variables' => $engine->generateLabelsAndValues(), + 'options' => [ + 'client' => $invoice->client, + 'invoices' => [$invoice] + ], ]; $maker = new PdfMaker($state); diff --git a/tests/Feature/PurchaseOrderTest.php b/tests/Feature/PurchaseOrderTest.php index 15183ac0f2..4a5dfe7984 100644 --- a/tests/Feature/PurchaseOrderTest.php +++ b/tests/Feature/PurchaseOrderTest.php @@ -233,7 +233,6 @@ class PurchaseOrderTest extends TestCase $x = $purchase_order->service()->markSent()->getPurchaseOrderPdf(); - // nlog($x); } public function testPurchaseOrderRest() diff --git a/tests/Feature/ReminderTest.php b/tests/Feature/ReminderTest.php index 51e141228a..2b9b63c554 100644 --- a/tests/Feature/ReminderTest.php +++ b/tests/Feature/ReminderTest.php @@ -271,16 +271,6 @@ class ReminderTest extends TestCase $this->assertEquals(103, $fee->cost); $this->assertEquals('Fee added '.now()->format('d/M/Y'), $fee->notes); - - - - - - - // $this->travelTo(now()->addHours(1)); -// } - - $this->travelBack(); } @@ -429,8 +419,8 @@ class ReminderTest extends TestCase $next_send_date = Carbon::parse($this->invoice->next_send_date); $calculatedReminderDate = Carbon::parse($this->invoice->due_date)->subDays(4)->addSeconds($this->invoice->client->timezone_offset()); - nlog($next_send_date->format('Y-m-d h:i:s')); - nlog($calculatedReminderDate->format('Y-m-d h:i:s')); + // nlog($next_send_date->format('Y-m-d h:i:s')); + // nlog($calculatedReminderDate->format('Y-m-d h:i:s')); $this->travelTo($calculatedReminderDate); @@ -451,7 +441,7 @@ class ReminderTest extends TestCase $next_send_date = Carbon::parse($this->invoice->next_send_date); - nlog($next_send_date->format('Y-m-d h:i:s')); + // nlog($next_send_date->format('Y-m-d h:i:s')); $calculatedReminderDate = Carbon::parse($this->invoice->due_date)->subDays(2)->addSeconds($this->invoice->client->timezone_offset()); $this->assertTrue($next_send_date->eq($calculatedReminderDate)); @@ -470,7 +460,7 @@ class ReminderTest extends TestCase $calculatedReminderDate = Carbon::parse($this->invoice->due_date)->addDays(3)->addSeconds($this->invoice->client->timezone_offset()); $this->assertTrue($next_send_date->eq($calculatedReminderDate)); - nlog($next_send_date->format('Y-m-d h:i:s')); + // nlog($next_send_date->format('Y-m-d h:i:s')); } public function testReminderQueryCatchesDate() diff --git a/tests/Feature/TaskApiTest.php b/tests/Feature/TaskApiTest.php index 2d7d9e4363..e93640852c 100644 --- a/tests/Feature/TaskApiTest.php +++ b/tests/Feature/TaskApiTest.php @@ -132,6 +132,26 @@ class TaskApiTest extends TestCase $this->assertEquals(41, $arr['data']['rate']); } + public function testTaskTimelogParse() + { + $data = [ + "description" => "xx", + "rate" => "6574", + "time_log" => "[[Oct 31, 2023 12:00 am,Oct 31, 2023 1:00 am]]" + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson("/api/v1/tasks", $data); + + $response->assertStatus(422); + $arr = $response->json(); + + + + } + public function testTaskProjectRateSet() { @@ -579,19 +599,16 @@ class TaskApiTest extends TestCase public function testTimeLogValidation() { $data = [ - 'timelog' => $this->faker->firstName(), + 'time_log' => $this->faker->firstName(), ]; - try { $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, - ])->post('/api/v1/tasks', $data); + ])->postJson('/api/v1/tasks', $data); + + $response->assertStatus(422); - $arr = $response->json(); - } catch (ValidationException $e) { - $response->assertStatus(302); - } } public function testTimeLogValidation1() @@ -629,19 +646,16 @@ class TaskApiTest extends TestCase public function testTimeLogValidation3() { $data = [ - 'timelog' => [["a","b",'d'],["c","d",'d']], + 'time_log' => [["a","b",'d'],["c","d",'d']], ]; - try { - $response = $this->withHeaders([ - 'X-API-SECRET' => config('ninja.api_secret'), - 'X-API-TOKEN' => $this->token, - ])->post('/api/v1/tasks', $data); + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/tasks', $data); + + $response->assertStatus(422); - $arr = $response->json(); - } catch (ValidationException $e) { - $response->assertStatus(302); - } } public function testTimeLogValidation4() diff --git a/tests/Feature/Template/TemplateTest.php b/tests/Feature/Template/TemplateTest.php new file mode 100644 index 0000000000..df95599ba3 --- /dev/null +++ b/tests/Feature/Template/TemplateTest.php @@ -0,0 +1,709 @@ + + $company.name +
+ // // '. $text .' //
+ + + + + + + + + + + {% for entity in invoices %} + {% for item in entity.line_items|filter(item => item.type_id == "1") %} + + + + + + + + {% endfor %} + {% endfor %} + +
Item #DescriptionOrderedDeliveredOutstanding
{{ item.product_key }}{{ item.notes }}{{ item.quantity }}{{ item.quantity }}0
+ + + '; + + private string $nested_body = ' + + + $company.name + + + + + + + + + + + + {% for entity in invoices %} + Client Name: {{ entity.client.name }} + Client Name with variables = $client.name + {% for item in entity.line_items|filter(item => item.type_id == "1") %} + + + + + + + + {% endfor %} + {% endfor %} + +
Item #DescriptionOrderedDeliveredOutstanding
{{ item.product_key }}{{ item.notes }}{{ item.quantity }}{{ item.quantity }}0
+
+ + '; + + private string $payments_body = ' + CoName: $company.name + ClName: $client.name + InNumber: $invoice.number + + CoName: $company.name + ClName: $client.name + InNumber: $invoice.number + + + + + + + + + + + + + + {% for invoice in invoices %} + + + + + + + + + + {% for payment in invoice.payments|filter(payment => payment.is_deleted == false) %} + + {% for pivot in payment.paymentables %} + + + + + + + + + + + {% endfor %} + {% endfor %} + {% endfor%} + +
Invoice #DateDue DateTotalTransactionOutstanding
{{ invoice.number }}{{ invoice.date }}{{ invoice.due_date }}{{ invoice.amount }}{{ invoice.balance }}
{{ payment.number }}{{ payment.date }} + {% if pivot.amount_raw > 0 %} + {{ pivot.amount }} - {{ payment.type.name }} + {% else %} + ({{ pivot.refunded }}) + {% endif %} +
+ +
+ '; + + protected function setUp() :void + { + parent::setUp(); + + $this->makeTestData(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + + } + + public function testDataMaps() + { + $start = microtime(true); + + Invoice::factory()->count(10)->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $this->client->id, + 'status_id' => Invoice::STATUS_SENT, + 'amount' => 100, + 'balance' => 100, + ]); + + $invoices = Invoice::orderBy('id','desc')->where('client_id', $this->client->id)->take(10)->get()->map(function($c){ + return $c->service()->markSent()->applyNumber()->save(); + })->map(function ($i){ + return ['invoice_id' => $i->hashed_id, 'amount' => rand(0, $i->balance)]; + })->toArray(); + + Credit::factory()->count(2)->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $this->client->id, + 'status_id' => Invoice::STATUS_SENT, + 'amount' => 50, + 'balance' => 50, + ]); + + $credits = Credit::orderBy('id', 'desc')->where('client_id', $this->client->id)->take(2)->get()->map(function($c){ + return $c->service()->markSent()->applyNumber()->save(); + })->map(function ($i) { + return ['credit_id' => $i->hashed_id, 'amount' => rand(0, $i->balance)]; + })->toArray(); + + $data = [ + 'invoices' => $invoices, + 'credits' => $credits, + 'date' => now()->format('Y-m-d'), + 'client_id' => $this->client->hashed_id, + 'transaction_reference' => 'My Batch Payment', + 'type_id' => "5", + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/payments/', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $start = microtime(true); + + $p = Payment::with('client','invoices','paymentables','credits') + ->where('id', $this->decodePrimaryKey($arr['data']['id'])) + ->cursor() + ->map(function ($payment){ + + $this->transformPayment($payment); + + })->toArray(); + + + nlog("end payments = " . microtime(true) - $start); + + $this->assertIsArray($data); + + $start = microtime(true); + + \DB::enableQueryLog(); + + $invoices = Invoice::with('client','payments.client','payments.paymentables','payments.credits','credits.client') + ->orderBy('id','desc') + ->where('client_id', $this->client->id) + ->take(10) + ->get() + ->map(function($invoice){ + + $payments = []; + $payments = $invoice->payments->map(function ($payment){ + // nlog(microtime(true)); + return $this->transformPayment($payment); + })->toArray(); + + return [ + 'amount' => Number::formatMoney($invoice->amount, $invoice->client), + 'balance' => Number::formatMoney($invoice->balance, $invoice->client), + 'balance_raw' => $invoice->balance, + 'number' => $invoice->number ?: '', + 'discount' => $invoice->discount, + 'po_number' => $invoice->po_number ?: '', + 'date' => $this->translateDate($invoice->date, $invoice->client->date_format(), $invoice->client->locale()), + 'last_sent_date' => $this->translateDate($invoice->last_sent_date, $invoice->client->date_format(), $invoice->client->locale()), + 'next_send_date' => $this->translateDate($invoice->next_send_date, $invoice->client->date_format(), $invoice->client->locale()), + 'due_date' => $this->translateDate($invoice->due_date, $invoice->client->date_format(), $invoice->client->locale()), + 'terms' => $invoice->terms ?: '', + 'public_notes' => $invoice->public_notes ?: '', + 'private_notes' => $invoice->private_notes ?: '', + 'uses_inclusive_taxes' => (bool) $invoice->uses_inclusive_taxes, + 'tax_name1' => $invoice->tax_name1 ?? '', + 'tax_rate1' => (float) $invoice->tax_rate1, + 'tax_name2' => $invoice->tax_name2 ?? '', + 'tax_rate2' => (float) $invoice->tax_rate2, + 'tax_name3' => $invoice->tax_name3 ?? '', + 'tax_rate3' => (float) $invoice->tax_rate3, + 'total_taxes' => Number::formatMoney($invoice->total_taxes, $invoice->client), + 'total_taxes_raw' => $invoice->total_taxes, + 'is_amount_discount' => (bool) $invoice->is_amount_discount ?? false, + 'footer' => $invoice->footer ?? '', + 'partial' => $invoice->partial ?? 0, + 'partial_due_date' => $this->translateDate($invoice->partial_due_date, $invoice->client->date_format(), $invoice->client->locale()), + 'custom_value1' => (string) $invoice->custom_value1 ?: '', + 'custom_value2' => (string) $invoice->custom_value2 ?: '', + 'custom_value3' => (string) $invoice->custom_value3 ?: '', + 'custom_value4' => (string) $invoice->custom_value4 ?: '', + 'custom_surcharge1' => (float) $invoice->custom_surcharge1, + 'custom_surcharge2' => (float) $invoice->custom_surcharge2, + 'custom_surcharge3' => (float) $invoice->custom_surcharge3, + 'custom_surcharge4' => (float) $invoice->custom_surcharge4, + 'exchange_rate' => (float) $invoice->exchange_rate, + 'custom_surcharge_tax1' => (bool) $invoice->custom_surcharge_tax1, + 'custom_surcharge_tax2' => (bool) $invoice->custom_surcharge_tax2, + 'custom_surcharge_tax3' => (bool) $invoice->custom_surcharge_tax3, + 'custom_surcharge_tax4' => (bool) $invoice->custom_surcharge_tax4, + 'line_items' => $invoice->line_items ?: (array) [], + 'reminder1_sent' => $this->translateDate($invoice->reminder1_sent, $invoice->client->date_format(), $invoice->client->locale()), + 'reminder2_sent' => $this->translateDate($invoice->reminder2_sent, $invoice->client->date_format(), $invoice->client->locale()), + 'reminder3_sent' => $this->translateDate($invoice->reminder3_sent, $invoice->client->date_format(), $invoice->client->locale()), + 'reminder_last_sent' => $this->translateDate($invoice->reminder_last_sent, $invoice->client->date_format(), $invoice->client->locale()), + 'paid_to_date' => Number::formatMoney($invoice->paid_to_date, $invoice->client), + 'auto_bill_enabled' => (bool) $invoice->auto_bill_enabled, + 'client' => [ + 'name' => $invoice->client->present()->name(), + 'balance' => $invoice->client->balance, + 'payment_balance' => $invoice->client->payment_balance, + 'credit_balance' => $invoice->client->credit_balance, + ], + 'payments' => $payments, + ]; + }); + + $queries = \DB::getQueryLog(); + $count = count($queries); + + nlog("query count = {$count}"); + $x = $invoices->toArray(); + // nlog(json_encode($x)); + // nlog(json_encode(htmlspecialchars(json_encode($x), ENT_QUOTES, 'UTF-8'))); + // nlog($invoices->toJson()); + + $this->assertIsArray($invoices->toArray()); + + nlog("end invoices = " . microtime(true) - $start); + + } + + private function transformPayment(Payment $payment): array + { + + $data = []; + + $credits = $payment->credits->map(function ($credit) use ($payment) { + return [ + 'credit' => $credit->number, + 'amount_raw' => $credit->pivot->amount, + 'refunded_raw' => $credit->pivot->refunded, + 'net_raw' => $credit->pivot->amount - $credit->pivot->refunded, + 'amount' => Number::formatMoney($credit->pivot->amount, $payment->client), + 'refunded' => Number::formatMoney($credit->pivot->refunded, $payment->client), + 'net' => Number::formatMoney($credit->pivot->amount - $credit->pivot->refunded, $payment->client), + 'is_credit' => true, + 'created_at' => $this->translateDate($credit->pivot->created_at, $payment->client->date_format(), $payment->client->locale()), + 'updated_at' => $this->translateDate($credit->pivot->updated_at, $payment->client->date_format(), $payment->client->locale()), + 'timestamp' => $credit->pivot->created_at->timestamp, + ]; + }); + + $pivot = $payment->invoices->map(function ($invoice) use ($payment) { + return [ + 'invoice' => $invoice->number, + 'amount_raw' => $invoice->pivot->amount, + 'refunded_raw' => $invoice->pivot->refunded, + 'net_raw' => $invoice->pivot->amount - $invoice->pivot->refunded, + 'amount' => Number::formatMoney($invoice->pivot->amount, $payment->client), + 'refunded' => Number::formatMoney($invoice->pivot->refunded, $payment->client), + 'net' => Number::formatMoney($invoice->pivot->amount - $invoice->pivot->refunded, $payment->client), + 'is_credit' => false, + 'created_at' => $this->translateDate($invoice->pivot->created_at, $payment->client->date_format(), $payment->client->locale()), + 'updated_at' => $this->translateDate($invoice->pivot->updated_at, $payment->client->date_format(), $payment->client->locale()), + 'timestamp' => $invoice->pivot->created_at->timestamp, + ]; + })->merge($credits)->sortBy('timestamp')->toArray(); + + return [ + 'status' => $payment->stringStatus($payment->status_id), + 'badge' => $payment->badgeForStatus($payment->status_id), + 'amount' => Number::formatMoney($payment->amount, $payment->client), + 'applied' => Number::formatMoney($payment->applied, $payment->client), + 'balance' => Number::formatMoney(($payment->amount - $payment->refunded - $payment->applied), $payment->client), + 'refunded' => Number::formatMoney($payment->refunded, $payment->client), + 'amount_raw' => $payment->amount, + 'applied_raw' => $payment->applied, + 'refunded_raw' => $payment->refunded, + 'balance_raw' => ($payment->amount - $payment->refunded - $payment->applied), + 'date' => $this->translateDate($payment->date, $payment->client->date_format(), $payment->client->locale()), + 'method' => $payment->translatedType(), + 'currency' => $payment->currency->code, + 'exchange_rate' => $payment->exchange_rate, + 'transaction_reference' => $payment->transaction_reference, + 'is_manual' => $payment->is_manual, + 'number' => $payment->number, + 'custom_value1' => $payment->custom_value1 ?? '', + 'custom_value2' => $payment->custom_value2 ?? '', + 'custom_value3' => $payment->custom_value3 ?? '', + 'custom_value4' => $payment->custom_value4 ?? '', + 'client' => [ + 'name' => $payment->client->present()->name(), + 'balance' => $payment->client->balance, + 'payment_balance' => $payment->client->payment_balance, + 'credit_balance' => $payment->client->credit_balance, + ], + 'paymentables' => $pivot, + ]; + + return $data; + + + + + } + + public function testVariableResolutionViaTransformersForPaymentsInStatements() + { + Invoice::factory()->count(20)->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $this->client->id, + 'status_id' => Invoice::STATUS_SENT, + 'amount' => 100, + 'balance' => 100, + ]); + + $i = Invoice::orderBy('id','desc') + ->where('client_id', $this->client->id) + ->where('status_id', 2) + ->cursor() + ->each(function ($i){ + $i->service()->applyPaymentAmount(random_int(1,100)); + }); + + $invoices = Invoice::withTrashed() + ->with('payments.type') + ->where('is_deleted', false) + ->where('company_id', $this->client->company_id) + ->where('client_id', $this->client->id) + ->whereIn('status_id', [2,3,4]) + ->orderBy('due_date', 'ASC') + ->orderBy('date', 'ASC') + ->cursor(); + + $invoices->each(function ($i){ + + $rand = [1,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,24,25,32,49,50]; + + $i->payments()->each(function ($p) use ($rand){ + shuffle($rand); + $p->type_id = $rand[0]; + $p->save(); + + }); + }); + + $design_model = Design::find(2); + + $replicated_design = $design_model->replicate(); + $replicated_design->company_id = $this->company->id; + $replicated_design->user_id = $this->user->id; + $design = $replicated_design->design; + $design->body .= $this->payments_body; + $replicated_design->design = $design; + $replicated_design->is_custom = true; + $replicated_design->is_template =true; + $replicated_design->entities = 'client'; + $replicated_design->save(); + + $data['invoices'] = $invoices; + $ts = $replicated_design->service()->build($data); + + // nlog("results = "); + // nlog($ts->getHtml()); + $this->assertNotNull($ts->getHtml()); + + } + + public function testDoubleEntityNestedDataTemplateServiceBuild() + { + $design_model = Design::find(2); + + $replicated_design = $design_model->replicate(); + $replicated_design->company_id = $this->company->id; + $replicated_design->user_id = $this->user->id; + $design = $replicated_design->design; + $design->body .= $this->nested_body; + $replicated_design->design = $design; + $replicated_design->is_custom = true; + $replicated_design->save(); + + $i2 = Invoice::factory() + ->for($this->client) + ->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'status_id' => Invoice::STATUS_SENT, + 'design_id' => $replicated_design->id, + 'balance' => 100, + ]); + + $data = []; + $data['invoices'] = collect([$this->invoice, $i2]); + + $ts = $replicated_design->service()->build($data); + + // nlog("results = "); + // nlog($ts->getHtml()); + $this->assertNotNull($ts->getHtml()); + } + + public function testDoubleEntityTemplateServiceBuild() + { + $design_model = Design::find(2); + + $replicated_design = $design_model->replicate(); + $replicated_design->company_id = $this->company->id; + $replicated_design->user_id = $this->user->id; + $design = $replicated_design->design; + $design->body .= $this->body; + $replicated_design->design = $design; + $replicated_design->is_custom = true; + $replicated_design->save(); + + $i2 = Invoice::factory() + ->for($this->client) + ->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'status_id' => Invoice::STATUS_SENT, + 'design_id' => $replicated_design->id, + 'balance' => 100, + ]); + + $data = []; + $data['invoices'] = collect([$this->invoice, $i2]); + + $ts = $replicated_design->service()->build($data); + + // nlog("results = "); + // nlog($ts->getHtml()); + $this->assertNotNull($ts->getHtml()); + } + + public function testTemplateServiceBuild() + { + $design_model = Design::find(2); + $replicated_design = $design_model->replicate(); + $replicated_design->company_id = $this->company->id; + $replicated_design->user_id = $this->user->id; + $design = $replicated_design->design; + $design->body .= $this->body; + $replicated_design->design = $design; + $replicated_design->is_custom = true; + $replicated_design->save(); + + $data = []; + $data['invoices'] = collect([$this->invoice]); + + $ts = $replicated_design->service()->build($data); + + // nlog("results = "); + // nlog($ts->getHtml()); + $this->assertNotNull($ts->getHtml()); + } + + public function testTemplateService() + { + $design_model = Design::find(2); + + $replicated_design = $design_model->replicate(); + $replicated_design->company_id = $this->company->id; + $replicated_design->user_id = $this->user->id; + $design = $replicated_design->design; + $design->body .= $this->body; + $replicated_design->design = $design; + $replicated_design->is_custom = true; + $replicated_design->save(); + + $this->assertNotNull($replicated_design->service()); + $this->assertInstanceOf(TemplateService::class, $replicated_design->service()); + } + + public function testTimingOnCleanDesign() + { + $design_model = Design::find(2); + + $replicated_design = $design_model->replicate(); + $replicated_design->company_id = $this->company->id; + $replicated_design->user_id = $this->user->id; + $design = $replicated_design->design; + $design->body .= $this->body; + $replicated_design->design = $design; + $replicated_design->is_custom = true; + $replicated_design->save(); + + $entity_obj = \App\Models\Invoice::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $this->client->id, + 'status_id' => Invoice::STATUS_SENT, + 'design_id' => $replicated_design->id, + ]); + + $i = \App\Models\InvoiceInvitation::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'invoice_id' => $entity_obj->id, + 'client_contact_id' => $this->client->contacts->first()->id, + ]); + + $start = microtime(true); + + $pdf = (new CreateRawPdf($i))->handle(); + + $end = microtime(true); + + $this->assertNotNull($pdf); + + nlog("Twig + PDF Gen Time: " . $end-$start); + + } + + public function testStaticPdfGeneration() + { + $start = microtime(true); + + $pdf = (new CreateRawPdf($this->invoice->invitations->first()))->handle(); + + $end = microtime(true); + + $this->assertNotNull($pdf); + + nlog("Plain PDF Gen Time: " . $end-$start); + } + + public function testTemplateGeneration() + { + $entity_obj = $this->invoice; + + $design = new Design(); + $design->design = json_decode(json_encode($this->invoice->company->settings->pdf_variables), true); + $design->name = 'test'; + $design->is_active = true; + $design->is_template = true; + $design->is_custom = true; + $design->user_id = $this->invoice->user_id; + $design->company_id = $this->invoice->company_id; + + $design_object = new \stdClass; + $design_object->includes = ''; + $design_object->header = ''; + $design_object->body = $this->body; + $design_object->product = ''; + $design_object->task = ''; + $design_object->footer = ''; + + $design->design = $design_object; + + $design->save(); + + $start = microtime(true); + + App::forgetInstance('translator'); + $t = app('translator'); + App::setLocale($entity_obj->client->locale()); + $t->replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings())); + + $html = new HtmlEngine($entity_obj->invitations()->first()); + + $options = [ + 'custom_partials' => json_decode(json_encode($design->design), true), + ]; + $template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options); + + $variables = $html->generateLabelsAndValues(); + + $state = [ + 'template' => $template->elements([ + 'client' => $entity_obj->client, + 'entity' => $entity_obj, + 'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables, + '$product' => $design->design->product, + 'variables' => $variables, + ]), + 'variables' => $variables, + 'options' => [ + 'all_pages_header' => $entity_obj->client->getSetting('all_pages_header'), + 'all_pages_footer' => $entity_obj->client->getSetting('all_pages_footer'), + 'client' => $entity_obj->client, + 'entity' => [$entity_obj], + 'invoices' => [$entity_obj], + 'variables' => $variables, + ], + 'process_markdown' => $entity_obj->client->company->markdown_enabled, + ]; + + $maker = new PdfMaker($state); + $maker + ->design($template) + ->build(); + + $html = $maker->getCompiledHTML(true); + + $end = microtime(true); + + $this->assertNotNull($html); + $this->assertStringContainsStringIgnoringCase($this->company->settings->name, $html); + + nlog("Twig Solo Gen Time: ". $end - $start); + } + +} \ No newline at end of file diff --git a/tests/Integration/InvoiceUploadTest.php b/tests/Integration/InvoiceUploadTest.php index e0222290d3..36c3a198a5 100644 --- a/tests/Integration/InvoiceUploadTest.php +++ b/tests/Integration/InvoiceUploadTest.php @@ -11,7 +11,6 @@ namespace Tests\Integration; -use App\Jobs\Entity\CreateEntityPdf; use Illuminate\Foundation\Testing\DatabaseTransactions; use Tests\MockAccountData; use Tests\TestCase; @@ -34,8 +33,6 @@ class InvoiceUploadTest extends TestCase public function testInvoiceUploadWorks() { - CreateEntityPdf::dispatchSync($this->invoice->invitations->first()); - $this->assertNotNull($this->invoice->service()->getInvoicePdf($this->invoice->client->primary_contact()->first())); } } diff --git a/tests/Unit/EInvoiceTest.php b/tests/Unit/EInvoiceTest.php index 59c2024017..658bedba98 100644 --- a/tests/Unit/EInvoiceTest.php +++ b/tests/Unit/EInvoiceTest.php @@ -11,9 +11,8 @@ use Tests\TestCase; use Tests\MockAccountData; -use App\Jobs\Entity\CreateEntityPdf; +use App\Jobs\Entity\CreateRawPdf; use App\Jobs\Invoice\CreateEInvoice; -use Illuminate\Support\Facades\Storage; use horstoeko\zugferd\ZugferdDocumentReader; use Illuminate\Routing\Middleware\ThrottleRequests; use Illuminate\Foundation\Testing\DatabaseTransactions; @@ -66,7 +65,7 @@ class EInvoiceTest extends TestCase */ public function checkEmbededPDFFile() { - $pdf = (new CreateEntityPdf($this->invoice->invitations()->first()))->handle(); + $pdf = (new CreateRawPdf($this->invoice->invitations()->first()))->handle(); $document = ZugferdDocumentReader::readAndGuessFromContent($pdf); $document->getDocumentInformation($documentno, $documenttypecode, $documentdate, $documentcurrency, $taxcurrency, $taxname, $documentlangeuage, $rest); $this->assertEquals($this->invoice->number, $documentno); diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php index 1f6877ca56..a631610f58 100644 --- a/tests/_bootstrap.php +++ b/tests/_bootstrap.php @@ -3,7 +3,7 @@ // This is global bootstrap for autoloading use Codeception\Util\Fixtures; -Fixtures::add('url', 'http://www.ninja.test'); +Fixtures::add('url', 'http://localhost'); Fixtures::add('username', 'user@example.com'); Fixtures::add('password', 'password');