mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-10-31 15:37:30 -04:00 
			
		
		
		
	Merge branch 'v5-develop' of https://github.com/invoiceninja/invoiceninja into feature-inbound-email-expenses
This commit is contained in:
		
						commit
						ce77eff7cb
					
				| @ -63,3 +63,5 @@ APPLE_REDIRECT_URI= | ||||
| 
 | ||||
| NORDIGEN_SECRET_ID= | ||||
| NORDIGEN_SECRET_KEY= | ||||
| 
 | ||||
| OPENEXCHANGE_APP_ID= | ||||
|  | ||||
							
								
								
									
										7
									
								
								.github/workflows/react_release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/react_release.yml
									
									
									
									
										vendored
									
									
								
							| @ -38,17 +38,22 @@ jobs: | ||||
|           sudo php artisan cache:clear | ||||
|           sudo find ./vendor/bin/ -type f -exec chmod +x {} \; | ||||
|           sudo find ./ -type d -exec chmod 755 {} \; | ||||
|       - name: Set current date to variable | ||||
|         id: set_date | ||||
|         run: echo "current_date=$(date '+%Y-%m-%d')" >> $GITHUB_ENV | ||||
| 
 | ||||
|       - name: Prepare React FrontEnd | ||||
|         run: | | ||||
|           git clone https://${{secrets.commit_secret}}@github.com/invoiceninja/ui.git | ||||
|           cd ui | ||||
|           git checkout develop | ||||
|           cp .env.example .env | ||||
|           cp ../vite.config.ts.react ./vite.config.js | ||||
|           sed -i '/"version"/c\  "version": " Latest Build - ${{ env.current_date }}",' package.json | ||||
|           npm i | ||||
|           npm run build | ||||
|           cp -r dist/* ../public/ | ||||
|           mv dist/index.html ../resources/views/react/index.blade.php | ||||
|           mv ../public/index.html ../resources/views/react/index.blade.php | ||||
|            | ||||
|       - name: Prepare JS/CSS assets | ||||
|         run: | | ||||
|  | ||||
							
								
								
									
										44
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								README.md
									
									
									
									
									
								
							| @ -1,5 +1,5 @@ | ||||
| <p align="center"> | ||||
|     <img src="https://raw.githubusercontent.com/hillelcoren/invoice-ninja/master/public/images/round_logo.png" alt="Sublime's custom image"/> | ||||
| <a href ="https://www.youtube.com/watch?v=CxGxXiotv0I" target="_blank" title="Invoice Ninja Overview Video"><img src="https://raw.githubusercontent.com/hillelcoren/invoice-ninja/master/public/images/round_logo.png" alt="Sublime's custom image"/></a> | ||||
| </p> | ||||
| 
 | ||||
|  | ||||
| @ -8,25 +8,30 @@ | ||||
| 
 | ||||
| # Invoice Ninja 5 | ||||
| 
 | ||||
| ## [Hosted](https://www.invoiceninja.com) | [Self-Hosted](https://www.invoiceninja.org) | ||||
| Invoice Ninja Version 5 is here! We've taken the best parts of version 4 and added the most requested features to create an invoicing application like no other. Check the [Invoice Ninja YouTube Channel](https://www.youtube.com/@appinvoiceninja) to get up to speed, or try the [Demo](https://react.invoicing.co/demo) now. | ||||
| 
 | ||||
| Join us on [Slack](http://slack.invoiceninja.com), [Discord](https://discord.gg/ZwEdtfCwXA), [Support Forum](https://forum.invoiceninja.com) | ||||
| **Choose your setup** | ||||
| 
 | ||||
| ## Introduction | ||||
| - [Hosted](https://www.invoiceninja.com): Our hosted version is a Software as a Service (SaaS) solution. You're up and running in under 5 minutes, with no need to worry about hosting or server infrastructure. | ||||
| - [Self-Hosted](https://www.invoiceninja.org): For those who prefer to manage their own hosting and server infrastructure. This version gives you full control and flexibility. | ||||
| 
 | ||||
| Version 5 of Invoice Ninja is here! | ||||
| We took the best parts of version 4 and add the most requested features  | ||||
| to produce a invoicing application like no other.  | ||||
| All Pro and Enterprise features from the hosted app are included in the open-source code. We offer a $30 per year white-label license to remove the Invoice Ninja branding from client-facing parts of the app.   | ||||
| 
 | ||||
| All Pro and Enterprise features from the hosted app are included in the open code. | ||||
| We offer a $30 per year white-label license to remove the Invoice Ninja branding from client facing parts of the app. | ||||
| #### Get social with us | ||||
| 
 | ||||
| * [Videos](https://www.youtube.com/@appinvoiceninja) | ||||
| * [API Documentation](https://api-docs.invoicing.co/) | ||||
| * [APP Documentation](https://invoiceninja.github.io/) | ||||
| * [Support Forum](https://forum.invoiceninja.com) | ||||
| * [Slack](http://slack.invoiceninja.com) | ||||
| * [Discord](https://discord.gg/ZwEdtfCwXA) | ||||
| * [Instagram](https://www.instagram.com/appinvoiceninja) | ||||
| 
 | ||||
| ## Setup | ||||
| #### Documentation | ||||
| 
 | ||||
| * [Invoice Ninja - API](https://api-docs.invoicing.co/) | ||||
| * [Invoice Ninja - Developer Guide](https://invoiceninja.github.io/en/developer-guide/) | ||||
| * [Invoice Ninja - User Guide](https://invoiceninja.github.io/en/user-guide/) | ||||
| * [Invoice Ninja - Self-Hosted Installation Guide](https://invoiceninja.github.io/en/self-host-installation/) | ||||
| 
 | ||||
| ## Installation Options and Clients | ||||
| 
 | ||||
| ### Mobile Apps | ||||
| * [iPhone](https://apps.apple.com/app/id1503970375?platform=iphone) | ||||
| @ -39,22 +44,26 @@ We offer a $30 per year white-label license to remove the Invoice Ninja branding | ||||
| * [Linux - Snap](https://snapcraft.io/invoiceninja) | ||||
| * [Linux - Flatpak](https://flathub.org/apps/com.invoiceninja.InvoiceNinja) | ||||
| 
 | ||||
| ### Installation Options | ||||
| ### Self-Hosted Server Installation  | ||||
| **Note:** The self-hosted options do support the desktop and mobile apps. | ||||
| 
 | ||||
| * [Server or VM](https://invoiceninja.github.io/en/self-host-installation/) | ||||
| * [Docker File](https://hub.docker.com/r/invoiceninja/invoiceninja/) | ||||
| * [Cloudron](https://cloudron.io/store/com.invoiceninja.cloudronapp.html) | ||||
| * [Cloudron](https://www.cloudron.io/store/com.invoiceninja.cloudronapp2.html) | ||||
| * [Softaculous](https://www.softaculous.com/apps/ecommerce/Invoice_Ninja) | ||||
|   | ||||
| ### Recommended Providers | ||||
| * [Stripe](https://stripe.com/) | ||||
| * [Postmark](https://postmarkapp.com/) | ||||
| 
 | ||||
| ## Quick Hosting Setup | ||||
| ## [Advanced] Quick Hosting Setup | ||||
| 
 | ||||
| In addition to the official [Invoice Ninja - Self-Hosted Installation Guide](https://invoiceninja.github.io/en/self-host-installation/) we have a few commands for you. | ||||
| 
 | ||||
| ```sh | ||||
| git clone --single-branch --branch v5-stable https://github.com/invoiceninja/invoiceninja.git | ||||
| cp .env.example .env | ||||
| composer i -o --no-dev | ||||
| php artisan key:generate | ||||
| ``` | ||||
| 
 | ||||
| Please Note:  | ||||
| @ -85,6 +94,7 @@ pass: password | ||||
| ``` | ||||
| ## Developers Guide | ||||
| 
 | ||||
| In addition to the official [Invoice Ninja - Developer Guide](https://invoiceninja.github.io/en/developer-guide/) we've got your back with some insights. | ||||
| 
 | ||||
| ### App Design | ||||
| 
 | ||||
|  | ||||
| @ -1 +1 @@ | ||||
| 5.10.4 | ||||
| 5.10.16 | ||||
| @ -177,7 +177,6 @@ class BackupUpdate extends Command | ||||
|                         $doc_bin = $document->getFile(); | ||||
|                     } catch(\Exception $e) { | ||||
|                         nlog("Exception:: BackupUpdate::" . $e->getMessage()); | ||||
|                         nlog($e->getMessage()); | ||||
|                     } | ||||
| 
 | ||||
|                     if ($doc_bin) { | ||||
|  | ||||
| @ -12,37 +12,38 @@ | ||||
| namespace App\Console\Commands; | ||||
| 
 | ||||
| use App; | ||||
| use App\Models\User; | ||||
| use App\Utils\Ninja; | ||||
| use App\Models\Quote; | ||||
| use App\Models\Client; | ||||
| use App\Models\Credit; | ||||
| use App\Models\Vendor; | ||||
| use App\Models\Account; | ||||
| use App\Models\Company; | ||||
| use App\Models\Contact; | ||||
| use App\Models\Expense; | ||||
| use App\Models\Invoice; | ||||
| use App\Models\Payment; | ||||
| use App\Libraries\MultiDB; | ||||
| use App\Models\CompanyUser; | ||||
| use Illuminate\Support\Str; | ||||
| use App\Models\CompanyToken; | ||||
| use App\Models\ClientContact; | ||||
| use App\Models\CompanyLedger; | ||||
| use App\Models\PurchaseOrder; | ||||
| use App\Models\VendorContact; | ||||
| use App\Models\BankTransaction; | ||||
| use App\Models\QuoteInvitation; | ||||
| use Illuminate\Console\Command; | ||||
| use App\Models\CreditInvitation; | ||||
| use App\Models\RecurringInvoice; | ||||
| use App\Models\InvoiceInvitation; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Mail; | ||||
| use App\Factory\ClientContactFactory; | ||||
| use App\Factory\VendorContactFactory; | ||||
| use App\Jobs\Company\CreateCompanyToken; | ||||
| use App\Libraries\MultiDB; | ||||
| use App\Models\Account; | ||||
| use App\Models\BankTransaction; | ||||
| use App\Models\Client; | ||||
| use App\Models\ClientContact; | ||||
| use App\Models\Company; | ||||
| use App\Models\CompanyLedger; | ||||
| use App\Models\CompanyToken; | ||||
| use App\Models\CompanyUser; | ||||
| use App\Models\Contact; | ||||
| use App\Models\Credit; | ||||
| use App\Models\CreditInvitation; | ||||
| use App\Models\Invoice; | ||||
| use App\Models\InvoiceInvitation; | ||||
| use App\Models\Payment; | ||||
| use App\Models\PurchaseOrder; | ||||
| use App\Models\Quote; | ||||
| use App\Models\QuoteInvitation; | ||||
| use App\Models\RecurringInvoice; | ||||
| use App\Models\RecurringInvoiceInvitation; | ||||
| use App\Models\User; | ||||
| use App\Models\Vendor; | ||||
| use App\Models\VendorContact; | ||||
| use App\Utils\Ninja; | ||||
| use Illuminate\Console\Command; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Mail; | ||||
| use Illuminate\Support\Str; | ||||
| use Symfony\Component\Console\Input\InputOption; | ||||
| 
 | ||||
| /* | ||||
| @ -130,6 +131,7 @@ class CheckData extends Command | ||||
|         $this->checkContactEmailAndSendEmailStatus(); | ||||
|         $this->checkPaymentCurrency(); | ||||
|         $this->checkSubdomainsSet(); | ||||
|         $this->checkExpenseCurrency(); | ||||
| 
 | ||||
|         if (Ninja::isHosted()) { | ||||
|             $this->checkAccountStatuses(); | ||||
| @ -1158,7 +1160,21 @@ class CheckData extends Command | ||||
| 
 | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function checkExpenseCurrency() | ||||
|     { | ||||
|         Expense::with('company') | ||||
|                 ->withTrashed() | ||||
|                 ->whereNull('exchange_rate') | ||||
|                 ->orWhere('exchange_rate', 0) | ||||
|                 ->cursor() | ||||
|                 ->each(function ($expense){ | ||||
|                     $expense->exchange_rate = 1; | ||||
|                     $expense->saveQuietly(); | ||||
|                      | ||||
|                     $this->logMessage("Fixing - exchange rate for expense :: {$expense->id}"); | ||||
| 
 | ||||
|                 }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -385,6 +385,9 @@ class CreateSingleAccount extends Command | ||||
| 
 | ||||
|         }); | ||||
| 
 | ||||
| 
 | ||||
|         $this->countryClients($company, $user); | ||||
| 
 | ||||
|         $this->info("finished"); | ||||
| 
 | ||||
|     } | ||||
| @ -1109,4 +1112,44 @@ class CreateSingleAccount extends Command | ||||
| 
 | ||||
|         event(new RecurringInvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars())); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private function countryClients($company, $user) | ||||
|     { | ||||
|          | ||||
|         Client::unguard(); | ||||
| 
 | ||||
|         Client::create([ | ||||
|             'company_id' => $company->id, | ||||
|             'user_id' => $user->id, | ||||
|             'name' => 'Swiss Company AG', | ||||
|             'website' => 'https://www.testcompany.ch', | ||||
|             'private_notes' => 'These are some private notes about the test client.', | ||||
|             'balance' => 0, | ||||
|             'paid_to_date' => 0, | ||||
|             'vat_number' => '654321987', | ||||
|             'id_number' => 'CH9300762011623852957', // Sample Swiss IBAN
 | ||||
|             'custom_value1' => '2024-07-22 10:00:00', | ||||
|             'custom_value2' => 'blue', | ||||
|             'custom_value3' => 'sampleword', | ||||
|             'custom_value4' => 'test@example.com', | ||||
|             'address1' => '123', | ||||
|             'address2' => 'Test Street 45', | ||||
|             'city' => 'Zurich', | ||||
|             'state' => 'Zurich', | ||||
|             'postal_code' => '8001', | ||||
|             'country_id' => '756', // Switzerland
 | ||||
|             'shipping_address1' => '123', | ||||
|             'shipping_address2' => 'Test Street 45', | ||||
|             'shipping_city' => 'Zurich', | ||||
|             'shipping_state' => 'Zurich', | ||||
|             'shipping_postal_code' => '8001', | ||||
|             'shipping_country_id' => '756', // Switzerland
 | ||||
|             'settings' => ClientSettings::Defaults(), | ||||
|             'client_hash' => \Illuminate\Support\Str::random(32), | ||||
|             'routing_id' => '', | ||||
|         ]); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -62,10 +62,10 @@ class SendRemindersCron extends Command | ||||
|     public function handle() | ||||
|     { | ||||
|         Invoice::where('next_send_date', '<=', now()->toDateTimeString()) | ||||
|                  ->whereNull('deleted_at') | ||||
|                  ->where('is_deleted', 0) | ||||
|                  ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) | ||||
|                  ->where('balance', '>', 0) | ||||
|                 ->whereNull('invoices.deleted_at') | ||||
|                 ->where('invoices.is_deleted', 0) | ||||
|                 ->whereIn('invoices.status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) | ||||
|                 ->where('invoices.balance', '>', 0) | ||||
|                  ->whereHas('client', function ($query) { | ||||
|                      $query->where('is_deleted', 0) | ||||
|                            ->where('deleted_at', null); | ||||
| @ -73,6 +73,7 @@ class SendRemindersCron extends Command | ||||
|                  ->whereHas('company', function ($query) { | ||||
|                      $query->where('is_disabled', 0); | ||||
|                  }) | ||||
| 
 | ||||
|                  ->with('invitations')->cursor()->each(function ($invoice) { | ||||
|                      if ($invoice->isPayable()) { | ||||
|                          $reminder_template = $invoice->calculateTemplate('invoice'); | ||||
|  | ||||
							
								
								
									
										44
									
								
								app/DataMapper/Tax/PL/Rule.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								app/DataMapper/Tax/PL/Rule.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\DataMapper\Tax\PL; | ||||
| 
 | ||||
| use App\DataMapper\Tax\DE\Rule as DERule; | ||||
| 
 | ||||
| class Rule extends DERule | ||||
| { | ||||
|     /** @var string $seller_region */ | ||||
|     public string $seller_region = 'EU'; | ||||
| 
 | ||||
|     /** @var bool $consumer_tax_exempt */ | ||||
|     public bool $consumer_tax_exempt = false; | ||||
| 
 | ||||
|     /** @var bool $business_tax_exempt */ | ||||
|     public bool $business_tax_exempt = false; | ||||
| 
 | ||||
|     /** @var bool $eu_business_tax_exempt */ | ||||
|     public bool $eu_business_tax_exempt = true; | ||||
| 
 | ||||
|     /** @var bool $foreign_business_tax_exempt */ | ||||
|     public bool $foreign_business_tax_exempt = false; | ||||
| 
 | ||||
|     /** @var bool $foreign_consumer_tax_exempt */ | ||||
|     public bool $foreign_consumer_tax_exempt = false; | ||||
| 
 | ||||
|     /** @var float $tax_rate */ | ||||
|     public float $tax_rate = 0; | ||||
| 
 | ||||
|     /** @var float $reduced_tax_rate */ | ||||
|     public float $reduced_tax_rate = 0; | ||||
| 
 | ||||
|     public string $tax_name1 = 'VAT'; | ||||
| 
 | ||||
| } | ||||
| @ -474,6 +474,12 @@ class TaxModel | ||||
|         $this->regions->EU->subregions->NL->reduced_tax_rate = 9; | ||||
|         $this->regions->EU->subregions->NL->apply_tax = false; | ||||
| 
 | ||||
|         $this->regions->EU->subregions->PL = new \stdClass(); | ||||
|         $this->regions->EU->subregions->PL->tax_rate = 23; | ||||
|         $this->regions->EU->subregions->PL->tax_name = 'VAT'; | ||||
|         $this->regions->EU->subregions->PL->reduced_tax_rate = 8; | ||||
|         $this->regions->EU->subregions->PL->apply_tax = false; | ||||
| 
 | ||||
|         $this->regions->EU->subregions->PT = new \stdClass(); | ||||
|         $this->regions->EU->subregions->PT->tax_rate = 23; | ||||
|         $this->regions->EU->subregions->PT->tax_name = 'IVA'; | ||||
|  | ||||
| @ -197,6 +197,10 @@ region: | ||||
|         vat: 21 | ||||
|         reduced_vat: 9 | ||||
|         apply_tax: false | ||||
|       PL: | ||||
|         vat: 23 | ||||
|         reduced_vat: 8 | ||||
|         apply_tax: false | ||||
|       PT: | ||||
|         vat: 23 | ||||
|         reduced_vat: 6 | ||||
|  | ||||
| @ -13,15 +13,21 @@ namespace App\Events\Client; | ||||
| 
 | ||||
| use App\Models\Client; | ||||
| use App\Models\Company; | ||||
| use League\Fractal\Manager; | ||||
| use League\Fractal\Resource\Item; | ||||
| use Illuminate\Broadcasting\Channel; | ||||
| use Illuminate\Broadcasting\InteractsWithSockets; | ||||
| use Illuminate\Foundation\Events\Dispatchable; | ||||
| use App\Transformers\ArraySerializer; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| use App\Transformers\ClientTransformer; | ||||
| use Illuminate\Broadcasting\PrivateChannel; | ||||
| use Illuminate\Foundation\Events\Dispatchable; | ||||
| use Illuminate\Broadcasting\InteractsWithSockets; | ||||
| use Illuminate\Contracts\Broadcasting\ShouldBroadcast; | ||||
| 
 | ||||
| /** | ||||
|  * Class ClientWasArchived. | ||||
|  */ | ||||
| class ClientWasArchived | ||||
| class ClientWasArchived implements ShouldBroadcast | ||||
| { | ||||
|     use Dispatchable; | ||||
|     use InteractsWithSockets; | ||||
| @ -50,13 +56,34 @@ class ClientWasArchived | ||||
|         $this->event_vars = $event_vars; | ||||
|     } | ||||
| 
 | ||||
|     // /**
 | ||||
|     //  * Get the channels the event should broadcast on.
 | ||||
|     //  *
 | ||||
|     //  * @return Channel|array
 | ||||
|     //  */
 | ||||
|     public function broadcastWith() | ||||
|     { | ||||
|          | ||||
|         $manager = new Manager(); | ||||
|         $manager->setSerializer(new ArraySerializer()); | ||||
|         $class = sprintf('App\\Transformers\\%sTransformer', class_basename($this->client)); | ||||
| 
 | ||||
|         $transformer = new $class(); | ||||
| 
 | ||||
|         $resource = new Item($this->client, $transformer, $this->client->getEntityType()); | ||||
|         $data = $manager->createData($resource)->toArray(); | ||||
| 
 | ||||
|         return $data; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the channels the event should broadcast on. | ||||
|      * | ||||
|      * @return Channel|array | ||||
|      */ | ||||
|     public function broadcastOn() | ||||
|     { | ||||
|         return []; | ||||
|          | ||||
|         return [ | ||||
|             new PrivateChannel("company-{$this->company->company_key}"), | ||||
|         ]; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -18,6 +18,7 @@ use Illuminate\Auth\Access\AuthorizationException; | ||||
| use Illuminate\Auth\AuthenticationException; | ||||
| use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException; | ||||
| use Illuminate\Database\Eloquent\RelationNotFoundException; | ||||
| use Illuminate\Encryption\MissingAppKeyException; | ||||
| use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; | ||||
| use Illuminate\Http\Exceptions\ThrottleRequestsException; | ||||
| use Illuminate\Http\Request; | ||||
| @ -94,18 +95,8 @@ class Handler extends ExceptionHandler | ||||
|      */ | ||||
|     public function report(Throwable $exception) | ||||
|     { | ||||
|         if (! Schema::hasTable('accounts')) { | ||||
|             info('account table not found'); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (Ninja::isHosted()) { | ||||
| 
 | ||||
|             // if($exception instanceof ThrottleRequestsException && class_exists(\Modules\Admin\Events\ThrottledExceptionRaised::class)) {
 | ||||
|             // $uri = urldecode(request()->getRequestUri());
 | ||||
|             // event(new \Modules\Admin\Events\ThrottledExceptionRaised(auth()->user()?->account?->key, $uri, request()->ip()));
 | ||||
|             // }
 | ||||
| 
 | ||||
|             Integration::configureScope(function (Scope $scope): void { | ||||
|                 $name = 'hosted@invoiceninja.com'; | ||||
| 
 | ||||
| @ -154,6 +145,10 @@ class Handler extends ExceptionHandler | ||||
|         } | ||||
| 
 | ||||
|         parent::report($exception); | ||||
| 
 | ||||
|         if (Ninja::isSelfHost() && $exception instanceof MissingAppKeyException) { | ||||
|             info('To setup the app run: cp .env.example .env'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function validException($exception) | ||||
|  | ||||
| @ -57,6 +57,7 @@ class ActivityExport extends BaseExport | ||||
| 
 | ||||
|         $report = $query->cursor() | ||||
|             ->map(function ($resource) { | ||||
|                 /** @var \App\Models\Activity $resource */ | ||||
|                 $row = $this->buildActivityRow($resource); | ||||
|                 return $this->processMetaData($row, $resource); | ||||
|             })->toArray(); | ||||
| @ -128,6 +129,9 @@ class ActivityExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|               ->each(function ($entity) { | ||||
|                  | ||||
|                 /** @var \App\Models\Activity $entity */ | ||||
| 
 | ||||
|                   $this->buildRow($entity); | ||||
|               }); | ||||
| 
 | ||||
|  | ||||
| @ -172,6 +172,7 @@ class BaseExport | ||||
|         'tax_rate3' => 'invoice.tax_rate3', | ||||
|         'recurring_invoice' => 'invoice.recurring_id', | ||||
|         'auto_bill' => 'invoice.auto_bill_enabled', | ||||
|         'project' => 'invoice.project', | ||||
|     ]; | ||||
| 
 | ||||
|     protected array $recurring_invoice_report_keys = [ | ||||
| @ -449,6 +450,7 @@ class BaseExport | ||||
|         'status' => 'task.status_id', | ||||
|         'project' => 'task.project_id', | ||||
|         'billable' => 'task.billable', | ||||
|         'item_notes' => 'task.item_notes', | ||||
|     ]; | ||||
| 
 | ||||
|     protected array $forced_client_fields = [ | ||||
| @ -1038,6 +1040,10 @@ class BaseExport | ||||
| 
 | ||||
|         $recurring_filters = []; | ||||
| 
 | ||||
|         if($this->company->getSetting('report_include_drafts')){ | ||||
|             $recurring_filters[] = RecurringInvoice::STATUS_DRAFT; | ||||
|         } | ||||
| 
 | ||||
|         if (in_array('active', $status_parameters)) { | ||||
|             $recurring_filters[] = RecurringInvoice::STATUS_ACTIVE; | ||||
|         } | ||||
|  | ||||
| @ -102,6 +102,8 @@ class ClientExport extends BaseExport | ||||
| 
 | ||||
|         $report = $query->cursor() | ||||
|                 ->map(function ($client) { | ||||
|                      | ||||
|                     /** @var \App\Models\Client $client */ | ||||
|                     $row = $this->buildRow($client); | ||||
|                     return $this->processMetaData($row, $client); | ||||
|                 })->toArray(); | ||||
| @ -154,6 +156,8 @@ class ClientExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|               ->each(function ($client) { | ||||
|                  | ||||
|                 /** @var \App\Models\Client $client */ | ||||
|                   $this->csv->insertOne($this->buildRow($client)); | ||||
|               }); | ||||
| 
 | ||||
|  | ||||
| @ -82,6 +82,7 @@ class ContactExport extends BaseExport | ||||
|         $this->csv->insertOne($this->buildHeader()); | ||||
| 
 | ||||
|         $query->cursor()->each(function ($contact) { | ||||
|             /** @var \App\Models\ClientContact $contact */ | ||||
|             $this->csv->insertOne($this->buildRow($contact)); | ||||
|         }); | ||||
| 
 | ||||
| @ -101,6 +102,7 @@ class ContactExport extends BaseExport | ||||
| 
 | ||||
|         $report = $query->cursor() | ||||
|                 ->map(function ($contact) { | ||||
|                     /** @var \App\Models\ClientContact $contact */ | ||||
|                     $row = $this->buildRow($contact); | ||||
|                     return $this->processMetaData($row, $contact); | ||||
|                 })->toArray(); | ||||
|  | ||||
| @ -52,6 +52,8 @@ class CreditExport extends BaseExport | ||||
| 
 | ||||
|         $report = $query->cursor() | ||||
|                 ->map(function ($credit) { | ||||
|                      | ||||
|                     /** @var \App\Models\Credit $credit */ | ||||
|                     $row = $this->buildRow($credit); | ||||
|                     return $this->processMetaData($row, $credit); | ||||
|                 })->toArray(); | ||||
| @ -139,6 +141,7 @@ class CreditExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|             ->each(function ($credit) { | ||||
|                 /** @var \App\Models\Credit $credit */ | ||||
|                 $this->csv->insertOne($this->buildRow($credit)); | ||||
|             }); | ||||
| 
 | ||||
|  | ||||
| @ -54,6 +54,8 @@ class DocumentExport extends BaseExport | ||||
| 
 | ||||
|         $report = $query->cursor() | ||||
|                 ->map(function ($document) { | ||||
|                      | ||||
|                     /** @var \App\Models\Document $document */ | ||||
|                     $row = $this->buildRow($document); | ||||
|                     return $this->processMetaData($row, $document); | ||||
|                 })->toArray(); | ||||
| @ -99,6 +101,7 @@ class DocumentExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|               ->each(function ($entity) { | ||||
|                     /** @var mixed $entity */ | ||||
|                   $this->csv->insertOne($this->buildRow($entity)); | ||||
|               }); | ||||
| 
 | ||||
|  | ||||
| @ -52,6 +52,8 @@ class ExpenseExport extends BaseExport | ||||
| 
 | ||||
|         $report = $query->cursor() | ||||
|                 ->map(function ($resource) { | ||||
|                      | ||||
|                     /** @var \App\Models\Expense $resource */ | ||||
|                     $row = $this->buildRow($resource); | ||||
|                     return $this->processMetaData($row, $resource); | ||||
|                 })->toArray(); | ||||
| @ -132,6 +134,8 @@ class ExpenseExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|                 ->each(function ($expense) { | ||||
|                      | ||||
|                     /** @var \App\Models\Expense $expense */ | ||||
|                     $this->csv->insertOne($this->buildRow($expense)); | ||||
|                 }); | ||||
| 
 | ||||
|  | ||||
| @ -99,6 +99,8 @@ class InvoiceExport extends BaseExport | ||||
| 
 | ||||
|         $report = $query->cursor() | ||||
|                 ->map(function ($resource) { | ||||
|                      | ||||
|                     /** @var \App\Models\Invoice $resource */ | ||||
|                     $row = $this->buildRow($resource); | ||||
|                     return $this->processMetaData($row, $resource); | ||||
|                 })->toArray(); | ||||
| @ -119,6 +121,8 @@ class InvoiceExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|             ->each(function ($invoice) { | ||||
|                  | ||||
|                 /** @var \App\Models\Invoice $invoice */ | ||||
|                 $this->csv->insertOne($this->buildRow($invoice)); | ||||
|             }); | ||||
| 
 | ||||
| @ -149,9 +153,9 @@ class InvoiceExport extends BaseExport | ||||
|     private function decorateAdvancedFields(Invoice $invoice, array $entity): array | ||||
|     { | ||||
| 
 | ||||
|         // if (in_array('invoice.status', $this->input['report_keys'])) {
 | ||||
|         //     $entity['invoice.status'] = $invoice->stringStatus($invoice->status_id);
 | ||||
|         // }
 | ||||
|         if (in_array('invoice.project', $this->input['report_keys'])) { | ||||
|             $entity['invoice.project'] = $invoice->project ? $invoice->project->name : ''; | ||||
|         } | ||||
| 
 | ||||
|         if (in_array('invoice.recurring_id', $this->input['report_keys'])) { | ||||
|             $entity['invoice.recurring_id'] = $invoice->recurring_invoice->number ?? ''; | ||||
|  | ||||
| @ -113,6 +113,8 @@ class InvoiceItemExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|             ->each(function ($resource) { | ||||
|                  | ||||
|                 /** @var \App\Models\Invoice $resource */ | ||||
|                 $this->iterateItems($resource); | ||||
| 
 | ||||
|                 foreach($this->storage_array as $row) { | ||||
| @ -141,6 +143,8 @@ class InvoiceItemExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|             ->each(function ($invoice) { | ||||
|                  | ||||
|                 /** @var \App\Models\Invoice $invoice */ | ||||
|                 $this->iterateItems($invoice); | ||||
|             }); | ||||
| 
 | ||||
| @ -261,6 +265,10 @@ class InvoiceItemExport extends BaseExport | ||||
|             $entity['invoice.user_id'] = $invoice->user ? $invoice->user->present()->name() : '';// @phpstan-ignore-line
 | ||||
|         } | ||||
| 
 | ||||
|         if (in_array('invoice.project', $this->input['report_keys'])) { | ||||
|             $entity['invoice.project'] = $invoice->project ? $invoice->project->name : '';// @phpstan-ignore-line        
 | ||||
|         } | ||||
|          | ||||
|         return $entity; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -92,6 +92,8 @@ class PaymentExport extends BaseExport | ||||
| 
 | ||||
|         $report = $query->cursor() | ||||
|                 ->map(function ($resource) { | ||||
|                      | ||||
|                     /** @var \App\Models\Payment $resource */ | ||||
|                     $row = $this->buildRow($resource); | ||||
|                     return $this->processMetaData($row, $resource); | ||||
|                 })->toArray(); | ||||
| @ -112,6 +114,8 @@ class PaymentExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|               ->each(function ($entity) { | ||||
|                  | ||||
|                     /** @var \App\Models\Payment $entity */ | ||||
|                   $this->csv->insertOne($this->buildRow($entity)); | ||||
|               }); | ||||
| 
 | ||||
|  | ||||
| @ -51,6 +51,8 @@ class ProductExport extends BaseExport | ||||
| 
 | ||||
|         $report = $query->cursor() | ||||
|                 ->map(function ($resource) { | ||||
|                      | ||||
|                     /** @var \App\Models\Product $resource */ | ||||
|                     $row = $this->buildRow($resource); | ||||
|                     return $this->processMetaData($row, $resource); | ||||
|                 })->toArray(); | ||||
| @ -103,7 +105,9 @@ class ProductExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|               ->each(function ($entity) { | ||||
|                   $this->csv->insertOne($this->buildRow($entity)); | ||||
| 
 | ||||
|                 /** @var \App\Models\Product $entity */ | ||||
|                 $this->csv->insertOne($this->buildRow($entity)); | ||||
|               }); | ||||
| 
 | ||||
|         return $this->csv->toString(); | ||||
|  | ||||
| @ -98,6 +98,8 @@ class PurchaseOrderExport extends BaseExport | ||||
| 
 | ||||
|         $report = $query->cursor() | ||||
|                 ->map(function ($resource) { | ||||
|                      | ||||
|                     /** @var \App\Models\PurchaseOrder $resource */ | ||||
|                     $row = $this->buildRow($resource); | ||||
|                     return $this->processMetaData($row, $resource); | ||||
|                 })->toArray(); | ||||
| @ -119,7 +121,9 @@ class PurchaseOrderExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|             ->each(function ($purchase_order) { | ||||
|                 $this->csv->insertOne($this->buildRow($purchase_order)); | ||||
|                  | ||||
|             /** @var \App\Models\PurchaseOrder $purchase_order */ | ||||
|             $this->csv->insertOne($this->buildRow($purchase_order)); | ||||
|             }); | ||||
| 
 | ||||
|         return $this->csv->toString(); | ||||
|  | ||||
| @ -101,13 +101,15 @@ class PurchaseOrderItemExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|               ->each(function ($resource) { | ||||
|                   $this->iterateItems($resource); | ||||
|                  | ||||
|                 /** @var \App\Models\PurchaseOrder $resource */ | ||||
|                 $this->iterateItems($resource); | ||||
| 
 | ||||
|                   foreach($this->storage_array as $row) { | ||||
|                       $this->storage_item_array[] = $this->processItemMetaData($row, $resource); | ||||
|                   } | ||||
|                 foreach($this->storage_array as $row) { | ||||
|                     $this->storage_item_array[] = $this->processItemMetaData($row, $resource); | ||||
|                 } | ||||
| 
 | ||||
|                   $this->storage_array = []; | ||||
|                 $this->storage_array = []; | ||||
| 
 | ||||
|               }); | ||||
| 
 | ||||
| @ -127,7 +129,9 @@ class PurchaseOrderItemExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|             ->each(function ($purchase_order) { | ||||
|                 $this->iterateItems($purchase_order); | ||||
|                 | ||||
|             /** @var \App\Models\PurchaseOrder $purchase_order */ | ||||
|             $this->iterateItems($purchase_order); | ||||
|             }); | ||||
| 
 | ||||
|         $this->csv->insertAll($this->storage_array); | ||||
|  | ||||
| @ -103,6 +103,8 @@ class QuoteExport extends BaseExport | ||||
| 
 | ||||
|         $report = $query->cursor() | ||||
|                 ->map(function ($resource) { | ||||
| 
 | ||||
|                     /** @var \App\Models\Quote $resource */ | ||||
|                     $row = $this->buildRow($resource); | ||||
|                     return $this->processMetaData($row, $resource); | ||||
|                 })->toArray(); | ||||
| @ -125,6 +127,8 @@ class QuoteExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|             ->each(function ($quote) { | ||||
|                  | ||||
|                 /** @var \App\Models\Quote $quote */ | ||||
|                 $this->csv->insertOne($this->buildRow($quote)); | ||||
|             }); | ||||
| 
 | ||||
|  | ||||
| @ -104,6 +104,8 @@ class QuoteItemExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|             ->each(function ($resource) { | ||||
|                  | ||||
|                 /** @var \App\Models\Quote $resource */ | ||||
|                 $this->iterateItems($resource); | ||||
| 
 | ||||
|                 foreach($this->storage_array as $row) { | ||||
| @ -134,6 +136,8 @@ class QuoteItemExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|             ->each(function ($quote) { | ||||
|                  | ||||
|                 /** @var \App\Models\Quote $quote */ | ||||
|                 $this->iterateItems($quote); | ||||
|             }); | ||||
| 
 | ||||
|  | ||||
| @ -93,6 +93,8 @@ class RecurringInvoiceExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|             ->each(function ($invoice) { | ||||
|                  | ||||
|                 /** @var \App\Models\RecurringInvoice $invoice */ | ||||
|                 $this->csv->insertOne($this->buildRow($invoice)); | ||||
|             }); | ||||
| 
 | ||||
| @ -112,6 +114,8 @@ class RecurringInvoiceExport extends BaseExport | ||||
| 
 | ||||
|         $report = $query->cursor() | ||||
|                 ->map(function ($resource) { | ||||
|                      | ||||
|                     /** @var \App\Models\RecurringInvoice $resource */ | ||||
|                     $row = $this->buildRow($resource); | ||||
|                     return $this->processMetaData($row, $resource); | ||||
|                 })->toArray(); | ||||
|  | ||||
| @ -106,7 +106,9 @@ class TaskExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|               ->each(function ($entity) { | ||||
|                   $this->buildRow($entity); | ||||
|                  | ||||
|                 /** @var \App\Models\Task $entity*/ | ||||
|                 $this->buildRow($entity); | ||||
|               }); | ||||
| 
 | ||||
|         $this->csv->insertAll($this->storage_array); | ||||
| @ -128,6 +130,7 @@ class TaskExport extends BaseExport | ||||
|         $query->cursor() | ||||
|                 ->each(function ($resource) { | ||||
| 
 | ||||
|                     /** @var \App\Models\Task $resource*/ | ||||
|                     $this->buildRow($resource); | ||||
| 
 | ||||
|                     foreach($this->storage_array as $row) { | ||||
| @ -153,7 +156,7 @@ class TaskExport extends BaseExport | ||||
|                 $entity[$key] = $transformed_entity[$parts[1]]; | ||||
|             } elseif (array_key_exists($key, $transformed_entity)) { | ||||
|                 $entity[$key] = $transformed_entity[$key]; | ||||
|             } elseif (in_array($key, ['task.start_date', 'task.end_date', 'task.duration'])) { | ||||
|             } elseif (in_array($key, ['task.start_date', 'task.end_date', 'task.duration', 'task.billable', 'task.item_notes'])) { | ||||
|                 $entity[$key] = ''; | ||||
|             } else { | ||||
|                 $entity[$key] = $this->decorator->transform($key, $task); | ||||
| @ -161,7 +164,7 @@ class TaskExport extends BaseExport | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         if (is_null($task->time_log) || (is_array(json_decode($task->time_log, 1)) && count(json_decode($task->time_log, 1)) == 0)) { | ||||
|         if (is_null($task->time_log) || (is_array(json_decode($task->time_log, true)) && count(json_decode($task->time_log, true)) == 0)) { | ||||
|             $this->storage_array[] = $entity; | ||||
|         } else { | ||||
|             $this->iterateLogs($task, $entity); | ||||
| @ -172,13 +175,13 @@ class TaskExport extends BaseExport | ||||
|     private function iterateLogs(Task $task, array $entity) | ||||
|     { | ||||
|         $timezone = Timezone::find($task->company->settings->timezone_id); | ||||
|         $timezone_name = 'US/Eastern'; | ||||
|         $timezone_name = 'America/New_York'; | ||||
| 
 | ||||
|         if ($timezone) { | ||||
|             $timezone_name = $timezone->name; | ||||
|         } | ||||
| 
 | ||||
|         $logs = json_decode($task->time_log, 1); | ||||
|         $logs = json_decode($task->time_log, true); | ||||
| 
 | ||||
|         $date_format_default = $this->date_format; | ||||
| 
 | ||||
| @ -206,6 +209,14 @@ class TaskExport extends BaseExport | ||||
|                 $entity['task.duration_words'] =  $seconds > 86400 ? CarbonInterval::seconds($seconds)->locale($this->company->locale())->cascade()->forHumans() : now()->startOfDay()->addSeconds($seconds)->format('H:i:s'); | ||||
|             } | ||||
| 
 | ||||
|             if (in_array('task.billable', $this->input['report_keys']) || in_array('billable', $this->input['report_keys'])) { | ||||
|                 $entity['task.billable'] = isset($item[3]) && $item[3] == 'true' ? ctrans('texts.yes') : ctrans('texts.no'); | ||||
|             } | ||||
| 
 | ||||
|             if (in_array('task.item_notes', $this->input['report_keys']) || in_array('item_notes', $this->input['report_keys'])) { | ||||
|                 $entity['task.item_notes'] = isset($item[2]) ? (string)$item[2] : ''; | ||||
|             } | ||||
| 
 | ||||
|             $entity = $this->decorateAdvancedFields($task, $entity); | ||||
| 
 | ||||
|             $this->storage_array[] = $entity; | ||||
| @ -216,6 +227,8 @@ class TaskExport extends BaseExport | ||||
|             $entity['task.end_time'] = ''; | ||||
|             $entity['task.duration'] = ''; | ||||
|             $entity['task.duration_words'] = ''; | ||||
|             $entity['task.billable'] = ''; | ||||
|             $entity['task.item_notes'] = ''; | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -90,6 +90,8 @@ class VendorExport extends BaseExport | ||||
| 
 | ||||
|         $report = $query->cursor() | ||||
|                 ->map(function ($resource) { | ||||
|                      | ||||
|                     /** @var \App\Models\Vendor $resource */ | ||||
|                     $row = $this->buildRow($resource); | ||||
|                     return $this->processMetaData($row, $resource); | ||||
|                 })->toArray(); | ||||
| @ -107,7 +109,9 @@ class VendorExport extends BaseExport | ||||
| 
 | ||||
|         $query->cursor() | ||||
|               ->each(function ($vendor) { | ||||
|                   $this->csv->insertOne($this->buildRow($vendor)); | ||||
|                    | ||||
|                 /** @var \App\Models\Vendor $vendor */ | ||||
|                 $this->csv->insertOne($this->buildRow($vendor)); | ||||
|               }); | ||||
| 
 | ||||
|         return $this->csv->toString(); | ||||
|  | ||||
| @ -92,6 +92,7 @@ class InvoiceDecorator extends Decorator implements DecoratorInterface | ||||
|     { | ||||
|         return $invoice->recurring_invoice ? $invoice->recurring_invoice->number : ''; | ||||
|     } | ||||
| 
 | ||||
|     public function auto_bill_enabled(Invoice $invoice) | ||||
|     { | ||||
|         return $invoice->auto_bill_enabled ? ctrans('texts.yes') : ctrans('texts.no'); | ||||
|  | ||||
| @ -18,6 +18,7 @@ use Carbon\Carbon; | ||||
| 
 | ||||
| class TaskDecorator extends Decorator implements DecoratorInterface | ||||
| { | ||||
|     //@todo - we do not handle iterating through the timelog here.
 | ||||
|     public function transform(string $key, mixed $entity): mixed | ||||
|     { | ||||
|         $task = false; | ||||
| @ -42,13 +43,13 @@ class TaskDecorator extends Decorator implements DecoratorInterface | ||||
|     { | ||||
| 
 | ||||
|         $timezone = Timezone::find($task->company->settings->timezone_id); | ||||
|         $timezone_name = 'US/Eastern'; | ||||
|         $timezone_name = 'America/New_York'; | ||||
| 
 | ||||
|         if ($timezone) { | ||||
|             $timezone_name = $timezone->name; | ||||
|         } | ||||
| 
 | ||||
|         $logs = json_decode($task->time_log, 1); | ||||
|         $logs = json_decode($task->time_log, true); | ||||
| 
 | ||||
|         $date_format_default = 'Y-m-d'; | ||||
| 
 | ||||
| @ -71,13 +72,13 @@ class TaskDecorator extends Decorator implements DecoratorInterface | ||||
|     { | ||||
| 
 | ||||
|         $timezone = Timezone::find($task->company->settings->timezone_id); | ||||
|         $timezone_name = 'US/Eastern'; | ||||
|         $timezone_name = 'America/New_York'; | ||||
| 
 | ||||
|         if ($timezone) { | ||||
|             $timezone_name = $timezone->name; | ||||
|         } | ||||
| 
 | ||||
|         $logs = json_decode($task->time_log, 1); | ||||
|         $logs = json_decode($task->time_log, true); | ||||
| 
 | ||||
|         $date_format_default = 'Y-m-d'; | ||||
| 
 | ||||
| @ -95,6 +96,26 @@ class TaskDecorator extends Decorator implements DecoratorInterface | ||||
|         return ''; | ||||
| 
 | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * billable | ||||
|      * | ||||
|      * @todo | ||||
|      */ | ||||
|     public function billable(Task $task) | ||||
|     { | ||||
|         return ''; | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * items_notes | ||||
|      * @todo | ||||
|      */ | ||||
|     public function items_notes(Task $task) | ||||
|     { | ||||
|         return ''; | ||||
|     } | ||||
|      | ||||
|     public function duration(Task $task) | ||||
|     { | ||||
|         return $task->calcDuration(); | ||||
|  | ||||
| @ -175,7 +175,7 @@ class RecurringExpenseToExpenseFactory | ||||
| 
 | ||||
|                     $_value = explode($_operation, $right); // [MONTHYEAR, 4]
 | ||||
| 
 | ||||
|                     $_right = Carbon::createFromDate(now()->year, now()->month)->addMonths($_value[1])->translatedFormat('F Y'); | ||||
|                     $_right = Carbon::createFromDate(now()->year, now()->month)->addMonths($_value[1])->translatedFormat('F Y'); //@phpstan-ignore-line
 | ||||
|                 } | ||||
| 
 | ||||
|                 $replacement = sprintf('%s to %s', $_left, $_right); | ||||
|  | ||||
| @ -97,7 +97,8 @@ class CreditFilters extends QueryFilters | ||||
|                               $q->where('first_name', 'like', '%'.$filter.'%') | ||||
|                                 ->orWhere('last_name', 'like', '%'.$filter.'%') | ||||
|                                 ->orWhere('email', 'like', '%'.$filter.'%'); | ||||
|                           }); | ||||
|                           }) | ||||
|                           ->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -124,7 +124,8 @@ class InvoiceFilters extends QueryFilters | ||||
|                               $q->where('first_name', 'like', '%'.$filter.'%') | ||||
|                                 ->orWhere('last_name', 'like', '%'.$filter.'%') | ||||
|                                 ->orWhere('email', 'like', '%'.$filter.'%'); | ||||
|                           }); | ||||
|                           }) | ||||
|                           ->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| @ -152,22 +153,22 @@ class InvoiceFilters extends QueryFilters | ||||
|     { | ||||
| 
 | ||||
|         return $this->builder->where(function ($query) { | ||||
|             $query->whereIn('invoices.status_id', [Invoice::STATUS_PARTIAL, Invoice::STATUS_SENT]) | ||||
|             ->where('invoices.is_deleted', 0) | ||||
|             ->where('invoices.balance', '>', 0) | ||||
|             ->orWhere(function ($query) { | ||||
|             $query->whereIn('status_id', [Invoice::STATUS_PARTIAL, Invoice::STATUS_SENT]) | ||||
|             ->where('is_deleted', 0) | ||||
|             ->where('balance', '>', 0) | ||||
|             ->where(function ($query) { | ||||
| 
 | ||||
|                 $query->whereNull('invoices.due_date') | ||||
|                 $query->whereNull('due_date') | ||||
|                     ->orWhere(function ($q) { | ||||
|                         $q->where('invoices.due_date', '>=', now()->startOfDay()->subSecond())->where('invoices.partial', 0); | ||||
|                         $q->where('due_date', '>=', now()->startOfDay()->subSecond())->where('partial', 0); | ||||
|                     }) | ||||
|                     ->orWhere(function ($q) { | ||||
|                         $q->where('invoices.partial_due_date', '>=', now()->startOfDay()->subSecond())->where('invoices.partial', '>', 0); | ||||
|                         $q->where('partial_due_date', '>=', now()->startOfDay()->subSecond())->where('partial', '>', 0); | ||||
|                     }); | ||||
| 
 | ||||
|             }) | ||||
|             ->orderByRaw('ISNULL(invoices.due_date), invoices.due_date ' . 'desc') | ||||
|             ->orderByRaw('ISNULL(invoices.partial_due_date), invoices.partial_due_date ' . 'desc'); | ||||
|             ->orderByRaw('ISNULL(due_date), due_date ' . 'desc') | ||||
|             ->orderByRaw('ISNULL(partial_due_date), partial_due_date ' . 'desc'); | ||||
|         }); | ||||
| 
 | ||||
|     } | ||||
| @ -320,7 +321,7 @@ class InvoiceFilters extends QueryFilters | ||||
|     { | ||||
|         $sort_col = explode('|', $sort); | ||||
| 
 | ||||
|         if (!is_array($sort_col) || count($sort_col) != 2) { | ||||
|         if (!is_array($sort_col) || count($sort_col) != 2 || in_array($sort_col[0], ['documents'])) { | ||||
|             return $this->builder; | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -45,7 +45,8 @@ class QuoteFilters extends QueryFilters | ||||
|                       $q->where('first_name', 'like', '%'.$filter.'%') | ||||
|                         ->orWhere('last_name', 'like', '%'.$filter.'%') | ||||
|                         ->orWhere('email', 'like', '%'.$filter.'%'); | ||||
|                   }); | ||||
|                   }) | ||||
|                   ->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -48,7 +48,8 @@ class RecurringInvoiceFilters extends QueryFilters | ||||
|                       $q->where('first_name', 'like', '%'.$filter.'%') | ||||
|                         ->orWhere('last_name', 'like', '%'.$filter.'%') | ||||
|                         ->orWhere('email', 'like', '%'.$filter.'%'); | ||||
|                   }); | ||||
|                   }) | ||||
|                   ->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -171,7 +171,7 @@ class TransactionTransformer implements BankRevenueInterface | ||||
|     private function formatDate(string $input) | ||||
|     { | ||||
|         $timezone = Timezone::find($this->company->settings->timezone_id); | ||||
|         $timezone_name = 'US/Eastern'; | ||||
|         $timezone_name = 'America/New_York'; | ||||
| 
 | ||||
|         if ($timezone) { | ||||
|             $timezone_name = $timezone->name; | ||||
|  | ||||
| @ -27,7 +27,7 @@ function nlog($output, $context = []): void | ||||
|     } | ||||
| 
 | ||||
|     if (gettype($output) == 'object') { | ||||
|         $output = print_r($output, 1); | ||||
|         $output = print_r($output, true); | ||||
|     } | ||||
| 
 | ||||
|     // $trace = debug_backtrace();
 | ||||
| @ -53,7 +53,7 @@ function nrlog($output, $context = []): void | ||||
|     } | ||||
| 
 | ||||
|     if (gettype($output) == 'object') { | ||||
|         $output = print_r($output, 1); | ||||
|         $output = print_r($output, true); | ||||
|     } | ||||
| 
 | ||||
|     // $trace = debug_backtrace();
 | ||||
|  | ||||
| @ -411,7 +411,7 @@ class InvoiceItemSumInclusive | ||||
| 
 | ||||
|             $this->rule = new $class(); | ||||
| 
 | ||||
|             if($this->rule->regionWithNoTaxCoverage($this->client->country->iso_3166_2)) { | ||||
|             if($this->rule->regionWithNoTaxCoverage($this->client->country->iso_3166_2 ?? false)) { | ||||
|                 return $this; | ||||
|             } | ||||
| 
 | ||||
|  | ||||
| @ -107,16 +107,16 @@ class InvoiceSumInclusive | ||||
|     private function calculateCustomValues() | ||||
|     { | ||||
| 
 | ||||
|         $this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge1, $this->invoice->custom_surcharge_tax1); | ||||
|         // $this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge1, $this->invoice->custom_surcharge_tax1);
 | ||||
|         $this->total_custom_values += $this->valuer($this->invoice->custom_surcharge1); | ||||
| 
 | ||||
|         $this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge2, $this->invoice->custom_surcharge_tax2); | ||||
|         // $this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge2, $this->invoice->custom_surcharge_tax2);
 | ||||
|         $this->total_custom_values += $this->valuer($this->invoice->custom_surcharge2); | ||||
| 
 | ||||
|         $this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge3, $this->invoice->custom_surcharge_tax3); | ||||
|         // $this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge3, $this->invoice->custom_surcharge_tax3);
 | ||||
|         $this->total_custom_values += $this->valuer($this->invoice->custom_surcharge3); | ||||
| 
 | ||||
|         $this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge4, $this->invoice->custom_surcharge_tax4); | ||||
|         // $this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge4, $this->invoice->custom_surcharge_tax4);
 | ||||
|         $this->total_custom_values += $this->valuer($this->invoice->custom_surcharge4); | ||||
| 
 | ||||
|         $this->total += $this->total_custom_values; | ||||
| @ -137,21 +137,21 @@ class InvoiceSumInclusive | ||||
|         } | ||||
| 
 | ||||
|         //Handles cases where the surcharge is not taxed
 | ||||
|         // if(is_numeric($this->invoice->custom_surcharge1) && $this->invoice->custom_surcharge1 > 0 && !$this->invoice->custom_surcharge_tax1) {
 | ||||
|         //     $amount += $this->invoice->custom_surcharge1;
 | ||||
|         // }
 | ||||
|         if(is_numeric($this->invoice->custom_surcharge1) && $this->invoice->custom_surcharge1 > 0 && $this->invoice->custom_surcharge_tax1) { | ||||
|             $amount += $this->invoice->custom_surcharge1; | ||||
|         } | ||||
| 
 | ||||
|         // if(is_numeric($this->invoice->custom_surcharge2) && $this->invoice->custom_surcharge2 > 0 && !$this->invoice->custom_surcharge_tax2) {
 | ||||
|         //     $amount += $this->invoice->custom_surcharge2;
 | ||||
|         // }
 | ||||
|         if(is_numeric($this->invoice->custom_surcharge2) && $this->invoice->custom_surcharge2 > 0 && $this->invoice->custom_surcharge_tax2) { | ||||
|             $amount += $this->invoice->custom_surcharge2; | ||||
|         } | ||||
| 
 | ||||
|         // if(is_numeric($this->invoice->custom_surcharge3) && $this->invoice->custom_surcharge3 > 0 && !$this->invoice->custom_surcharge_tax3) {
 | ||||
|         //     $amount += $this->invoice->custom_surcharge3;
 | ||||
|         // }
 | ||||
|         if(is_numeric($this->invoice->custom_surcharge3) && $this->invoice->custom_surcharge3 > 0 && $this->invoice->custom_surcharge_tax3) { | ||||
|             $amount += $this->invoice->custom_surcharge3; | ||||
|         } | ||||
| 
 | ||||
|         // if(is_numeric($this->invoice->custom_surcharge4) && $this->invoice->custom_surcharge4 > 0 && !$this->invoice->custom_surcharge_tax4) {
 | ||||
|         //     $amount += $this->invoice->custom_surcharge4;
 | ||||
|         // }
 | ||||
|         if(is_numeric($this->invoice->custom_surcharge4) && $this->invoice->custom_surcharge4 > 0 && $this->invoice->custom_surcharge_tax4) { | ||||
|             $amount += $this->invoice->custom_surcharge4; | ||||
|         } | ||||
| 
 | ||||
|         if (is_string($this->invoice->tax_name1) && strlen($this->invoice->tax_name1) > 1) { | ||||
|             $tax = $this->calcInclusiveLineTax($this->invoice->tax_rate1, $amount); | ||||
|  | ||||
| @ -31,7 +31,7 @@ class GmailTransport extends AbstractTransport | ||||
|     protected function doSend(SentMessage $message): void | ||||
|     { | ||||
|         nlog("In Do Send"); | ||||
|         $message = MessageConverter::toEmail($message->getOriginalMessage()); | ||||
|         $message = MessageConverter::toEmail($message->getOriginalMessage()); //@phpstan-ignore-line
 | ||||
| 
 | ||||
|         /** @phpstan-ignore-next-line **/ | ||||
|         $token = $message->getHeaders()->get('gmailtoken')->getValue(); // @phpstan-ignore-line
 | ||||
|  | ||||
| @ -25,7 +25,8 @@ class Office365MailTransport extends AbstractTransport | ||||
| 
 | ||||
|     protected function doSend(SentMessage $message): void | ||||
|     { | ||||
|         $symfony_message = MessageConverter::toEmail($message->getOriginalMessage()); | ||||
|         $symfony_message = MessageConverter::toEmail($message->getOriginalMessage()); //@phpstan-ignore-line
 | ||||
| 
 | ||||
| 
 | ||||
|         $graph = new Graph(); | ||||
| 
 | ||||
|  | ||||
| @ -115,13 +115,8 @@ class AccountController extends BaseController | ||||
| 
 | ||||
|     public function update(UpdateAccountRequest $request, Account $account) | ||||
|     { | ||||
|         $fi = new \FilesystemIterator(public_path('react'), \FilesystemIterator::SKIP_DOTS); | ||||
| 
 | ||||
|         if (iterator_count($fi) < 30) { | ||||
|             return response()->json(['message' => 'React App Not Installed, Please install the React app before attempting to switch.'], 400); | ||||
|         } | ||||
| 
 | ||||
|         $account->fill($request->all()); | ||||
|         $account->set_react_as_default_ap = $request->input('set_react_as_default_ap'); | ||||
|         $account->save(); | ||||
| 
 | ||||
|         $this->entity_type = Account::class; | ||||
|  | ||||
| @ -376,6 +376,7 @@ class LoginController extends BaseController | ||||
|         /** @var \App\Models\User $user */ | ||||
|         $user = auth()->user(); | ||||
| 
 | ||||
|         /** @var Builder $cu */ | ||||
|         $cu = CompanyUser::query()->where('user_id', $user->id); | ||||
| 
 | ||||
|         if ($cu->count() == 0) { | ||||
| @ -398,18 +399,15 @@ class LoginController extends BaseController | ||||
|         $truth->setCompany($set_company); | ||||
| 
 | ||||
|         //21-03-2024
 | ||||
|          | ||||
|          | ||||
|         $cu->each(function ($cu) { | ||||
|             if(CompanyToken::where('company_id', $cu->company_id)->where('user_id', $cu->user_id)->where('is_system', true)->doesntExist()) { | ||||
|             /** @var \App\Models\CompanyUser $cu */ | ||||
|             if(CompanyToken::query()->where('company_id', $cu->company_id)->where('user_id', $cu->user_id)->where('is_system', true)->doesntExist()) { | ||||
|                 (new CreateCompanyToken($cu->company, $cu->user, request()->server('HTTP_USER_AGENT')))->handle(); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         // $user->account->companies->each(function ($company) use ($user) {
 | ||||
|         //     if ($company->tokens()->where('user_id',$user->id)->where('is_system', true)->count() == 0) {
 | ||||
|         //         (new CreateCompanyToken($company, $user, request()->server('HTTP_USER_AGENT')))->handle();
 | ||||
|         //     }
 | ||||
|         // });
 | ||||
| 
 | ||||
|         $truth->setCompanyToken(CompanyToken::where('user_id', $user->id)->where('company_id', $set_company->id)->where('is_system', true)->first()); | ||||
| 
 | ||||
|         return CompanyUser::query()->where('user_id', $user->id); | ||||
|  | ||||
| @ -97,7 +97,7 @@ class YodleeController extends BaseController | ||||
|         } | ||||
| 
 | ||||
|         $company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->each(function ($bank_integration) use ($company) { // TODO: filter to yodlee only
 | ||||
|             ProcessBankTransactionsYodlee::dispatch($company->account->id, $bank_integration); | ||||
|             ProcessBankTransactionsYodlee::dispatch($company->account->bank_integration_account_id, $bank_integration); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -197,6 +197,7 @@ class BankIntegrationController extends BaseController | ||||
|         /** @var \App\Models\User $user */ | ||||
|         $user = auth()->user(); | ||||
| 
 | ||||
|         /** @var \App\Models\Account $user_account */ | ||||
|         $user_account = $user->account; | ||||
| 
 | ||||
|         $this->refreshAccountsYodlee($user); | ||||
| @ -210,12 +211,14 @@ class BankIntegrationController extends BaseController | ||||
|         // Processing transactions for each bank account
 | ||||
|         if (Ninja::isHosted() && $user->account->bank_integration_account_id) { | ||||
|             $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->each(function ($bank_integration) use ($user_account) { | ||||
|                 ProcessBankTransactionsYodlee::dispatch($user_account->id, $bank_integration); | ||||
|                 /** @var \App\Models\BankIntegration $bank_integration */ | ||||
|                 ProcessBankTransactionsYodlee::dispatch($user_account->bank_integration_account_id, $bank_integration); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         if (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key') && (Ninja::isSelfHost() || (Ninja::isHosted() && $user_account->isEnterprisePaidClient()))) { | ||||
|             $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function ($bank_integration) { | ||||
|             $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function ($bank_integration) {                 | ||||
|                 /** @var \App\Models\BankIntegration $bank_integration */ | ||||
|                 ProcessBankTransactionsNordigen::dispatch($bank_integration); | ||||
|             }); | ||||
|         } | ||||
| @ -345,7 +348,7 @@ class BankIntegrationController extends BaseController | ||||
| 
 | ||||
|         if (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise') { | ||||
|             $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { | ||||
|                 (new ProcessBankTransactionsYodlee($account->id, $bank_integration))->handle(); | ||||
|                 (new ProcessBankTransactionsYodlee($account->bank_integration_account_id, $bank_integration))->handle(); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -35,6 +35,7 @@ use League\Fractal\Resource\Item; | ||||
| use App\Models\BankTransactionRule; | ||||
| use Illuminate\Support\Facades\Auth; | ||||
| use App\Transformers\ArraySerializer; | ||||
| use Illuminate\Support\Facades\Schema as DbSchema; | ||||
| use App\Transformers\EntityTransformer; | ||||
| use League\Fractal\Resource\Collection; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| @ -653,7 +654,7 @@ class BaseController extends Controller | ||||
|     /** | ||||
|      * Passes back the miniloaded data response | ||||
|      * | ||||
|      * @param  Builder $query | ||||
|      * @param  mixed $query | ||||
|      * | ||||
|      */ | ||||
|     protected function timeConstrainedResponse($query) | ||||
| @ -894,11 +895,7 @@ class BaseController extends Controller | ||||
|             $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); | ||||
|         } | ||||
| 
 | ||||
|         // else {
 | ||||
|         //     $resource = new Collection($query, $transformer, $this->entity_type);
 | ||||
|         // }
 | ||||
| 
 | ||||
|         return $this->response($this->manager->createData($resource)->toArray()); | ||||
|         return $this->response($this->manager->createData($resource)->toArray()); //@phpstan-ignore-line
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -1106,7 +1103,7 @@ class BaseController extends Controller | ||||
|     public function flutterRoute() | ||||
|     { | ||||
| 
 | ||||
|         if ((bool) $this->checkAppSetup() !== false && $account = Account::first()) { | ||||
|         if ((bool) $this->checkAppSetup() !== false && DbSchema::hasTable('accounts') && $account = Account::first()) { | ||||
| 
 | ||||
|             /** @var \App\Models\Account $account */ | ||||
| 
 | ||||
|  | ||||
| @ -66,7 +66,7 @@ class ChartController extends BaseController | ||||
|         return response()->json($cs->chart_summary($request->input('start_date'), $request->input('end_date')), 200); | ||||
|     } | ||||
| 
 | ||||
|     public function calculatedField(ShowCalculatedFieldRequest $request) | ||||
|     public function calculatedFields(ShowCalculatedFieldRequest $request) | ||||
|     { | ||||
| 
 | ||||
|         /** @var \App\Models\User auth()->user() */ | ||||
|  | ||||
| @ -300,7 +300,9 @@ class InvitationController extends Controller | ||||
|                 'signature' => false, | ||||
|                 'contact_first_name' => $invitation->contact->first_name ?? '', | ||||
|                 'contact_last_name' => $invitation->contact->last_name ?? '', | ||||
|                 'contact_email' => $invitation->contact->email ?? '' | ||||
|                 'contact_email' => $invitation->contact->email ?? '', | ||||
|                 'client_city' => $invitation->client->city ?? '', | ||||
|                 'client_postal_code' => $invitation->client->postal_code ?? '', | ||||
|             ]; | ||||
| 
 | ||||
|             $request->replace($data); | ||||
|  | ||||
| @ -108,11 +108,11 @@ class PaymentController extends Controller | ||||
|      */ | ||||
|     public function process(Request $request) | ||||
|     { | ||||
|         $request->validate([ | ||||
|             'contact_first_name' => ['required'], | ||||
|             'contact_last_name' => ['required'], | ||||
|             'contact_email' => ['required', 'email'], | ||||
|         ]); | ||||
|         // $request->validate([
 | ||||
|         //     'contact_first_name' => ['required'],
 | ||||
|         //     'contact_last_name' => ['required'],
 | ||||
|         //     'contact_email' => ['required', 'email'],
 | ||||
|         // ]);
 | ||||
| 
 | ||||
|         return (new InstantPayment($request))->run(); | ||||
|     } | ||||
| @ -209,7 +209,7 @@ class PaymentController extends Controller | ||||
| 
 | ||||
|         if (property_exists($payment_hash->data, 'billing_context')) { | ||||
|             $billing_subscription = \App\Models\Subscription::find($this->decodePrimaryKey($payment_hash->data->billing_context->subscription_id)); | ||||
| 
 | ||||
|             /** @var \App\Models\Subscription $billing_subscription */ | ||||
|             return (new SubscriptionService($billing_subscription))->completePurchase($payment_hash); | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -183,7 +183,7 @@ class DocumentController extends BaseController | ||||
|         } | ||||
| 
 | ||||
|         if ($action == 'download') { | ||||
|             ZipDocuments::dispatch($documents->pluck('id'), $user->company(), auth()->user()); | ||||
|             ZipDocuments::dispatch($documents->pluck('id'), $user->company(), auth()->user()); //@phpstan-ignore-line
 | ||||
| 
 | ||||
|             return response()->json(['message' => ctrans('texts.sent_message')], 200); | ||||
|         } | ||||
|  | ||||
| @ -85,7 +85,7 @@ class ImportController extends Controller | ||||
|             $contents = $this->convertEncoding($contents); | ||||
| 
 | ||||
|             // Store the csv in cache with an expiry of 10 minutes
 | ||||
|             Cache::put($hash.'-'.$entityType, base64_encode($contents), 600); | ||||
|             Cache::put($hash.'-'.$entityType, base64_encode($contents), 1200); | ||||
| 
 | ||||
|             // Parse CSV
 | ||||
|             $csv_array = $this->getCsvData($contents); | ||||
|  | ||||
| @ -267,7 +267,7 @@ class MigrationController extends BaseController | ||||
| 
 | ||||
|         if ($request->companies) { | ||||
|             //handle Laravel 5.5 UniHTTP
 | ||||
|             $companies = json_decode($request->companies, 1); | ||||
|             $companies = json_decode($request->companies, true); | ||||
|         } else { | ||||
|             //handle Laravel 6 Guzzle
 | ||||
|             $companies = []; | ||||
| @ -275,7 +275,7 @@ class MigrationController extends BaseController | ||||
|             foreach ($request->all() as $input) { | ||||
|                 if ($input instanceof UploadedFile) { | ||||
|                 } else { | ||||
|                     $companies[] = json_decode($input, 1); | ||||
|                     $companies[] = json_decode($input, true); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @ -24,8 +24,9 @@ class PaymentNotificationWebhookController extends Controller | ||||
|     public function __invoke(PaymentNotificationWebhookRequest $request, string $company_key, string $company_gateway_id, string $client_hash) | ||||
|     { | ||||
|         /** @var \App\Models\CompanyGateway $company_gateway */ | ||||
| 
 | ||||
|         $company_gateway = CompanyGateway::find($this->decodePrimaryKey($company_gateway_id)); | ||||
|          | ||||
|         /** @var \App\Models\Client $client */ | ||||
|         $client = Client::find($this->decodePrimaryKey($client_hash)); | ||||
| 
 | ||||
|         return $company_gateway | ||||
|  | ||||
| @ -81,4 +81,34 @@ class PingController extends BaseController | ||||
| 
 | ||||
|         return response()->json(SystemHealth::check(), 200); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the last error from storage/logs/laravel.log | ||||
|      * | ||||
|      * @return Response| \Illuminate\Http\JsonResponse | ||||
|      * | ||||
|      * @OA\Get( | ||||
|      *      path="/api/v1/last_error", | ||||
|      *      operationId="getLastError", | ||||
|      *      tags={"last_error"}, | ||||
|      *      summary="Get the last error from storage/logs/laravel.log", | ||||
|      *      description="Get the last error from storage/logs/laravel.log", | ||||
|      *      @OA\Parameter(ref="#/components/parameters/X-Requested-With"), | ||||
|      *      @OA\Response( | ||||
|      *          response=200, | ||||
|      *          description="The last error from the logs", | ||||
|      *          @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"), | ||||
|      *       ) | ||||
|      *     ) | ||||
|      */ | ||||
|     public function lastError() | ||||
|     { | ||||
|         if (Ninja::isNinja() || ! auth()->user()->isAdmin()) { | ||||
|             return response()->json(['message' => ctrans('texts.route_not_available'), 'errors' => []], 403); | ||||
|         } | ||||
| 
 | ||||
|         return response()->json(['last_error' => SystemHealth::lastError()], 200); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -125,7 +125,7 @@ class PreviewController extends BaseController | ||||
| 
 | ||||
|         $response = Response::make($pdf, 200); | ||||
|         $response->header('Content-Type', 'application/pdf'); | ||||
|         $response->header('Server-Timing', microtime(true) - $start); | ||||
|         $response->header('Server-Timing', (string) (microtime(true) - $start)); | ||||
| 
 | ||||
|         return $response; | ||||
|     } | ||||
| @ -288,7 +288,7 @@ class PreviewController extends BaseController | ||||
|         /** @var \App\Models\Company $company */ | ||||
|         $company = $user->company(); | ||||
| 
 | ||||
|         $design_object = json_decode(json_encode(request()->input('design')), 1); | ||||
|         $design_object = json_decode(json_encode(request()->input('design')), true); | ||||
| 
 | ||||
|         $ts = (new TemplateService()); | ||||
| 
 | ||||
|  | ||||
| @ -81,14 +81,20 @@ class SearchController extends Controller | ||||
|         $invoices = Invoice::query() | ||||
|                      ->company() | ||||
|                      ->with('client') | ||||
|                      ->where('is_deleted', 0) | ||||
|                      ->whereHas('client', function ($q) { | ||||
|                          $q->where('is_deleted', 0); | ||||
|                      }) | ||||
|                      ->where('invoices.is_deleted', 0) | ||||
|                     //  ->whereHas('client', function ($q) {
 | ||||
|                     //      $q->where('is_deleted', 0);
 | ||||
|                     //  })
 | ||||
|                  | ||||
|                     ->leftJoin('clients', function ($join) { | ||||
|                         $join->on('invoices.client_id', '=', 'clients.id') | ||||
|                             ->where('clients.is_deleted', 0); | ||||
|                     }) | ||||
|                       | ||||
|                      ->when(!$user->hasPermission('view_all') || !$user->hasPermission('view_invoice'), function ($query) use ($user) { | ||||
|                          $query->where('user_id', $user->id); | ||||
|                          $query->where('invoices.user_id', $user->id); | ||||
|                      }) | ||||
|                      ->orderBy('id', 'desc') | ||||
|                      ->orderBy('invoices.id', 'desc') | ||||
|                     ->take(3000) | ||||
|                     ->get(); | ||||
| 
 | ||||
|  | ||||
| @ -181,6 +181,9 @@ class SelfUpdateController extends BaseController | ||||
| 
 | ||||
|     public function checkVersion() | ||||
|     { | ||||
|         if(Ninja::isHosted()) | ||||
|             return '5.10.SaaS'; | ||||
| 
 | ||||
|         return trim(file_get_contents(config('ninja.version_url'))); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -43,7 +43,7 @@ class SetupController extends Controller | ||||
| 
 | ||||
|     public function index() | ||||
|     { | ||||
|         $check = SystemHealth::check(false); | ||||
|         $check = SystemHealth::check(false, false); | ||||
| 
 | ||||
|         if ($check['system_health'] == true && $check['simple_db_check'] && Schema::hasTable('accounts') && $account = Account::first()) { | ||||
|             return redirect('/'); | ||||
| @ -59,7 +59,7 @@ class SetupController extends Controller | ||||
|     public function doSetup(StoreSetupRequest $request) | ||||
|     { | ||||
|         try { | ||||
|             $check = SystemHealth::check(false); | ||||
|             $check = SystemHealth::check(false, false); | ||||
|         } catch (Exception $e) { | ||||
|             nlog(['message' => $e->getMessage(), 'action' => 'SetupController::doSetup()']); | ||||
| 
 | ||||
| @ -145,6 +145,7 @@ class SetupController extends Controller | ||||
| 
 | ||||
|             Artisan::call('config:clear'); | ||||
| 
 | ||||
|             Artisan::call('key:generate', ['--force' => true]); | ||||
| 
 | ||||
|             Artisan::call('migrate', ['--force' => true]); | ||||
|             Artisan::call('db:seed', ['--force' => true]); | ||||
|  | ||||
| @ -11,13 +11,14 @@ | ||||
| 
 | ||||
| namespace App\Http\Requests\Client; | ||||
| 
 | ||||
| use App\DataMapper\CompanySettings; | ||||
| use App\Http\Requests\Request; | ||||
| use App\Http\ValidationRules\ValidClientGroupSettingsRule; | ||||
| use App\Utils\Traits\ChecksEntityStatus; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use Illuminate\Support\Facades\Cache; | ||||
| use Illuminate\Validation\Rule; | ||||
| use App\DataMapper\CompanySettings; | ||||
| use Illuminate\Support\Facades\Cache; | ||||
| use App\Utils\Traits\ChecksEntityStatus; | ||||
| use App\Http\ValidationRules\EInvoice\ValidClientScheme; | ||||
| use App\Http\ValidationRules\ValidClientGroupSettingsRule; | ||||
| 
 | ||||
| class UpdateClientRequest extends Request | ||||
| { | ||||
| @ -66,6 +67,8 @@ class UpdateClientRequest extends Request | ||||
|         $rules['id_number'] = ['sometimes', 'bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)->ignore($this->client->id)]; | ||||
|         $rules['number'] = ['sometimes', 'bail', Rule::unique('clients')->where('company_id', $user->company()->id)->ignore($this->client->id)]; | ||||
| 
 | ||||
|         $rules['e_invoice'] = ['sometimes','nullable', new ValidClientScheme()]; | ||||
| 
 | ||||
|         $rules['settings'] = new ValidClientGroupSettingsRule(); | ||||
|         $rules['contacts'] = 'array'; | ||||
|         $rules['contacts.*.email'] = 'bail|nullable|distinct|sometimes|email'; | ||||
|  | ||||
| @ -29,7 +29,7 @@ class CreatePaymentMethodRequest extends FormRequest | ||||
|         $available_methods = []; | ||||
| 
 | ||||
|         collect($client->service()->getPaymentMethods(-1)) | ||||
|             ->filter(function ($method) use (&$available_methods) { | ||||
|             ->filter(function ($method) use (&$available_methods) { //@phpstan-ignore-line
 | ||||
|                 $available_methods[] = $method['gateway_type_id']; | ||||
|             }); | ||||
| 
 | ||||
|  | ||||
| @ -14,8 +14,9 @@ namespace App\Http\Requests\Company; | ||||
| use App\DataMapper\CompanySettings; | ||||
| use App\Http\Requests\Request; | ||||
| use App\Http\ValidationRules\Company\ValidExpenseMailbox; | ||||
| use App\Http\ValidationRules\Company\ValidSubdomain; | ||||
| use App\Http\ValidationRules\ValidSettingsRule; | ||||
| use App\Http\ValidationRules\EInvoice\ValidCompanyScheme; | ||||
| use App\Http\ValidationRules\Company\ValidSubdomain; | ||||
| use App\Utils\Ninja; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| 
 | ||||
| @ -65,7 +66,7 @@ class UpdateCompanyRequest extends Request | ||||
|         $rules['smtp_local_domain'] = 'sometimes|string|nullable'; | ||||
|         // $rules['smtp_verify_peer'] = 'sometimes|string';
 | ||||
| 
 | ||||
|         // $rules['e_invoice'] = ['sometimes','nullable', new ValidScheme()];
 | ||||
|         $rules['e_invoice'] = ['sometimes', 'nullable', new ValidCompanyScheme()]; | ||||
| 
 | ||||
|         if (isset($input['portal_mode']) && ($input['portal_mode'] == 'domain' || $input['portal_mode'] == 'iframe')) { | ||||
|             $rules['portal_domain'] = 'bail|nullable|sometimes|url'; | ||||
|  | ||||
| @ -48,7 +48,7 @@ class StoreCompanyGatewayRequest extends Request | ||||
|     { | ||||
|         $input = $this->all(); | ||||
| 
 | ||||
|         if ($gateway = Gateway::where('key', $input['gateway_key'])->first()) { | ||||
|         if ($gateway = Gateway::query()->where('key', $input['gateway_key'])->first()) { | ||||
|             $default_gateway_fields = json_decode($gateway->fields); | ||||
| 
 | ||||
|             /*Force gateway properties */ | ||||
|  | ||||
| @ -48,7 +48,7 @@ class UpdateCompanyGatewayRequest extends Request | ||||
| 
 | ||||
|         /*Force gateway properties */ | ||||
|         if (isset($input['config']) && is_object(json_decode($input['config'])) && array_key_exists('gateway_key', $input)) { | ||||
|             $gateway = Gateway::where('key', $input['gateway_key'])->first(); | ||||
|             $gateway = Gateway::query()->where('key', $input['gateway_key'])->first(); | ||||
|             $default_gateway_fields = json_decode($gateway->fields); | ||||
| 
 | ||||
|             foreach (json_decode($input['config']) as $key => $value) { | ||||
|  | ||||
| @ -63,7 +63,7 @@ class SendEmailRequest extends Request | ||||
|         $user = auth()->user(); | ||||
| 
 | ||||
|         return [ | ||||
|             'template' => 'bail|required|in:'.implode(',', $this->templates), | ||||
|             'template' => 'bail|required|string|in:'.implode(',', $this->templates), | ||||
|             'entity' => 'bail|required|in:App\Models\Invoice,App\Models\Quote,App\Models\Credit,App\Models\RecurringInvoice,App\Models\PurchaseOrder,App\Models\Payment', | ||||
|             'entity_id' => ['bail', 'required', Rule::exists($this->entity_plural, 'id')->where('company_id', $user->company()->id)], | ||||
|             'cc_email.*' => 'bail|sometimes|email', | ||||
| @ -94,7 +94,7 @@ class SendEmailRequest extends Request | ||||
| 
 | ||||
|         $this->entity_plural = Str::plural($input['entity']) ?? ''; | ||||
| 
 | ||||
|         if (isset($input['entity'])) { | ||||
|         if (isset($input['entity']) && in_array($input['entity'], ['invoice','quote','credit','recurring_invoice','purchase_order','payment'])) { | ||||
|             $input['entity'] = "App\Models\\".ucfirst(Str::camel($input['entity'])); | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -56,7 +56,7 @@ class UpdateExpenseRequest extends Request | ||||
|         $rules['invoice_id'] = 'bail|sometimes|nullable|exists:invoices,id,company_id,'.$user->company()->id; | ||||
|         $rules['documents'] = 'bail|sometimes|array'; | ||||
|         $rules['amount'] = ['sometimes', 'bail', 'nullable', 'numeric', 'max:99999999999999']; | ||||
| 
 | ||||
|          | ||||
|         return $this->globalRules($rules); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -78,7 +78,9 @@ class StoreInvoiceRequest extends Request | ||||
|         $rules['tax_name3'] = 'bail|sometimes|string|nullable'; | ||||
|         $rules['exchange_rate'] = 'bail|sometimes|numeric'; | ||||
|         $rules['partial'] = 'bail|sometimes|nullable|numeric|gte:0'; | ||||
|         $rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date']; | ||||
|         $rules['partial_due_date'] = ['bail', 'sometimes', 'nullable', 'exclude_if:partial,0', 'date', 'before:due_date', 'after_or_equal:date']; | ||||
|         $rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date ?? '') > 1), 'date']; | ||||
| 
 | ||||
|         $rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999']; | ||||
| 
 | ||||
|         // $rules['amount'] = ['sometimes', 'bail', 'max:99999999999999'];
 | ||||
|  | ||||
| @ -82,8 +82,8 @@ class UpdateInvoiceRequest extends Request | ||||
| 
 | ||||
|         $rules['date'] = 'bail|sometimes|date:Y-m-d'; | ||||
| 
 | ||||
|         // $rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date', 'before:due_date'];
 | ||||
|         // $rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date'];
 | ||||
|         $rules['partial_due_date'] = ['bail', 'sometimes', 'nullable', 'exclude_if:partial,0', 'date', 'before:due_date', 'after_or_equal:date']; | ||||
|         $rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', 'after_or_equal:date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date']; | ||||
| 
 | ||||
|         return $rules; | ||||
|     } | ||||
|  | ||||
| @ -46,7 +46,7 @@ class StorePaymentRequest extends Request | ||||
| 
 | ||||
|         $rules = [ | ||||
|             'client_id' => ['bail','required',Rule::exists('clients', 'id')->where('company_id', $user->company()->id)->where('is_deleted', 0)], | ||||
|             'invoices' => ['bail','sometimes', 'nullable', 'array', new ValidPayableInvoicesRule()], | ||||
|             'invoices' => ['bail', 'sometimes', 'nullable', 'array', new ValidPayableInvoicesRule()], | ||||
|             'invoices.*.amount' => ['bail','required'], | ||||
|             'invoices.*.invoice_id' => ['bail','required','distinct', new ValidInvoicesRules($this->all()),Rule::exists('invoices', 'id')->where('company_id', $user->company()->id)->where('client_id', $this->client_id)], | ||||
|             'credits.*.credit_id' => ['bail','required','distinct', new ValidCreditsRules($this->all()),Rule::exists('credits', 'id')->where('company_id', $user->company()->id)->where('client_id', $this->client_id)], | ||||
|  | ||||
| @ -80,8 +80,8 @@ class StoreProjectRequest extends Request | ||||
|             $input['budgeted_hours'] = 0; | ||||
|         } | ||||
| 
 | ||||
|         $input['task_rate'] = isset($input['task_rate']) ? $input['task_rate'] : 0; | ||||
| 
 | ||||
|         $input['task_rate'] = (isset($input['task_rate']) && floatval($input['task_rate']) >= 0) ? $input['task_rate'] : 0; | ||||
|          | ||||
|         $this->replace($input); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -45,7 +45,8 @@ class UpdateProjectRequest extends Request | ||||
|             $rules['number'] = Rule::unique('projects')->where('company_id', $user->company()->id)->ignore($this->project->id); | ||||
|         } | ||||
| 
 | ||||
|         $rules['budgeted_hours'] = 'sometimes|numeric'; | ||||
|         $rules['budgeted_hours'] = 'sometimes|bail|numeric'; | ||||
|         $rules['task_rate'] = 'sometimes|bail|numeric'; | ||||
| 
 | ||||
|         if ($this->file('documents') && is_array($this->file('documents'))) { | ||||
|             $rules['documents.*'] = $this->fileValidation(); | ||||
|  | ||||
| @ -66,8 +66,8 @@ class StoreQuoteRequest extends Request | ||||
|         $rules['exchange_rate'] = 'bail|sometimes|numeric'; | ||||
|         $rules['line_items'] = 'array'; | ||||
|         $rules['date'] = 'bail|sometimes|date:Y-m-d'; | ||||
|         $rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date', 'before:due_date', 'after_or_equal:date']; | ||||
|         $rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date']; | ||||
|         $rules['partial_due_date'] = ['bail', 'sometimes', 'nullable', 'exclude_if:partial,0', 'date', 'before:due_date', 'after_or_equal:date']; | ||||
|         $rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date ?? '') > 1), 'date']; | ||||
|         $rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999']; | ||||
| 
 | ||||
|         return $rules; | ||||
|  | ||||
| @ -65,7 +65,7 @@ class UpdateQuoteRequest extends Request | ||||
| 
 | ||||
|         $rules['date'] = 'bail|sometimes|date:Y-m-d'; | ||||
| 
 | ||||
|         $rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date', 'before:due_date']; | ||||
|         $rules['partial_due_date'] = ['bail', 'sometimes', 'nullable', 'exclude_if:partial,0', 'date', 'before:due_date', 'after_or_equal:date']; | ||||
|         $rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', 'after_or_equal:date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date']; | ||||
|         $rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999']; | ||||
| 
 | ||||
|  | ||||
| @ -34,7 +34,10 @@ class BulkRecurringExpenseRequest extends Request | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return auth()->user()->can(auth()->user()->isAdmin(), RecurringExpense::class); | ||||
|         /** @var \App\Models\User $user */ | ||||
|         $user = auth()->user(); | ||||
|          | ||||
|         return $user->can('edit', RecurringExpense::class); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -69,7 +69,7 @@ class StoreTaskRequest extends Request | ||||
| 
 | ||||
|             foreach ($values as $k) { | ||||
|                 if (!is_int($k[0]) || !is_int($k[1])) { | ||||
|                     return $fail('The '.$attribute.' - '.print_r($k, 1).' is invalid. Unix timestamps only.'); | ||||
|                     return $fail('The '.$attribute.' - '.print_r($k, true).' is invalid. Unix timestamps only.'); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|  | ||||
| @ -75,7 +75,7 @@ class UpdateTaskRequest extends Request | ||||
| 
 | ||||
|             foreach ($values as $k) { | ||||
|                 if (!is_int($k[0]) || !is_int($k[1])) { | ||||
|                     return $fail('The '.$attribute.' - '.print_r($k, 1).' is invalid. Unix timestamps only.'); | ||||
|                     return $fail('The '.$attribute.' - '.print_r($k, true).' is invalid. Unix timestamps only.'); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|  | ||||
| @ -34,7 +34,11 @@ class BulkVendorRequest extends Request | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return auth()->user()->can(auth()->user()->isAdmin(), Vendor::class); | ||||
|         /** @var \App\Models\User $user */ | ||||
|         $user = auth()->user(); | ||||
| 
 | ||||
|         return $user->can('edit', Vendor::class); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -19,8 +19,9 @@ use Illuminate\Contracts\Validation\ValidationRule; | ||||
|  */ | ||||
| class BlackListRule implements ValidationRule | ||||
| { | ||||
|     /** Bad domains +/- dispoable email domains */ | ||||
|     /** Bad domains +/- disposable email domains */ | ||||
|     private array $blacklist = [ | ||||
|         'padvn.com', | ||||
|         'anonaddy.me', | ||||
|         'nqmo.com', | ||||
|         'wireconnected.com', | ||||
|  | ||||
| @ -11,6 +11,7 @@ | ||||
| 
 | ||||
| namespace App\Http\ValidationRules\EInvoice; | ||||
| 
 | ||||
| use App\Services\EDocument\Standards\Validation\Peppol\ClientLevel; | ||||
| use Closure; | ||||
| use InvoiceNinja\EInvoice\EInvoice; | ||||
| use Illuminate\Validation\Validator; | ||||
| @ -19,11 +20,10 @@ use Illuminate\Contracts\Validation\ValidationRule; | ||||
| use Illuminate\Contracts\Validation\ValidatorAwareRule; | ||||
| 
 | ||||
| /** | ||||
|  * Class BlackListRule. | ||||
|  * Class ValidClientScheme. | ||||
|  */ | ||||
| class ValidScheme implements ValidationRule, ValidatorAwareRule | ||||
| class ValidClientScheme implements ValidationRule, ValidatorAwareRule | ||||
| { | ||||
|   | ||||
|     /** | ||||
|      * The validator instance. | ||||
|      * | ||||
| @ -34,27 +34,28 @@ class ValidScheme implements ValidationRule, ValidatorAwareRule | ||||
|     public function validate(string $attribute, mixed $value, Closure $fail): void | ||||
|     { | ||||
| 
 | ||||
|         $r = new EInvoice(); | ||||
|         $errors = $r->validateRequest($value['Invoice'], Invoice::class); | ||||
|          | ||||
|         foreach ($errors as $key => $msg) { | ||||
|         if(isset($value['Invoice'])) | ||||
|         { | ||||
|             $r = new EInvoice(); | ||||
|             $errors = $r->validateRequest($value['Invoice'], ClientLevel::class); | ||||
| 
 | ||||
|             $this->validator->errors()->add( | ||||
|                 "e_invoice.{$key}", | ||||
|                 "{$key} - {$msg}" | ||||
|             ); | ||||
|             foreach ($errors as $key => $msg) { | ||||
| 
 | ||||
|                 $this->validator->errors()->add( | ||||
|                     "e_invoice.{$key}", | ||||
|                     "{$key} - {$msg}" | ||||
|                 ); | ||||
| 
 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
|   | ||||
| 
 | ||||
|     /** | ||||
|      * Set the current validator. | ||||
|      */ | ||||
|     public function setValidator(Validator $validator): static | ||||
|     { | ||||
|         $this->validator = $validator; | ||||
|   | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
							
								
								
									
										66
									
								
								app/Http/ValidationRules/EInvoice/ValidCompanyScheme.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								app/Http/ValidationRules/EInvoice/ValidCompanyScheme.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  *1` | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Http\ValidationRules\EInvoice; | ||||
| 
 | ||||
| use App\Services\EDocument\Standards\Validation\Peppol\CompanyLevel; | ||||
| use Closure; | ||||
| use InvoiceNinja\EInvoice\EInvoice; | ||||
| use Illuminate\Validation\Validator; | ||||
| use InvoiceNinja\EInvoice\Models\Peppol\Invoice; | ||||
| use Illuminate\Contracts\Validation\ValidationRule; | ||||
| use Illuminate\Contracts\Validation\ValidatorAwareRule; | ||||
| 
 | ||||
| /** | ||||
|  * Class ValidScheme. | ||||
|  */ | ||||
| class ValidCompanyScheme implements ValidationRule, ValidatorAwareRule | ||||
| { | ||||
|   | ||||
|     /** | ||||
|      * The validator instance. | ||||
|      * | ||||
|      * @var Validator | ||||
|      */ | ||||
|     protected $validator; | ||||
| 
 | ||||
|     public function validate(string $attribute, mixed $value, Closure $fail): void | ||||
|     { | ||||
| 
 | ||||
|         if(isset($value['Invoice'])) | ||||
|         { | ||||
|             $r = new EInvoice(); | ||||
|             $errors = $r->validateRequest($value['Invoice'], CompanyLevel::class); | ||||
|              | ||||
|             foreach ($errors as $key => $msg) { | ||||
| 
 | ||||
|                 $this->validator->errors()->add( | ||||
|                     "e_invoice.{$key}", | ||||
|                     "{$key} - {$msg}" | ||||
|                 ); | ||||
| 
 | ||||
|             } | ||||
|         } | ||||
|      | ||||
|     } | ||||
|   | ||||
|     /** | ||||
|      * Set the current validator. | ||||
|      */ | ||||
|     public function setValidator(Validator $validator): static | ||||
|     { | ||||
|         $this->validator = $validator; | ||||
|   | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -85,11 +85,12 @@ class ValidInvoicesRules implements Rule | ||||
|                 //catch here nothing to do - we need this to prevent the last elseif triggering
 | ||||
|             } elseif ($inv->status_id == Invoice::STATUS_DRAFT && floatval($invoice['amount']) > floatval($inv->amount)) { | ||||
|                 $this->error_msg = 'Amount cannot be greater than invoice balance'; | ||||
| 
 | ||||
|                 return false; | ||||
|             } elseif (floatval($invoice['amount']) > floatval($inv->balance)) { | ||||
|                 $this->error_msg = ctrans('texts.amount_greater_than_balance_v5'); | ||||
| 
 | ||||
|                 return false; | ||||
|             } elseif($inv->is_deleted){ | ||||
|                 $this->error_msg = 'One or more invoices in this request have since been deleted'; | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @ -35,7 +35,7 @@ class ValidPayableInvoicesRule implements Rule | ||||
|         $invoices = []; | ||||
| 
 | ||||
|         if (is_array($value)) { | ||||
|             $invoices = Invoice::query()->whereIn('id', array_column($value, 'invoice_id'))->company()->get(); | ||||
|             $invoices = Invoice::query()->withTrashed()->whereIn('id', array_column($value, 'invoice_id'))->company()->get(); | ||||
|         } | ||||
| 
 | ||||
|         foreach ($invoices as $invoice) { | ||||
|  | ||||
| @ -31,7 +31,10 @@ class ProductMap | ||||
|             12 => 'product.custom_value2', | ||||
|             13 => 'product.custom_value3', | ||||
|             14 => 'product.custom_value4', | ||||
|             15 => 'product.image_url' | ||||
|             15 => 'product.image_url', | ||||
|             16 => 'product.in_stock_quantity', | ||||
|             17 => 'product.tax_category', | ||||
|             18 => 'product.max_quantity', | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
| @ -54,6 +57,9 @@ class ProductMap | ||||
|             13 => 'texts.custom_value', | ||||
|             14 => 'texts.custom_value', | ||||
|             15 => 'texts.image_url', | ||||
|             16 => 'texts.in_stock_quantity', | ||||
|             17 => 'texts.tax_category', | ||||
|             18 => 'texts.max_quantity', | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -98,7 +98,7 @@ class BaseImport | ||||
|         } | ||||
| 
 | ||||
|         /** @var string $base64_encoded_csv */ | ||||
|         $base64_encoded_csv = Cache::pull($this->hash.'-'.$entity_type); | ||||
|         $base64_encoded_csv = Cache::get($this->hash.'-'.$entity_type); | ||||
| 
 | ||||
|         if (empty($base64_encoded_csv)) { | ||||
|             return null; | ||||
| @ -473,6 +473,8 @@ class BaseImport | ||||
| 
 | ||||
|         $tasks = $this->groupTasks($tasks, $task_number_key); | ||||
| 
 | ||||
|         nlog($tasks); | ||||
|          | ||||
|         foreach ($tasks as $raw_task) { | ||||
|             $task_data = []; | ||||
| 
 | ||||
| @ -702,16 +704,16 @@ class BaseImport | ||||
|                 ->save(); | ||||
|         } | ||||
| 
 | ||||
|         if ($invoice->status_id === Invoice::STATUS_DRAFT) { | ||||
|         } elseif ($invoice->status_id === Invoice::STATUS_SENT) { | ||||
|             $invoice = $invoice | ||||
|                 ->service() | ||||
|                 ->markSent() | ||||
|                 ->save(); | ||||
|         } elseif ( | ||||
|             $invoice->status_id <= Invoice::STATUS_SENT && | ||||
|             $invoice->amount > 0 | ||||
|         ) { | ||||
|         if ($invoice->status_id == Invoice::STATUS_DRAFT) { | ||||
|             return $invoice; | ||||
|         }  | ||||
|          | ||||
|         $invoice = $invoice | ||||
|             ->service() | ||||
|             ->markSent() | ||||
|             ->save(); | ||||
| 
 | ||||
|         if ($invoice->status_id <= Invoice::STATUS_SENT && $invoice->amount > 0) { | ||||
|             if ($invoice->balance <= 0) { | ||||
|                 $invoice->status_id = Invoice::STATUS_PAID; | ||||
|                 $invoice->save(); | ||||
|  | ||||
| @ -172,7 +172,7 @@ class Wave extends BaseImport implements ImportInterface | ||||
|     { | ||||
|         $entity_type = 'expense'; | ||||
| 
 | ||||
|         $data = $this->getCsvData($entity_type); | ||||
|         $data = $this->getCsvData('invoice'); | ||||
| 
 | ||||
|         if (!$data) { | ||||
|             $this->entity_count['expense'] = 0; | ||||
| @ -244,14 +244,17 @@ class Wave extends BaseImport implements ImportInterface | ||||
|                 if (empty($expense_data['vendor_id'])) { | ||||
|                     $vendor_data['user_id'] = $this->getUserIDForRecord($expense_data); | ||||
| 
 | ||||
|                     $vendor_repository->save( | ||||
|                         ['name' => $raw_expense['Vendor Name']], | ||||
|                         $vendor = VendorFactory::create( | ||||
|                             $this->company->id, | ||||
|                             $vendor_data['user_id'] | ||||
|                         ) | ||||
|                     ); | ||||
|                     $expense_data['vendor_id'] = $vendor->id; | ||||
|                     if(isset($raw_expense['Vendor Name']) || isset($raw_expense['Vendor'])) | ||||
|                     { | ||||
|                         $vendor_repository->save( | ||||
|                             ['name' => isset($raw_expense['Vendor Name']) ? $raw_expense['Vendor Name'] : isset($raw_expense['Vendor'])], | ||||
|                             $vendor = VendorFactory::create( | ||||
|                                 $this->company->id, | ||||
|                                 $vendor_data['user_id'] | ||||
|                             ) | ||||
|                         ); | ||||
|                         $expense_data['vendor_id'] = $vendor->id; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 $validator = Validator::make( | ||||
|  | ||||
| @ -42,7 +42,7 @@ class ExpenseTransformer extends BaseTransformer | ||||
|             'client_id' => isset($data['expense.client']) | ||||
|                 ? $this->getClientId($data['expense.client']) | ||||
|                 : null, | ||||
|             'date' => strlen($this->getString($data, 'expense.date') > 1) ? $this->parseDate($data['expense.date']) : now()->format('Y-m-d'), | ||||
|             'date' => strlen($this->getString($data, 'expense.date')) > 1 ? $this->parseDate($data['expense.date']) : now()->format('Y-m-d'), | ||||
|             'public_notes' => $this->getString($data, 'expense.public_notes'), | ||||
|             'private_notes' => $this->getString($data, 'expense.private_notes'), | ||||
|             'category_id' => isset($data['expense.category']) | ||||
|  | ||||
| @ -43,6 +43,7 @@ class ProductTransformer extends BaseTransformer | ||||
|             'custom_value3' => $this->getString($data, 'product.custom_value3'), | ||||
|             'custom_value4' => $this->getString($data, 'product.custom_value4'), | ||||
|             'product_image' => $this->getString($data, 'product.image_url'), | ||||
|             'in_stock_quantity' => $this->getFloat($data, 'product.in_stock_quantity'), | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -46,6 +46,7 @@ class TaskTransformer extends BaseTransformer | ||||
|             'company_id' => $this->company->id, | ||||
|             'number' => $this->getString($task_data, 'task.number'), | ||||
|             'user_id' => $this->getString($task_data, 'task.user_id'), | ||||
|             'rate' => $this->getFloat($task_data, 'task.rate'), | ||||
|             'client_id' => $clientId, | ||||
|             'project_id' => $this->getProjectId($projectId, $clientId), | ||||
|             'description' => $this->getString($task_data, 'task.description'), | ||||
| @ -87,8 +88,7 @@ class TaskTransformer extends BaseTransformer | ||||
|             $is_billable = true; | ||||
|         } | ||||
| 
 | ||||
|         if(isset($item['task.start_date']) && | ||||
|         isset($item['task.end_date'])) { | ||||
|         if(isset($item['task.start_date'])) { | ||||
|             $start_date = $this->resolveStartDate($item); | ||||
|             $end_date = $this->resolveEndDate($item); | ||||
|         } elseif(isset($item['task.duration'])) { | ||||
| @ -136,7 +136,7 @@ class TaskTransformer extends BaseTransformer | ||||
|     private function resolveEndDate($item) | ||||
|     { | ||||
| 
 | ||||
|         $stub_end_date = $item['task.end_date']; | ||||
|         $stub_end_date = isset($item['task.end_date']) ? $item['task.end_date'] : $item['task.start_date']; | ||||
|         $stub_end_date .= isset($item['task.end_time']) ? " ".$item['task.end_time'] : ''; | ||||
| 
 | ||||
|         try { | ||||
|  | ||||
| @ -36,18 +36,26 @@ class ExpenseTransformer extends BaseTransformer | ||||
|             $total_tax += floatval($record['Sales Tax Amount']); | ||||
|         } | ||||
| 
 | ||||
|         $tax_rate = round(($total_tax / $amount) * 100, 3); | ||||
|         $tax_rate = $total_tax > 0 ? round(($total_tax / $amount) * 100, 3) : 0; | ||||
| 
 | ||||
|         if(isset($data['Notes / Memo']) && strlen($data['Notes / Memo']) > 1) | ||||
|             $public_notes = $data['Notes / Memo']; | ||||
|         elseif (isset($data['Transaction Description']) && strlen($data['Transaction Description']) > 1) | ||||
|             $public_notes = $data['Transaction Description']; | ||||
|         else | ||||
|             $public_notes = ''; | ||||
|          | ||||
| 
 | ||||
|         $transformed = [ | ||||
|             'company_id'  => $this->company->id, | ||||
|             'vendor_id'   => $this->getVendorIdOrCreate($this->getString($data, 'Vendor')), | ||||
|             'number' 	  => $this->getString($data, 'Bill Number'), | ||||
|             'public_notes' => $this->getString($data, 'Notes / Memo'), | ||||
|             'public_notes' => $public_notes, | ||||
|             'date'        => $this->parseDate($data['Transaction Date Added']) ?: now()->format('Y-m-d'), //27-01-2022
 | ||||
|             'currency_id' => $this->company->settings->currency_id, | ||||
|             'category_id' => $this->getOrCreateExpenseCategry($data['Account Name']), | ||||
|             'amount'	  => $amount, | ||||
|             'tax_name1'   => $data['Sales Tax Name'], | ||||
|             'tax_name1'   => isset($data['Sales Tax Name']) ? $data['Sales Tax Name'] : '', | ||||
|             'tax_rate1'	  => $tax_rate, | ||||
|         ]; | ||||
| 
 | ||||
|  | ||||
| @ -448,6 +448,6 @@ class MatchBankTransactions implements ShouldQueue | ||||
| 
 | ||||
|     public function middleware() | ||||
|     { | ||||
|         return [new WithoutOverlapping($this->company_id)]; | ||||
|         return [new WithoutOverlapping($this->company->account->bank_integration_account_id)]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -35,10 +35,6 @@ class ProcessBankTransactionsYodlee implements ShouldQueue | ||||
|     use Queueable; | ||||
|     use SerializesModels; | ||||
| 
 | ||||
|     private string $bank_integration_account_id; | ||||
| 
 | ||||
|     private BankIntegration $bank_integration; | ||||
| 
 | ||||
|     private ?string $from_date; | ||||
| 
 | ||||
|     private bool $stop_loop = true; | ||||
| @ -50,10 +46,8 @@ class ProcessBankTransactionsYodlee implements ShouldQueue | ||||
|     /** | ||||
|      * Create a new job instance. | ||||
|      */ | ||||
|     public function __construct(string $bank_integration_account_id, BankIntegration $bank_integration) | ||||
|     public function __construct(private string $bank_integration_account_id, private BankIntegration $bank_integration) | ||||
|     { | ||||
|         $this->bank_integration_account_id = $bank_integration_account_id; | ||||
|         $this->bank_integration = $bank_integration; | ||||
|         $this->from_date = $bank_integration->from_date; | ||||
|         $this->company = $this->bank_integration->company; | ||||
|     } | ||||
|  | ||||
| @ -93,8 +93,7 @@ class ProcessBrevoWebhook implements ShouldQueue | ||||
|     { | ||||
|         MultiDB::findAndSetDbByCompanyKey($this->request['tags'][0]); | ||||
| 
 | ||||
|         /** @phpstan-ignore-next-line */ | ||||
|         $this->company = Company::where('company_key', $this->request['tags'][0])->first(); | ||||
|         $this->company = Company::query()->where('company_key', $this->request['tags'][0])->first(); | ||||
| 
 | ||||
|         $this->invitation = $this->discoverInvitation($this->request['message-id']); | ||||
| 
 | ||||
|  | ||||
| @ -58,7 +58,7 @@ class CheckVat implements ShouldQueue | ||||
| 
 | ||||
|     public function middleware() | ||||
|     { | ||||
|         return [new WithoutOverlapping($this->client->id)]; | ||||
|         return [new WithoutOverlapping($this->client->client_hash)]; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -108,7 +108,8 @@ class CompanyExport implements ShouldQueue | ||||
| 
 | ||||
| 
 | ||||
|         $this->export_data['users'] = $this->company->users()->withTrashed()->cursor()->map(function ($user) { | ||||
|             $user->account_id = $this->encodePrimaryKey($user->account_id); | ||||
|             /** @var \App\Models\User $user */ | ||||
|             $user->account_id = $this->encodePrimaryKey($user->account_id); //@phpstan-ignore-line
 | ||||
|             return $user; | ||||
|         })->all(); | ||||
| 
 | ||||
|  | ||||
| @ -94,7 +94,7 @@ class CompanyTaxRate implements ShouldQueue | ||||
| 
 | ||||
|     public function middleware() | ||||
|     { | ||||
|         return [new WithoutOverlapping($this->company->id)]; | ||||
|         return [new WithoutOverlapping($this->company->company_key)]; | ||||
|     } | ||||
| 
 | ||||
|     public function failed($e) | ||||
|  | ||||
| @ -65,7 +65,7 @@ class AutoBillCron | ||||
| 
 | ||||
|             $auto_bill_partial_invoices->chunk(400, function ($invoices) { | ||||
|                 foreach ($invoices as $invoice) { | ||||
|                     AutoBill::dispatch($invoice->id, false); | ||||
|                     AutoBill::dispatch($invoice->id, null); | ||||
|                 } | ||||
| 
 | ||||
|                 sleep(2); | ||||
| @ -87,7 +87,7 @@ class AutoBillCron | ||||
| 
 | ||||
|             $auto_bill_invoices->chunk(400, function ($invoices) { | ||||
|                 foreach ($invoices as $invoice) { | ||||
|                     AutoBill::dispatch($invoice->id, false); | ||||
|                     AutoBill::dispatch($invoice->id, null); | ||||
|                 } | ||||
| 
 | ||||
|                 sleep(2); | ||||
|  | ||||
| @ -11,16 +11,17 @@ | ||||
| 
 | ||||
| namespace App\Jobs\Cron; | ||||
| 
 | ||||
| use App\Events\Expense\ExpenseWasCreated; | ||||
| use App\Factory\RecurringExpenseToExpenseFactory; | ||||
| use App\Utils\Ninja; | ||||
| use App\Libraries\MultiDB; | ||||
| use Illuminate\Support\Carbon; | ||||
| use App\Models\RecurringExpense; | ||||
| use App\Models\RecurringInvoice; | ||||
| use App\Utils\Ninja; | ||||
| use App\Utils\Traits\GeneratesCounter; | ||||
| use Illuminate\Foundation\Bus\Dispatchable; | ||||
| use Illuminate\Support\Carbon; | ||||
| use Illuminate\Support\Facades\Auth; | ||||
| use App\Utils\Traits\GeneratesCounter; | ||||
| use App\Events\Expense\ExpenseWasCreated; | ||||
| use Illuminate\Foundation\Bus\Dispatchable; | ||||
| use App\Factory\RecurringExpenseToExpenseFactory; | ||||
| use App\Libraries\Currency\Conversion\CurrencyApi; | ||||
| 
 | ||||
| class RecurringExpensesCron | ||||
| { | ||||
| @ -109,6 +110,15 @@ class RecurringExpensesCron | ||||
|             $expense->payment_date = now()->format('Y-m-d'); | ||||
|         } | ||||
| 
 | ||||
|         if ((int)$expense->company->settings->currency_id != $expense->currency_id) { | ||||
|             $exchange_rate = new CurrencyApi(); | ||||
| 
 | ||||
|             $expense->exchange_rate = $exchange_rate->exchangeRate($expense->currency_id, (int)$expense->company->settings->currency_id, Carbon::parse($expense->date)); | ||||
|         } | ||||
|         else { | ||||
|             $expense->exchange_rate = 1; | ||||
|         } | ||||
| 
 | ||||
|         $expense->number = $this->getNextExpenseNumber($expense); | ||||
|         $expense->saveQuietly(); | ||||
| 
 | ||||
|  | ||||
| @ -48,12 +48,12 @@ class RecurringInvoicesCron | ||||
|         Auth::logout(); | ||||
| 
 | ||||
|         if (! config('ninja.db.multi_db_enabled')) { | ||||
|             $recurring_invoices = RecurringInvoice::query()->where('status_id', RecurringInvoice::STATUS_ACTIVE) | ||||
|                                                         ->where('is_deleted', false) | ||||
|                                                         ->where('remaining_cycles', '!=', '0') | ||||
|                                                         ->whereNotNull('next_send_date') | ||||
|                                                         ->whereNull('deleted_at') | ||||
|                                                         ->where('next_send_date', '<=', now()->toDateTimeString()) | ||||
|             $recurring_invoices = RecurringInvoice::query()->where('recurring_invoices.status_id', RecurringInvoice::STATUS_ACTIVE) | ||||
|                                                         ->where('recurring_invoices.is_deleted', false) | ||||
|                                                         ->where('recurring_invoices.remaining_cycles', '!=', '0') | ||||
|                                                         ->whereNotNull('recurring_invoices.next_send_date') | ||||
|                                                         ->whereNull('recurring_invoices.deleted_at') | ||||
|                                                         ->where('recurring_invoices.next_send_date', '<=', now()->toDateTimeString()) | ||||
|                                                         ->whereHas('client', function ($query) { | ||||
|                                                             $query->where('is_deleted', 0) | ||||
|                                                                    ->where('deleted_at', null); | ||||
| @ -87,18 +87,27 @@ class RecurringInvoicesCron | ||||
|             foreach (MultiDB::$dbs as $db) { | ||||
|                 MultiDB::setDB($db); | ||||
| 
 | ||||
|                 $recurring_invoices = RecurringInvoice::query()->where('status_id', RecurringInvoice::STATUS_ACTIVE) | ||||
|                                                         ->where('is_deleted', false) | ||||
|                                                         ->where('remaining_cycles', '!=', '0') | ||||
|                                                         ->whereNull('deleted_at') | ||||
|                                                         ->whereNotNull('next_send_date') | ||||
|                                                         ->where('next_send_date', '<=', now()->toDateTimeString()) | ||||
|                                                         ->whereHas('client', function ($query) { | ||||
|                                                             $query->where('is_deleted', 0) | ||||
|                                                                    ->where('deleted_at', null); | ||||
|                 $recurring_invoices = RecurringInvoice::query()->where('recurring_invoices.status_id', RecurringInvoice::STATUS_ACTIVE) | ||||
|                                                         ->where('recurring_invoices.is_deleted', false) | ||||
|                                                         ->where('recurring_invoices.remaining_cycles', '!=', '0') | ||||
|                                                         ->whereNull('recurring_invoices.deleted_at') | ||||
|                                                         ->whereNotNull('recurring_invoices.next_send_date') | ||||
|                                                         ->where('recurring_invoices.next_send_date', '<=', now()->toDateTimeString()) | ||||
|                                                         // ->whereHas('client', function ($query) {
 | ||||
|                                                         //     $query->where('is_deleted', 0)
 | ||||
|                                                         //            ->where('deleted_at', null);
 | ||||
|                                                         // })
 | ||||
|                                                         // ->whereHas('company', function ($query) {
 | ||||
|                                                         //     $query->where('is_disabled', 0);
 | ||||
|                                                         // })
 | ||||
|                                                         ->leftJoin('clients', function ($join) { | ||||
|                                                             $join->on('recurring_invoices.client_id', '=', 'clients.id') | ||||
|                                                                 ->where('clients.is_deleted', 0) | ||||
|                                                                 ->whereNull('clients.deleted_at'); | ||||
|                                                         }) | ||||
|                                                         ->whereHas('company', function ($query) { | ||||
|                                                             $query->where('is_disabled', 0); | ||||
|                                                         ->leftJoin('companies', function ($join) { | ||||
|                                                             $join->on('recurring_invoices.company_id', '=', 'companies.id') | ||||
|                                                                 ->where('companies.is_disabled', 0); | ||||
|                                                         }) | ||||
|                                                         ->with('company') | ||||
|                                                         ->cursor(); | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user