Compare commits

..

3 Commits

Author SHA1 Message Date
David Bomba
a148eec09b
Merge pull request #10011 from invoiceninja/revert-9970-blockonomics-driver
Revert "Add Blockonomics payment capabilities"
2024-09-19 08:11:34 +10:00
David Bomba
5cdc036c74
Revert "Add Blockonomics payment capabilities" 2024-09-19 08:11:20 +10:00
David Bomba
8247873fd3
Merge pull request #9970 from cnohall/blockonomics-driver
Add Blockonomics payment capabilities
2024-09-19 08:04:53 +10:00
1026 changed files with 327474 additions and 386073 deletions

View File

@ -24,7 +24,6 @@ QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MAIL_MAILER=smtp
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

View File

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

View File

@ -18,7 +18,7 @@ jobs:
phpunit-versions: ['latest']
ci_node_total: [ 8 ]
ci_node_index: [ 0, 1, 2, 3, 4, 5, 6, 7]
laravel: [11.*]
laravel: [10.*]
dependency-version: [prefer-stable]
env:
@ -103,6 +103,7 @@ jobs:
restore-keys: |
${{ runner.os }}-${{ matrix.php }}-composer-
- name: Install composer dependencies
run: |
composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }}

View File

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

View File

@ -1 +1 @@
5.10.29
5.10.17

View File

@ -1,50 +0,0 @@
<?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

@ -1,83 +0,0 @@
<?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

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

View File

@ -516,13 +516,9 @@ class CompanySettings extends BaseSettings
public $quote_late_fee_amount1 = 0;
public $quote_late_fee_percent1 = 0;
public string $payment_flow = 'default'; //smooth
public string $email_subject_payment_failed = '';
public string $email_template_payment_failed = '';
public static $casts = [
'payment_flow' => 'string',
'enable_quote_reminder1' => 'bool',
'quote_num_days_reminder1' => 'int',
'quote_schedule_reminder1' => 'string',
@ -772,8 +768,6 @@ class CompanySettings extends BaseSettings
'portal_custom_js' => 'string',
'client_portal_enable_uploads' => 'bool',
'purchase_order_number_counter' => 'integer',
'email_template_payment_failed' => 'string',
'email_subject_payment_failed' => 'string',
];
public static $free_plan_casts = [

View File

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

View File

@ -1,61 +0,0 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper;
use Illuminate\Contracts\Database\Eloquent\Castable;
use App\Casts\QuickbooksSettingsCast;
/**
* QuickbooksSettings.
*/
class QuickbooksSettings implements Castable
{
public string $accessTokenKey;
public string $refresh_token;
public string $realmID;
public int $accessTokenExpiresAt;
public int $refreshTokenExpiresAt;
public string $baseURL;
/**
* entity client,invoice,quote,purchase_order,vendor,payment
* sync true/false
* update_record true/false
* direction push/pull/birectional
* */
public array $settings = [
'client' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
'vendor' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
'invoice' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
'sales' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
'quote' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
'purchase_order' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
'product' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
'payment' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
];
/**
* 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,7 +17,6 @@ use App\Models\Invoice;
use App\Models\Product;
use App\DataProviders\USStates;
use App\DataMapper\Tax\ZipTax\Response;
use App\Models\RecurringInvoice;
class BaseRule implements RuleInterface
{
@ -48,9 +47,6 @@ class BaseRule implements RuleInterface
'DK', // Denmark
'EE', // Estonia
'ES', // Spain
'ES-CN', // Canary Islands
'ES-CE', // Ceuta
'ES-ML', // Melilla
'FI', // Finland
'FR', // France
'GR', // Greece
@ -81,9 +77,6 @@ class BaseRule implements RuleInterface
'DK' => 'EU', // Denmark
'EE' => 'EU', // Estonia
'ES' => 'EU', // Spain
'ES-CN' => 'EU', // Canary Islands
'ES-CE' => 'EU', // Ceuta
'ES-ML' => 'EU', // Melilla
'FI' => 'EU', // Finland
'FR' => 'EU', // France
'GR' => 'EU', // Greece
@ -139,7 +132,7 @@ class BaseRule implements RuleInterface
public function shouldCalcTax(): bool
{
return $this->should_calc_tax && $this->checkIfInvoiceLocked();
return $this->should_calc_tax;
}
/**
* Initializes the tax rule for the entity.
@ -222,7 +215,7 @@ class BaseRule implements RuleInterface
$this->invoice->tax_data = $tax_data;
if(\DB::transactionLevel() == 0 && isset($this->invoice->id)) {
if(\DB::transactionLevel() == 0) {
try {
$this->invoice->saveQuietly();
@ -407,40 +400,4 @@ class BaseRule implements RuleInterface
return ! in_array($iso_3166_2, array_merge($this->eu_country_codes, array_keys($this->region_codes)));
}
private function checkIfInvoiceLocked(): bool
{
$lock_invoices = $this->client->getSetting('lock_invoices');
if($this->invoice instanceof RecurringInvoice) {
return true;
}
switch ($lock_invoices) {
case 'off':
return true;
case 'when_sent':
if ($this->invoice->status_id == Invoice::STATUS_SENT) {
return false;
}
return true;
case 'when_paid':
if ($this->invoice->status_id == Invoice::STATUS_PAID) {
return false;
}
return true;
//if now is greater than the end of month the invoice was dated - do not modify
case 'end_of_month':
if(\Carbon\Carbon::parse($this->invoice->date)->setTimezone($this->invoice->company->timezone()->name)->endOfMonth()->lte(now())) {
return false;
}
return true;
default:
return true;
}
}
}

View File

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

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@ class TaxModel
public string $seller_subregion = 'CA';
/** @var string $version */
public string $version = 'gamma';
public string $version = 'beta';
/** @var object $regions */
public object $regions;
@ -48,7 +48,8 @@ class TaxModel
public function migrate(): self
{
if($this->version == 'alpha') {
if($this->version == 'alpha')
{
$this->regions->EU->subregions->PL = new \stdClass();
$this->regions->EU->subregions->PL->tax_rate = 23;
$this->regions->EU->subregions->PL->tax_name = 'VAT';
@ -58,32 +59,6 @@ class TaxModel
$this->version = 'beta';
}
if($this->version == 'beta') {
//CEUTA
$this->regions->EU->subregions->{'ES-CE'} = new \stdClass();
$this->regions->EU->subregions->{'ES-CE'}->tax_rate = 4;
$this->regions->EU->subregions->{'ES-CE'}->tax_name = 'IGIC';
$this->regions->EU->subregions->{'ES-CE'}->reduced_tax_rate = 4;
$this->regions->EU->subregions->{'ES-CE'}->apply_tax = false;
//MELILLA ML 4
$this->regions->EU->subregions->{'ES-ML'} = new \stdClass();
$this->regions->EU->subregions->{'ES-ML'}->tax_rate = 4;
$this->regions->EU->subregions->{'ES-ML'}->tax_name = 'IGIC';
$this->regions->EU->subregions->{'ES-ML'}->reduced_tax_rate = 4;
$this->regions->EU->subregions->{'ES-ML'}->apply_tax = false;
//CANARIAS CN 7/3
$this->regions->EU->subregions->{'ES-CN'} = new \stdClass();
$this->regions->EU->subregions->{'ES-CN'}->tax_rate = 7;
$this->regions->EU->subregions->{'ES-CN'}->tax_name = 'IGIC';
$this->regions->EU->subregions->{'ES-CN'}->reduced_tax_rate = 4;
$this->regions->EU->subregions->{'ES-CN'}->apply_tax = false;
$this->version = 'gamma';
}
return $this;
}
@ -445,25 +420,6 @@ class TaxModel
$this->regions->EU->subregions->ES->reduced_tax_rate = 10;
$this->regions->EU->subregions->ES->apply_tax = false;
$this->regions->EU->subregions->{'ES-CE'} = new \stdClass();
$this->regions->EU->subregions->{'ES-CE'}->tax_rate = 4;
$this->regions->EU->subregions->{'ES-CE'}->tax_name = 'IGIC';
$this->regions->EU->subregions->{'ES-CE'}->reduced_tax_rate = 4;
$this->regions->EU->subregions->{'ES-CE'}->apply_tax = false;
$this->regions->EU->subregions->{'ES-ML'} = new \stdClass();
$this->regions->EU->subregions->{'ES-ML'}->tax_rate = 4;
$this->regions->EU->subregions->{'ES-ML'}->tax_name = 'IGIC';
$this->regions->EU->subregions->{'ES-ML'}->reduced_tax_rate = 4;
$this->regions->EU->subregions->{'ES-ML'}->apply_tax = false;
$this->regions->EU->subregions->{'ES-CN'} = new \stdClass();
$this->regions->EU->subregions->{'ES-CN'}->tax_rate = 7;
$this->regions->EU->subregions->{'ES-CN'}->tax_name = 'IGIC';
$this->regions->EU->subregions->{'ES-CN'}->reduced_tax_rate = 3;
$this->regions->EU->subregions->{'ES-CN'}->apply_tax = false;
$this->regions->EU->subregions->FI = new \stdClass();
$this->regions->EU->subregions->FI->tax_rate = 24;
$this->regions->EU->subregions->FI->tax_name = 'ALV';

View File

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

View File

@ -0,0 +1,19 @@
<?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

@ -1,22 +0,0 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Enum;
enum HttpVerb: string
{
case POST = 'post';
case PUT = 'put';
case GET = 'get';
case PATCH = 'patch';
case DELETE = 'delete';
}

View File

@ -1,35 +0,0 @@
<?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

@ -1,35 +0,0 @@
<?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

@ -1,34 +0,0 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;
use Exception;
class PeppolValidationException extends Exception
{
protected string $field = '';
public function __construct($message, $field, $code = 0, Exception $previous = null)
{
// Store the custom data
$this->field = $field;
// Ensure that everything is assigned properly by calling the parent constructor
parent::__construct($message, $code, $previous);
}
public function getInvalidField()
{
return $this->field;
}
}

View File

@ -101,10 +101,7 @@ class ActivityExport extends BaseExport
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->company->settings));
/** @var \App\Models\DateFormat $df */
$df = DateFormat::query()->find($this->company->settings->date_format_id);
$this->date_format = $df->format;
$this->date_format = DateFormat::find($this->company->settings->date_format_id)->format;
if (count($this->input['report_keys']) == 0) {
$this->input['report_keys'] = array_values($this->entity_keys);

View File

@ -451,7 +451,6 @@ class BaseExport
'project' => 'task.project_id',
'billable' => 'task.billable',
'item_notes' => 'task.item_notes',
'time_log' => 'task.time_log',
];
protected array $forced_client_fields = [
@ -845,7 +844,7 @@ class BaseExport
/**
* Apply Product Filters
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param Builder $query
*
* @return Builder
*/
@ -870,7 +869,7 @@ class BaseExport
/**
* Add Client Filter
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param Builder $query
* @param mixed $clients
*
* @return Builder
@ -893,7 +892,7 @@ class BaseExport
/**
* Add Vendor Filter
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param Builder $query
* @param string $vendors
*
* @return Builder
@ -917,7 +916,7 @@ class BaseExport
/**
* AddProjectFilter
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param Builder $query
* @param string $projects
*
* @return Builder
@ -941,7 +940,7 @@ class BaseExport
/**
* Add Category Filter
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param Builder $query
* @param string $expense_categories
*
* @return Builder
@ -966,7 +965,7 @@ class BaseExport
/**
* Add Payment Status Filters
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param Builder $query
* @param string $status
*
* @return Builder
@ -1024,10 +1023,10 @@ class BaseExport
/**
* Add RecurringInvoice Status Filter
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param Builder $query
* @param string $status
*
* @return \Illuminate\Database\Eloquent\Builder
* @return Builder
*/
protected function addRecurringInvoiceStatusFilter(Builder $query, string $status): Builder
{
@ -1067,7 +1066,7 @@ class BaseExport
/**
* Add QuoteStatus Filter
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param Builder $query
* @param string $status
*
* @return Builder
@ -1133,7 +1132,7 @@ class BaseExport
/**
* Add PurchaseOrder Status Filter
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param Builder $query
* @param string $status
*
* @return Builder
@ -1183,7 +1182,7 @@ class BaseExport
/**
* Add Invoice Status Filter
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param Builder $query
* @param string $status
* @return Builder
*/
@ -1249,7 +1248,7 @@ class BaseExport
/**
* Add Date Range
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param Builder $query
* @param ?string $table_name
* @return Builder
*/

View File

@ -269,7 +269,8 @@ class ExpenseExport extends BaseExport
if($expense->uses_inclusive_taxes) {
$entity['expense.net_amount'] = round($expense->amount, $precision) - $total_tax_amount;
} else {
}
else {
$entity['expense.net_amount'] = round($expense->amount, $precision);
}

View File

@ -229,6 +229,10 @@ class InvoiceItemExport extends BaseExport
// $entity['currency'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code;
// }
// if(array_key_exists('type', $entity)) {
// $entity['type'] = $invoice->typeIdString($entity['type']);
// }
// if(array_key_exists('tax_category', $entity)) {
// $entity['tax_category'] = $invoice->taxTypeString($entity['tax_category']);
// }

View File

@ -213,6 +213,10 @@ class PurchaseOrderItemExport extends BaseExport
// $entity['currency'] = $purchase_order->vendor->currency() ? $purchase_order->vendor->currency()->code : $purchase_order->company->currency()->code;
// }
// if(array_key_exists('type', $entity)) {
// $entity['type'] = $purchase_order->typeIdString($entity['type']);
// }
// if(array_key_exists('tax_category', $entity)) {
// $entity['tax_category'] = $purchase_order->taxTypeString($entity['tax_category']);
// }

View File

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

View File

@ -16,7 +16,7 @@ use App\Models\Credit;
class CreditFactory
{
public static function create(int $company_id, int $user_id): Credit
public static function create(int $company_id, int $user_id, object $settings = null, Client $client = null): Credit
{
$credit = new Credit();
$credit->status_id = Credit::STATUS_DRAFT;

View File

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

View File

@ -68,7 +68,7 @@ class BankTransactionFilters extends QueryFilters
*/
public function client_status(string $value = ''): Builder
{
if (strlen($value ?? '') == 0) {
if (strlen($value) == 0) {
return $this->builder;
}
@ -108,24 +108,13 @@ class BankTransactionFilters extends QueryFilters
}
if (count($debit_or_withdrawal_array) >= 1) {
$query->whereIn('base_type', $debit_or_withdrawal_array);
$query->orWhereIn('base_type', $debit_or_withdrawal_array);
}
});
return $this->builder;
}
public function active_banks(string $value = ''): Builder
{
if (strlen($value) == 0 || $value != 'true') {
return $this->builder;
}
return $this->builder->whereHas('bank_integration', function ($query){
$query->where('is_deleted', 0)->whereNull('deleted_at');
});
}
/**
* Filters the list based on Bank Accounts.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -99,12 +99,11 @@ class TransactionTransformer implements BankRevenueInterface
} elseif (array_key_exists('internalTransactionId', $transaction)) {
$transactionId = $transaction["internalTransactionId"];
} 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');
}
$amount = (float) $transaction["transactionAmount"]["amount"];
$base_type = $amount < 0 ? 'DEBIT' : 'CREDIT';
// description could be in varios places
$description = '';
@ -141,7 +140,7 @@ class TransactionTransformer implements BankRevenueInterface
return [
'transaction_id' => 0,
'nordigen_transaction_id' => $transactionId,
'amount' => abs($amount),
'amount' => $amount,
'currency_id' => $this->convertCurrency($transaction["transactionAmount"]["currency"]),
'category_id' => null,
'category_type' => array_key_exists('additionalInformation', $transaction) ? $transaction["additionalInformation"] : '',
@ -149,7 +148,7 @@ class TransactionTransformer implements BankRevenueInterface
'description' => $description,
'participant' => $participant,
'participant_name' => $participant_name,
'base_type' => $base_type,
'base_type' => $amount < 0 ? 'DEBIT' : 'CREDIT',
];
}

View File

@ -42,9 +42,6 @@ class InvoiceItemSum
'DK', // Denmark
'EE', // Estonia
'ES', // Spain
'ES-CE',
'ES-CN',
'ES-ML',
'FI', // Finland
'FR', // France
'GR', // Greece
@ -75,9 +72,6 @@ class InvoiceItemSum
'DK', // Denmark
'EE', // Estonia
'ES', // Spain
'ES-CE',
'ES-CN',
'ES-ML',
'FI', // Finland
'FR', // France
'GR', // Greece
@ -188,7 +182,7 @@ class InvoiceItemSum
/** @var \App\DataMapper\Tax\BaseRule $class */
$class = "App\DataMapper\Tax\\".str_replace("-","_",$this->client->company->country()->iso_3166_2)."\\Rule";
$class = "App\DataMapper\Tax\\".$this->client->company->country()->iso_3166_2."\\Rule";
$this->rule = new $class();

View File

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

View File

@ -1,28 +0,0 @@
<?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\Helpers;
class Sanitizer
{
public static function removeBlanks($input): array
{
foreach ($input as &$value) {
if (is_array($value)) {
// Recursively apply the filter to nested arrays
$value = self::removeBlanks($value);
}
}
// Use array_filter to remove empty or null values
return array_filter($input);
}
}

View File

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

View File

@ -254,20 +254,17 @@ class ActivityController extends BaseController
$activity->client_id = $entity->client_id;
$activity->project_id = $entity->project_id;
$activity->vendor_id = $entity->vendor_id;
// no break
case Task::class:
$activity->task_id = $entity->id;
$activity->expense_id = $entity->id;
$activity->client_id = $entity->client_id;
$activity->project_id = $entity->project_id;
$activity->vendor_id = $entity->vendor_id;
// no break
case Payment::class:
$activity->payment_id = $entity->id;
$activity->expense_id = $entity->id;
$activity->client_id = $entity->client_id;
$activity->project_id = $entity->project_id;
// no break
default:
# code...
break;

View File

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

View File

@ -11,7 +11,6 @@
namespace App\Http\Controllers;
use App\Jobs\Brevo\ProcessBrevoInboundWebhook;
use App\Jobs\Brevo\ProcessBrevoWebhook;
use Illuminate\Http\Request;
@ -20,19 +19,20 @@ use Illuminate\Http\Request;
*/
class BrevoController extends BaseController
{
public function __construct()
{
}
/**
* Process Brevo Webhook.
* Process Postmark Webhook.
*
*
* @OA\Post(
* path="/api/v1/brevo_webhook",
* operationId="brevoWebhook",
* tags={"brevo"},
* summary="Processing webhooks from Brevo",
* path="/api/v1/postmark_webhook",
* operationId="postmarkWebhook",
* tags={"postmark"},
* summary="Processing webhooks from PostMark",
* description="Adds an credit to the system",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
@ -60,150 +60,12 @@ class BrevoController extends BaseController
*/
public function webhook(Request $request)
{
if ($request->has('token') && $request->get('token') == config('services.brevo.secret')) {
ProcessBrevoWebhook::dispatch($request->all())->delay(rand(2, 10));
if ($request->has('token') && $request->get('token') == config('services.brevo.key')) {
ProcessBrevoWebhook::dispatch($request->all())->delay(10);
return response()->json(['message' => 'Success'], 200);
}
return response()->json(['message' => 'Unauthorized'], 403);
}
/**
* Process Brevo Inbound Webhook.
*
* IMPORTANT NOTICE: brevo strips old sended emails, therefore only current attachements are present
*
* IMPORTANT NOTICE: brevo saves the message and attachemnts for later retrieval, therefore we can process it within a async job for performance reasons
*
* @OA\Post(
* path="/api/v1/brevo_inbound_webhook",
* operationId="brevoInboundWebhook",
* tags={"brevo"},
* summary="Processing inbound webhooks from Brevo",
* description="Adds an credit to the system",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Response(
* response=200,
* description="Returns the saved credit object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Credit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*
* array (
* 'items' =>
* array (
* 0 =>
* array (
* 'Uuid' =>
* array (
* 0 => 'd9f48d52-a344-42a4-9056-9733488d9fa3',
* ),
* 'Recipients' =>
* array (
* 0 => 'test@test.de',
* ),
* 'MessageId' => '<CADfEuNvumhUdqAUa0j6MxzVp0ooMYqdb_KZ7nZqHNAfdDqwWEQ@mail.gmail.com>',
* 'InReplyTo' => NULL,
* 'From' =>
* array (
* 'Name' => 'Max Mustermann',
* 'Address' => 'max@mustermann.de',
* ),
* 'To' =>
* array (
* 0 =>
* array (
* 'Name' => NULL,
* 'Address' => 'test@test.de',
* ),
* ),
* 'Cc' =>
* array (
* ),
* 'Bcc' =>
* array (
* ),
* 'ReplyTo' => NULL,
* 'SentAtDate' => 'Sat, 23 Mar 2024 18:18:20 +0100',
* 'Subject' => 'TEST',
* 'Attachments' =>
* array (
* 0 =>
* array (
* 'Name' => 'flag--sv-1x1.svg',
* 'ContentType' => 'image/svg+xml',
* 'ContentLength' => 79957,
* 'ContentID' => 'f_lu4ct6s20',
* 'DownloadToken' => 'eyJmb2xkZXIiOiIyMDI0MDMyMzE3MTgzNi45OS43OTgwMDM4MDQiLCJmaWxlbmFtZSI6ImZsYWctLXN2LTF4MS5zdmcifQ',
* ),
* ),
* 'Headers' =>
* array (
* 'Received' => 'by mail-ed1-f51.google.com with SMTP id 4fb4d7f45d1cf-56b0af675deso3877288a12.1 for <test@test.de>; Sat, 23 Mar 2024 10:18:36 -0700 (PDT)',
* 'DKIM-Signature' => 'v=1; a=rsa-sha256; c=relaxed/relaxed; d=mustermann.de; s=google; t=1711214316; x=1711819116; darn=test.de; h=to:subject:message-id:date:from:mime-version:from:to:cc:subject :date:message-id:reply-to; bh=eBSl5M0zvmTd+dFXGXMMSWrQ4nCvUdyVx+1Xpl+YuX8=; b=ackw3d+qTvZk4JKxomvH626MvfwmH23mikOUc2hWwYiO6unmQgPs2w5spnkmD9aCZ9 G+3nPSYKntugOmqWstZH3z4B063U4Y6j5hTc19WtCyyb9UR+XD+C6L10yc6ez8QUhlZT uAGqDoJ+E8+dBxiMul2pow19lC88t3QxRXU+i8zScniV7SFkwzziCEODaB61yI0DXsZB bUkx5Gx6cztKaNVF2QgguF2nQnJFUnD2nabVFsihyJ5r6y61rkSM/YTfMJuES772lnhv IeF+vwiFNEPKafrchce6YJcvo5Vd5lYFK4LtHyCy3mwJpX2QY+WnWAfferZ2YfgEL0Sf K3Pw==',
* 'X-Google-DKIM-Signature' => 'v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711214316; x=1711819116; h=to:subject:message-id:date:from:mime-version:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=eBSl5M0zvmTd+dFXGXMMSWrQ4nCvUdyVx+1Xpl+YuX8=; b=fg4tXZnstRBexYlC6MD7C7is0kQj+xY66cSJ78tSa7PtSFQzY0zajDMsepMCGiiWmN /Pc/tRtk53pru/OtfzRT9pbM6mhM1arIt+QaQBQGU5xZVV5JXfPmdnPzXqAbQztyeHrk UcEkz+qDN3JNoidw2dJhhdt5MxdKssR572NwtBrn/rN7f1o/ThWzEz+P0o06GVBpxVYP wM0EkvcJj2SUOcn36kmp1ccbMUwYCU2h1JmniEFY8RTqu2il13iXoBvG4YPxe0c0hJ6z zw1N5rONeQM113N1rpbQzS1QLSngczuOhN24M3TOwrHJIec/BxrOW6KWl/uPUqiZAf65 f0tg==',
* 'X-Gm-Message-State' => 'AOJu0YzKhR1HY1oUXoq++LLpl6UOz1S60NfPxuPXBLcP+6aACYle8rqQ fYHe2rQYTpg4KWiOswu858STOW8qmiewXD6gH/LbmEFs7sknRyDPNr/+L0cv828A3o+SOvXu3uP SY6H1aNSwIpqTRhJ+nNjTuSUpuSoABd9fYXFwPuivV0DtBhoVmpE=',
* 'X-Google-Smtp-Source' => 'AGHT+IHdA9ZhW0dQxgOYx2OXBGmu4pzSR/zwJ0vcPNXFSqttKCPS2oTw1a9b2mMdhyUeoRAwP5TmhHlAtqUUrOPwkgg=',
* 'X-Received' => 'by 2002:a50:d74c:0:b0:567:3c07:8bbc with SMTP id i12-20020a50d74c000000b005673c078bbcmr2126401edj.21.1711214316135; Sat, 23 Mar 2024 10:18:36 -0700 (PDT)',
* 'MIME-Version' => '1.0',
* 'From' => 'Max Mustermann <max@mustermann.de>',
* 'Date' => 'Sat, 23 Mar 2024 18:18:20 +0100',
* 'Message-ID' => '<CADfEuNvumhUdqAUa0j6MxzVp0ooMYqdb_KZ7nZqHNAfdDqwWEQ@mail.gmail.com>',
* 'Subject' => 'TEST',
* 'To' => 'test@test.de',
* 'Content-Type' => 'multipart/mixed',
* ),
* 'SpamScore' => 2.8,
* 'ExtractedMarkdownMessage' => 'TEST',
* 'ExtractedMarkdownSignature' => NULL,
* 'RawHtmlBody' => '<div dir="ltr">TEST</div>',
* 'RawTextBody' => 'TEST',
* 'EMLDownloadToken' => 'eyJmb2xkZXIiOiIyMDI0MDMyMzE3MTgzNi45OS43OTgwMDM4MDQiLCJmaWxlbmFtZSI6InNtdHAuZW1sIn0',
* ),
* ),
* )
*/
public function inboundWebhook(Request $request)
{
$input = $request->all();
if (!($request->has('token') && $request->get('token') == config('ninja.inbound_mailbox.inbound_webhook_token')))
return response()->json(['message' => 'Unauthorized'], 403);
if (!array_key_exists('items', $input)) {
nlog('Failed: Message could not be parsed, because required parameters are missing.');
return response()->json(['message' => 'Failed. Invalid Parameters.'], 400);
}
foreach ($input["items"] as $item) {
if (!array_key_exists('Recipients', $item) || !array_key_exists('MessageId', $item)) {
nlog('Failed: Message could not be parsed, because required parameters are missing. At least one item was invalid.');
return response()->json(['message' => 'Failed. Invalid Parameters. At least one item was invalid.'], 400);
}
ProcessBrevoInboundWebhook::dispatch($item)->delay(rand(2, 10));
}
return response()->json(['message' => 'Success'], 201);
}
}

View File

@ -117,7 +117,6 @@ class InvitationController extends Controller
if(!auth()->guard('contact')->check()){
$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])]);
}

View File

@ -62,7 +62,6 @@ class InvoiceController extends Controller
$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) {
$invitation->markViewed();
@ -78,20 +77,13 @@ class InvoiceController extends Controller
'key' => $invitation ? $invitation->key : false,
'hash' => $hash,
'variables' => $variables,
'invoices' => [$invoice->hashed_id],
'db' => $invoice->company->db,
];
if ($request->query('mode') === 'fullscreen') {
return render('invoices.show-fullscreen', $data);
}
if(!$invoice->isPayable())
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)
@ -243,12 +235,9 @@ class InvoiceController extends Controller
'hashed_ids' => $invoices->pluck('hashed_id'),
'total' => $total,
'variables' => $variables,
'invitation' => $invitation,
'db' => $invitation->company->db,
];
// 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);
return $this->render('invoices.payment', $data);
}
/**

View File

@ -88,8 +88,6 @@ class NinjaPlanController extends Controller
{
$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->private_notes = $trial_started;
$client->fill($request->all());

View File

@ -108,6 +108,12 @@ class PaymentController extends Controller
*/
public function process(Request $request)
{
// $request->validate([
// 'contact_first_name' => ['required'],
// 'contact_last_name' => ['required'],
// 'contact_email' => ['required', 'email'],
// ]);
return (new InstantPayment($request))->run();
}
@ -117,7 +123,13 @@ class PaymentController extends Controller
$gateway = CompanyGateway::findOrFail($request->input('company_gateway_id'));
$payment_hash = PaymentHash::with('fee_invoice')->where('hash', $request->payment_hash)->firstOrFail();
// if($payment_hash)
$invoice = $payment_hash->fee_invoice;
// else
// $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);
$client = $invoice ? $invoice->client : auth()->guard('contact')->user()->client;

View File

@ -56,8 +56,8 @@ class PaymentMethodController extends Controller
$data['gateway'] = $gateway;
/** @var \App\Models\ClientContact auth()->guard('contact')->user() **/
$client_contact = auth()->guard('contact')->user();
/** @var \App\Models\ClientContact auth()->user() **/
$client_contact = auth()->user();
$data['client'] = $client_contact->client;
return $gateway
@ -77,8 +77,8 @@ class PaymentMethodController extends Controller
{
$gateway = $this->getClientGateway();
/** @var \App\Models\ClientContact auth()->guard('contact')->user() **/
$client_contact = auth()->guard('contact')->user();
/** @var \App\Models\ClientContact auth()->user() **/
$client_contact = auth()->user();
return $gateway
->driver($client_contact->client)
@ -103,8 +103,8 @@ class PaymentMethodController extends Controller
public function verify(ClientGatewayToken $payment_method)
{
/** @var \App\Models\ClientContact auth()->guard('contact')->user() **/
$client_contact = auth()->guard('contact')->user();
/** @var \App\Models\ClientContact auth()->user() **/
$client_contact = auth()->user();
return $payment_method->gateway
->driver($client_contact->client)
@ -114,8 +114,8 @@ class PaymentMethodController extends Controller
public function processVerification(Request $request, ClientGatewaytoken $payment_method)
{
/** @var \App\Models\ClientContact auth()->guard('contact')->user() **/
$client_contact = auth()->guard('contact')->user();
/** @var \App\Models\ClientContact auth()->user() **/
$client_contact = auth()->user();
return $payment_method->gateway
->driver($client_contact->client)
@ -131,8 +131,8 @@ class PaymentMethodController extends Controller
*/
public function destroy(ClientGatewayToken $payment_method)
{
/** @var \App\Models\ClientContact auth()->guard('contact')->user() **/
$client_contact = auth()->guard('contact')->user();
/** @var \App\Models\ClientContact auth()->user() **/
$client_contact = auth()->user();
if ($payment_method->gateway()->exists()) {
$payment_method->gateway
@ -145,19 +145,9 @@ class PaymentMethodController extends Controller
event(new MethodDeleted($payment_method, auth()->guard('contact')->user()->company, Ninja::eventVars(auth()->guard('contact')->user()->id)));
$payment_method->is_deleted = true;
$payment_method->is_default = false;
$payment_method->delete();
$payment_method->save();
$def_cgt = auth()->guard('contact')->user()->client->gateway_tokens()->orderBy('id','desc')->first();
if($def_cgt)
{
$def_cgt->is_default = true;
$def_cgt->save();
}
} catch (Exception $e) {
nlog($e->getMessage());
@ -171,8 +161,8 @@ class PaymentMethodController extends Controller
private function getClientGateway()
{
/** @var \App\Models\ClientContact auth()->guard('contact')->user() **/
$client_contact = auth()->guard('contact')->user();
/** @var \App\Models\ClientContact auth()->user() **/
$client_contact = auth()->user();
if (request()->query('method') == GatewayType::CREDIT_CARD) {
return $client_contact->client->getCreditCardGateway();

View File

@ -35,16 +35,11 @@ class PrePaymentController extends Controller
/**
* Show the list of payments.
*
* @return Factory|View|\Illuminate\Http\RedirectResponse
* @return Factory|View
*/
public function index()
{
$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_amount = $minimum == 0 ? "" : Number::formatMoney($minimum, $client);

View File

@ -150,10 +150,6 @@ class DocumentController extends BaseController
$document->fill($request->all());
$document->save();
if($document->documentable) { //@phpstan-ignore-line
$document->documentable->touch();
}
return $this->itemResponse($document->fresh());
}

View File

@ -1,26 +0,0 @@
<?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

@ -1,39 +0,0 @@
<?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\EInvoice\ValidateEInvoiceRequest;
use App\Services\EDocument\Standards\Validation\Peppol\EntityLevel;
class EInvoiceController extends BaseController
{
public function validateEntity(ValidateEInvoiceRequest $request)
{
$el = new EntityLevel();
$data = [];
match($request->entity){
'invoices' => $data = $el->checkInvoice($request->getEntity()),
'clients' => $data = $el->checkClient($request->getEntity()),
'companies' => $data = $el->checkCompany($request->getEntity()),
default => $data['passes'] = false,
};
nlog($data);
return response()->json($data, $data['passes'] ? 200 : 400);
}
}

View File

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

View File

@ -584,60 +584,14 @@ class ExpenseController extends BaseController
return $this->itemResponse($expense->fresh());
}
/**
* @OA\Post(
* path="/api/v1/expenses/edocument",
* operationId="edocumentExpense",
* tags={"expenses"},
* summary="Uploads an electronic document to a expense",
* description="Handles the uploading of an electronic document to a expense",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\RequestBody(
* description="User credentials",
* required=true,
* @OA\MediaType(
* mediaType="multipart/form-data",
* @OA\Schema(
* type="array",
* @OA\Items(
* type="string",
* format="binary",
* description="The files to be uploaded",
* ),
* )
* )
* ),
* @OA\Response(
* response=200,
* description="Returns a HTTP status",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function edocument(EDocumentRequest $request)
public function edocument(EDocumentRequest $request): string
{
$user = auth()->user();
foreach ($request->file("documents") as $file) {
ImportEDocument::dispatch($file->get(), $file->getClientOriginalName(), $request->file("documents")->getMimeType(), $user->company());
if ($request->hasFile("documents")) {
return (new ImportEDocument($request->file("documents")[0]->get(), $request->file("documents")[0]->getClientOriginalName()))->handle();
}
else {
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 */
$user = auth()->user();
$hash = Str::uuid()->toString();
$hash = Str::uuid();
$url = \Illuminate\Support\Facades\URL::temporarySignedRoute('protected_download', now()->addHour(), ['hash' => $hash]);
Cache::put($hash, $url, 3600);
Cache::put($hash, $url, now()->addHour());
CompanyExport::dispatch($user->getCompany(), $user, $hash);

View File

@ -1,58 +0,0 @@
<?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\Gateways;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request; // Import the Request class
use Illuminate\Support\Facades\Http; // Import the Http facade
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use BaconQrCode\Writer;
class BlockonomicsController extends Controller
{
public function getBTCPrice(Request $request)
{
$currency = $request->query('currency');
$response = Http::get("https://www.blockonomics.co/api/price?currency={$currency}");
if ($response->successful()) {
return response()->json(['price' => $response->json('price')]);
}
return response()->json(['error' => 'Unable to fetch BTC price'], 500);
}
public function getQRCode(Request $request)
{
$qr_string = $request->query('qr_string');
$svg = $this->getPaymentQrCodeRaw($qr_string);
return response($svg)->header('Content-Type', 'image/svg+xml');
}
private function getPaymentQrCodeRaw($qr_string)
{
$renderer = new ImageRenderer(
new RendererStyle(150, margin: 0),
new SvgImageBackEnd()
);
$writer = new Writer($renderer);
$qr = $writer->writeString($qr_string, 'utf-8');
return $qr;
}
}

View File

@ -20,7 +20,6 @@ class GoCardlessController extends Controller
{
public function ibpRedirect(IbpRequest $request)
{
return $request
->getCompanyGateway()
->driver($request->getClient())

View File

@ -118,36 +118,9 @@ class ImportController extends Controller
})->toArray();
//Exact string match
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) {
if($this->testMatch($value, $tvalue['label'])) {
@ -161,9 +134,10 @@ class ImportController extends Controller
}
}
//Index matching pass using the index of the translation here
//second pass using the index of the translation here
foreach($headers as $key => $value) {
if(isset($hints[$key])) {
continue;

View File

@ -1,63 +0,0 @@
<?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

@ -475,10 +475,7 @@ class InvoiceController extends BaseController
*/
public function destroy(DestroyInvoiceRequest $request, Invoice $invoice)
{
if (!$invoice->is_deleted) {
$this->invoice_repo->delete($invoice);
}
return $this->itemResponse($invoice->fresh());
}

View File

@ -1,147 +0,0 @@
<?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\Models\Company;
use App\Libraries\MultiDB;
use Illuminate\Http\Request;
use App\Jobs\Mailgun\ProcessMailgunWebhook;
use App\Jobs\Mailgun\ProcessMailgunInboundWebhook;
/**
* Class MailgunController.
*/
class MailgunController extends BaseController
{
public function __construct()
{
}
/**
* Process Mailgun Webhook.
*
*
* @OA\Post(
* path="/api/v1/mailgun_webhook",
* operationId="mailgunWebhook",
* tags={"mailgun"},
* summary="Processing webhooks from Mailgun",
* description="Adds an credit to the system",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Response(
* response=200,
* description="Returns the saved credit object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Credit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function webhook(Request $request)
{
$input = $request->all();
nlog($input);
if (\abs(\time() - $request['signature']['timestamp']) > 15) {
return response()->json(['message' => 'Success'], 200);
}
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));
}
return response()->json(['message' => 'Success.'], 200);
}
/**
* Process Mailgun Inbound Webhook.
*
* IMPORTANT NOTICE: mailgun does NOT strip old sended emails, therefore all past attachements are present
*
* IMPORTANT NOTICE: mailgun saves the message and attachemnts for later retrieval, therefore we can process it within a async job for performance reasons
*
*
* @OA\Post(
* path="/api/v1/mailgun_inbound_webhook",
* operationId="mailgunInboundWebhook",
* tags={"mailgun"},
* summary="Processing inbound webhooks from Mailgun",
* description="Adds an credit to the system",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Response(
* response=200,
* description="Returns the saved credit object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Credit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function inboundWebhook(Request $request)
{
$input = $request->all();
nlog($input);
if (!array_key_exists('sender', $input) || !array_key_exists('recipient', $input) || !array_key_exists('message-url', $input)) {
nlog('Failed: Message could not be parsed, because required parameters are missing. Please ensure contacting this api-endpoint with a store & notify operation instead of a forward operation!');
return response()->json(['message' => 'Failed. Missing Parameters. Use store and notify!'], 400);
}
// @turbo124 TODO: how to check for services.mailgun.webhook_signing_key on company level, when custom credentials are defined
// TODO: validation for client mail credentials by recipient
$authorizedByHash = \hash_equals(\hash_hmac('sha256', $input['timestamp'] . $input['token'], config('services.mailgun.webhook_signing_key')), $input['signature']);
$authorizedByToken = $request->has('token') && $request->get('token') == config('ninja.inbound_mailbox.inbound_webhook_token');
if (!$authorizedByHash && !$authorizedByToken)
return response()->json(['message' => 'Unauthorized'], 403);
/** @var \App\Models\Company $company */
$company = MultiDB::findAndSetDbByExpenseMailbox($input["recipient"]);
if(!$company)
return response()->json(['message' => 'Ok'], 200); // Fail gracefully
ProcessMailgunInboundWebhook::dispatch($input["sender"], $input["recipient"], $input["message-url"], $company)->delay(rand(2, 10));
return response()->json(['message' => 'Success.'], 200);
}
}

View File

@ -0,0 +1,43 @@
<?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\Jobs\Mailgun\ProcessMailgunWebhook;
use App\Jobs\PostMark\ProcessPostmarkWebhook;
use Illuminate\Http\Request;
/**
* Class MailgunWebhookController.
*/
class MailgunWebhookController extends BaseController
{
public function __construct()
{
}
public function webhook(Request $request)
{
$input = $request->all();
if (\abs(\time() - $request['signature']['timestamp']) > 15) {
return response()->json(['message' => 'Success'], 200);
}
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));
}
return response()->json(['message' => 'Success.'], 200);
}
}

View File

@ -22,6 +22,7 @@ use Illuminate\Support\Str;
class OneTimeTokenController extends BaseController
{
public function __construct()
{
parent::__construct();
@ -73,10 +74,6 @@ class OneTimeTokenController extends BaseController
'is_react' => $request->hasHeader('X-REACT') ? true : false,
];
if($request->institution_id) {
$data['institution_id'] = $request->institution_id;
}
Cache::put($hash, $data, 3600);
return response()->json(['hash' => $hash], 200);

View File

@ -12,11 +12,6 @@
namespace App\Http\Controllers;
use App\Jobs\PostMark\ProcessPostmarkWebhook;
use App\Libraries\MultiDB;
use App\Services\InboundMail\InboundMail;
use App\Services\InboundMail\InboundMailEngine;
use App\Utils\TempFile;
use Illuminate\Support\Carbon;
use Illuminate\Http\Request;
/**
@ -24,6 +19,7 @@ use Illuminate\Http\Request;
*/
class PostMarkController extends BaseController
{
public function __construct()
{
}
@ -65,271 +61,11 @@ class PostMarkController extends BaseController
public function webhook(Request $request)
{
if ($request->header('X-API-SECURITY') && $request->header('X-API-SECURITY') == config('services.postmark.token')) {
ProcessPostmarkWebhook::dispatch($request->all())->delay(rand(2, 10));
ProcessPostmarkWebhook::dispatch($request->all())->delay(10);
return response()->json(['message' => 'Success'], 200);
}
return response()->json(['message' => 'Unauthorized'], 403);
}
/**
* Process Postmark Webhook.
*
* IMPORTANT NOTICE: postmark does NOT strip old sended emails, therefore also all past attachements are present
*
* IMPORTANT NOTICE: postmark does not saves attachements for later retrieval, therefore we cannot process it within a async job
*
* @OA\Post(
* path="/api/v1/postmark_inbound_webhook",
* operationId="postmarkInboundWebhook",
* tags={"postmark"},
* summary="Processing inbound webhooks from PostMark",
* description="Adds an credit to the system",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Response(
* response=200,
* description="Returns the saved credit object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Credit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*
* array (
* 'FromName' => 'Max Mustermann',
* 'MessageStream' => 'inbound',
* 'From' => 'max@mustermann.de',
* 'FromFull' =>
* array (
* 'Email' => 'max@mustermann.de',
* 'Name' => 'Max Mustermann',
* 'MailboxHash' => NULL,
* ),
* 'To' => '370c69ad9e41d616fc914b3c60250224@inbound.postmarkapp.com',
* 'ToFull' =>
* array (
* 0 =>
* array (
* 'Email' => '370c69ad9e41d616fc914b3c60250224@inbound.postmarkapp.com',
* 'Name' => NULL,
* 'MailboxHash' => NULL,
* ),
* ),
* 'Cc' => NULL,
* 'CcFull' =>
* array (
* ),
* 'Bcc' => NULL,
* 'BccFull' =>
* array (
* ),
* 'OriginalRecipient' => '370c69ad9e41d616fc914b3c60250224@inbound.postmarkapp.com',
* 'Subject' => 'Re: adaw',
* 'MessageID' => 'd37fde00-b4cf-4b64-ac64-e9f6da523c25',
* 'ReplyTo' => NULL,
* 'MailboxHash' => NULL,
* 'Date' => 'Sun, 24 Mar 2024 13:17:52 +0100',
* 'TextBody' => 'wadwad
*
* Am So., 24. März 2024 um 13:17 Uhr schrieb Max Mustermann <max@mustermann.de>:
*
* > test
* >
*
* --
* test.de - Max Mustermann <https://test.de/>kontakt@test.de
* <mailto:kontakt@test.de>',
* 'HtmlBody' => '<div dir="ltr">wadwad</div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">Am So., 24. März 2024 um 13:17 Uhr schrieb Max Mustermann &lt;<a href="mailto:max@mustermann.de">max@mustermann.de</a>&gt;:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr">test</div>
* </blockquote></div>
*
* <br>
* <font size="3"><a href="https://test.de/" target="_blank">test.de - Max Mustermann</a></font><div><a href="mailto:kontakt@test.de" style="font-size:medium" target="_blank">kontakt@test.de</a><br></div>',
* 'StrippedTextReply' => 'wadwad
*
* Am So., 24. März 2024 um 13:17 Uhr schrieb Max Mustermann <max@mustermann.de>:',
* 'Tag' => NULL,
* 'Headers' =>
* array (
* 0 =>
* array (
* 'Name' => 'Return-Path',
* 'Value' => '<max@mustermann.de>',
* ),
* 1 =>
* array (
* 'Name' => 'Received',
* 'Value' => 'by p-pm-inboundg02a-aws-euwest1a.inbound.postmarkapp.com (Postfix, from userid 996) id 8ED1A453CA4; Sun, 24 Mar 2024 12:18:10 +0000 (UTC)',
* ),
* 2 =>
* array (
* 'Name' => 'X-Spam-Checker-Version',
* 'Value' => 'SpamAssassin 3.4.0 (2014-02-07) on p-pm-inboundg02a-aws-euwest1a',
* ),
* 3 =>
* array (
* 'Name' => 'X-Spam-Status',
* 'Value' => 'No',
* ),
* 4 =>
* array (
* 'Name' => 'X-Spam-Score',
* 'Value' => '-0.1',
* ),
* 5 =>
* array (
* 'Name' => 'X-Spam-Tests',
* 'Value' => 'DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HTML_MESSAGE, RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,RCVD_IN_ZEN_BLOCKED_OPENDNS, SPF_HELO_NONE,SPF_PASS,URIBL_DBL_BLOCKED_OPENDNS,URIBL_ZEN_BLOCKED_OPENDNS',
* ),
* 6 =>
* array (
* 'Name' => 'Received-SPF',
* 'Value' => 'pass (test.de: Sender is authorized to use \'max@mustermann.de\' in \'mfrom\' identity (mechanism \'include:_spf.google.com\' matched)) receiver=p-pm-inboundg02a-aws-euwest1a; identity=mailfrom; envelope-from="max@mustermann.de"; helo=mail-lf1-f51.google.com; client-ip=209.85.167.51',
* ),
* 7 =>
* array (
* 'Name' => 'Received',
* 'Value' => 'from mail-lf1-f51.google.com (mail-lf1-f51.google.com [209.85.167.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by p-pm-inboundg02a-aws-euwest1a.inbound.postmarkapp.com (Postfix) with ESMTPS id 437BD453CA2 for <370c69ad9e41d616fc914b3c60250224@inbound.postmarkapp.com>; Sun, 24 Mar 2024 12:18:10 +0000 (UTC)',
* ),
* 8 =>
* array (
* 'Name' => 'Received',
* 'Value' => 'by mail-lf1-f51.google.com with SMTP id 2adb3069b0e04-513cf9bacf1so4773866e87.0 for <370c69ad9e41d616fc914b3c60250224@inbound.postmarkapp.com>; Sun, 24 Mar 2024 05:18:10 -0700 (PDT)',
* ),
* 9 =>
* array (
* 'Name' => 'DKIM-Signature',
* 'Value' => 'v=1; a=rsa-sha256; c=relaxed/relaxed; d=test.de; s=google; t=1711282689; x=1711887489; darn=inbound.postmarkapp.com; h=to:subject:message-id:date:from:in-reply-to:references:mime-version :from:to:cc:subject:date:message-id:reply-to; bh=NvjmqLXF/5L5ZrpToR/6FgVOhTOGC9j0/B2Na5Ke6J8=; b=AMXIEoh6yGrOT6X3eBBClQ3NXFNuEoqxeM6aPONsqbpShAcT24iAJmqXylaLHv3fyX Hm6mwp3a029NnrLP/VRyKZbzIMBN2iycidtrEMXF/Eg2e42Q/08/2dZ7nxH6NqE/jz01 3M7qvwHvuoZ2Knhj7rnZc6I5m/nFxBsZc++Aj0Vv9sFoWZZooqAeTXbux1I5NyE17MrL D6byca43iINARZN7XOkoChRRZoZbOqZEtc2Va5yw7v+aYguLB4HHrIFC7G+L8hAJ0IAo 3R3DFeBw58M1xtxXCREI8Y6qMQTw60XyFw0gVmZzqR4hZiTerBSJJsZLZOBgmXxq3WLS +xVQ==',
* ),
* 10 =>
* array (
* 'Name' => 'X-Google-DKIM-Signature',
* 'Value' => 'v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711282689; x=1711887489; h=to:subject:message-id:date:from:in-reply-to:references:mime-version :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=NvjmqLXF/5L5ZrpToR/6FgVOhTOGC9j0/B2Na5Ke6J8=; b=uKoMhir+MX/wycNEr29Sffj45ooKksCJ1OfSRkIIGHk0rnHn8Vh+c7beYipwRPW4F2 h46K64vtIX00guYMdL2Qo2eY96+wALTqHCy67PGhvotVTROz21yxjx62pCDPGs5tefOu IkyxoybpIK8zAfLoDTd9p2GIrr5brKJyB2w1NQc1htxTQ5D4RgBxUAOKv4uVEr8r47iA MIo5d8/AifA+vCOAh7iJ7EmvDQ1R+guhQyH9m1Jo8PLapiYuHXggpBJvooyGuflKqbnt gJ/dscEr4d5aWJbw/x1dmIJ5gyJPGdBWq8NRqV/qbkXQW3H/gylifDUPXbki+EQBD5Yu EuLQ==',
* ),
* 11 =>
* array (
* 'Name' => 'X-Gm-Message-State',
* 'Value' => 'AOJu0Yxpbp1sRh17lNzg+pLnIx1jCn8ZFJQMgFuHK+6Z8RqFS5KKKTxR 8onpEbxWYYVUbrJFExNBHPD/3jdxqifCVVNaDmbpwHgmW5lHLJmA5vYRq5NFZ9OA6zKx/N6Gipr iXE4fXmSqghFNTzy9V/RT08Zp+F5RiFh/Ta6ltQl8XfCPFfSawLz6cagUgt8bBuF4RqdrYmWwzj ty86V5Br1htRNEFYivoXnNmaRcsD0tca1D23ny62O6RwWugrj1IpAYhViNyTZAWu+loKgfjJJoI MsyiSU=',
* ),
* 12 =>
* array (
* 'Name' => 'X-Google-Smtp-Source',
* 'Value' => 'AGHT+IEdtZqbVI6j7WLeaSL3dABGSnWIXaSjbYqXvFvE2H+f2zsn0gknQ4OdTJecQRCabpypVF2ue91Jb7aKl6RiyEQ=',
* ),
* 13 =>
* array (
* 'Name' => 'X-Received',
* 'Value' => 'by 2002:a19:385a:0:b0:513:c876:c80a with SMTP id d26-20020a19385a000000b00513c876c80amr2586776lfj.34.1711282689140; Sun, 24 Mar 2024 05:18:09 -0700 (PDT)',
* ),
* 14 =>
* array (
* 'Name' => 'MIME-Version',
* 'Value' => '1.0',
* ),
* 15 =>
* array (
* 'Name' => 'References',
* 'Value' => '<CADfEuNsNFmNNCJDPjpS36amoLv2XEm41HmgYJT7Tj=R96PkxnA@mail.gmail.com>',
* ),
* 16 =>
* array (
* 'Name' => 'In-Reply-To',
* 'Value' => '<CADfEuNsNFmNNCJDPjpS36amoLv2XEm41HmgYJT7Tj=R96PkxnA@mail.gmail.com>',
* ),
* 17 =>
* array (
* 'Name' => 'Message-ID',
* 'Value' => '<CADfEuNvyCLsnp=CwJ3BF=-L6rn=o+DmUOPP6Cp4F-SO0p0hVwQ@mail.gmail.com>',
* ),
* ),
* 'Attachments' =>
* array (
* array (
* 'Content' => "base64-String",
* 'ContentLength' => 60164,
* 'Name' => 'Unbenannt.png',
* 'ContentType' => 'image/png',
* 'ContentID' => 'ii_luh2h8lg0',
* )
* ),
* )
*/
public function inboundWebhook(Request $request)
{
$input = $request->all();
if (!$request->has('token') || $request->token != config('ninja.inbound_mailbox.inbound_webhook_token'))
return response()->json(['message' => 'Unauthorized'], 403);
if (!(array_key_exists("MessageStream", $input) && $input["MessageStream"] == "inbound") || !array_key_exists("To", $input) || !array_key_exists("From", $input) || !array_key_exists("MessageID", $input)) {
nlog('Failed: Message could not be parsed, because required parameters are missing.');
return response()->json(['message' => 'Failed. Missing/Invalid Parameters.'], 400);
}
$company = MultiDB::findAndSetDbByExpenseMailbox($input["ToFull"][0]["Email"]);
if (!$company) {
nlog('[PostmarkInboundWebhook] unknown Expense Mailbox occured while handling an inbound email from postmark: ' . $input["To"]);
return response()->json(['message' => 'Ok'], 200);
}
$inboundEngine = new InboundMailEngine($company);
if ($inboundEngine->isInvalidOrBlocked($input["From"], $input["ToFull"][0]["Email"])) {
return response()->json(['message' => 'Blocked.'], 403);
}
try { // important to save meta if something fails here to prevent spam
// prepare data for ingresEngine
$inboundMail = new InboundMail();
$inboundMail->from = $input["From"] ?? '';
$inboundMail->to = $input["To"]; // usage of data-input, because we need a single email here
$inboundMail->subject = $input["Subject"] ?? '';
$inboundMail->body = $input["HtmlBody"] ?? '';
$inboundMail->text_body = $input["TextBody"] ?? '';
$inboundMail->date = Carbon::createFromTimeString($input["Date"]);
// parse documents as UploadedFile from webhook-data
foreach ($input["Attachments"] as $attachment) {
$inboundMail->documents[] = TempFile::UploadedFileFromBase64($attachment["Content"], $attachment["Name"], $attachment["ContentType"]);
}
} catch (\Exception $e) {
$inboundEngine->saveMeta($input["From"], $input["To"]); // important to save this, to protect from spam
throw $e;
}
// perform
try {
$inboundEngine->handleExpenseMailbox($inboundMail);
} catch (\Exception $e) {
if ($e->getCode() == 409)
return response()->json(['message' => $e->getMessage()], 409);
throw $e;
}
return response()->json(['message' => 'Success'], 200);
}
}

View File

@ -148,9 +148,6 @@ class PreviewController extends BaseController
! empty(request()->input('entity')) &&
! empty(request()->input('entity_id'))) {
if($request->input('entity') == 'purchase_order')
return $preview = app(\App\Http\Controllers\PreviewPurchaseOrderController::class)->show($request);
$design_object = json_decode(json_encode(request()->input('design')));
if (! is_object($design_object)) {
@ -296,14 +293,10 @@ class PreviewController extends BaseController
$ts = (new TemplateService());
try {
$ts->setCompany($company)
->setTemplate($design_object)
->mock();
} 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') {

View File

@ -36,7 +36,6 @@ use App\Utils\Traits\Pdf\PageNumbering;
use App\Utils\VendorHtmlEngine;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Response;
use Turbo124\Beacon\Facades\LightLogs;
@ -84,7 +83,7 @@ class PreviewPurchaseOrderController extends BaseController
* ),
* )
*/
public function show($request)
public function show()
{
if (request()->has('entity') &&
request()->has('entity_id') &&

View File

@ -517,7 +517,7 @@ class QuoteController extends BaseController
{
/** @var \App\Models\User $user */
$user = auth()->user();
nlog("booop");
$action = $request->input('action');
$ids = $request->input('ids');

View File

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

View File

@ -70,7 +70,7 @@ class SetupController extends Controller
return response('Oops, something went wrong. Check your logs.'); /* We should never reach this block, but just in case. */
}
$mail_driver = $request->input('mail_driver', 'smtp');
$mail_driver = $request->input('mail_driver');
$url = $request->input('url');
$db_host = $request->input('db_host');

View File

@ -11,25 +11,23 @@
namespace App\Http\Controllers\VendorPortal;
use App\Utils\Ninja;
use App\Models\Webhook;
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\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\Events\PurchaseOrder\PurchaseOrderWasViewed;
use App\Http\Controllers\Controller;
use App\Http\Requests\VendorPortal\PurchaseOrders\ProcessPurchaseOrdersInBulkRequest;
use App\Http\Requests\VendorPortal\PurchaseOrders\ShowPurchaseOrderRequest;
use App\Http\Requests\VendorPortal\PurchaseOrders\ShowPurchaseOrdersRequest;
use App\Http\Requests\VendorPortal\PurchaseOrders\ProcessPurchaseOrdersInBulkRequest;
use App\Jobs\Entity\CreateRawPdf;
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
{
@ -189,9 +187,6 @@ class PurchaseOrderController extends Controller
}
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) {

View File

@ -14,7 +14,7 @@ namespace App\Http\Controllers\VendorPortal;
use App\Http\Controllers\Controller;
use App\Models\VendorContact;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Request;
use App\Utils\TranslationHelper;
class VendorContactController extends Controller
{
@ -58,14 +58,14 @@ class VendorContactController extends Controller
'settings' => $vendor_contact->vendor->company->settings,
'company' => $vendor_contact->vendor->company,
'sidebar' => $this->sidebarMenu(),
'countries' => app('countries'),
'countries' => TranslationHelper::getCountries(),
]);
}
public function update(Request $request, VendorContact $vendor_contact)
public function update(VendorContact $vendor_contact)
{
$vendor_contact->fill($request->all());
$vendor_contact->vendor->fill($request->all());
$vendor_contact->fill(request()->all());
$vendor_contact->vendor->fill(request()->all());
$vendor_contact->push();
return back()->withSuccess(ctrans('texts.profile_updated_successfully'));
@ -76,10 +76,16 @@ class VendorContactController extends Controller
$enabled_modules = auth()->guard('vendor')->user()->company->enabled_modules;
$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) {
$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;
}
}

View File

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

View File

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

View File

@ -17,7 +17,6 @@ use Illuminate\Validation\Rule;
class StoreNoteRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
@ -69,9 +68,8 @@ class StoreNoteRequest extends Request
public function getEntity()
{
if(!$this->entity) {
if(!$this->entity)
return false;
}
$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);

View File

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

View File

@ -66,7 +66,8 @@ class ShowCalculatedFieldRequest extends Request
$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());
$input['start_date'] = $dates[0];
$input['end_date'] = $dates[1];

View File

@ -13,11 +13,13 @@ namespace App\Http\Requests\Client;
use App\DataMapper\ClientSettings;
use App\Http\Requests\Request;
use App\Http\ValidationRules\Client\CountryCodeExistsRule;
use App\Http\ValidationRules\Ninja\CanStoreClientsRule;
use App\Http\ValidationRules\ValidClientGroupSettingsRule;
use App\Models\Client;
use App\Models\GroupSetting;
use App\Utils\Traits\MakesHash;
use Illuminate\Support\Facades\Cache;
use Illuminate\Validation\Rule;
class StoreClientRequest extends Request

View File

@ -136,10 +136,6 @@ class UpdateClientRequest extends Request
$input['shipping_country_id'] = $this->getCountryCode($input['shipping_country_code']);
}
if (isset($input['e_invoice']) && is_array($input['e_invoice'])) {
//ensure it is normalized first!
$input['e_invoice'] = $this->client->filterNullsRecursive($input['e_invoice']);
}
$this->replace($input);
}

View File

@ -11,15 +11,14 @@
namespace App\Http\Requests\Company;
use App\Http\Requests\Request;
use App\Http\ValidationRules\Company\ValidCompanyQuantity;
use App\Http\ValidationRules\Company\ValidExpenseMailbox;
use App\Http\ValidationRules\Company\ValidSubdomain;
use App\Http\ValidationRules\ValidSettingsRule;
use App\Models\Company;
use App\Utils\Ninja;
use App\Models\Company;
use App\Libraries\MultiDB;
use App\Http\Requests\Request;
use App\Utils\Traits\MakesHash;
use App\Http\ValidationRules\ValidSettingsRule;
use App\Http\ValidationRules\Company\ValidSubdomain;
use App\Http\ValidationRules\Company\ValidCompanyQuantity;
class StoreCompanyRequest extends Request
{
@ -51,14 +50,12 @@ class StoreCompanyRequest extends Request
$rules['portal_domain'] = 'sometimes|url';
} else {
if (Ninja::isHosted()) {
$rules['subdomain'] = ['nullable', 'regex:/^[a-zA-Z0-9-]{1,63}$/', new ValidSubdomain()];
$rules['subdomain'] = ['nullable', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9.-]+[a-zA-Z0-9]$/', new ValidSubdomain()];
} else {
$rules['subdomain'] = 'nullable|alpha_num';
}
}
$rules['expense_mailbox'] = new ValidExpenseMailbox();
$rules['smtp_host'] = 'sometimes|string|nullable';
$rules['smtp_port'] = 'sometimes|integer|nullable';
$rules['smtp_encryption'] = 'sometimes|string';
@ -87,10 +84,6 @@ class StoreCompanyRequest extends Request
$input['portal_domain'] = rtrim(strtolower($input['portal_domain']), "/");
}
if (isset($input['expense_mailbox']) && Ninja::isHosted() && !($this->company->account->isPaid() && $this->company->account->plan == 'enterprise')) {
unset($input['expense_mailbox']);
}
if(Ninja::isHosted() && !isset($input['subdomain'])) {
$input['subdomain'] = MultiDB::randomSubdomainGenerator();
}

View File

@ -14,12 +14,10 @@ namespace App\Http\Requests\Company;
use App\Utils\Ninja;
use App\Http\Requests\Request;
use App\Utils\Traits\MakesHash;
use Illuminate\Validation\Rule;
use App\DataMapper\CompanySettings;
use App\Http\ValidationRules\ValidSettingsRule;
use App\Http\ValidationRules\Company\ValidSubdomain;
use App\Http\ValidationRules\Company\ValidExpenseMailbox;
use App\Http\ValidationRules\EInvoice\ValidCompanyScheme;
use App\Http\ValidationRules\Company\ValidSubdomain;
class UpdateCompanyRequest extends Request
{
@ -77,15 +75,6 @@ class UpdateCompanyRequest extends Request
$rules['subdomain'] = ['nullable', 'regex:/^[a-zA-Z0-9.-]+[a-zA-Z0-9]$/', new ValidSubdomain()];
}
$rules['expense_mailbox'] = ['sometimes','email', 'nullable', new ValidExpenseMailbox(), Rule::unique('companies')->ignore($this->company->id)];
$rules['expense_mailbox_active'] = ['sometimes','boolean'];
$rules['inbound_mailbox_allow_company_users'] = ['sometimes','boolean'];
$rules['inbound_mailbox_allow_vendors'] = ['sometimes','boolean'];
$rules['inbound_mailbox_allow_clients'] = ['sometimes','boolean'];
$rules['inbound_mailbox_allow_unknown'] = ['sometimes','boolean'];
$rules['inbound_mailbox_whitelist'] = ['sometimes', 'string', 'nullable', 'regex:/^[\w\-\.\+]+@([\w-]+\.)+[\w-]{2,4}(,[\w\-\.\+]+@([\w-]+\.)+[\w-]{2,4})*$/'];
$rules['inbound_mailbox_blacklist'] = ['sometimes', 'string', 'nullable', 'regex:/^[\w\-\.\+]+@([\w-]+\.)+[\w-]{2,4}(,[\w\-\.\+]+@([\w-]+\.)+[\w-]{2,4})*$/'];
return $rules;
}
@ -98,11 +87,6 @@ class UpdateCompanyRequest extends Request
$input['portal_domain'] = rtrim(strtolower($input['portal_domain']), "/");
}
// /** Disabled on the hosted platform */
// if (isset($input['expense_mailbox']) && Ninja::isHosted() && !($this->company->account->isPaid() && $this->company->account->plan == 'enterprise')) {
// unset($input['expense_mailbox']);
// }
if (isset($input['settings'])) {
$input['settings'] = (array)$this->filterSaveableSettings($input['settings']);
}
@ -131,11 +115,6 @@ class UpdateCompanyRequest extends Request
$input['smtp_verify_peer'] == 'true' ? true : false;
}
if (isset($input['e_invoice']) && is_array($input['e_invoice'])) {
//ensure it is normalized first!
$input['e_invoice'] = $this->company->filterNullsRecursive($input['e_invoice']);
}
$this->replace($input);
}

View File

@ -107,7 +107,6 @@ class StoreCreditRequest extends Request
$input = $this->decodePrimaryKeys($input);
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
$input['line_items'] = $this->cleanFeeItems($input['line_items']);
$input['amount'] = $this->entityTotalAmount($input['line_items']);
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {

View File

@ -1,28 +0,0 @@
<?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\Requests\EInvoice;
use App\Utils\Ninja;
use App\Http\Requests\Request;
class SignupRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
return Ninja::isSelfHost();
}
}

View File

@ -1,93 +0,0 @@
<?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\Requests\EInvoice;
use App\Utils\Ninja;
use App\Models\Client;
use App\Models\Company;
use App\Models\Invoice;
use App\Http\Requests\Request;
use Illuminate\Validation\Rule;
class ValidateEInvoiceRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
/** @var \App\Models\User $user */
$user = auth()->user();
$entity = $this->getEntity();
if($entity instanceof Company)
return $entity->id == $user->company()->id;
return $user->can('view', $entity);
}
public function rules()
{
/** @var \App\Models\User $user */
$user = auth()->user();
return [
'entity' => 'required|bail|in:invoices,clients,companies',
'entity_id' => ['required','bail', Rule::exists($this->entity, 'id')
->when($this->entity != 'companies', function ($q) use($user){
$q->where('company_id', $user->company()->id);
})
],
];
}
public function prepareForValidation()
{
$input = $this->all();
if (isset($input['entity_id']) && $input['entity_id'] != null) {
$input['entity_id'] = $this->decodePrimaryKey($input['entity_id']);
}
$this->replace($input);
}
public function getEntity()
{
if(!$this->entity) {
return false;
}
$class = Invoice::class;
match ($this->entity) {
'invoices' => $class = Invoice::class,
'clients' => $class = Client::class,
'companies' => $class = Company::class,
default => $class = Invoice::class,
};
if($this->entity == 'companies')
return auth()->user()->company();
return $class::withTrashed()->find(is_string($this->entity_id) ? $this->decodePrimaryKey($this->entity_id) : $this->entity_id);
}
}

View File

@ -25,9 +25,9 @@ class EDocumentRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = 'required|file|max:1000000';
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = 'required|file|max:1000000';
$rules['documents'] = $this->fileValidation();
}
return $rules;
}

View File

@ -40,11 +40,10 @@ class BulkInvoiceRequest extends Request
/** @var \App\Models\User $user */
$user = auth()->user();
if(\Illuminate\Support\Facades\Cache::has($this->ip()."|".$this->input('action', 0)."|".$user->company()->company_key)) {
if(\Illuminate\Support\Facades\Cache::has($this->ip()."|".$this->input('action', 0)."|".json_encode($this->input('ids', ''))."|".$user->company()->company_key))
throw new DuplicatePaymentException('Duplicate request.', 429);
}
\Illuminate\Support\Facades\Cache::put(($this->ip()."|".$this->input('action', 0)."|".$user->company()->company_key), true, 1);
\Illuminate\Support\Facades\Cache::put(($this->ip()."|".$this->input('action', 0)."|".json_encode($this->input('ids', ''))."|".$user->company()->company_key), true, 1);
}

View File

@ -24,23 +24,4 @@ class DestroyInvoiceRequest extends Request
{
return auth()->user()->can('edit', $this->invoice);
}
public function rules()
{
return [];
}
public function prepareForValidation()
{
/** @var \App\Models\User $user */
$user = auth()->user();
if(\Illuminate\Support\Facades\Cache::has($this->ip()."|".$this->invoice->id."|".$user->company()->company_key))
throw new \App\Exceptions\DuplicatePaymentException('Duplicate request.', 429);
\Illuminate\Support\Facades\Cache::put(($this->ip()."|".$this->invoice->id."|".$user->company()->company_key), true, 1);
}
}

View File

@ -93,12 +93,6 @@ class StoreInvoiceRequest extends Request
/** @var \App\Models\User $user */
$user = auth()->user();
if(\Illuminate\Support\Facades\Cache::has($this->ip()."|INVOICE|".$this->input('client_id', '')."|".$user->company()->company_key)) {
usleep(200000);
}
\Illuminate\Support\Facades\Cache::put($this->ip()."|INVOICE|".$this->input('client_id', '')."|".$user->company()->company_key,1);
$input = $this->all();
$input = $this->decodePrimaryKeys($input);
@ -108,7 +102,6 @@ class StoreInvoiceRequest extends Request
if (isset($input['line_items']) && is_array($input['line_items'])) {
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
$input['line_items'] = $this->cleanFeeItems($input['line_items']);
$input['amount'] = $this->entityTotalAmount($input['line_items']);
}
if(isset($input['partial']) && $input['partial'] == 0) {

View File

@ -122,11 +122,6 @@ class UpdateInvoiceRequest extends Request
$input['due_date'] = \Illuminate\Support\Carbon::parse($input['date'])->addDays((int)$client->getSetting('payment_terms'))->format('Y-m-d');
}
if (isset($input['e_invoice']) && is_array($input['e_invoice'])) {
//ensure it is normalized first!
$input['e_invoice'] = $this->invoice->filterNullsRecursive($input['e_invoice']);
}
$this->replace($input);
}

View File

@ -70,8 +70,8 @@ class RefundPaymentRequest extends Request
'id' => ['bail','required', new ValidRefundableRequest($input)],
'amount' => ['numeric', 'max:99999999999999'],
'date' => 'required',
'invoices.*.invoice_id' => 'required|bail',
'invoices.*.amount' => 'required|bail|gt:0',
'invoices.*.invoice_id' => 'required',
'invoices.*.amount' => 'required',
'invoices' => new ValidRefundableInvoices($input),
];

View File

@ -80,9 +80,8 @@ class StorePaymentRequest extends Request
/** @var \App\Models\User $user */
$user = auth()->user();
if(\Illuminate\Support\Facades\Cache::has($this->ip()."|".$this->input('amount', 0)."|".$this->input('client_id', '')."|".$user->company()->company_key)) {
if(\Illuminate\Support\Facades\Cache::has($this->ip()."|".$this->input('amount', 0)."|".$this->input('client_id', '')."|".$user->company()->company_key))
throw new DuplicatePaymentException('Duplicate request.', 429);
}
\Illuminate\Support\Facades\Cache::put(($this->ip()."|".$this->input('amount', 0)."|".$this->input('client_id', '')."|".$user->company()->company_key), true, 1);

View File

@ -95,7 +95,6 @@ class StorePurchaseOrderRequest extends Request
if (isset($input['line_items']) && is_array($input['line_items'])) {
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
$input['line_items'] = $this->cleanFeeItems($input['line_items']);
$input['amount'] = $this->entityTotalAmount($input['line_items']);
}

View File

@ -1,69 +0,0 @@
<?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\Requests\Quickbooks;
use App\Models\Company;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Cache;
class AuthQuickbooksRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
return is_array($this->getTokenContent());
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
//
];
}
/**
* Resolve one-time token instance.
*
* @return mixed
*/
public function getTokenContent()
{
if ($this->state) {
$this->token = $this->state;
}
$data = Cache::get($this->token);
return $data;
}
public function getContact(): ?User
{
return User::findOrFail($this->getTokenContent()['user_id']);
}
public function getCompany(): ?Company
{
return Company::query()->where('company_key', $this->getTokenContent()['company_key'])->firstOrFail();
}
}

View File

@ -1,69 +0,0 @@
<?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\Requests\Quickbooks;
use App\Models\Company;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Cache;
class AuthorizedQuickbooksRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
return is_array($this->getTokenContent());
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'code' => 'required|string',
'state' => 'required|string',
'realmId' => 'required|string',
];
}
/**
* Resolve one-time token instance.
*
* @return mixed
*/
public function getTokenContent()
{
$token = Cache::get($this->state);
$data = Cache::get($token);
return $data;
}
public function getContact()
{
return User::findOrFail($this->getTokenContent()['user_id']);
}
public function getCompany()
{
return Company::where('company_key', $this->getTokenContent()['company_key'])->firstOrFail();
}
}

View File

@ -38,7 +38,7 @@ class BulkActionQuoteRequest extends Request
'send_email' => 'sometimes|bool'
];
if (in_array($input['action'], ['convert','convert_to_invoice'])) {
if (in_array($input['action'], ['convert,convert_to_invoice'])) {
$rules['action'] = [new ConvertableQuoteRule()];
}

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