Merge remote-tracking branch 'origin/v5-develop' into HEAD

This commit is contained in:
Lucas D Hedding 2024-09-04 08:47:35 -06:00
commit 72009d2269
No known key found for this signature in database
GPG Key ID: EAA3108D0077A8B9
742 changed files with 391673 additions and 340791 deletions

View File

@ -8,7 +8,7 @@ assignees: ''
--- ---
<!-- Before posting please check our "Troubleshooting" category in the docs: <!-- Before posting please check our "Troubleshooting" category in the docs:
https://invoiceninja.github.io/docs/self-host-troubleshooting/ --> https://invoiceninja.github.io/en/self-host-troubleshooting/ -->
## Setup ## Setup
- Version: <!-- i.e. v4.5.25 / v5.0.30 --> - Version: <!-- i.e. v4.5.25 / v5.0.30 -->

View File

@ -51,7 +51,9 @@ All Pro and Enterprise features from the hosted app are included in the open-sou
* [Docker File](https://hub.docker.com/r/invoiceninja/invoiceninja/) * [Docker File](https://hub.docker.com/r/invoiceninja/invoiceninja/)
* [Cloudron](https://www.cloudron.io/store/com.invoiceninja.cloudronapp2.html) * [Cloudron](https://www.cloudron.io/store/com.invoiceninja.cloudronapp2.html)
* [Softaculous](https://www.softaculous.com/apps/ecommerce/Invoice_Ninja) * [Softaculous](https://www.softaculous.com/apps/ecommerce/Invoice_Ninja)
* [Elestio](https://elest.io/open-source/invoiceninja)
* [YunoHost](https://apps.yunohost.org/app/invoiceninja5)
### Recommended Providers ### Recommended Providers
* [Stripe](https://stripe.com/) * [Stripe](https://stripe.com/)
* [Postmark](https://postmarkapp.com/) * [Postmark](https://postmarkapp.com/)

View File

@ -1 +1 @@
5.10.17 5.10.27

View File

@ -0,0 +1,50 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Casts;
use App\DataMapper\QuickbooksSettings;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class QuickbooksSettingsCast implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes)
{
$data = json_decode($value, true);
if(!is_array($data))
return null;
$qb = new QuickbooksSettings();
$qb->accessTokenKey = $data['accessTokenKey'];
$qb->refresh_token = $data['refresh_token'];
$qb->realmID = $data['realmID'];
$qb->accessTokenExpiresAt = $data['accessTokenExpiresAt'];
$qb->refreshTokenExpiresAt = $data['refreshTokenExpiresAt'];
$qb->settings = $data['settings'] ?? [];
return $qb;
}
public function set($model, string $key, $value, array $attributes)
{
return [
$key => json_encode([
'accessTokenKey' => $value->accessTokenKey,
'refresh_token' => $value->refresh_token,
'realmID' => $value->realmID,
'accessTokenExpiresAt' => $value->accessTokenExpiresAt,
'refreshTokenExpiresAt' => $value->refreshTokenExpiresAt,
'settings' => $value->settings,
])
];
}
}

View File

@ -1169,10 +1169,10 @@ class CheckData extends Command
->whereNull('exchange_rate') ->whereNull('exchange_rate')
->orWhere('exchange_rate', 0) ->orWhere('exchange_rate', 0)
->cursor() ->cursor()
->each(function ($expense){ ->each(function ($expense) {
$expense->exchange_rate = 1; $expense->exchange_rate = 1;
$expense->saveQuietly(); $expense->saveQuietly();
$this->logMessage("Fixing - exchange rate for expense :: {$expense->id}"); $this->logMessage("Fixing - exchange rate for expense :: {$expense->id}");
}); });

View File

@ -1116,7 +1116,7 @@ class CreateSingleAccount extends Command
private function countryClients($company, $user) private function countryClients($company, $user)
{ {
Client::unguard(); Client::unguard();
Client::create([ Client::create([

View File

@ -67,7 +67,7 @@ class Kernel extends ConsoleKernel
/* Checks Rotessa Transactions */ /* Checks Rotessa Transactions */
$schedule->job(new TransactionReport())->dailyAt('01:48')->withoutOverlapping()->name('rotessa-transaction-report')->onOneServer(); $schedule->job(new TransactionReport())->dailyAt('01:48')->withoutOverlapping()->name('rotessa-transaction-report')->onOneServer();
/* Stale Invoice Cleanup*/ /* Stale Invoice Cleanup*/
$schedule->job(new CleanStaleInvoiceOrder())->hourlyAt(30)->withoutOverlapping()->name('stale-invoice-job')->onOneServer(); $schedule->job(new CleanStaleInvoiceOrder())->hourlyAt(30)->withoutOverlapping()->name('stale-invoice-job')->onOneServer();

View File

@ -0,0 +1,83 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Analytics;
use Turbo124\Beacon\ExampleMetric\GenericMixedMetric;
class LegalEntityCreated extends GenericMixedMetric
{
/**
* The type of Sample.
*
* Monotonically incrementing counter
*
* - counter
*
* @var string
*/
public $type = 'mixed_metric';
/**
* The name of the counter.
* @var string
*/
public $name = 'einvoice.legal_entity.created';
/**
* The datetime of the counter measurement.
*
* date("Y-m-d H:i:s")
*
*/
public $datetime;
/**
* The Class failure name
* set to 0.
*
* @var string
*/
public $string_metric5 = 'stub';
/**
* The exception string
* set to 0.
*
* @var string
*/
public $string_metric6 = 'stub';
/**
* The counter
* set to 1.
*
*/
public $int_metric1 = 1;
/**
* Company Key
* @var string
*/
public $string_metric7 = '';
/**
* Subject
* @var string
*/
public $string_metric8 = '';
public function __construct($string_metric7 = '', $string_metric8 = '')
{
$this->string_metric7 = $string_metric7;
$this->string_metric8 = $string_metric8;
}
}

View File

@ -515,10 +515,16 @@ class CompanySettings extends BaseSettings
public $quote_schedule_reminder1 = ''; //before_valid_until_date,after_valid_until_date,after_quote_date public $quote_schedule_reminder1 = ''; //before_valid_until_date,after_valid_until_date,after_quote_date
public $quote_late_fee_amount1 = 0; public $quote_late_fee_amount1 = 0;
public $quote_late_fee_percent1 = 0; public $quote_late_fee_percent1 = 0;
public string $payment_flow = 'default'; //smooth
public string $email_subject_payment_failed = '';
public string $email_template_payment_failed = '';
public static $casts = [ public static $casts = [
'email_template_payment_failed' => 'string',
'email_subject_payment_failed' => 'string',
'payment_flow' => 'string',
'enable_quote_reminder1' => 'bool', 'enable_quote_reminder1' => 'bool',
'quote_num_days_reminder1' => 'int', 'quote_num_days_reminder1' => 'int',
'quote_schedule_reminder1' => 'string', 'quote_schedule_reminder1' => 'string',

View File

@ -30,6 +30,7 @@ class EmailTemplateDefaults
'email_template_custom2', 'email_template_custom2',
'email_template_custom3', 'email_template_custom3',
'email_template_purchase_order', 'email_template_purchase_order',
'email_template_payment_failed'
]; ];
public static function getDefaultTemplate($template, $locale) public static function getDefaultTemplate($template, $locale)
@ -39,6 +40,8 @@ class EmailTemplateDefaults
switch ($template) { switch ($template) {
/* Template */ /* Template */
case 'email_template_payment_failed':
return self::emailPaymentFailedTemplate();
case 'email_template_invoice': case 'email_template_invoice':
return self::emailInvoiceTemplate(); return self::emailInvoiceTemplate();
case 'email_template_quote': case 'email_template_quote':
@ -73,6 +76,9 @@ class EmailTemplateDefaults
case 'email_subject_invoice': case 'email_subject_invoice':
return self::emailInvoiceSubject(); return self::emailInvoiceSubject();
case 'email_subject_payment_failed':
return self::emailPaymentFailedSubject();
case 'email_subject_quote': case 'email_subject_quote':
return self::emailQuoteSubject(); return self::emailQuoteSubject();
@ -120,27 +126,35 @@ class EmailTemplateDefaults
case 'email_quote_subject_reminder1': case 'email_quote_subject_reminder1':
return self::emailQuoteReminder1Subject(); return self::emailQuoteReminder1Subject();
default: default:
return self::emailInvoiceTemplate(); return self::emailInvoiceTemplate();
} }
} }
public static function emailPaymentFailedSubject()
{
return ctrans('texts.notification_invoice_payment_failed_subject', ['invoice' => '$number']);
}
public static function emailPaymentFailedTemplate()
{
return '<p>$client<br><br>'.ctrans('texts.client_payment_failure_body', ['invoice' => '$number', 'amount' => '$amount']).'</p><div>$payment_error</div><br><div>$payment_button</div>';
}
public static function emailQuoteReminder1Subject() public static function emailQuoteReminder1Subject()
{ {
return ctrans('texts.quote_reminder_subject', ['quote' => '$number', 'company' => '$company.name']); return ctrans('texts.quote_reminder_subject', ['quote' => '$number', 'company' => '$company.name']);
} }
public static function emailQuoteReminder1Body() public static function emailQuoteReminder1Body()
{ {
$invoice_message = '<p>$client<br><br>'.self::transformText('quote_reminder_message').'</p><div class="center">$view_button</div>'; return '<p>$client<br><br>'.self::transformText('quote_reminder_message').'</p><div>$view_button</div>';
return $invoice_message;
} }
public static function emailVendorNotificationSubject() public static function emailVendorNotificationSubject()
{ {
return self::transformText('vendor_notification_subject'); return self::transformText('vendor_notification_subject');
@ -163,14 +177,14 @@ class EmailTemplateDefaults
public static function emailInvoiceTemplate() public static function emailInvoiceTemplate()
{ {
$invoice_message = '<p>$client<br><br>'.self::transformText('invoice_message').'</p><div class="center">$view_button</div>'; $invoice_message = '<p>$client<br><br>'.self::transformText('invoice_message').'</p><div>$view_button</div>';
return $invoice_message; return $invoice_message;
} }
public static function emailInvoiceReminderTemplate() public static function emailInvoiceReminderTemplate()
{ {
$invoice_message = '<p>$client<br><br>'.self::transformText('reminder_message').'</p><div class="center">$view_button</div>'; $invoice_message = '<p>$client<br><br>'.self::transformText('reminder_message').'</p><div>$view_button</div>';
return $invoice_message; return $invoice_message;
} }
@ -182,7 +196,7 @@ class EmailTemplateDefaults
public static function emailQuoteTemplate() public static function emailQuoteTemplate()
{ {
$quote_message = '<p>$client<br><br>'.self::transformText('quote_message').'</p><div class="center">$view_button</div>'; $quote_message = '<p>$client<br><br>'.self::transformText('quote_message').'</p><div>$view_button</div>';
return $quote_message; return $quote_message;
} }
@ -199,28 +213,28 @@ class EmailTemplateDefaults
public static function emailPurchaseOrderTemplate() public static function emailPurchaseOrderTemplate()
{ {
$purchase_order_message = '<p>$vendor<br><br>'.self::transformText('purchase_order_message').'</p><div class="center">$view_button</div>'; $purchase_order_message = '<p>$vendor<br><br>'.self::transformText('purchase_order_message').'</p><div>$view_button</div>';
return $purchase_order_message; return $purchase_order_message;
} }
public static function emailPaymentTemplate() public static function emailPaymentTemplate()
{ {
$payment_message = '<p>$client<br><br>'.self::transformText('payment_message').'<br><br>$invoices</p><div class="center">$view_button</div>'; $payment_message = '<p>$client<br><br>'.self::transformText('payment_message').'<br><br>$invoices</p><div>$view_button</div>';
return $payment_message; return $payment_message;
} }
public static function emailCreditTemplate() public static function emailCreditTemplate()
{ {
$credit_message = '<p>$client<br><br>'.self::transformText('credit_message').'</p><div class="center">$view_button</div>'; $credit_message = '<p>$client<br><br>'.self::transformText('credit_message').'</p><div>$view_button</div>';
return $credit_message; return $credit_message;
} }
public static function emailPaymentPartialTemplate() public static function emailPaymentPartialTemplate()
{ {
$payment_message = '<p>$client<br><br>'.self::transformText('payment_message').'<br><br>$invoices</p><div class="center">$view_button</div>'; $payment_message = '<p>$client<br><br>'.self::transformText('payment_message').'<br><br>$invoices</p><div>$view_button</div>';
return $payment_message; return $payment_message;
} }

View File

@ -0,0 +1,60 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper;
use Illuminate\Contracts\Database\Eloquent\Castable;
use App\Casts\QuickbooksSettingsCast;
/**
* QuickbooksSettings.
*/
class QuickbooksSettings implements Castable
{
public string $accessTokenKey;
public string $refresh_token;
public string $realmID;
public int $accessTokenExpiresAt;
public int $refreshTokenExpiresAt;
public string $baseURL;
/**
* entity client,invoice,quote,purchase_order,vendor,payment
* sync true/false
* update_record true/false
* direction push/pull/birectional
* */
public array $settings = [
'client' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
'vendor' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
'invoice' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
'quote' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
'purchase_order' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
'product' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
'payment' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
];
/**
* Get the name of the caster class to use when casting from / to this cast target.
*
* @param array<string, mixed> $arguments
*/
public static function castUsing(array $arguments): string
{
return QuickbooksSettingsCast::class;
}
}

View File

@ -17,6 +17,7 @@ use App\Models\Invoice;
use App\Models\Product; use App\Models\Product;
use App\DataProviders\USStates; use App\DataProviders\USStates;
use App\DataMapper\Tax\ZipTax\Response; use App\DataMapper\Tax\ZipTax\Response;
use App\Models\RecurringInvoice;
class BaseRule implements RuleInterface class BaseRule implements RuleInterface
{ {
@ -132,7 +133,7 @@ class BaseRule implements RuleInterface
public function shouldCalcTax(): bool public function shouldCalcTax(): bool
{ {
return $this->should_calc_tax; return $this->should_calc_tax && $this->checkIfInvoiceLocked();
} }
/** /**
* Initializes the tax rule for the entity. * Initializes the tax rule for the entity.
@ -215,7 +216,7 @@ class BaseRule implements RuleInterface
$this->invoice->tax_data = $tax_data; $this->invoice->tax_data = $tax_data;
if(\DB::transactionLevel() == 0) { if(\DB::transactionLevel() == 0 && isset($this->invoice->id)) {
try { try {
$this->invoice->saveQuietly(); $this->invoice->saveQuietly();
@ -400,4 +401,40 @@ class BaseRule implements RuleInterface
return ! in_array($iso_3166_2, array_merge($this->eu_country_codes, array_keys($this->region_codes))); return ! in_array($iso_3166_2, array_merge($this->eu_country_codes, array_keys($this->region_codes)));
} }
private function checkIfInvoiceLocked(): bool
{
$lock_invoices = $this->client->getSetting('lock_invoices');
if($this->invoice instanceof RecurringInvoice) {
return true;
}
switch ($lock_invoices) {
case 'off':
return true;
case 'when_sent':
if ($this->invoice->status_id == Invoice::STATUS_SENT) {
return false;
}
return true;
case 'when_paid':
if ($this->invoice->status_id == Invoice::STATUS_PAID) {
return false;
}
return true;
//if now is greater than the end of month the invoice was dated - do not modify
case 'end_of_month':
if(\Carbon\Carbon::parse($this->invoice->date)->setTimezone($this->invoice->company->timezone()->name)->endOfMonth()->lte(now())) {
return false;
}
return true;
default:
return true;
}
}
} }

View File

@ -43,6 +43,8 @@ class Rule extends BaseRule implements RuleInterface
public float $reduced_tax_rate = 0; public float $reduced_tax_rate = 0;
public string $tax_name1 = 'MwSt.'; public string $tax_name1 = 'MwSt.';
private string $tax_name;
/** /**
* Initializes the rules and builds any required data. * Initializes the rules and builds any required data.
* *
@ -50,6 +52,7 @@ class Rule extends BaseRule implements RuleInterface
*/ */
public function init(): self public function init(): self
{ {
$this->tax_name = $this->tax_name1;
$this->calculateRates(); $this->calculateRates();
return $this; return $this;
@ -91,6 +94,7 @@ class Rule extends BaseRule implements RuleInterface
*/ */
public function reverseTax($item): self public function reverseTax($item): self
{ {
$this->tax_name1 = $this->tax_name;
$this->tax_rate1 = 0; $this->tax_rate1 = 0;
return $this; return $this;
@ -103,6 +107,8 @@ class Rule extends BaseRule implements RuleInterface
*/ */
public function taxReduced($item): self public function taxReduced($item): self
{ {
$this->tax_name1 = $this->tax_name;
$this->tax_rate1 = $this->reduced_tax_rate; $this->tax_rate1 = $this->reduced_tax_rate;
return $this; return $this;
@ -115,6 +121,8 @@ class Rule extends BaseRule implements RuleInterface
*/ */
public function zeroRated($item): self public function zeroRated($item): self
{ {
$this->tax_name1 = $this->tax_name;
$this->tax_rate1 = 0; $this->tax_rate1 = 0;
return $this; return $this;
@ -142,6 +150,7 @@ class Rule extends BaseRule implements RuleInterface
public function taxDigital($item): self public function taxDigital($item): self
{ {
$this->tax_name1 = $this->tax_name;
$this->tax_rate1 = $this->tax_rate; $this->tax_rate1 = $this->tax_rate;
return $this; return $this;
@ -155,6 +164,7 @@ class Rule extends BaseRule implements RuleInterface
public function taxService($item): self public function taxService($item): self
{ {
$this->tax_name1 = $this->tax_name;
$this->tax_rate1 = $this->tax_rate; $this->tax_rate1 = $this->tax_rate;
return $this; return $this;
@ -168,6 +178,7 @@ class Rule extends BaseRule implements RuleInterface
public function taxShipping($item): self public function taxShipping($item): self
{ {
$this->tax_name1 = $this->tax_name;
$this->tax_rate1 = $this->tax_rate; $this->tax_rate1 = $this->tax_rate;
return $this; return $this;
@ -181,6 +192,7 @@ class Rule extends BaseRule implements RuleInterface
public function taxPhysical($item): self public function taxPhysical($item): self
{ {
$this->tax_name1 = $this->tax_name;
$this->tax_rate1 = $this->tax_rate; $this->tax_rate1 = $this->tax_rate;
return $this; return $this;
@ -229,8 +241,7 @@ class Rule extends BaseRule implements RuleInterface
// nlog("tax exempt"); // nlog("tax exempt");
$this->tax_rate = 0; $this->tax_rate = 0;
$this->reduced_tax_rate = 0; $this->reduced_tax_rate = 0;
} elseif($this->client_subregion != $this->client->company->tax_data->seller_subregion && in_array($this->client_subregion, $this->eu_country_codes) && $this->client->vat_number && $this->eu_business_tax_exempt) { } elseif($this->client_subregion != $this->client->company->tax_data->seller_subregion && in_array($this->client_subregion, $this->eu_country_codes) && $this->client->vat_number && $this->client->has_valid_vat_number && $this->eu_business_tax_exempt) {
// elseif($this->client_subregion != $this->client->company->tax_data->seller_subregion && in_array($this->client_subregion, $this->eu_country_codes) && $this->client->has_valid_vat_number && $this->eu_business_tax_exempt)
// nlog("euro zone and tax exempt"); // nlog("euro zone and tax exempt");
$this->tax_rate = 0; $this->tax_rate = 0;
$this->reduced_tax_rate = 0; $this->reduced_tax_rate = 0;
@ -240,8 +251,8 @@ class Rule extends BaseRule implements RuleInterface
$this->reduced_tax_rate = 0; $this->reduced_tax_rate = 0;
} elseif(!in_array($this->client_subregion, $this->eu_country_codes)) { } elseif(!in_array($this->client_subregion, $this->eu_country_codes)) {
$this->defaultForeign(); $this->defaultForeign();
} elseif(in_array($this->client_subregion, $this->eu_country_codes) && !$this->client->vat_number) { //eu country / no valid vat } elseif(in_array($this->client_subregion, $this->eu_country_codes) && ((strlen($this->client->vat_number ?? '') == 1) || !$this->client->has_valid_vat_number)) { //eu country / no valid vat
if(($this->client->company->tax_data->seller_subregion != $this->client_subregion) && $this->client->company->tax_data->regions->EU->has_sales_above_threshold) { if($this->client->company->tax_data->seller_subregion != $this->client_subregion) {
// nlog("eu zone with sales above threshold"); // nlog("eu zone with sales above threshold");
$this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->tax_rate ?? 0; $this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->tax_rate ?? 0;
$this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->reduced_tax_rate ?? 0; $this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->reduced_tax_rate ?? 0;

View File

@ -34,10 +34,10 @@ class TaxModel
if(!$model) { if(!$model) {
$this->regions = $this->init(); $this->regions = $this->init();
} else { } else {
//@phpstan-ignore-next-line //@phpstan-ignore-next-line
foreach($model as $key => $value) { foreach($model as $key => $value) {
$this->{$key} = $value; $this->{$key} = $value;
} }
} }
@ -48,8 +48,7 @@ class TaxModel
public function migrate(): self public function migrate(): self
{ {
if($this->version == 'alpha') if($this->version == 'alpha') {
{
$this->regions->EU->subregions->PL = new \stdClass(); $this->regions->EU->subregions->PL = new \stdClass();
$this->regions->EU->subregions->PL->tax_rate = 23; $this->regions->EU->subregions->PL->tax_rate = 23;
$this->regions->EU->subregions->PL->tax_name = 'VAT'; $this->regions->EU->subregions->PL->tax_name = 'VAT';

View File

@ -1,8 +1,18 @@
<?php <?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataProviders; namespace App\DataProviders;
final class CAProvinces { final class CAProvinces
{
/** /**
* The provinces and territories of Canada * The provinces and territories of Canada
* *
@ -30,7 +40,8 @@ final class CAProvinces {
* @param string $abbreviation * @param string $abbreviation
* @return string * @return string
*/ */
public static function getName($abbreviation) { public static function getName($abbreviation)
{
return self::$provinces[$abbreviation]; return self::$provinces[$abbreviation];
} }
@ -39,7 +50,8 @@ final class CAProvinces {
* *
* @return array * @return array
*/ */
public static function get() { public static function get()
{
return self::$provinces; return self::$provinces;
} }
@ -49,7 +61,8 @@ final class CAProvinces {
* @param string $name * @param string $name
* @return string * @return string
*/ */
public static function getAbbreviation($name) { public static function getAbbreviation($name)
{
return array_search(ucwords($name), self::$provinces); return array_search(ucwords($name), self::$provinces);
} }
} }

View File

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

View File

@ -58,7 +58,7 @@ class ClientWasArchived implements ShouldBroadcast
public function broadcastWith() public function broadcastWith()
{ {
$manager = new Manager(); $manager = new Manager();
$manager->setSerializer(new ArraySerializer()); $manager->setSerializer(new ArraySerializer());
$class = sprintf('App\\Transformers\\%sTransformer', class_basename($this->client)); $class = sprintf('App\\Transformers\\%sTransformer', class_basename($this->client));
@ -79,7 +79,7 @@ class ClientWasArchived implements ShouldBroadcast
*/ */
public function broadcastOn() public function broadcastOn()
{ {
return [ return [
new PrivateChannel("company-{$this->company->company_key}"), new PrivateChannel("company-{$this->company->company_key}"),
]; ];

View File

@ -0,0 +1,35 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Events\Invoice;
use App\Models\Company;
use App\Models\Invoice;
use Illuminate\Queue\SerializesModels;
/**
* Class InvoiceAutoBillFailed.
*/
class InvoiceAutoBillFailed
{
use SerializesModels;
/**
* Create a new event instance.
*
* @param Invoice $invoice
* @param Company $company
* @param array $event_vars
*/
public function __construct(public Invoice $invoice, public Company $company, public array $event_vars, public ?string $notes)
{
}
}

View File

@ -0,0 +1,35 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Events\Invoice;
use App\Models\Company;
use App\Models\Invoice;
use Illuminate\Queue\SerializesModels;
/**
* Class InvoiceAutoBillSuccess.
*/
class InvoiceAutoBillSuccess
{
use SerializesModels;
/**
* Create a new event instance.
*
* @param Invoice $invoice
* @param Company $company
* @param array $event_vars
*/
public function __construct(public Invoice $invoice, public Company $company, public array $event_vars)
{
}
}

View File

@ -39,6 +39,6 @@ class DuplicatePaymentException extends Exception
return response()->json([ return response()->json([
'message' => 'Duplicate request', 'message' => 'Duplicate request',
], 400); ], 400);
} }
} }

View File

@ -129,8 +129,8 @@ class ActivityExport extends BaseExport
$query->cursor() $query->cursor()
->each(function ($entity) { ->each(function ($entity) {
/** @var \App\Models\Activity $entity */ /** @var \App\Models\Activity $entity */
$this->buildRow($entity); $this->buildRow($entity);
}); });

View File

@ -451,6 +451,7 @@ class BaseExport
'project' => 'task.project_id', 'project' => 'task.project_id',
'billable' => 'task.billable', 'billable' => 'task.billable',
'item_notes' => 'task.item_notes', 'item_notes' => 'task.item_notes',
'time_log' => 'task.time_log',
]; ];
protected array $forced_client_fields = [ protected array $forced_client_fields = [
@ -1040,7 +1041,7 @@ class BaseExport
$recurring_filters = []; $recurring_filters = [];
if($this->company->getSetting('report_include_drafts')){ if($this->company->getSetting('report_include_drafts')) {
$recurring_filters[] = RecurringInvoice::STATUS_DRAFT; $recurring_filters[] = RecurringInvoice::STATUS_DRAFT;
} }
@ -1188,7 +1189,7 @@ class BaseExport
*/ */
protected function addInvoiceStatusFilter(Builder $query, string $status): Builder protected function addInvoiceStatusFilter(Builder $query, string $status): Builder
{ {
/** @var array $status_parameters */ /** @var array $status_parameters */
$status_parameters = explode(',', $status); $status_parameters = explode(',', $status);
@ -1269,7 +1270,7 @@ class BaseExport
$custom_start_date = now()->startOfYear(); $custom_start_date = now()->startOfYear();
$custom_end_date = now(); $custom_end_date = now();
} }
switch ($date_range) { switch ($date_range) {
case 'all': case 'all':
$this->start_date = 'All available data'; $this->start_date = 'All available data';
@ -1615,10 +1616,10 @@ class BaseExport
ZipDocuments::dispatch($documents, $this->company, $user); ZipDocuments::dispatch($documents, $this->company, $user);
} }
} }
/** /**
* Tests that the column exists * Tests that the column exists
* on the table prior to adding it to * on the table prior to adding it to
* the query builder * the query builder
* *
* @param string $table * @param string $table

View File

@ -102,7 +102,7 @@ class ClientExport extends BaseExport
$report = $query->cursor() $report = $query->cursor()
->map(function ($client) { ->map(function ($client) {
/** @var \App\Models\Client $client */ /** @var \App\Models\Client $client */
$row = $this->buildRow($client); $row = $this->buildRow($client);
return $this->processMetaData($row, $client); return $this->processMetaData($row, $client);
@ -133,7 +133,7 @@ class ClientExport extends BaseExport
$query->where('is_deleted', 0); $query->where('is_deleted', 0);
} }
$query = $this->addDateRange($query,' clients'); $query = $this->addDateRange($query, ' clients');
if($this->input['document_email_attachment'] ?? false) { if($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query); $this->queueDocuments($query);
@ -156,8 +156,8 @@ class ClientExport extends BaseExport
$query->cursor() $query->cursor()
->each(function ($client) { ->each(function ($client) {
/** @var \App\Models\Client $client */ /** @var \App\Models\Client $client */
$this->csv->insertOne($this->buildRow($client)); $this->csv->insertOne($this->buildRow($client));
}); });

View File

@ -52,7 +52,7 @@ class CreditExport extends BaseExport
$report = $query->cursor() $report = $query->cursor()
->map(function ($credit) { ->map(function ($credit) {
/** @var \App\Models\Credit $credit */ /** @var \App\Models\Credit $credit */
$row = $this->buildRow($credit); $row = $this->buildRow($credit);
return $this->processMetaData($row, $credit); return $this->processMetaData($row, $credit);

View File

@ -54,7 +54,7 @@ class DocumentExport extends BaseExport
$report = $query->cursor() $report = $query->cursor()
->map(function ($document) { ->map(function ($document) {
/** @var \App\Models\Document $document */ /** @var \App\Models\Document $document */
$row = $this->buildRow($document); $row = $this->buildRow($document);
return $this->processMetaData($row, $document); return $this->processMetaData($row, $document);
@ -101,7 +101,7 @@ class DocumentExport extends BaseExport
$query->cursor() $query->cursor()
->each(function ($entity) { ->each(function ($entity) {
/** @var mixed $entity */ /** @var mixed $entity */
$this->csv->insertOne($this->buildRow($entity)); $this->csv->insertOne($this->buildRow($entity));
}); });

View File

@ -52,7 +52,7 @@ class ExpenseExport extends BaseExport
$report = $query->cursor() $report = $query->cursor()
->map(function ($resource) { ->map(function ($resource) {
/** @var \App\Models\Expense $resource */ /** @var \App\Models\Expense $resource */
$row = $this->buildRow($resource); $row = $this->buildRow($resource);
return $this->processMetaData($row, $resource); return $this->processMetaData($row, $resource);
@ -134,7 +134,7 @@ class ExpenseExport extends BaseExport
$query->cursor() $query->cursor()
->each(function ($expense) { ->each(function ($expense) {
/** @var \App\Models\Expense $expense */ /** @var \App\Models\Expense $expense */
$this->csv->insertOne($this->buildRow($expense)); $this->csv->insertOne($this->buildRow($expense));
}); });
@ -266,11 +266,10 @@ class ExpenseExport extends BaseExport
if($expense->calculate_tax_by_amount) { if($expense->calculate_tax_by_amount) {
$total_tax_amount = round($expense->tax_amount1 + $expense->tax_amount2 + $expense->tax_amount3, $precision); $total_tax_amount = round($expense->tax_amount1 + $expense->tax_amount2 + $expense->tax_amount3, $precision);
if($expense->uses_inclusive_taxes) { if($expense->uses_inclusive_taxes) {
$entity['expense.net_amount'] = round($expense->amount, $precision) - $total_tax_amount; $entity['expense.net_amount'] = round($expense->amount, $precision) - $total_tax_amount;
} } else {
else {
$entity['expense.net_amount'] = round($expense->amount, $precision); $entity['expense.net_amount'] = round($expense->amount, $precision);
} }

View File

@ -99,7 +99,7 @@ class InvoiceExport extends BaseExport
$report = $query->cursor() $report = $query->cursor()
->map(function ($resource) { ->map(function ($resource) {
/** @var \App\Models\Invoice $resource */ /** @var \App\Models\Invoice $resource */
$row = $this->buildRow($resource); $row = $this->buildRow($resource);
return $this->processMetaData($row, $resource); return $this->processMetaData($row, $resource);
@ -121,7 +121,7 @@ class InvoiceExport extends BaseExport
$query->cursor() $query->cursor()
->each(function ($invoice) { ->each(function ($invoice) {
/** @var \App\Models\Invoice $invoice */ /** @var \App\Models\Invoice $invoice */
$this->csv->insertOne($this->buildRow($invoice)); $this->csv->insertOne($this->buildRow($invoice));
}); });

View File

@ -113,7 +113,7 @@ class InvoiceItemExport extends BaseExport
$query->cursor() $query->cursor()
->each(function ($resource) { ->each(function ($resource) {
/** @var \App\Models\Invoice $resource */ /** @var \App\Models\Invoice $resource */
$this->iterateItems($resource); $this->iterateItems($resource);
@ -143,7 +143,7 @@ class InvoiceItemExport extends BaseExport
$query->cursor() $query->cursor()
->each(function ($invoice) { ->each(function ($invoice) {
/** @var \App\Models\Invoice $invoice */ /** @var \App\Models\Invoice $invoice */
$this->iterateItems($invoice); $this->iterateItems($invoice);
}); });
@ -229,10 +229,6 @@ class InvoiceItemExport extends BaseExport
// $entity['currency'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code; // $entity['currency'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code;
// } // }
// if(array_key_exists('type', $entity)) {
// $entity['type'] = $invoice->typeIdString($entity['type']);
// }
// if(array_key_exists('tax_category', $entity)) { // if(array_key_exists('tax_category', $entity)) {
// $entity['tax_category'] = $invoice->taxTypeString($entity['tax_category']); // $entity['tax_category'] = $invoice->taxTypeString($entity['tax_category']);
// } // }
@ -266,9 +262,9 @@ class InvoiceItemExport extends BaseExport
} }
if (in_array('invoice.project', $this->input['report_keys'])) { if (in_array('invoice.project', $this->input['report_keys'])) {
$entity['invoice.project'] = $invoice->project ? $invoice->project->name : '';// @phpstan-ignore-line $entity['invoice.project'] = $invoice->project ? $invoice->project->name : '';// @phpstan-ignore-line
} }
return $entity; return $entity;
} }

View File

@ -92,7 +92,7 @@ class PaymentExport extends BaseExport
$report = $query->cursor() $report = $query->cursor()
->map(function ($resource) { ->map(function ($resource) {
/** @var \App\Models\Payment $resource */ /** @var \App\Models\Payment $resource */
$row = $this->buildRow($resource); $row = $this->buildRow($resource);
return $this->processMetaData($row, $resource); return $this->processMetaData($row, $resource);
@ -114,8 +114,8 @@ class PaymentExport extends BaseExport
$query->cursor() $query->cursor()
->each(function ($entity) { ->each(function ($entity) {
/** @var \App\Models\Payment $entity */ /** @var \App\Models\Payment $entity */
$this->csv->insertOne($this->buildRow($entity)); $this->csv->insertOne($this->buildRow($entity));
}); });

View File

@ -51,7 +51,7 @@ class ProductExport extends BaseExport
$report = $query->cursor() $report = $query->cursor()
->map(function ($resource) { ->map(function ($resource) {
/** @var \App\Models\Product $resource */ /** @var \App\Models\Product $resource */
$row = $this->buildRow($resource); $row = $this->buildRow($resource);
return $this->processMetaData($row, $resource); return $this->processMetaData($row, $resource);
@ -106,8 +106,8 @@ class ProductExport extends BaseExport
$query->cursor() $query->cursor()
->each(function ($entity) { ->each(function ($entity) {
/** @var \App\Models\Product $entity */ /** @var \App\Models\Product $entity */
$this->csv->insertOne($this->buildRow($entity)); $this->csv->insertOne($this->buildRow($entity));
}); });
return $this->csv->toString(); return $this->csv->toString();

View File

@ -98,7 +98,7 @@ class PurchaseOrderExport extends BaseExport
$report = $query->cursor() $report = $query->cursor()
->map(function ($resource) { ->map(function ($resource) {
/** @var \App\Models\PurchaseOrder $resource */ /** @var \App\Models\PurchaseOrder $resource */
$row = $this->buildRow($resource); $row = $this->buildRow($resource);
return $this->processMetaData($row, $resource); return $this->processMetaData($row, $resource);
@ -121,9 +121,9 @@ class PurchaseOrderExport extends BaseExport
$query->cursor() $query->cursor()
->each(function ($purchase_order) { ->each(function ($purchase_order) {
/** @var \App\Models\PurchaseOrder $purchase_order */ /** @var \App\Models\PurchaseOrder $purchase_order */
$this->csv->insertOne($this->buildRow($purchase_order)); $this->csv->insertOne($this->buildRow($purchase_order));
}); });
return $this->csv->toString(); return $this->csv->toString();

View File

@ -101,15 +101,15 @@ class PurchaseOrderItemExport extends BaseExport
$query->cursor() $query->cursor()
->each(function ($resource) { ->each(function ($resource) {
/** @var \App\Models\PurchaseOrder $resource */
$this->iterateItems($resource);
foreach($this->storage_array as $row) { /** @var \App\Models\PurchaseOrder $resource */
$this->storage_item_array[] = $this->processItemMetaData($row, $resource); $this->iterateItems($resource);
}
$this->storage_array = []; foreach($this->storage_array as $row) {
$this->storage_item_array[] = $this->processItemMetaData($row, $resource);
}
$this->storage_array = [];
}); });
@ -129,9 +129,9 @@ class PurchaseOrderItemExport extends BaseExport
$query->cursor() $query->cursor()
->each(function ($purchase_order) { ->each(function ($purchase_order) {
/** @var \App\Models\PurchaseOrder $purchase_order */ /** @var \App\Models\PurchaseOrder $purchase_order */
$this->iterateItems($purchase_order); $this->iterateItems($purchase_order);
}); });
$this->csv->insertAll($this->storage_array); $this->csv->insertAll($this->storage_array);
@ -213,10 +213,6 @@ class PurchaseOrderItemExport extends BaseExport
// $entity['currency'] = $purchase_order->vendor->currency() ? $purchase_order->vendor->currency()->code : $purchase_order->company->currency()->code; // $entity['currency'] = $purchase_order->vendor->currency() ? $purchase_order->vendor->currency()->code : $purchase_order->company->currency()->code;
// } // }
// if(array_key_exists('type', $entity)) {
// $entity['type'] = $purchase_order->typeIdString($entity['type']);
// }
// if(array_key_exists('tax_category', $entity)) { // if(array_key_exists('tax_category', $entity)) {
// $entity['tax_category'] = $purchase_order->taxTypeString($entity['tax_category']); // $entity['tax_category'] = $purchase_order->taxTypeString($entity['tax_category']);
// } // }

View File

@ -127,7 +127,7 @@ class QuoteExport extends BaseExport
$query->cursor() $query->cursor()
->each(function ($quote) { ->each(function ($quote) {
/** @var \App\Models\Quote $quote */ /** @var \App\Models\Quote $quote */
$this->csv->insertOne($this->buildRow($quote)); $this->csv->insertOne($this->buildRow($quote));
}); });

View File

@ -104,7 +104,7 @@ class QuoteItemExport extends BaseExport
$query->cursor() $query->cursor()
->each(function ($resource) { ->each(function ($resource) {
/** @var \App\Models\Quote $resource */ /** @var \App\Models\Quote $resource */
$this->iterateItems($resource); $this->iterateItems($resource);
@ -136,7 +136,7 @@ class QuoteItemExport extends BaseExport
$query->cursor() $query->cursor()
->each(function ($quote) { ->each(function ($quote) {
/** @var \App\Models\Quote $quote */ /** @var \App\Models\Quote $quote */
$this->iterateItems($quote); $this->iterateItems($quote);
}); });

View File

@ -93,7 +93,7 @@ class RecurringInvoiceExport extends BaseExport
$query->cursor() $query->cursor()
->each(function ($invoice) { ->each(function ($invoice) {
/** @var \App\Models\RecurringInvoice $invoice */ /** @var \App\Models\RecurringInvoice $invoice */
$this->csv->insertOne($this->buildRow($invoice)); $this->csv->insertOne($this->buildRow($invoice));
}); });
@ -114,7 +114,7 @@ class RecurringInvoiceExport extends BaseExport
$report = $query->cursor() $report = $query->cursor()
->map(function ($resource) { ->map(function ($resource) {
/** @var \App\Models\RecurringInvoice $resource */ /** @var \App\Models\RecurringInvoice $resource */
$row = $this->buildRow($resource); $row = $this->buildRow($resource);
return $this->processMetaData($row, $resource); return $this->processMetaData($row, $resource);

View File

@ -106,9 +106,9 @@ class TaskExport extends BaseExport
$query->cursor() $query->cursor()
->each(function ($entity) { ->each(function ($entity) {
/** @var \App\Models\Task $entity*/ /** @var \App\Models\Task $entity*/
$this->buildRow($entity); $this->buildRow($entity);
}); });
$this->csv->insertAll($this->storage_array); $this->csv->insertAll($this->storage_array);
@ -156,7 +156,7 @@ class TaskExport extends BaseExport
$entity[$key] = $transformed_entity[$parts[1]]; $entity[$key] = $transformed_entity[$parts[1]];
} elseif (array_key_exists($key, $transformed_entity)) { } elseif (array_key_exists($key, $transformed_entity)) {
$entity[$key] = $transformed_entity[$key]; $entity[$key] = $transformed_entity[$key];
} elseif (in_array($key, ['task.start_date', 'task.end_date', 'task.duration', 'task.billable', 'task.item_notes'])) { } elseif (in_array($key, ['task.start_date', 'task.end_date', 'task.duration', 'task.billable', 'task.item_notes', 'task.time_log'])) {
$entity[$key] = ''; $entity[$key] = '';
} else { } else {
$entity[$key] = $this->decorator->transform($key, $task); $entity[$key] = $this->decorator->transform($key, $task);
@ -207,6 +207,9 @@ class TaskExport extends BaseExport
$seconds = $task->calcDuration(); $seconds = $task->calcDuration();
$entity['task.duration'] = $seconds; $entity['task.duration'] = $seconds;
$entity['task.duration_words'] = $seconds > 86400 ? CarbonInterval::seconds($seconds)->locale($this->company->locale())->cascade()->forHumans() : now()->startOfDay()->addSeconds($seconds)->format('H:i:s'); $entity['task.duration_words'] = $seconds > 86400 ? CarbonInterval::seconds($seconds)->locale($this->company->locale())->cascade()->forHumans() : now()->startOfDay()->addSeconds($seconds)->format('H:i:s');
$entity['task.time_log'] = (isset($item[1]) && $item[1] != 0) ? $item[1] - $item[0] : ctrans('texts.is_running');
} }
if (in_array('task.billable', $this->input['report_keys']) || in_array('billable', $this->input['report_keys'])) { if (in_array('task.billable', $this->input['report_keys']) || in_array('billable', $this->input['report_keys'])) {

View File

@ -90,7 +90,7 @@ class VendorExport extends BaseExport
$report = $query->cursor() $report = $query->cursor()
->map(function ($resource) { ->map(function ($resource) {
/** @var \App\Models\Vendor $resource */ /** @var \App\Models\Vendor $resource */
$row = $this->buildRow($resource); $row = $this->buildRow($resource);
return $this->processMetaData($row, $resource); return $this->processMetaData($row, $resource);
@ -109,9 +109,9 @@ class VendorExport extends BaseExport
$query->cursor() $query->cursor()
->each(function ($vendor) { ->each(function ($vendor) {
/** @var \App\Models\Vendor $vendor */ /** @var \App\Models\Vendor $vendor */
$this->csv->insertOne($this->buildRow($vendor)); $this->csv->insertOne($this->buildRow($vendor));
}); });
return $this->csv->toString(); return $this->csv->toString();

View File

@ -96,7 +96,7 @@ class TaskDecorator extends Decorator implements DecoratorInterface
return ''; return '';
} }
/** /**
* billable * billable
* *
@ -106,7 +106,7 @@ class TaskDecorator extends Decorator implements DecoratorInterface
{ {
return ''; return '';
} }
/** /**
* items_notes * items_notes
* @todo * @todo
@ -115,7 +115,7 @@ class TaskDecorator extends Decorator implements DecoratorInterface
{ {
return ''; return '';
} }
public function duration(Task $task) public function duration(Task $task)
{ {
return $task->calcDuration(); return $task->calcDuration();

View File

@ -76,6 +76,26 @@ class InvoiceItemFactory
$data[] = $item; $data[] = $item;
} }
$item = self::create();
$item->quantity = $faker->numberBetween(1, 10);
$item->cost = $faker->randomFloat(2, 1, 1000);
$item->line_total = $item->quantity * $item->cost;
$item->is_amount_discount = true;
$item->discount = $faker->numberBetween(1, 10);
$item->notes = str_replace(['"',"'"], ['',""], $faker->realText(20));
$item->product_key = $faker->word();
// $item->custom_value1 = $faker->realText(10);
// $item->custom_value2 = $faker->realText(10);
// $item->custom_value3 = $faker->realText(10);
// $item->custom_value4 = $faker->realText(10);
$item->tax_name1 = 'GST';
$item->tax_rate1 = 10.00;
$item->type_id = '2';
$data[] = $item;
return $data; return $data;
} }

View File

@ -149,43 +149,43 @@ class RecurringExpenseToExpenseFactory
} }
// if (Str::contains($match, '|')) { // if (Str::contains($match, '|')) {
$parts = explode('|', $match); // [ '[MONTH', 'MONTH+2]' ] $parts = explode('|', $match); // [ '[MONTH', 'MONTH+2]' ]
$left = substr($parts[0], 1); // 'MONTH' $left = substr($parts[0], 1); // 'MONTH'
$right = substr($parts[1], 0, -1); // MONTH+2 $right = substr($parts[1], 0, -1); // MONTH+2
// If left side is not part of replacements, skip. // If left side is not part of replacements, skip.
if (! array_key_exists($left, $replacements['ranges'])) { if (! array_key_exists($left, $replacements['ranges'])) {
continue; continue;
} }
$_left = Carbon::createFromDate(now()->year, now()->month)->translatedFormat('F Y'); $_left = Carbon::createFromDate(now()->year, now()->month)->translatedFormat('F Y');
$_right = ''; $_right = '';
// If right side doesn't have any calculations, replace with raw ranges keyword. // If right side doesn't have any calculations, replace with raw ranges keyword.
if (! Str::contains($right, ['-', '+', '/', '*'])) { if (! Str::contains($right, ['-', '+', '/', '*'])) {
$_right = Carbon::createFromDate(now()->year, now()->month)->translatedFormat('F Y'); $_right = Carbon::createFromDate(now()->year, now()->month)->translatedFormat('F Y');
} }
// If right side contains one of math operations, calculate. // If right side contains one of math operations, calculate.
if (Str::contains($right, ['+'])) { if (Str::contains($right, ['+'])) {
$operation = preg_match_all('/(?!^-)[+*\/-](\s?-)?/', $right, $_matches); $operation = preg_match_all('/(?!^-)[+*\/-](\s?-)?/', $right, $_matches);
$_operation = array_shift($_matches)[0]; // + - $_operation = array_shift($_matches)[0]; // + -
$_value = explode($_operation, $right); // [MONTHYEAR, 4] $_value = explode($_operation, $right); // [MONTHYEAR, 4]
$_right = Carbon::createFromDate(now()->year, now()->month)->addMonths($_value[1])->translatedFormat('F Y'); //@phpstan-ignore-line $_right = Carbon::createFromDate(now()->year, now()->month)->addMonths($_value[1])->translatedFormat('F Y'); //@phpstan-ignore-line
} }
$replacement = sprintf('%s to %s', $_left, $_right); $replacement = sprintf('%s to %s', $_left, $_right);
$value = preg_replace( $value = preg_replace(
sprintf('/%s/', preg_quote($match)), sprintf('/%s/', preg_quote($match)),
$replacement, $replacement,
$value, $value,
1 1
); );
// } // }
} }

View File

@ -98,7 +98,14 @@ class CreditFilters extends QueryFilters
->orWhere('last_name', 'like', '%'.$filter.'%') ->orWhere('last_name', 'like', '%'.$filter.'%')
->orWhere('email', 'like', '%'.$filter.'%'); ->orWhere('email', 'like', '%'.$filter.'%');
}) })
->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']); ->orWhereRaw("
JSON_UNQUOTE(JSON_EXTRACT(
JSON_ARRAY(
JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')),
JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].product_key'))
), '$[*]')
) LIKE ?", ['%'.$filter.'%']);
// ->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
}); });
} }

View File

@ -99,6 +99,12 @@ class ExpenseFilters extends QueryFilters
}); });
} }
if (in_array('uninvoiced', $status_parameters)) {
$query->orWhere(function ($query) {
$query->whereNull('invoice_id');
});
}
if (in_array('paid', $status_parameters)) { if (in_array('paid', $status_parameters)) {
$query->orWhere(function ($query) { $query->orWhere(function ($query) {
$query->whereNotNull('payment_date'); $query->whereNotNull('payment_date');
@ -158,6 +164,19 @@ class ExpenseFilters extends QueryFilters
return $this->builder; return $this->builder;
} }
public function categories(string $categories = ''): Builder
{
$categories_exploded = explode(",", $categories);
if(empty($categories) || count(array_filter($categories_exploded)) == 0) {
return $this->builder;
}
$categories_keys = $this->transformKeys($categories_exploded);
return $this->builder->whereIn('category_id', $categories_keys);
}
public function number(string $number = ''): Builder public function number(string $number = ''): Builder
{ {
if (strlen($number) == 0) { if (strlen($number) == 0) {
@ -205,6 +224,11 @@ class ExpenseFilters extends QueryFilters
->whereColumn('expense_categories.id', 'expenses.category_id'), $sort_col[1]); ->whereColumn('expense_categories.id', 'expenses.category_id'), $sort_col[1]);
} }
if ($sort_col[0] == 'payment_date' && in_array($sort_col[1], ['asc', 'desc'])) {
return $this->builder
->orderByRaw('ISNULL(payment_date), payment_date '. $sort_col[1]);
}
if($sort_col[0] == 'number') { if($sort_col[0] == 'number') {
return $this->builder->orderByRaw("REGEXP_REPLACE(number,'[^0-9]+','')+0 " . $dir); return $this->builder->orderByRaw("REGEXP_REPLACE(number,'[^0-9]+','')+0 " . $dir);
} }

View File

@ -125,7 +125,14 @@ class InvoiceFilters extends QueryFilters
->orWhere('last_name', 'like', '%'.$filter.'%') ->orWhere('last_name', 'like', '%'.$filter.'%')
->orWhere('email', 'like', '%'.$filter.'%'); ->orWhere('email', 'like', '%'.$filter.'%');
}) })
->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']); ->orWhereRaw("
JSON_UNQUOTE(JSON_EXTRACT(
JSON_ARRAY(
JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')),
JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].product_key'))
), '$[*]')
) LIKE ?", ['%'.$filter.'%']);
// ->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
}); });
} }
@ -259,58 +266,6 @@ class InvoiceFilters extends QueryFilters
return $this->builder->where('due_date', '>=', $date); return $this->builder->where('due_date', '>=', $date);
} }
/**
* Filter by date range
*
* @param string $date_range
* @return Builder
*/
public function date_range(string $date_range = ''): Builder
{
$parts = explode(",", $date_range);
if (count($parts) != 2) {
return $this->builder;
}
try {
$start_date = Carbon::parse($parts[0]);
$end_date = Carbon::parse($parts[1]);
return $this->builder->whereBetween('date', [$start_date, $end_date]);
} catch(\Exception $e) {
return $this->builder;
}
}
/**
* Filter by due date range
*
* @param string $date_range
* @return Builder
*/
public function due_date_range(string $date_range = ''): Builder
{
$parts = explode(",", $date_range);
if (count($parts) != 2) {
return $this->builder;
}
try {
$start_date = Carbon::parse($parts[0]);
$end_date = Carbon::parse($parts[1]);
return $this->builder->whereBetween('due_date', [$start_date, $end_date]);
} catch(\Exception $e) {
return $this->builder;
}
}
/** /**
* Sorts the list based on $sort. * Sorts the list based on $sort.
* *

View File

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

View File

@ -96,7 +96,14 @@ class PurchaseOrderFilters extends QueryFilters
->orWhere('custom_value4', 'like', '%'.$filter.'%') ->orWhere('custom_value4', 'like', '%'.$filter.'%')
->orWhereHas('vendor', function ($q) use ($filter) { ->orWhereHas('vendor', function ($q) use ($filter) {
$q->where('name', 'like', '%'.$filter.'%'); $q->where('name', 'like', '%'.$filter.'%');
}); })
->orWhereRaw("
JSON_UNQUOTE(JSON_EXTRACT(
JSON_ARRAY(
JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')),
JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].product_key'))
), '$[*]')
) LIKE ?", ['%'.$filter.'%']);
}); });
} }

View File

@ -331,4 +331,61 @@ abstract class QueryFilters
->orderByRaw("{$this->with_property} = ? DESC", [$value]) ->orderByRaw("{$this->with_property} = ? DESC", [$value])
->company(); ->company();
} }
/**
* Filter by date range
*
* @param string $date_range
* @return Builder
*/
public function date_range(string $date_range = ''): Builder
{
$parts = explode(",", $date_range);
if (count($parts) != 2 || !in_array('date', \Illuminate\Support\Facades\Schema::getColumnListing($this->builder->getModel()->getTable()))) {
return $this->builder;
}
try {
$start_date = Carbon::parse($parts[0]);
$end_date = Carbon::parse($parts[1]);
return $this->builder->whereBetween('date', [$start_date, $end_date]);
} catch(\Exception $e) {
return $this->builder;
}
}
/**
* Filter by due date range
*
* @param string $date_range
* @return Builder
*/
public function due_date_range(string $date_range = ''): Builder
{
$parts = explode(",", $date_range);
if (count($parts) != 2 || !in_array('due_date', \Illuminate\Support\Facades\Schema::getColumnListing($this->builder->getModel()->getTable()))) {
return $this->builder;
}
try {
$start_date = Carbon::parse($parts[0]);
$end_date = Carbon::parse($parts[1]);
return $this->builder->whereBetween('due_date', [$start_date, $end_date]);
} catch(\Exception $e) {
return $this->builder;
}
}
} }

View File

@ -46,7 +46,14 @@ class QuoteFilters extends QueryFilters
->orWhere('last_name', 'like', '%'.$filter.'%') ->orWhere('last_name', 'like', '%'.$filter.'%')
->orWhere('email', 'like', '%'.$filter.'%'); ->orWhere('email', 'like', '%'.$filter.'%');
}) })
->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']); ->orWhereRaw("
JSON_UNQUOTE(JSON_EXTRACT(
JSON_ARRAY(
JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')),
JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].product_key'))
), '$[*]')
) LIKE ?", ['%'.$filter.'%']);
// ->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
}); });
} }

View File

@ -49,7 +49,14 @@ class RecurringInvoiceFilters extends QueryFilters
->orWhere('last_name', 'like', '%'.$filter.'%') ->orWhere('last_name', 'like', '%'.$filter.'%')
->orWhere('email', 'like', '%'.$filter.'%'); ->orWhere('email', 'like', '%'.$filter.'%');
}) })
->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']); ->orWhereRaw("
JSON_UNQUOTE(JSON_EXTRACT(
JSON_ARRAY(
JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')),
JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].product_key'))
), '$[*]')
) LIKE ?", ['%'.$filter.'%']);
//->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(line_items, '$[*].notes')) LIKE ?", ['%'.$filter.'%']);
}); });
} }
@ -134,7 +141,7 @@ class RecurringInvoiceFilters extends QueryFilters
return $this->builder->orderByRaw("REGEXP_REPLACE(number,'[^0-9]+','')+0 " . $dir); return $this->builder->orderByRaw("REGEXP_REPLACE(number,'[^0-9]+','')+0 " . $dir);
} }
if($sort_col[0] == 'status_id'){ if($sort_col[0] == 'status_id') {
return $this->builder->orderBy('status_id', $dir)->orderBy('last_sent_date', $dir); return $this->builder->orderBy('status_id', $dir)->orderBy('last_sent_date', $dir);
} }

View File

@ -85,6 +85,10 @@ class TaskFilters extends QueryFilters
$this->builder->whereNull('invoice_id'); $this->builder->whereNull('invoice_id');
} }
if (in_array('is_running', $status_parameters)) {
$this->builder->where('is_running', true);
}
return $this->builder; return $this->builder;
} }

View File

@ -99,11 +99,12 @@ class TransactionTransformer implements BankRevenueInterface
} elseif (array_key_exists('internalTransactionId', $transaction)) { } elseif (array_key_exists('internalTransactionId', $transaction)) {
$transactionId = $transaction["internalTransactionId"]; $transactionId = $transaction["internalTransactionId"];
} else { } else {
nlog(`Invalid Input for nordigen transaction transformer: ` . $transaction); nlog('Invalid Input for nordigen transaction transformer: ' . $transaction);
throw new \Exception('invalid dataset: missing transactionId - Please report this error to the developer'); throw new \Exception('invalid dataset: missing transactionId - Please report this error to the developer');
} }
$amount = (float) $transaction["transactionAmount"]["amount"]; $amount = (float) $transaction["transactionAmount"]["amount"];
$base_type = $amount < 0 ? 'DEBIT' : 'CREDIT';
// description could be in varios places // description could be in varios places
$description = ''; $description = '';
@ -140,7 +141,7 @@ class TransactionTransformer implements BankRevenueInterface
return [ return [
'transaction_id' => 0, 'transaction_id' => 0,
'nordigen_transaction_id' => $transactionId, 'nordigen_transaction_id' => $transactionId,
'amount' => $amount, 'amount' => abs($amount),
'currency_id' => $this->convertCurrency($transaction["transactionAmount"]["currency"]), 'currency_id' => $this->convertCurrency($transaction["transactionAmount"]["currency"]),
'category_id' => null, 'category_id' => null,
'category_type' => array_key_exists('additionalInformation', $transaction) ? $transaction["additionalInformation"] : '', 'category_type' => array_key_exists('additionalInformation', $transaction) ? $transaction["additionalInformation"] : '',
@ -148,7 +149,7 @@ class TransactionTransformer implements BankRevenueInterface
'description' => $description, 'description' => $description,
'participant' => $participant, 'participant' => $participant,
'participant_name' => $participant_name, 'participant_name' => $participant_name,
'base_type' => $amount < 0 ? 'DEBIT' : 'CREDIT', 'base_type' => $base_type,
]; ];
} }
@ -165,7 +166,7 @@ class TransactionTransformer implements BankRevenueInterface
/** @var \App\Models\Currency $currency */ /** @var \App\Models\Currency $currency */
return $currency ? $currency->id : 1; //@phpstan-ignore-line return $currency ? $currency->id : 1; //@phpstan-ignore-line
} }
private function formatDate(string $input) private function formatDate(string $input)

View File

@ -61,7 +61,7 @@ class EpcQrGenerator
} catch(\Throwable $e) { } catch(\Throwable $e) {
nlog("EPC QR failure => ".$e->getMessage()); nlog("EPC QR failure => ".$e->getMessage());
return ''; return '';
} }
} }

View File

@ -64,7 +64,7 @@ class InvoiceSumInclusive
{ {
$this->invoice = $invoice; $this->invoice = $invoice;
$this->client = $invoice->client ?? $invoice->vendor; $this->client = $invoice->client ?? $invoice->vendor;
$this->precision = $this->client->currency()->precision; $this->precision = $this->client->currency()->precision;
$this->rappen_rounding = $this->client->getSetting('enable_rappen_rounding'); $this->rappen_rounding = $this->client->getSetting('enable_rappen_rounding');

View File

@ -23,12 +23,12 @@ class ProRata
* the time interval and the frequency duration * the time interval and the frequency duration
* *
* @param float $amount * @param float $amount
* @param Carbon $from_date * @param \Illuminate\Support\Carbon | \Carbon\Carbon $from_date
* @param Carbon $to_date * @param \Illuminate\Support\Carbon | \Carbon\Carbon $to_date
* @param int $frequency * @param int $frequency
* @return float * @return float
*/ */
public function refund(float $amount, Carbon $from_date, Carbon $to_date, int $frequency): float public function refund(float $amount, $from_date, $to_date, int $frequency): float
{ {
$days = intval(abs($from_date->copy()->diffInDays($to_date))); $days = intval(abs($from_date->copy()->diffInDays($to_date)));
$days_in_frequency = $this->getDaysInFrequency($frequency); $days_in_frequency = $this->getDaysInFrequency($frequency);
@ -41,12 +41,12 @@ class ProRata
* the time interval and the frequency duration * the time interval and the frequency duration
* *
* @param float $amount * @param float $amount
* @param Carbon $from_date * @param \Illuminate\Support\Carbon | \Carbon\Carbon $from_date
* @param Carbon $to_date * @param \Illuminate\Support\Carbon | \Carbon\Carbon $to_date
* @param int $frequency * @param int $frequency
* @return float * @return float
*/ */
public function charge(float $amount, Carbon $from_date, Carbon $to_date, int $frequency): float public function charge(float $amount, $from_date, $to_date, int $frequency): float
{ {
$days = intval(abs($from_date->copy()->diffInDays($to_date))); $days = intval(abs($from_date->copy()->diffInDays($to_date)));
$days_in_frequency = $this->getDaysInFrequency($frequency); $days_in_frequency = $this->getDaysInFrequency($frequency);

View File

@ -53,7 +53,7 @@ class GmailTransport extends AbstractTransport
if ($bccs) { if ($bccs) {
$bcc_list = 'Bcc: '; $bcc_list = 'Bcc: ';
foreach ($bccs->getAddresses() as $address) { foreach ($bccs->getAddresses() as $address) {
$bcc_list .= $address->getAddress() .','; $bcc_list .= $address->getAddress() .',';
} }

View File

@ -22,5 +22,5 @@
*/ */
function ctrans(string $string, $replace = [], $locale = null): string function ctrans(string $string, $replace = [], $locale = null): string
{ {
return trans($string, $replace, $locale); return html_entity_decode(trans($string, $replace, $locale));
} }

View File

@ -117,7 +117,7 @@ class ActivityController extends BaseController
} }
/** /**
* downloadHistoricalEntity * downloadHistoricalEntity
* *
@ -204,7 +204,7 @@ class ActivityController extends BaseController
$activity->user_id = $user->id; $activity->user_id = $user->id;
$activity->ip = $request->ip(); $activity->ip = $request->ip();
$activity->activity_type_id = Activity::USER_NOTE; $activity->activity_type_id = Activity::USER_NOTE;
switch (get_class($entity)) { switch (get_class($entity)) {
case Invoice::class: case Invoice::class:
$activity->invoice_id = $entity->id; $activity->invoice_id = $entity->id;
@ -254,17 +254,20 @@ class ActivityController extends BaseController
$activity->client_id = $entity->client_id; $activity->client_id = $entity->client_id;
$activity->project_id = $entity->project_id; $activity->project_id = $entity->project_id;
$activity->vendor_id = $entity->vendor_id; $activity->vendor_id = $entity->vendor_id;
// no break
case Task::class: case Task::class:
$activity->task_id = $entity->id; $activity->task_id = $entity->id;
$activity->expense_id = $entity->id; $activity->expense_id = $entity->id;
$activity->client_id = $entity->client_id; $activity->client_id = $entity->client_id;
$activity->project_id = $entity->project_id; $activity->project_id = $entity->project_id;
$activity->vendor_id = $entity->vendor_id; $activity->vendor_id = $entity->vendor_id;
// no break
case Payment::class: case Payment::class:
$activity->payment_id = $entity->id; $activity->payment_id = $entity->id;
$activity->expense_id = $entity->id; $activity->expense_id = $entity->id;
$activity->client_id = $entity->client_id; $activity->client_id = $entity->client_id;
$activity->project_id = $entity->project_id; $activity->project_id = $entity->project_id;
// no break
default: default:
# code... # code...
break; break;

View File

@ -41,9 +41,10 @@ class ContactLoginController extends Controller
$company = false; $company = false;
$account = false; $account = false;
if($request->query('intended')) if($request->query('intended')) {
$request->session()->put('url.intended', $request->query('intended')); $request->session()->put('url.intended', $request->query('intended'));
}
if ($request->session()->has('company_key')) { if ($request->session()->has('company_key')) {
MultiDB::findAndSetDbByCompanyKey($request->session()->get('company_key')); MultiDB::findAndSetDbByCompanyKey($request->session()->get('company_key'));
$company = Company::where('company_key', $request->session()->get('company_key'))->first(); $company = Company::where('company_key', $request->session()->get('company_key'))->first();
@ -141,9 +142,10 @@ class ContactLoginController extends Controller
} }
$this->setRedirectPath(); $this->setRedirectPath();
if($intended) if($intended) {
$this->redirectTo = $intended; $this->redirectTo = $intended;
}
return $request->wantsJson() return $request->wantsJson()
? new JsonResponse([], 204) ? new JsonResponse([], 204)

View File

@ -399,8 +399,8 @@ class LoginController extends BaseController
$truth->setCompany($set_company); $truth->setCompany($set_company);
//21-03-2024 //21-03-2024
$cu->each(function ($cu) { $cu->each(function ($cu) {
/** @var \App\Models\CompanyUser $cu */ /** @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()) { if(CompanyToken::query()->where('company_id', $cu->company_id)->where('user_id', $cu->user_id)->where('is_system', true)->doesntExist()) {

View File

@ -217,7 +217,7 @@ class BankIntegrationController extends BaseController
} }
if (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key') && (Ninja::isSelfHost() || (Ninja::isHosted() && $user_account->isEnterprisePaidClient()))) { 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 */ /** @var \App\Models\BankIntegration $bank_integration */
ProcessBankTransactionsNordigen::dispatch($bank_integration); ProcessBankTransactionsNordigen::dispatch($bank_integration);
}); });

View File

@ -934,7 +934,7 @@ class BaseController extends Controller
} elseif (in_array($this->entity_type, [Design::class, GroupSetting::class, PaymentTerm::class, TaskStatus::class])) { } elseif (in_array($this->entity_type, [Design::class, GroupSetting::class, PaymentTerm::class, TaskStatus::class])) {
// nlog($this->entity_type); // nlog($this->entity_type);
} else { } else {
$query->where(function ($q) use ($user){ //grouping these together improves query performance significantly) $query->where(function ($q) use ($user) { //grouping these together improves query performance significantly)
$q->where('user_id', '=', $user->id)->orWhere('assigned_user_id', $user->id); $q->where('user_id', '=', $user->id)->orWhere('assigned_user_id', $user->id);
}); });
} }
@ -996,7 +996,7 @@ class BaseController extends Controller
if(request()->has('einvoice')) { if(request()->has('einvoice')) {
if(class_exists(Schema::class)){ if(class_exists(Schema::class)) {
$ro = new Schema(); $ro = new Schema();
$response_data['einvoice_schema'] = $ro('Peppol'); $response_data['einvoice_schema'] = $ro('Peppol');
} }

View File

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

View File

@ -73,7 +73,7 @@ class ChartController extends BaseController
$user = auth()->user(); $user = auth()->user();
$cs = new ChartService($user->company(), $user, $user->isAdmin()); $cs = new ChartService($user->company(), $user, $user->isAdmin());
$result = $cs->getCalculatedField($request->all()); $result = $cs->getCalculatedField($request->all());
return response()->json($result, 200); return response()->json($result, 200);
} }

View File

@ -114,9 +114,10 @@ class InvitationController extends Controller
'invitation_key' => $invitation_key 'invitation_key' => $invitation_key
]); ]);
} }
if(!auth()->guard('contact')->check()){ if(!auth()->guard('contact')->check()) {
$this->middleware('auth:contact'); $this->middleware('auth:contact');
/** @var \App\Models\InvoiceInvitation | \App\Models\QuoteInvitation | \App\Models\CreditInvitation | \App\Models\RecurringInvoiceInvitation $invitation */
return redirect()->route('client.login', ['intended' => route('client.'.$entity.'.show', [$entity => $this->encodePrimaryKey($invitation->{$key}), 'silent' => $is_silent])]); return redirect()->route('client.login', ['intended' => route('client.'.$entity.'.show', [$entity => $this->encodePrimaryKey($invitation->{$key}), 'silent' => $is_silent])]);
} }
@ -146,7 +147,7 @@ class InvitationController extends Controller
} }
private function fireEntityViewedEvent($invitation, $entity_string) private function fireEntityViewedEvent($invitation, $entity_string)
{ {
switch ($entity_string) { switch ($entity_string) {

View File

@ -62,6 +62,7 @@ class InvoiceController extends Controller
$invitation = $invoice->invitations()->where('client_contact_id', auth()->guard('contact')->user()->id)->first(); $invitation = $invoice->invitations()->where('client_contact_id', auth()->guard('contact')->user()->id)->first();
// @phpstan-ignore-next-line
if ($invitation && auth()->guard('contact') && ! session()->get('is_silent') && ! $invitation->viewed_date) { if ($invitation && auth()->guard('contact') && ! session()->get('is_silent') && ! $invitation->viewed_date) {
$invitation->markViewed(); $invitation->markViewed();
@ -77,13 +78,17 @@ class InvoiceController extends Controller
'key' => $invitation ? $invitation->key : false, 'key' => $invitation ? $invitation->key : false,
'hash' => $hash, 'hash' => $hash,
'variables' => $variables, 'variables' => $variables,
'invoices' => [$invoice->hashed_id],
'db' => $invoice->company->db,
]; ];
if ($request->query('mode') === 'fullscreen') { if ($request->query('mode') === 'fullscreen') {
return render('invoices.show-fullscreen', $data); return render('invoices.show-fullscreen', $data);
} }
return $this->render('invoices.show', $data); return auth()->guard('contact')->user()->client->getSetting('payment_flow') == 'default' ? $this->render('invoices.show', $data) : $this->render('invoices.show_smooth', $data);
// return $this->render('invoices.show_smooth', $data);
} }
public function showBlob($hash) public function showBlob($hash)
@ -235,9 +240,12 @@ class InvoiceController extends Controller
'hashed_ids' => $invoices->pluck('hashed_id'), 'hashed_ids' => $invoices->pluck('hashed_id'),
'total' => $total, 'total' => $total,
'variables' => $variables, 'variables' => $variables,
'invitation' => $invitation,
'db' => $invitation->company->db,
]; ];
return $this->render('invoices.payment', $data); // return $this->render('invoices.payment', $data);
return auth()->guard('contact')->user()->client->getSetting('payment_flow') === 'default' ? $this->render('invoices.payment', $data) : $this->render('invoices.show_smooth_multi', $data);
} }
/** /**

View File

@ -88,6 +88,8 @@ class NinjaPlanController extends Controller
{ {
$trial_started = "Trial Started @ ".now()->format('Y-m-d H:i:s'); $trial_started = "Trial Started @ ".now()->format('Y-m-d H:i:s');
auth()->guard('contact')->user()->fill($request->only(['first_name','last_name']))->save();
$client = auth()->guard('contact')->user()->client; $client = auth()->guard('contact')->user()->client;
$client->private_notes = $trial_started; $client->private_notes = $trial_started;
$client->fill($request->all()); $client->fill($request->all());

View File

@ -126,7 +126,7 @@ class PaymentController extends Controller
// if($payment_hash) // if($payment_hash)
$invoice = $payment_hash->fee_invoice; $invoice = $payment_hash->fee_invoice;
// else // else
// $invoice = Invoice::with('client')->where('id',$payment_hash->fee_invoice_id)->orderBy('id','desc')->first(); // $invoice = Invoice::with('client')->where('id',$payment_hash->fee_invoice_id)->orderBy('id','desc')->first();
// $invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id); // $invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);

View File

@ -35,11 +35,16 @@ class PrePaymentController extends Controller
/** /**
* Show the list of payments. * Show the list of payments.
* *
* @return Factory|View * @return Factory|View|\Illuminate\Http\RedirectResponse
*/ */
public function index() public function index()
{ {
$client = auth()->guard('contact')->user()->client; $client = auth()->guard('contact')->user()->client;
if(!$client->getSetting('client_initiated_payments'))
return redirect()->route('client.dashboard');
$minimum = $client->getSetting('client_initiated_payments_minimum'); $minimum = $client->getSetting('client_initiated_payments_minimum');
$minimum_amount = $minimum == 0 ? "" : Number::formatMoney($minimum, $client); $minimum_amount = $minimum == 0 ? "" : Number::formatMoney($minimum, $client);

View File

@ -94,7 +94,7 @@ class SubscriptionPurchaseController extends Controller
*/ */
private function setLocale(string $locale): string private function setLocale(string $locale): string
{ {
/** @var \Illuminate\Support\Collection<\App\Models\Language> */ /** @var \Illuminate\Support\Collection<\App\Models\Language> */
$languages = app('languages'); $languages = app('languages');
@ -104,6 +104,6 @@ class SubscriptionPurchaseController extends Controller
}); });
return $record ? $record->locale : 'en'; return $record ? $record->locale : 'en';
} }
} }

View File

@ -707,7 +707,7 @@ class CompanyController extends BaseController
} }
/** /**
* *
* *
* @return \Symfony\Component\HttpFoundation\StreamedResponse | \Illuminate\Http\JsonResponse * @return \Symfony\Component\HttpFoundation\StreamedResponse | \Illuminate\Http\JsonResponse
*/ */

View File

@ -615,7 +615,7 @@ class CreditController extends BaseController
return response()->streamDownload(function () use ($file) { return response()->streamDownload(function () use ($file) {
echo $file; echo $file;
}, $credit->numberFormatter() . '.pdf', ['Content-Type' => 'application/pdf']); }, $credit->numberFormatter() . '.pdf', ['Content-Type' => 'application/pdf']);
case 'archive': case 'archive':
$this->credit_repository->archive($credit); $this->credit_repository->archive($credit);

View File

@ -0,0 +1,26 @@
<?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\Http\Controllers\EInvoice;
use App\Http\Controllers\Controller;
use App\Http\Requests\EInvoice\SignupRequest;
class SelfhostController extends Controller
{
public function index(SignupRequest $request)
{
return view('einvoice.index');
}
}

View File

@ -51,9 +51,9 @@ class EmailHistoryController extends BaseController
/** @var \App\Models\User $user */ /** @var \App\Models\User $user */
$user = auth()->user(); $user = auth()->user();
$data = SystemLog::where('company_id', $user->company()->id) $data = SystemLog::where('company_id', $user->company()->id)
->where('category_id', SystemLog::CATEGORY_MAIL) ->where('category_id', SystemLog::CATEGORY_MAIL)
->whereJsonContains('log->history->entity', $request->entity)
->whereJsonContains('log->history->entity_id', $this->encodePrimaryKey($request->entity_id)) ->whereJsonContains('log->history->entity_id', $this->encodePrimaryKey($request->entity_id))
->orderBy('id', 'DESC') ->orderBy('id', 'DESC')
->cursor() ->cursor()

View File

@ -584,14 +584,15 @@ class ExpenseController extends BaseController
return $this->itemResponse($expense->fresh()); return $this->itemResponse($expense->fresh());
} }
public function edocument(EDocumentRequest $request): string public function edocument(EDocumentRequest $request)
{ {
if ($request->hasFile("documents")) { $user = auth()->user();
return (new ImportEDocument($request->file("documents")[0]->get(), $request->file("documents")[0]->getClientOriginalName()))->handle();
} foreach($request->file("documents") as $file) {
else { ImportEDocument::dispatch($file->get(), $file->getClientOriginalName(), $user->company());
return "No file found";
} }
return response()->json(['message' => 'Processing....'], 200);
} }
} }

View File

@ -59,9 +59,9 @@ class ExportController extends BaseController
/** @var \App\Models\User $user */ /** @var \App\Models\User $user */
$user = auth()->user(); $user = auth()->user();
$hash = Str::uuid(); $hash = Str::uuid()->toString();
$url = \Illuminate\Support\Facades\URL::temporarySignedRoute('protected_download', now()->addHour(), ['hash' => $hash]); $url = \Illuminate\Support\Facades\URL::temporarySignedRoute('protected_download', now()->addHour(), ['hash' => $hash]);
Cache::put($hash, $url, now()->addHour()); Cache::put($hash, $url, 3600);
CompanyExport::dispatch($user->getCompany(), $user, $hash); CompanyExport::dispatch($user->getCompany(), $user, $hash);

View File

@ -118,9 +118,36 @@ class ImportController extends Controller
})->toArray(); })->toArray();
//Exact string match
foreach($headers as $key => $value) { foreach($headers as $key => $value) {
foreach($translated_keys as $tkey => $tvalue) {
$concat_needle = str_ireplace(" ", "", $tvalue['index'].$tvalue['label']);
$concat_value = str_ireplace(" ", "", $value);
if($this->testMatch($concat_value, $concat_needle)) {
$hit = $tvalue['key'];
$hints[$key] = $hit;
unset($translated_keys[$tkey]);
break;
} else {
$hints[$key] = null;
}
}
}
//Label Match
foreach($headers as $key => $value) {
if(isset($hints[$key])) {
continue;
}
foreach($translated_keys as $tkey => $tvalue) { foreach($translated_keys as $tkey => $tvalue) {
if($this->testMatch($value, $tvalue['label'])) { if($this->testMatch($value, $tvalue['label'])) {
@ -134,10 +161,9 @@ class ImportController extends Controller
} }
} }
//second pass using the index of the translation here //Index matching pass using the index of the translation here
foreach($headers as $key => $value) { foreach($headers as $key => $value) {
if(isset($hints[$key])) { if(isset($hints[$key])) {
continue; continue;

View File

@ -0,0 +1,63 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Controllers;
use App\Http\Requests\Quickbooks\AuthorizedQuickbooksRequest;
use App\Libraries\MultiDB;
use Illuminate\Support\Facades\Cache;
use App\Http\Requests\Quickbooks\AuthQuickbooksRequest;
use App\Services\Quickbooks\QuickbooksService;
class ImportQuickbooksController extends BaseController
{
// private array $import_entities = [
// 'client' => 'Customer',
// 'invoice' => 'Invoice',
// 'product' => 'Item',
// 'payment' => 'Payment'
// ];
public function onAuthorized(AuthorizedQuickbooksRequest $request)
{
MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']);
$company = $request->getCompany();
$qb = new QuickbooksService($company);
$realm = $request->query('realmId');
$access_token_object = $qb->sdk()->accessTokenFromCode($request->query('code'), $realm);
$qb->sdk()->saveOAuthToken($access_token_object);
return redirect(config('ninja.react_url'));
}
/**
* Determine if the user is authorized to make this request.
*
*/
public function authorizeQuickbooks(AuthQuickbooksRequest $request, string $token)
{
MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']);
$company = $request->getCompany();
$qb = new QuickbooksService($company);
$authorizationUrl = $qb->sdk()->getAuthorizationUrl();
$state = $qb->sdk()->getState();
Cache::put($state, $token, 190);
return redirect()->to($authorizationUrl);
}
}

View File

@ -503,7 +503,7 @@ class InvoiceController extends BaseController
$invoices = Invoice::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get(); $invoices = Invoice::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get();
if ($invoices->count() == 0 ) { if ($invoices->count() == 0) {
return response()->json(['message' => 'No Invoices Found']); return response()->json(['message' => 'No Invoices Found']);
} }

View File

@ -20,7 +20,6 @@ use Illuminate\Http\Request;
*/ */
class MailgunWebhookController extends BaseController class MailgunWebhookController extends BaseController
{ {
public function __construct() public function __construct()
{ {
} }
@ -35,7 +34,7 @@ class MailgunWebhookController extends BaseController
} }
if(\hash_equals(\hash_hmac('sha256', $input['signature']['timestamp'] . $input['signature']['token'], config('services.mailgun.webhook_signing_key')), $input['signature']['signature'])) { if(\hash_equals(\hash_hmac('sha256', $input['signature']['timestamp'] . $input['signature']['token'], config('services.mailgun.webhook_signing_key')), $input['signature']['signature'])) {
ProcessMailgunWebhook::dispatch($request->all())->delay(rand(2,10)); ProcessMailgunWebhook::dispatch($request->all())->delay(rand(2, 10));
} }
return response()->json(['message' => 'Success.'], 200); return response()->json(['message' => 'Success.'], 200);

View File

@ -22,7 +22,6 @@ use Illuminate\Support\Str;
class OneTimeTokenController extends BaseController class OneTimeTokenController extends BaseController
{ {
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();

View File

@ -25,7 +25,7 @@ class PaymentNotificationWebhookController extends Controller
{ {
/** @var \App\Models\CompanyGateway $company_gateway */ /** @var \App\Models\CompanyGateway $company_gateway */
$company_gateway = CompanyGateway::find($this->decodePrimaryKey($company_gateway_id)); $company_gateway = CompanyGateway::find($this->decodePrimaryKey($company_gateway_id));
/** @var \App\Models\Client $client */ /** @var \App\Models\Client $client */
$client = Client::find($this->decodePrimaryKey($client_hash)); $client = Client::find($this->decodePrimaryKey($client_hash));

View File

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

View File

@ -297,6 +297,8 @@ class PreviewController extends BaseController
->setTemplate($design_object) ->setTemplate($design_object)
->mock(); ->mock();
} catch(SyntaxError $e) { } catch(SyntaxError $e) {
} catch(\Exception $e) {
return response()->json(['message' => 'invalid data access', 'errors' => ['design.design.body' => $e->getMessage()]], 422);
} }
if (request()->query('html') == 'true') { if (request()->query('html') == 'true') {

View File

@ -85,12 +85,12 @@ class SearchController extends Controller
// ->whereHas('client', function ($q) { // ->whereHas('client', function ($q) {
// $q->where('is_deleted', 0); // $q->where('is_deleted', 0);
// }) // })
->leftJoin('clients', function ($join) { ->leftJoin('clients', function ($join) {
$join->on('invoices.client_id', '=', 'clients.id') $join->on('invoices.client_id', '=', 'clients.id')
->where('clients.is_deleted', 0); ->where('clients.is_deleted', 0);
}) })
->when(!$user->hasPermission('view_all') || !$user->hasPermission('view_invoice'), function ($query) use ($user) { ->when(!$user->hasPermission('view_all') || !$user->hasPermission('view_invoice'), function ($query) use ($user) {
$query->where('invoices.user_id', $user->id); $query->where('invoices.user_id', $user->id);
}) })

View File

@ -181,8 +181,9 @@ class SelfUpdateController extends BaseController
public function checkVersion() public function checkVersion()
{ {
if(Ninja::isHosted()) if(Ninja::isHosted()) {
return '5.10.SaaS'; return '5.10.SaaS';
}
return trim(file_get_contents(config('ninja.version_url'))); return trim(file_get_contents(config('ninja.version_url')));
} }

View File

@ -11,23 +11,25 @@
namespace App\Http\Controllers\VendorPortal; namespace App\Http\Controllers\VendorPortal;
use App\Events\Misc\InvitationWasViewed; use App\Utils\Ninja;
use App\Events\PurchaseOrder\PurchaseOrderWasAccepted; use App\Models\Webhook;
use App\Events\PurchaseOrder\PurchaseOrderWasViewed; use Illuminate\View\View;
use App\Models\PurchaseOrder;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesDates;
use App\Jobs\Entity\CreateRawPdf;
use App\Jobs\Util\WebhookHandler;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\VendorPortal\PurchaseOrders\ProcessPurchaseOrdersInBulkRequest; use App\Jobs\Invoice\InjectSignature;
use Illuminate\Support\Facades\Cache;
use Illuminate\Contracts\View\Factory;
use App\Models\PurchaseOrderInvitation;
use App\Events\Misc\InvitationWasViewed;
use App\Events\PurchaseOrder\PurchaseOrderWasViewed;
use App\Events\PurchaseOrder\PurchaseOrderWasAccepted;
use App\Http\Requests\VendorPortal\PurchaseOrders\ShowPurchaseOrderRequest; use App\Http\Requests\VendorPortal\PurchaseOrders\ShowPurchaseOrderRequest;
use App\Http\Requests\VendorPortal\PurchaseOrders\ShowPurchaseOrdersRequest; use App\Http\Requests\VendorPortal\PurchaseOrders\ShowPurchaseOrdersRequest;
use App\Jobs\Entity\CreateRawPdf; use App\Http\Requests\VendorPortal\PurchaseOrders\ProcessPurchaseOrdersInBulkRequest;
use App\Jobs\Invoice\InjectSignature;
use App\Models\PurchaseOrder;
use App\Models\PurchaseOrderInvitation;
use App\Utils\Ninja;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash;
use Illuminate\Contracts\View\Factory;
use Illuminate\Support\Facades\Cache;
use Illuminate\View\View;
class PurchaseOrderController extends Controller class PurchaseOrderController extends Controller
{ {
@ -187,6 +189,9 @@ class PurchaseOrderController extends Controller
} }
event(new PurchaseOrderWasAccepted($purchase_order, auth()->guard('vendor')->user(), $purchase_order->company, Ninja::eventVars())); event(new PurchaseOrderWasAccepted($purchase_order, auth()->guard('vendor')->user(), $purchase_order->company, Ninja::eventVars()));
WebhookHandler::dispatch(Webhook::EVENT_ACCEPTED_PURCHASE_ORDER, $purchase_order, $purchase_order->company, 'vendor')->delay(0);
}); });
if ($purchase_count_query->count() == 1) { if ($purchase_count_query->count() == 1) {

View File

@ -14,7 +14,7 @@ namespace App\Http\Controllers\VendorPortal;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\VendorContact; use App\Models\VendorContact;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use App\Utils\TranslationHelper; use Illuminate\Http\Request;
class VendorContactController extends Controller class VendorContactController extends Controller
{ {
@ -58,14 +58,14 @@ class VendorContactController extends Controller
'settings' => $vendor_contact->vendor->company->settings, 'settings' => $vendor_contact->vendor->company->settings,
'company' => $vendor_contact->vendor->company, 'company' => $vendor_contact->vendor->company,
'sidebar' => $this->sidebarMenu(), 'sidebar' => $this->sidebarMenu(),
'countries' => TranslationHelper::getCountries(), 'countries' => app('countries'),
]); ]);
} }
public function update(VendorContact $vendor_contact) public function update(Request $request, VendorContact $vendor_contact)
{ {
$vendor_contact->fill(request()->all()); $vendor_contact->fill($request->all());
$vendor_contact->vendor->fill(request()->all()); $vendor_contact->vendor->fill($request->all());
$vendor_contact->push(); $vendor_contact->push();
return back()->withSuccess(ctrans('texts.profile_updated_successfully')); return back()->withSuccess(ctrans('texts.profile_updated_successfully'));
@ -76,16 +76,10 @@ class VendorContactController extends Controller
$enabled_modules = auth()->guard('vendor')->user()->company->enabled_modules; $enabled_modules = auth()->guard('vendor')->user()->company->enabled_modules;
$data = []; $data = [];
// TODO: Enable dashboard once it's completed.
// $this->settings->enable_client_portal_dashboard
// $data[] = [ 'title' => ctrans('texts.dashboard'), 'url' => 'client.dashboard', 'icon' => 'activity'];
if (self::MODULE_PURCHASE_ORDERS & $enabled_modules) { if (self::MODULE_PURCHASE_ORDERS & $enabled_modules) {
$data[] = ['title' => ctrans('texts.purchase_orders'), 'url' => 'vendor.purchase_orders.index', 'icon' => 'file-text']; $data[] = ['title' => ctrans('texts.purchase_orders'), 'url' => 'vendor.purchase_orders.index', 'icon' => 'file-text'];
} }
// $data[] = ['title' => ctrans('texts.documents'), 'url' => 'client.documents.index', 'icon' => 'download'];
return $data; return $data;
} }
} }

View File

@ -34,7 +34,7 @@ class VendorContactHashLoginController extends Controller
{ {
return redirect($this->setRedirectPath()); return redirect($this->setRedirectPath());
} }
/** /**
* errorPage * errorPage
* *

View File

@ -79,7 +79,7 @@ class ContactRegister
// As a fallback for self-hosted, it will use default company in the system // As a fallback for self-hosted, it will use default company in the system
// if key isn't provided in the url. // if key isn't provided in the url.
if (! $request->route()->parameter('company_key') && Ninja::isSelfHost()) { if (! $request->route()->parameter('company_key') && Ninja::isSelfHost()) {
$company = Account::query()->first()->default_company; $company = Account::query()->first()->default_company ?? Account::query()->first()->companies->first();
if (! $company->client_can_register) { if (! $company->client_can_register) {
abort(400, 'Registration disabled'); abort(400, 'Registration disabled');

View File

@ -4,15 +4,15 @@ namespace App\Http\Middleware;
use Closure; use Closure;
use Illuminate\Cache\RateLimiter; use Illuminate\Cache\RateLimiter;
use Illuminate\Contracts\Redis\Factory as Redis;
use Illuminate\Redis\Limiters\DurationLimiter; use Illuminate\Redis\Limiters\DurationLimiter;
use Illuminate\Routing\Middleware\ThrottleRequests;
class ThrottleRequestsWithPredis extends ThrottleRequests class ThrottleRequestsWithPredis extends \Illuminate\Routing\Middleware\ThrottleRequests
{ {
/** /**
* The Redis factory implementation. * The Redis factory implementation.
* *
* @var \Illuminate\Redis\Connections\Connection * @var \Illuminate\Contracts\Redis\Factory
*/ */
protected $redis; protected $redis;
@ -32,14 +32,14 @@ class ThrottleRequestsWithPredis extends ThrottleRequests
/** /**
* Create a new request throttler. * Create a new request throttler.
*
* @param \Illuminate\Cache\RateLimiter $limiter
* @return void * @return void
*/ */
public function __construct(RateLimiter $limiter)
/** @phpstan-ignore-next-line */
public function __construct(RateLimiter $limiter, Redis $redis)
{ {
parent::__construct($limiter); parent::__construct($limiter);
/** @phpstan-ignore-next-line */
$this->redis = \Illuminate\Support\Facades\Redis::connection('sentinel-cache'); $this->redis = \Illuminate\Support\Facades\Redis::connection('sentinel-cache');
} }
@ -56,7 +56,7 @@ class ThrottleRequestsWithPredis extends ThrottleRequests
protected function handleRequest($request, Closure $next, array $limits) protected function handleRequest($request, Closure $next, array $limits)
{ {
foreach ($limits as $limit) { foreach ($limits as $limit) {
if ($this->tooManyAttempts($limit->key, $limit->maxAttempts, $limit->decayMinutes)) { if ($this->tooManyAttempts($limit->key, $limit->maxAttempts, $limit->decaySeconds)) {
throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback); throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback);
} }
} }
@ -79,16 +79,16 @@ class ThrottleRequestsWithPredis extends ThrottleRequests
* *
* @param string $key * @param string $key
* @param int $maxAttempts * @param int $maxAttempts
* @param int $decayMinutes * @param int $decaySeconds
* @return mixed * @return mixed
*/ */
protected function tooManyAttempts($key, $maxAttempts, $decayMinutes) protected function tooManyAttempts($key, $maxAttempts, $decaySeconds)
{ {
$limiter = new DurationLimiter( $limiter = new DurationLimiter(
$this->redis, $this->getRedisConnection(),
$key, $key,
$maxAttempts, $maxAttempts,
$decayMinutes * 60 $decaySeconds
); );
return tap(! $limiter->acquire(), function () use ($key, $limiter) { return tap(! $limiter->acquire(), function () use ($key, $limiter) {
@ -121,4 +121,13 @@ class ThrottleRequestsWithPredis extends ThrottleRequests
{ {
return $this->decaysAt[$key] - $this->currentTime(); return $this->decaysAt[$key] - $this->currentTime();
} }
/**
* Get the Redis connection that should be used for throttling.
*
*/
protected function getRedisConnection()
{
return $this->redis;
}
} }

View File

@ -68,11 +68,12 @@ class StoreNoteRequest extends Request
public function getEntity() public function getEntity()
{ {
if(!$this->entity) if(!$this->entity) {
return false; return false;
}
$class = "\\App\\Models\\".ucfirst(Str::camel(rtrim($this->entity, 's'))); $class = "\\App\\Models\\".ucfirst(Str::camel(rtrim($this->entity, 's')));
return $class::withTrashed()->find(is_string($this->entity_id) ? $this->decodePrimaryKey($this->entity_id) : $this->entity_id); return $class::withTrashed()->find(is_string($this->entity_id) ? $this->decodePrimaryKey($this->entity_id) : $this->entity_id);
} }

View File

@ -48,7 +48,8 @@ class StoreBankTransactionRuleRequest extends Request
'rules.*.value' => 'bail|required|nullable', 'rules.*.value' => 'bail|required|nullable',
'auto_convert' => 'bail|sometimes|bool', 'auto_convert' => 'bail|sometimes|bool',
'matches_on_all' => 'bail|sometimes|bool', 'matches_on_all' => 'bail|sometimes|bool',
'applies_to' => 'bail|sometimes|string', 'applies_to' => 'bail|sometimes|string|in:CREDIT,DEBIT',
'on_credit_match' => 'bail|sometimes|in:create_payment,link_payment'
]; ];
$rules['category_id'] = 'bail|sometimes|nullable|exists:expense_categories,id,company_id,'.$user->company()->id.',is_deleted,0'; $rules['category_id'] = 'bail|sometimes|nullable|exists:expense_categories,id,company_id,'.$user->company()->id.',is_deleted,0';

View File

@ -66,8 +66,7 @@ class ShowCalculatedFieldRequest extends Request
$input['end_date'] = now()->format('Y-m-d'); $input['end_date'] = now()->format('Y-m-d');
} }
if(isset($input['period']) && $input['period'] == 'previous') if(isset($input['period']) && $input['period'] == 'previous') {
{
$dates = $this->calculatePreviousPeriodStartAndEndDates($input, $user->company()); $dates = $this->calculatePreviousPeriodStartAndEndDates($input, $user->company());
$input['start_date'] = $dates[0]; $input['start_date'] = $dates[0];
$input['end_date'] = $dates[1]; $input['end_date'] = $dates[1];

View File

@ -13,13 +13,11 @@ namespace App\Http\Requests\Client;
use App\DataMapper\ClientSettings; use App\DataMapper\ClientSettings;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use App\Http\ValidationRules\Client\CountryCodeExistsRule;
use App\Http\ValidationRules\Ninja\CanStoreClientsRule; use App\Http\ValidationRules\Ninja\CanStoreClientsRule;
use App\Http\ValidationRules\ValidClientGroupSettingsRule; use App\Http\ValidationRules\ValidClientGroupSettingsRule;
use App\Models\Client; use App\Models\Client;
use App\Models\GroupSetting; use App\Models\GroupSetting;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Support\Facades\Cache;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
class StoreClientRequest extends Request class StoreClientRequest extends Request
@ -200,7 +198,7 @@ class StoreClientRequest extends Request
private function getCountryCode(string $country_code) private function getCountryCode(string $country_code)
{ {
/** @var \Illuminate\Support\Collection<\App\Models\Country> */ /** @var \Illuminate\Support\Collection<\App\Models\Country> */
$countries = app('countries'); $countries = app('countries');
@ -209,12 +207,12 @@ class StoreClientRequest extends Request
}); });
return $country ? (string) $country->id : ''; return $country ? (string) $country->id : '';
} }
private function getCurrencyCode($code) private function getCurrencyCode($code)
{ {
/** @var \Illuminate\Support\Collection<\App\Models\Currency> */ /** @var \Illuminate\Support\Collection<\App\Models\Currency> */
$currencies = app('currencies'); $currencies = app('currencies');
@ -223,6 +221,6 @@ class StoreClientRequest extends Request
}); });
return $currency ? (string)$currency->id : ''; return $currency ? (string)$currency->id : '';
} }
} }

View File

@ -142,7 +142,7 @@ class UpdateClientRequest extends Request
private function getCountryCode($country_code) private function getCountryCode($country_code)
{ {
/** @var \Illuminate\Support\Collection<\App\Models\Country> */ /** @var \Illuminate\Support\Collection<\App\Models\Country> */
$countries = app('countries'); $countries = app('countries');
@ -155,7 +155,7 @@ class UpdateClientRequest extends Request
private function getLanguageId($language_code) private function getLanguageId($language_code)
{ {
/** @var \Illuminate\Support\Collection<\App\Models\Language> */ /** @var \Illuminate\Support\Collection<\App\Models\Language> */
$languages = app('languages'); $languages = app('languages');

View File

@ -14,7 +14,7 @@ class ShowCreditRequest extends FormRequest
*/ */
public function authorize() public function authorize()
{ {
auth()->guard('contact')->user()->loadMissing(['company']); auth()->guard('contact')->user()->loadMissing(['company']);
return ! $this->credit->is_deleted return ! $this->credit->is_deleted
&& (bool)(auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_CREDITS) && (bool)(auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_CREDITS)

View File

@ -19,7 +19,7 @@ class ProcessInvoicesInBulkRequest extends FormRequest
{ {
public function authorize() public function authorize()
{ {
auth()->guard('contact')->user()->loadMissing(['company']); auth()->guard('contact')->user()->loadMissing(['company']);
return (bool)(auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_INVOICES); return (bool)(auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_INVOICES);

View File

@ -23,7 +23,7 @@ class ShowInvoiceRequest extends Request
*/ */
public function authorize(): bool public function authorize(): bool
{ {
auth()->guard('contact')->user()->loadMissing(['company']); auth()->guard('contact')->user()->loadMissing(['company']);
return (int) auth()->guard('contact')->user()->client_id === (int) $this->invoice->client_id return (int) auth()->guard('contact')->user()->client_id === (int) $this->invoice->client_id

View File

@ -19,7 +19,7 @@ class ShowInvoicesRequest extends FormRequest
{ {
public function authorize() public function authorize()
{ {
auth()->guard('contact')->user()->loadMissing(['company']); auth()->guard('contact')->user()->loadMissing(['company']);
return (bool)(auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_INVOICES); return (bool)(auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_INVOICES);

View File

@ -18,7 +18,7 @@ class CreatePaymentMethodRequest extends FormRequest
*/ */
public function authorize(): bool public function authorize(): bool
{ {
auth()->guard('contact')->user()->loadMissing(['client' => function ($query) { auth()->guard('contact')->user()->loadMissing(['client' => function ($query) {
$query->without('gateway_tokens', 'documents', 'contacts.company', 'contacts'); // Exclude 'grandchildren' relation of 'client' $query->without('gateway_tokens', 'documents', 'contacts.company', 'contacts'); // Exclude 'grandchildren' relation of 'client'
}]); }]);

View File

@ -15,7 +15,7 @@ class StorePrePaymentRequest extends FormRequest
*/ */
public function authorize() public function authorize()
{ {
auth()->guard('contact')->user()->loadMissing(['company']); auth()->guard('contact')->user()->loadMissing(['company']);
auth()->guard('contact')->user()->loadMissing(['client' => function ($query) { auth()->guard('contact')->user()->loadMissing(['client' => function ($query) {

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