mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2026-03-03 02:37:02 +00:00
@@ -1 +1 @@
|
||||
5.12.45
|
||||
5.12.46
|
||||
@@ -236,7 +236,7 @@ class PayPalBalanceAffecting
|
||||
|
||||
|
||||
|
||||
// $csv = Reader::createFromString($csvFile);
|
||||
// $csv = Reader::fromString($csvFile);
|
||||
// // $csvdelimiter = self::detectDelimiter($csvfile);
|
||||
// $csv->setDelimiter(",");
|
||||
// $stmt = new Statement();
|
||||
|
||||
@@ -11,37 +11,42 @@
|
||||
|
||||
namespace App\Helpers\Cache;
|
||||
|
||||
use Illuminate\Contracts\Redis\Factory as RedisFactory;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
|
||||
class Atomic
|
||||
{
|
||||
public static function set($key, $value = true, $ttl = 1): bool
|
||||
public static function set(string $key, mixed $value = true, int $ttl = 1): bool
|
||||
{
|
||||
$new_ttl = now()->addSeconds($ttl);
|
||||
|
||||
try {
|
||||
return Redis::connection('sentinel-cache')->set($key, $value, 'EX', $ttl, 'NX') ? true : false;
|
||||
/** @var RedisFactory $redis */
|
||||
$redis = app('redis');
|
||||
$result = $redis->connection('sentinel-cache')->command('set', [$key, $value, 'EX', $ttl, 'NX']);
|
||||
return (bool) $result;
|
||||
} catch (\Throwable) {
|
||||
return Cache::add($key, $value, $new_ttl) ? true : false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static function get($key)
|
||||
public static function get(string $key): mixed
|
||||
{
|
||||
try {
|
||||
return Redis::connection('sentinel-cache')->get($key);
|
||||
/** @var RedisFactory $redis */
|
||||
$redis = app('redis');
|
||||
return $redis->connection('sentinel-cache')->command('get', [$key]);
|
||||
} catch (\Throwable) {
|
||||
return Cache::get($key);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static function del($key)
|
||||
public static function del(string $key): mixed
|
||||
{
|
||||
try {
|
||||
return Redis::connection('sentinel-cache')->del($key);
|
||||
/** @var RedisFactory $redis */
|
||||
$redis = app('redis');
|
||||
return $redis->connection('sentinel-cache')->command('del', [$key]);
|
||||
} catch (\Throwable) {
|
||||
return Cache::forget($key);
|
||||
}
|
||||
|
||||
@@ -112,11 +112,11 @@ class SwissQrGenerator
|
||||
// Add payment reference
|
||||
// This is what you will need to identify incoming payments.
|
||||
|
||||
if (stripos($this->invoice->number, "Live") === 0) {
|
||||
if (stripos($this->invoice->number ?? '', "Live") === 0) {
|
||||
// we're currently in preview status. Let's give a dummy reference for now
|
||||
$invoice_number = "123456789";
|
||||
} else {
|
||||
$tempInvoiceNumber = $this->invoice->number;
|
||||
$tempInvoiceNumber = $this->invoice->number ?? '';
|
||||
$tempInvoiceNumber = preg_replace('/[^A-Za-z0-9]/', '', $tempInvoiceNumber);
|
||||
// $tempInvoiceNumber = substr($tempInvoiceNumber, 1);
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ use App\Jobs\Util\ApplePayDomain;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Factory\CompanyGatewayFactory;
|
||||
use App\Filters\CompanyGatewayFilters;
|
||||
use App\Repositories\CompanyRepository;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use App\Repositories\CompanyGatewayRepository;
|
||||
use App\Transformers\CompanyGatewayTransformer;
|
||||
use App\PaymentDrivers\Stripe\Jobs\StripeWebhook;
|
||||
use App\PaymentDrivers\CheckoutCom\CheckoutSetupWebhook;
|
||||
@@ -63,9 +63,9 @@ class CompanyGatewayController extends BaseController
|
||||
|
||||
/**
|
||||
* CompanyGatewayController constructor.
|
||||
* @param CompanyRepository $company_repo
|
||||
* @param CompanyGatewayRepository $company_repo
|
||||
*/
|
||||
public function __construct(CompanyRepository $company_repo)
|
||||
public function __construct(CompanyGatewayRepository $company_repo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
@@ -210,10 +210,14 @@ class CompanyGatewayController extends BaseController
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$company = $user->company();
|
||||
|
||||
$company_gateway = CompanyGatewayFactory::create($user->company()->id, $user->id);
|
||||
$company_gateway->fill($request->all());
|
||||
$company_gateway->save();
|
||||
|
||||
$this->company_repo->addGatewayToCompanyGatewayIds($company_gateway);
|
||||
|
||||
/*Always ensure at least one fees and limits object is set per gateway*/
|
||||
$gateway_types = $company_gateway->driver(new Client())->getAvailableMethods();
|
||||
|
||||
|
||||
@@ -446,7 +446,6 @@ class ImportController extends Controller
|
||||
|
||||
$csv = Reader::fromString($csvfile);
|
||||
|
||||
// $csv = Reader::createFromString($csvfile);
|
||||
$csvdelimiter = self::detectDelimiter($csvfile);
|
||||
$csv->setDelimiter($csvdelimiter);
|
||||
$stmt = new Statement();
|
||||
@@ -456,7 +455,7 @@ class ImportController extends Controller
|
||||
$headers = $data[0];
|
||||
|
||||
// Remove Invoice Ninja headers
|
||||
if (count($headers) && count($data) > 4) {
|
||||
if (is_array($headers) && count($headers) > 0 && count($data) > 4) {
|
||||
$firstCell = $headers[0];
|
||||
|
||||
if (strstr($firstCell, (string) config('ninja.app_name'))) {
|
||||
|
||||
@@ -161,6 +161,10 @@ class StoreTaskRequest extends Request
|
||||
|
||||
}
|
||||
|
||||
if(isset($input['description']) && is_string($input['description'])) {
|
||||
$input['description'] = str_ireplace(['</sc', 'file:/', 'iframe', '<embed', '<embed', '<object', '<object', '127.0.0.1', 'localhost', '<?xml encoding="UTF-8">', '/etc/'], "", $input['description']);
|
||||
}
|
||||
|
||||
/* Ensure the project is related */
|
||||
if (array_key_exists('project_id', $input) && isset($input['project_id'])) {
|
||||
$project = Project::withTrashed()->where('id', $input['project_id'])->company()->first();
|
||||
|
||||
@@ -136,6 +136,10 @@ class UpdateTaskRequest extends Request
|
||||
$input['status_id'] = $this->decodePrimaryKey($input['status_id']);
|
||||
}
|
||||
|
||||
if(isset($input['description']) && is_string($input['description'])) {
|
||||
$input['description'] = str_ireplace(['</sc', 'file:/', 'iframe', '<embed', '<embed', '<object', '<object', '127.0.0.1', 'localhost', '<?xml encoding="UTF-8">', '/etc/'], "", $input['description']);
|
||||
}
|
||||
|
||||
if (isset($input['documents'])) {
|
||||
unset($input['documents']);
|
||||
}
|
||||
|
||||
@@ -12,33 +12,35 @@
|
||||
|
||||
namespace App\Import\Providers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Quote;
|
||||
use League\Csv\Reader;
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use League\Csv\Statement;
|
||||
use App\Factory\TaskFactory;
|
||||
use App\Factory\QuoteFactory;
|
||||
use App\Factory\ClientFactory;
|
||||
use Illuminate\Support\Carbon;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Factory\PaymentFactory;
|
||||
use App\Factory\QuoteFactory;
|
||||
use App\Factory\RecurringInvoiceFactory;
|
||||
use App\Factory\TaskFactory;
|
||||
use App\Http\Requests\Quote\StoreQuoteRequest;
|
||||
use App\Import\ImportException;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Mail\Import\CsvImportCompleted;
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Quote;
|
||||
use App\Models\User;
|
||||
use App\Repositories\ClientRepository;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
use App\Repositories\PaymentRepository;
|
||||
use App\Repositories\QuoteRepository;
|
||||
use App\Repositories\RecurringInvoiceRepository;
|
||||
use App\Repositories\TaskRepository;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use Illuminate\Support\Carbon;
|
||||
use App\Repositories\QuoteRepository;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Repositories\ClientRepository;
|
||||
use App\Mail\Import\CsvImportCompleted;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
use App\Repositories\PaymentRepository;
|
||||
use App\Factory\RecurringInvoiceFactory;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use League\Csv\Reader;
|
||||
use League\Csv\Statement;
|
||||
use App\Http\Requests\Quote\StoreQuoteRequest;
|
||||
use App\Repositories\RecurringInvoiceRepository;
|
||||
use App\Notifications\Ninja\GenericNinjaAdminNotification;
|
||||
|
||||
class BaseImport
|
||||
{
|
||||
@@ -70,6 +72,8 @@ class BaseImport
|
||||
|
||||
public array $entity_count = [];
|
||||
|
||||
public bool $store_import_for_research = false;
|
||||
|
||||
public function __construct(array $request, Company $company)
|
||||
{
|
||||
$this->company = $company;
|
||||
@@ -107,7 +111,7 @@ class BaseImport
|
||||
$csv = base64_decode($base64_encoded_csv);
|
||||
// $csv = mb_convert_encoding($csv, 'UTF-8', 'UTF-8');
|
||||
|
||||
$csv = Reader::createFromString($csv);
|
||||
$csv = Reader::fromString($csv);
|
||||
$csvdelimiter = self::detectDelimiter($csv);
|
||||
|
||||
$csv->setDelimiter($csvdelimiter);
|
||||
@@ -119,7 +123,8 @@ class BaseImport
|
||||
|
||||
// Remove Invoice Ninja headers
|
||||
if (
|
||||
count($headers) &&
|
||||
is_array($headers) &&
|
||||
count($headers) > 0 &&
|
||||
count($data) > 4 &&
|
||||
$this->import_type === 'csv'
|
||||
) {
|
||||
@@ -320,7 +325,8 @@ class BaseImport
|
||||
$entity->saveQuietly();
|
||||
$count++;
|
||||
}
|
||||
} catch (\Exception $ex) {
|
||||
}
|
||||
catch (\Exception $ex) {
|
||||
if (\DB::connection(config('database.default'))->transactionLevel() > 0) {
|
||||
\DB::connection(config('database.default'))->rollBack();
|
||||
}
|
||||
@@ -339,6 +345,20 @@ class BaseImport
|
||||
|
||||
nlog("Ingest {$ex->getMessage()}");
|
||||
nlog($record);
|
||||
|
||||
$this->store_import_for_research = true;
|
||||
|
||||
}
|
||||
catch(\Throwable $ex){
|
||||
if (\DB::connection(config('database.default'))->transactionLevel() > 0) {
|
||||
\DB::connection(config('database.default'))->rollBack();
|
||||
}
|
||||
|
||||
nlog("Throwable:: Ingest {$ex->getMessage()}");
|
||||
nlog($record);
|
||||
|
||||
$this->store_import_for_research = true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -945,6 +965,39 @@ class BaseImport
|
||||
$nmo->to_user = $this->company->owner();
|
||||
|
||||
NinjaMailerJob::dispatch($nmo, true);
|
||||
|
||||
/** Debug for import failures */
|
||||
if (Ninja::isHosted() && $this->store_import_for_research) {
|
||||
|
||||
$content = [
|
||||
'company_key - '. $this->company->company_key,
|
||||
'class_name - ' . class_basename($this),
|
||||
'hash - ' => $this->hash,
|
||||
];
|
||||
|
||||
$potential_imports = [
|
||||
'client',
|
||||
'product',
|
||||
'invoice',
|
||||
'payment',
|
||||
'vendor',
|
||||
'expense',
|
||||
'quote',
|
||||
'bank_transaction',
|
||||
'task',
|
||||
'recurring_invoice',
|
||||
];
|
||||
|
||||
foreach ($potential_imports as $import) {
|
||||
|
||||
if(Cache::has($this->hash.'-'.$import)) {
|
||||
Cache::put($this->hash.'-'.$import, Cache::get($this->hash.'-'.$import), 60*60*24*2);
|
||||
}
|
||||
}
|
||||
|
||||
$this->company->notification(new GenericNinjaAdminNotification($content))->ninja();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function preTransform(array $data, $entity_type)
|
||||
|
||||
@@ -382,7 +382,6 @@ class Csv extends BaseImport implements ImportInterface
|
||||
|
||||
$this->entity_count['tasks'] = $task_count;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function transform(array $data)
|
||||
|
||||
@@ -166,7 +166,7 @@ class ProcessPostmarkWebhook implements ShouldQueue
|
||||
|
||||
private function processOpen()
|
||||
{
|
||||
$this->invitation->opened_date = now();
|
||||
$this->invitation->opened_date = now()->setTimezone($this->invitation->company->timezone()->name);
|
||||
$this->invitation->saveQuietly();
|
||||
|
||||
$data = array_merge($this->request, ['history' => $this->fetchMessage()]);
|
||||
|
||||
@@ -279,7 +279,7 @@ class BaseModel extends Model
|
||||
|
||||
public function numberFormatter()
|
||||
{
|
||||
$number = strlen($this->number) >= 1 ? $this->translate_entity() . "_" . $this->number : class_basename($this) . "_" . Str::random(5);
|
||||
$number = strlen($this->number ?? '') >= 1 ? $this->translate_entity() . "_" . $this->number : class_basename($this) . "_" . Str::random(5);
|
||||
|
||||
$formatted_number = mb_ereg_replace("([^\w\s\d\-_~,;\[\]\(\).])", '', $number);
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
* @property bool $is_deleted
|
||||
* @property string $config
|
||||
* @property object $settings
|
||||
* @property mixed $fees_and_limits
|
||||
* @property array|object|mixed $fees_and_limits
|
||||
* @property string|null $custom_value1
|
||||
* @property string|null $custom_value2
|
||||
* @property string|null $custom_value3
|
||||
|
||||
@@ -38,7 +38,7 @@ use App\Utils\Number;
|
||||
* App\Models\Invoice
|
||||
*
|
||||
* @property int $id
|
||||
* @property object|null $e_invoice
|
||||
* @property object|array|null $e_invoice
|
||||
* @property int $client_id
|
||||
* @property int $user_id
|
||||
* @property int|null $location_id
|
||||
|
||||
@@ -83,7 +83,8 @@ class PaymentIntentProcessingWebhook implements ShouldQueue
|
||||
/** @var \App\Models\ClientGatewayToken $cgt **/
|
||||
$cgt = ClientGatewayToken::where('token', $transaction['payment_method'])->first();
|
||||
|
||||
if ($cgt && $cgt->meta?->state == 'unauthorized') {
|
||||
if ($cgt && isset($cgt->meta)) {
|
||||
// if ($cgt && $cgt->meta?->state == 'unauthorized') {
|
||||
$meta = $cgt->meta;
|
||||
$meta->state = 'authorized';
|
||||
$cgt->meta = $meta;
|
||||
|
||||
94
app/Repositories/CompanyGatewayRepository.php
Normal file
94
app/Repositories/CompanyGatewayRepository.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Repositories\BaseRepository;
|
||||
|
||||
/**
|
||||
* CompanyGatewayRepository.
|
||||
*/
|
||||
class CompanyGatewayRepository extends BaseRepository
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function archive($company_gateway): CompanyGateway
|
||||
{
|
||||
|
||||
parent::archive($company_gateway);
|
||||
|
||||
$this->removeGatewayFromCompanyGatewayIds($company_gateway);
|
||||
|
||||
return $company_gateway;
|
||||
}
|
||||
|
||||
public function delete($company_gateway): CompanyGateway
|
||||
{
|
||||
parent::delete($company_gateway);
|
||||
|
||||
$this->removeGatewayFromCompanyGatewayIds($company_gateway);
|
||||
|
||||
return $company_gateway;
|
||||
}
|
||||
|
||||
public function restore($company_gateway): CompanyGateway
|
||||
{
|
||||
parent::restore($company_gateway);
|
||||
|
||||
$this->addGatewayToCompanyGatewayIds($company_gateway);
|
||||
|
||||
return $company_gateway;
|
||||
}
|
||||
|
||||
public function addGatewayToCompanyGatewayIds(CompanyGateway $company_gateway)
|
||||
{
|
||||
$company_gateway_ids = $company_gateway->company->getSetting('company_gateway_ids');
|
||||
|
||||
if(strlen($company_gateway_ids ?? '') > 2){
|
||||
$transformed_ids = collect($this->transformKeys(explode(',', $company_gateway_ids)))
|
||||
->push($company_gateway->hashed_id)
|
||||
->implode(",");
|
||||
|
||||
$company = $company_gateway->company;
|
||||
$settings = $company->settings;
|
||||
$settings->company_gateway_ids = $transformed_ids;
|
||||
$company->settings = $settings;
|
||||
$company->save();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function removeGatewayFromCompanyGatewayIds(CompanyGateway $company_gateway)
|
||||
{
|
||||
$company_gateway_ids = $company_gateway->company->getSetting('company_gateway_ids');
|
||||
|
||||
if(strpos($company_gateway_ids, $company_gateway->hashed_id) !== false){
|
||||
$transformed_ids = collect($this->transformKeys(explode(',', $company_gateway_ids)))
|
||||
->filter(function ($id) use ($company_gateway){
|
||||
return $id !== $company_gateway->hashed_id;
|
||||
})
|
||||
->implode(",");
|
||||
|
||||
$company = $company_gateway->company;
|
||||
$settings = $company->settings;
|
||||
$settings->company_gateway_ids = $transformed_ids;
|
||||
$company->settings = $settings;
|
||||
$company->save();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -219,6 +219,9 @@ class StorecoveRouter
|
||||
$country = 'BE';
|
||||
$identifier = 'BE:VAT';
|
||||
}
|
||||
elseif($country == 'GLN'){
|
||||
return 'routing_id';
|
||||
}
|
||||
|
||||
$rules = $this->routing_rules[$country];
|
||||
|
||||
|
||||
@@ -189,13 +189,12 @@ class StorecoveExpense
|
||||
$tax_totals[] = (array)$tdf;
|
||||
}
|
||||
|
||||
$totals = collect($tax_totals);
|
||||
|
||||
$party = $storecove_invoice->getAccountingSupplierParty()->getParty();
|
||||
$pis = $storecove_invoice->getAccountingSupplierParty()->getPublicIdentifiers();
|
||||
|
||||
$vat_number = '';
|
||||
$id_number = '';
|
||||
$routing_id = '';
|
||||
|
||||
foreach ($pis as $pi) {
|
||||
if ($ident = $this->storecove->router->resolveIdentifierTypeByValue($pi->getScheme())) {
|
||||
@@ -203,6 +202,8 @@ class StorecoveExpense
|
||||
$vat_number = $pi->getId();
|
||||
} elseif ($ident == 'id_number') {
|
||||
$id_number = $pi->getId();
|
||||
} elseif ($ident == 'routing_id') {
|
||||
$routing_id = $pi->getId();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -271,6 +272,7 @@ class StorecoveExpense
|
||||
'currency_id' => $currency,
|
||||
'id_number' => $id_number,
|
||||
'vat_number' => $vat_number,
|
||||
'routing_id' => $routing_id,
|
||||
'address1' => $party->getAddress()->getStreet1() ?? '',
|
||||
'address2' => $party->getAddress()->getStreet2() ?? '',
|
||||
'city' => $party->getAddress()->getCity() ?? '',
|
||||
|
||||
@@ -65,7 +65,7 @@ class Peppol extends AbstractService
|
||||
*
|
||||
*/
|
||||
|
||||
private ?string $override_vat_number;
|
||||
private string $override_vat_number = '';
|
||||
|
||||
/** @var array $InvoiceTypeCodes */
|
||||
private array $InvoiceTypeCodes = [
|
||||
@@ -657,7 +657,8 @@ class Peppol extends AbstractService
|
||||
$tax_type = 'S';
|
||||
break;
|
||||
case Product::PRODUCT_TYPE_REDUCED_TAX:
|
||||
$tax_type = 'AA';
|
||||
// $tax_type = 'AA';
|
||||
$tax_type = 'S'; //2026-01-14 - using AA breaks PEPPOL VALIDATION!!
|
||||
break;
|
||||
case Product::PRODUCT_TYPE_EXEMPT:
|
||||
$tax_type = 'E';
|
||||
@@ -1032,18 +1033,18 @@ class Peppol extends AbstractService
|
||||
$party->PartyName[] = $party_name;
|
||||
|
||||
if (strlen($this->company->settings->vat_number ?? '') > 1) {
|
||||
|
||||
|
||||
$pi = new PartyIdentification();
|
||||
$vatID = new ID();
|
||||
$vatID->schemeID = $this->resolveScheme();
|
||||
$vatID->value = $this->override_vat_number ?? preg_replace("/[^a-zA-Z0-9]/", "", $this->invoice->company->settings->vat_number); //todo if we are cross border - switch to the supplier local vat number
|
||||
$vatID->value = strlen($this->override_vat_number ?? '') > 1 ? $this->override_vat_number : preg_replace("/[^a-zA-Z0-9]/", "", $this->invoice->company->settings->vat_number); //todo if we are cross border - switch to the supplier local vat number
|
||||
|
||||
$pi->ID = $vatID;
|
||||
$party->PartyIdentification[] = $pi;
|
||||
$pts = new \InvoiceNinja\EInvoice\Models\Peppol\PartyTaxSchemeType\PartyTaxScheme();
|
||||
|
||||
$companyID = new \InvoiceNinja\EInvoice\Models\Peppol\IdentifierType\CompanyID();
|
||||
$companyID->value = $this->override_vat_number ?? preg_replace("/[^a-zA-Z0-9]/", "", $this->invoice->company->settings->vat_number);
|
||||
$companyID->value = strlen($this->override_vat_number ?? '') > 1 ? $this->override_vat_number : preg_replace("/[^a-zA-Z0-9]/", "", $this->invoice->company->settings->vat_number); //todo if we are cross border - switch to the supplier local vat number
|
||||
$pts->CompanyID = $companyID;
|
||||
|
||||
$ts = new TaxScheme();
|
||||
@@ -1290,7 +1291,6 @@ class Peppol extends AbstractService
|
||||
|
||||
///////////////// Helper Methods /////////////////////////
|
||||
|
||||
|
||||
/**
|
||||
* setInvoiceDefaults
|
||||
*
|
||||
@@ -1498,9 +1498,9 @@ class Peppol extends AbstractService
|
||||
$category_id->value = $this->getTaxType($grouped_tax['tax_id']); // Standard rate
|
||||
|
||||
// Temp fix for reduced tax rate categorization.
|
||||
if($grouped_tax['tax_rate'] < 15 && $grouped_tax['tax_rate'] >= 0) {
|
||||
$category_id->value = 'AA';
|
||||
}
|
||||
// if($grouped_tax['tax_rate'] < 15 && $grouped_tax['tax_rate'] >= 0) {
|
||||
// $category_id->value = 'AA';
|
||||
// }
|
||||
|
||||
$tax_category->ID = $category_id;
|
||||
|
||||
|
||||
@@ -33,9 +33,12 @@ use App\Events\Invoice\InvoiceWasPaid;
|
||||
use App\Repositories\CreditRepository;
|
||||
use App\Repositories\PaymentRepository;
|
||||
use App\Events\Payment\PaymentWasCreated;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class AutoBillInvoice extends AbstractService
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
private Client $client;
|
||||
|
||||
private array $used_credit = [];
|
||||
@@ -45,9 +48,7 @@ class AutoBillInvoice extends AbstractService
|
||||
|
||||
public function __construct(private Invoice $invoice, protected string $db)
|
||||
{
|
||||
|
||||
$this->client = $this->invoice->client;
|
||||
|
||||
}
|
||||
|
||||
public function run()
|
||||
@@ -55,7 +56,6 @@ class AutoBillInvoice extends AbstractService
|
||||
MultiDB::setDb($this->db);
|
||||
|
||||
/* @var \App\Modesl\Client $client */
|
||||
|
||||
$is_partial = false;
|
||||
|
||||
/* Is the invoice payable? */
|
||||
@@ -444,14 +444,32 @@ class AutoBillInvoice extends AbstractService
|
||||
*/
|
||||
public function getGateway($amount)
|
||||
{
|
||||
$company_gateway_ids = $this->client->getSetting('company_gateway_ids');
|
||||
|
||||
$transformed_ids = false;
|
||||
|
||||
//gateways are disabled!
|
||||
if($company_gateway_ids == "0") {
|
||||
return false;
|
||||
}
|
||||
elseif(strlen($company_gateway_ids ?? '') > 2){
|
||||
|
||||
// If the client has a special gateway configuration, we need to ensure we only use the ones that are enabled!
|
||||
$transformed_ids = $this->transformKeys(explode(',', $company_gateway_ids));
|
||||
}
|
||||
|
||||
//get all client gateway tokens and set the is_default one to the first record
|
||||
$gateway_tokens = \App\Models\ClientGatewayToken::query()
|
||||
->where('client_id', $this->client->id)
|
||||
->where('is_deleted', 0)
|
||||
->whereHas('gateway', function ($query) {
|
||||
->whereHas('gateway', function ($query) use ($transformed_ids) {
|
||||
$query->where('is_deleted', 0)
|
||||
->where('deleted_at', null);
|
||||
})->orderBy('is_default', 'DESC')
|
||||
->where('deleted_at', null)
|
||||
->when($transformed_ids, function ($q) use ($transformed_ids) {
|
||||
$q->whereIn('id', $transformed_ids);
|
||||
});
|
||||
})
|
||||
->orderBy('is_default', 'DESC')
|
||||
->get();
|
||||
|
||||
$filtered_gateways = $gateway_tokens->filter(function ($gateway_token) use ($amount) {
|
||||
|
||||
@@ -450,7 +450,7 @@ class TaxPeriodReport extends BaseExport
|
||||
$this->data['invoices'][] = $invoice_row_builder->build();
|
||||
|
||||
// Build and add invoice item rows for each tax detail
|
||||
foreach ($event->metadata->tax_report->tax_details as $tax_detail_data) {
|
||||
foreach ($event->metadata->tax_report->tax_details ?? [] as $tax_detail_data) {
|
||||
$tax_detail = TaxDetail::fromMetadata($tax_detail_data);
|
||||
|
||||
$item_row_builder = new InvoiceItemReportRow(
|
||||
|
||||
@@ -937,11 +937,10 @@ Código seguro de verificación (CSV): {$verifactu_log->status}";
|
||||
$tax_label = '';
|
||||
|
||||
if (collect($this->entity->line_items)->contains('tax_id', \App\Models\Product::PRODUCT_TYPE_REVERSE_TAX)) {
|
||||
$tax_label .= ctrans('texts.reverse_tax_info') . "<br>";
|
||||
$tax_label .= ctrans('texts.reverse_tax_info') . " <br>";
|
||||
}
|
||||
|
||||
if ((int)$this->client->country_id !== (int)$this->company->settings->country_id) {
|
||||
$tax_label .= ctrans('texts.intracommunity_tax_info') . "<br>";
|
||||
else if ((int)$this->client->country_id !== (int)$this->company->settings->country_id) {
|
||||
$tax_label .= ctrans('texts.intracommunity_tax_info') . " <br>";
|
||||
|
||||
if ($this->entity_calc->getTotalTaxes() > 0) {
|
||||
$tax_label = '';
|
||||
|
||||
@@ -17,8 +17,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => env('APP_VERSION', '5.12.45'),
|
||||
'app_tag' => env('APP_TAG', '5.12.45'),
|
||||
'app_version' => env('APP_VERSION', '5.12.46'),
|
||||
'app_tag' => env('APP_TAG', '5.12.46'),
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', false),
|
||||
|
||||
@@ -5687,6 +5687,8 @@ $lang = array(
|
||||
'notification_invoice_overdue_summary_subject' => 'Invoice Overdue Summary: :date',
|
||||
'notification_invoice_overdue_summary' => 'The following invoices are overdue:',
|
||||
'purge_user_confirmation' => 'Warning! This action will reassign all entities to the account owner and permanently delete the user across all companies and accounts. Are you sure you want to proceed?',
|
||||
'peppol_sending_failed' => 'E-Invoice sending failed!',
|
||||
'peppol_sending_success' => 'E-Invoice sent successfully!',
|
||||
);
|
||||
|
||||
return $lang;
|
||||
|
||||
@@ -4688,6 +4688,8 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
|
||||
'show_tasks_in_client_portal' => 'Afficher les tâches sur le portail du client',
|
||||
'notification_quote_expired_subject' => 'La soumission :invoice a expiré pour :client',
|
||||
'notification_quote_expired' => 'La soumission :invoice pour le client :client au montant de :amount est expirée',
|
||||
'notification_invoice_overdue_subject' => 'La facture :invoice est échue pour :client',
|
||||
'notification_invoice_overdue' => 'La facture :invoice pour le client :client de :amount est échue.',
|
||||
'auto_sync' => 'Synchronisation automatique',
|
||||
'refresh_accounts' => 'Rafraîchir les comptes',
|
||||
'upgrade_to_connect_bank_account' => 'Passer au plan Entreprise pour connecter votre compte bancaire',
|
||||
@@ -5634,7 +5636,6 @@ Développe automatiquement la section des notes dans le tableau de produits pour
|
||||
'einvoice_received_subject' => 'E-facture(s) reçues',
|
||||
'einvoice_received_body' => 'Vous avez reçu :count nouvelle(s) E-facture(s).<br><br>Connectez-vous pour les consulter.',
|
||||
'download_files_too_large' => 'La taille de certains fichiers dépassait la limite pour être joints directement au message courriel.',
|
||||
|
||||
'restore_disabled_verifactu' => 'Vous ne pouvez pas restaurer une facture une fois qu\'elle a été supprimée.',
|
||||
'delete_disabled_verifactu' => 'Vous ne pouvez pas supprimer une facture une fois qu\'elle a été annulée ou modifiée.',
|
||||
'rectify' => 'Rectificar',
|
||||
@@ -5643,10 +5644,6 @@ Développe automatiquement la section des notes dans le tableau de produits pour
|
||||
'verifactu_cancellation_send_success' => 'Annulation de facture pour :invoice envoyée à AEAT',
|
||||
'verifactu_cancellation_send_failure' => 'Annulation de facture pour :invoice n\'a pas été envoyée AEAT :notes',
|
||||
'verifactu' => 'Verifactu',
|
||||
'activity_150' => 'Compte supprimé :notes',
|
||||
'activity_151' => 'Le client :notes a été fusionné avec :client par :user',
|
||||
'activity_152' => 'Le fournisseur :notes a été fusionné avec :vendor par :user',
|
||||
'activity_153' => 'Le client :notes a été purgé par :user',
|
||||
'justify' => 'Justifier',
|
||||
'outdent' => 'Désindenter',
|
||||
'indent' => 'Indenter',
|
||||
@@ -5665,6 +5662,30 @@ Développe automatiquement la section des notes dans le tableau de produits pour
|
||||
'thank_you_for_feedback' => 'Merci pour vos commentaires !',
|
||||
'use_legacy_editor' => 'Utiliser l\'éditeur classique Wysiwyg',
|
||||
'use_legacy_editor_help' => 'Utiliser l\'éditeur TinyMCE.',
|
||||
'enable_e_invoice_received_notification' => 'Activer la notification de réception de facture électronique',
|
||||
'enable_e_invoice_received_notification_help' => 'Recevoir une notification par courriel lorsqu\'une nouvelle facture électronique est reçue.',
|
||||
'price_changes' => 'Changements de prix des forfaits à partir du 1er janvier 2026',
|
||||
'notification_quote_rejected_subject' => 'La soumission :quote n\'a pas été acceptée par :client',
|
||||
'notification_quote_rejected' => 'Le client :client n\'a pas accepté la soumission :quote pour :amount :notes.',
|
||||
'activity_150' => 'Compte supprimé :notes',
|
||||
'activity_151' => 'Le client :notes a été fusionné avec :client par :user',
|
||||
'activity_152' => 'Le fournisseur :notes a été fusionné avec :vendor par :user',
|
||||
'activity_153' => 'Le client :notes a été purgé par :user',
|
||||
'activity_154' => 'La facture électronique :invoice pour :client a été envoyer à AEAT.',
|
||||
'activity_155' => 'L\'envoi de la facture électronique :invoice pour :client à l\'AEAT a échoué :notes',
|
||||
'activity_156' => 'L\'annulation de la facture pour :invoice a été envoyée',
|
||||
'activity_157' => 'L\'envoi de l\'annulation de la facture pour :invoice a échoué pour AEAT :notes',
|
||||
'activity_158' => 'La soumission :quote n\'a pas été accepté par :client :notes',
|
||||
'quotes_with_status_sent_can_be_rejected' => 'Seules les soumission avec le statut "Envoyée" peuvent être rejetées.',
|
||||
'reject' => 'Rejeter',
|
||||
'rejected' => 'Rejeté',
|
||||
'reject_quote' => 'Rejeter la soumission',
|
||||
'reject_quote_confirmation' => 'Êtes-vous sûr de vouloir rejeter cette soumission ?',
|
||||
'reason' => 'Raison',
|
||||
'enter_reason' => 'Préciser la raison...',
|
||||
'notification_invoice_overdue_summary_subject' => 'Invoice Overdue Summary: :date',
|
||||
'notification_invoice_overdue_summary' => 'The following invoices are overdue:',
|
||||
'purge_user_confirmation' => 'Warning! This action will reassign all entities to the account owner and permanently delete the user across all companies and accounts. Are you sure you want to proceed?',
|
||||
);
|
||||
|
||||
return $lang;
|
||||
|
||||
@@ -3831,9 +3831,9 @@ Kom terug naar deze betaalmethode pagina zodra u de bedragen heeft ontvangen en
|
||||
'to_view_entity_password' => 'Om de :entity te bekijken moet u een wachtwoord invoeren.',
|
||||
'showing_x_of' => 'Toont de :first tot :last van de :total resultaten',
|
||||
'no_results' => 'Geen resultaten gevonden.',
|
||||
'payment_failed_subject' => 'Betaling mislukt voor klant :klant',
|
||||
'payment_failed_subject' => 'Betaling mislukt voor klant :client',
|
||||
'payment_failed_body' => 'Een betaling gedaan door de klant :client is mislukt met bericht :bericht',
|
||||
'register' => 'Registreer',
|
||||
'register' => 'Registreren',
|
||||
'register_label' => 'Maak binnen enkele seconden uw account aan',
|
||||
'password_confirmation' => 'Bevestig uw wachtwoord',
|
||||
'verification' => 'Verificatie',
|
||||
@@ -3925,8 +3925,8 @@ Kom terug naar deze betaalmethode pagina zodra u de bedragen heeft ontvangen en
|
||||
'invoice_number_taken' => 'Factuurnummer reeds in gebruik',
|
||||
'payment_id_required' => 'Betalings-id verplicht',
|
||||
'unable_to_retrieve_payment' => 'Niet in staat om gevraagde betaling op te halen',
|
||||
'invoice_not_related_to_payment' => 'Factuur ID :invoice is niet herleidbaar naar deze betaling',
|
||||
'credit_not_related_to_payment' => 'Creditfactuur ID :credit is niet verwant aan deze betaling',
|
||||
'invoice_not_related_to_payment' => 'Factuur # :invoice is niet gerelateerd aan deze betaling.',
|
||||
'credit_not_related_to_payment' => 'Krediet # :credit is niet gerelateerd aan deze betaling.',
|
||||
'max_refundable_invoice' => 'Poging tot terugbetaling is groter dan toegestaan voor invoice id :invoice, maximum terug te betalen bedrag is :amount',
|
||||
'refund_without_invoices' => 'Wanneer u een betaling met bijgevoegde facturen wilt terugbetalen, geef dan aan welke geldige factuur/facturen u wilt terugbetalen.',
|
||||
'refund_without_credits' => 'Wanneer u een betaling met bijgevoegde tegoeden wilt terugbetalen, geef dan aan welke tegoeden geldig zijn en u deze wilt terugbetalen.',
|
||||
@@ -4691,6 +4691,8 @@ E-mail: :email<b><br><b>',
|
||||
'show_tasks_in_client_portal' => 'Toon taken in klantenportaal',
|
||||
'notification_quote_expired_subject' => 'Offerte :invoice is verlopen voor :client',
|
||||
'notification_quote_expired' => 'De volgende Offerte :invoice voor klant :client en :amount is nu verlopen.',
|
||||
'notification_invoice_overdue_subject' => 'Invoice :invoice is overdue for :client',
|
||||
'notification_invoice_overdue' => 'The following Invoice :invoice for client :client and :amount is now overdue.',
|
||||
'auto_sync' => 'Automatisch synchroniseren',
|
||||
'refresh_accounts' => 'Ververs accounts',
|
||||
'upgrade_to_connect_bank_account' => 'Upgrade naar Enterprise om uw bankrekening te koppelen',
|
||||
@@ -4946,7 +4948,7 @@ E-mail: :email<b><br><b>',
|
||||
'here' => 'hier',
|
||||
'industry_Restaurant & Catering' => 'Restaurant & Horeca',
|
||||
'show_credits_table' => 'Credittabel tonen',
|
||||
'manual_payment' => 'Betalingshandleiding',
|
||||
'manual_payment' => 'Handmatige betaling',
|
||||
'tax_summary_report' => 'Fiscaal overzichtsrapport',
|
||||
'tax_category' => 'Belastingcategorie',
|
||||
'physical_goods' => 'Fysieke goederen',
|
||||
@@ -5186,7 +5188,7 @@ E-mail: :email<b><br><b>',
|
||||
'step_authentication_fail' => 'U moet ten minste één van de authenticatiemethoden opnemen.',
|
||||
'auth.login' => 'Log in',
|
||||
'auth.login-or-register' => 'Log in of registreer',
|
||||
'auth.register' => 'Register',
|
||||
'auth.register' => 'Registreren',
|
||||
'cart' => 'Winkelwagen',
|
||||
'methods' => 'Methoden',
|
||||
'rff' => 'Verplichte velden formulier',
|
||||
@@ -5366,7 +5368,7 @@ E-mail: :email<b><br><b>',
|
||||
'step' => 'Stap',
|
||||
'peppol_whitelabel_warning' => 'Voor het gebruik van e-facturatie via het PEPPOL-netwerk is een whitelabellicentie vereist.',
|
||||
'peppol_plan_warning' => 'Voor het gebruik van e-facturatie via het PEPPOL-netwerk is een Enterprise-abonnement vereist.',
|
||||
'peppol_credits_info' => 'Ecredits zijn vereist om e-facturen te versturen en ontvangen. Deze worden per document in rekening gebracht.',
|
||||
'peppol_credits_info' => 'Ecredits are required to send and receive einvoices. These are charged on a per document basis. If you already have credits, click Continue.',
|
||||
'buy_credits' => 'Koop E-credits',
|
||||
'peppol_successfully_configured' => 'PEPPOL succesvol geconfigureerd.',
|
||||
'peppol_not_paid_message' => 'Enterprise-abonnement vereist voor PEPPOL. Upgrade uw abonnement.',
|
||||
@@ -5615,6 +5617,76 @@ E-mail: :email<b><br><b>',
|
||||
'tax_nexus' => 'Belastingnexus',
|
||||
'tax_period_report' => 'Belastingperioderapport',
|
||||
'creator' => 'Gemaakt door',
|
||||
'ses_topic_arn_help' => 'The SES topic (optional, only for webhook tracking)',
|
||||
'ses_region_help' => 'The AWS region, ie us-east-1',
|
||||
'ses_secret_key' => 'SES Secret Key',
|
||||
'ses_access_key' => 'SES Access Key ID',
|
||||
'activity_151' => 'Klant :notes is samengevoegd met :client door :user',
|
||||
'activity_152' => 'Leverancier :notes is samengevoegd met :vendor door :user',
|
||||
'activity_153' => 'Klant :notes verwijderd door :user',
|
||||
'lifecycle' => 'Lifecycle',
|
||||
'order_columns' => 'Sorteer kolommen',
|
||||
'topic_arn' => 'Topic ARN',
|
||||
'lang_Catalan' => 'Catalan',
|
||||
'lang_Afrikaans' => 'Afrikaans',
|
||||
'lang_Indonesian' => 'Indonesian',
|
||||
'replaced' => 'Replaced',
|
||||
'ses_from_address' => 'SES From Address',
|
||||
'ses_from_address_help' => 'The Sending Email Address, must be verified in AWS',
|
||||
'unauthorized_action' => 'U bent niet gemachtigd om deze actie uit te voeren.',
|
||||
'einvoice_received_subject' => 'E-Invoice/s Received',
|
||||
'einvoice_received_body' => 'You have received :count new E-Invoice/s.<br><br>Login to view.',
|
||||
'download_files_too_large' => 'Some files were too large to attach directly to the email. Please use the links below to download these individually.',
|
||||
'restore_disabled_verifactu' => 'You cannot restore an invoice once it has been deleted',
|
||||
'delete_disabled_verifactu' => 'You cannot delete an invoice once it has been cancelled or modified',
|
||||
'rectify' => 'Rectificar',
|
||||
'verifactu_invoice_send_success' => 'Invoice :invoice for :client sent to AEAT successfully',
|
||||
'verifactu_invoice_sent_failure' => 'Invoice :invoice for :client failed to send to AEAT :notes',
|
||||
'verifactu_cancellation_send_success' => 'Invoice cancellation for :invoice sent to AEAT successfully',
|
||||
'verifactu_cancellation_send_failure' => 'Invoice cancellation for :invoice failed to send to AEAT :notes',
|
||||
'verifactu' => 'Verifactu',
|
||||
'justify' => 'Justify',
|
||||
'outdent' => 'Outdent',
|
||||
'indent' => 'Indent',
|
||||
'clear_filters' => 'Clear Filters',
|
||||
'feedback' => 'Feedback',
|
||||
'feedback_modal_description' => 'We would love to hear your feedback!',
|
||||
'do_not_ask_again' => 'Do not ask again',
|
||||
'not_likely' => 'Not likely',
|
||||
'extremely_likely' => 'Extremely likely',
|
||||
'feedback_slider_title' => 'How likely are you to recommend Invoice Ninja to a friend or colleague?',
|
||||
'actual_delivery_date' => 'Actual Delivery Date',
|
||||
'actual_delivery_date_help' => 'Sometimes required when billing across borders. Defines the EXACT date of delivery of goods.',
|
||||
'invoice_period' => 'Invoice Period',
|
||||
'invoice_period_help' => 'Defines the time period for which the services were provided.',
|
||||
'paused_recurring_invoice_helper' => 'Caution! When restarting a recurring invoice, ensure the next send date is in the future.',
|
||||
'thank_you_for_feedback' => 'Thank you for your feedback!',
|
||||
'use_legacy_editor' => 'Use Legacy Wysiwyg Editor',
|
||||
'use_legacy_editor_help' => 'Use the TinyMCE editor.',
|
||||
'enable_e_invoice_received_notification' => 'Enable E-Invoice Received Notification',
|
||||
'enable_e_invoice_received_notification_help' => 'Receive an email notification when a new E-Invoice is received.',
|
||||
'price_changes' => 'Plan Price Changes from January 1st 2026',
|
||||
'notification_quote_rejected_subject' => 'Offerte :quote werd afgewezen door :client',
|
||||
'notification_quote_rejected' => 'The following client :client rejected Quote :quote for :amount :notes.',
|
||||
'activity_150' => 'Account verwijderd :notes',
|
||||
'activity_151' => 'Klant :notes is samengevoegd met :client door :user',
|
||||
'activity_152' => 'Leverancier :notes is samengevoegd met :vendor door :user',
|
||||
'activity_153' => 'Klant :notes verwijderd door :user',
|
||||
'activity_154' => 'E-Invoice :invoice for :client sent to AEAT successfully',
|
||||
'activity_155' => 'E-Invoice :invoice for :client failed to send to AEAT :notes',
|
||||
'activity_156' => 'Invoice cancellation for :invoice sent to AEAT successfully',
|
||||
'activity_157' => 'Invoice cancellation for :invoice failed to send to AEAT :notes',
|
||||
'activity_158' => 'Quote :quote was rejected by :client :notes',
|
||||
'quotes_with_status_sent_can_be_rejected' => 'Only quotes with "Sent" status can be rejected.',
|
||||
'reject' => 'Reject',
|
||||
'rejected' => 'Rejected',
|
||||
'reject_quote' => 'Reject Quote',
|
||||
'reject_quote_confirmation' => 'Are you sure you want to reject this quote?',
|
||||
'reason' => 'Reden',
|
||||
'enter_reason' => 'Geef een reden op...',
|
||||
'notification_invoice_overdue_summary_subject' => 'Invoice Overdue Summary: :date',
|
||||
'notification_invoice_overdue_summary' => 'The following invoices are overdue:',
|
||||
'purge_user_confirmation' => 'Warning! This action will reassign all entities to the account owner and permanently delete the user across all companies and accounts. Are you sure you want to proceed?',
|
||||
);
|
||||
|
||||
return $lang;
|
||||
|
||||
@@ -4691,6 +4691,8 @@ $lang = array(
|
||||
'show_tasks_in_client_portal' => 'Hiển thị nhiệm vụ trong Cổng thông tin khách hàng',
|
||||
'notification_quote_expired_subject' => 'Báo giá :invoice đã hết hạn cho :client',
|
||||
'notification_quote_expired' => 'Báo giá :invoice sau đây dành cho khách hàng :client và :amount hiện đã hết hạn.',
|
||||
'notification_invoice_overdue_subject' => 'Hóa đơn :invoice quá hạn cho :client',
|
||||
'notification_invoice_overdue' => 'Hóa đơn :invoice cho khách hàng :client và :amount hiện đã quá hạn.',
|
||||
'auto_sync' => 'Tự động đồng bộ',
|
||||
'refresh_accounts' => 'Làm mới tài khoản',
|
||||
'upgrade_to_connect_bank_account' => 'Nâng cấp lên Enterprise để kết nối tài khoản ngân hàng của bạn',
|
||||
@@ -5635,7 +5637,6 @@ $lang = array(
|
||||
'einvoice_received_subject' => 'E- Hóa đơn /s Received',
|
||||
'einvoice_received_body' => 'Bạn đã nhận được :count mới E- Hóa đơn /s.<br><br> Đăng nhập đến Xem .',
|
||||
'download_files_too_large' => 'Một số tệp quá lớn đến không thể đính kèm trực tiếp đến email . Vui lòng sử dụng các liên kết bên dưới đến tải xuống từng tệp riêng lẻ.',
|
||||
|
||||
'restore_disabled_verifactu' => 'Bạn không thể Khôi phục Hóa đơn một khi nó đã bị đã xóa',
|
||||
'delete_disabled_verifactu' => 'Bạn không thể Xóa một Hóa đơn sau khi nó đã bị hủy hoặc sửa đổi',
|
||||
'rectify' => 'Rectificar',
|
||||
@@ -5644,10 +5645,6 @@ $lang = array(
|
||||
'verifactu_cancellation_send_success' => 'Hóa đơn hủy :invoice đã gửi đến AEAT Thành công',
|
||||
'verifactu_cancellation_send_failure' => 'Hóa đơn hủy cho :invoice không gửi đến đến AEAT :notes',
|
||||
'verifactu' => 'Verifactu',
|
||||
'activity_150' => 'tài khoản đã xóa :notes',
|
||||
'activity_151' => 'Khách hàng :notes merged into :client by :user',
|
||||
'activity_152' => 'Người bán :notes merged into :vendor by :user',
|
||||
'activity_153' => 'Khách hàng :notes bị :user thanh lọc',
|
||||
'justify' => 'Căn chỉnh',
|
||||
'outdent' => 'Lồi ra ngoài',
|
||||
'indent' => 'thụt lề',
|
||||
@@ -5666,6 +5663,30 @@ $lang = array(
|
||||
'thank_you_for_feedback' => 'Cảm ơn phản hồi của bạn!',
|
||||
'use_legacy_editor' => 'Sử dụng Legacy Wysiwyg Editor',
|
||||
'use_legacy_editor_help' => 'Sử dụng trình soạn thảo TinyMCE.',
|
||||
'enable_e_invoice_received_notification' => 'Enable E- Hóa đơn Đã nhận Thông báo',
|
||||
'enable_e_invoice_received_notification_help' => 'Nhận thông báo email khi nhận được Hóa đơn mới .',
|
||||
'price_changes' => 'Thay đổi giá gói dịch vụ từ ngày 1 tháng 1 năm 2026',
|
||||
'notification_quote_rejected_subject' => 'báo giá :quote đã bị từ chối bởi :client',
|
||||
'notification_quote_rejected' => 'khách hàng sau đây :client đã từ chối báo giá :quote cho :amount :notes .',
|
||||
'activity_150' => 'tài khoản đã xóa :notes',
|
||||
'activity_151' => 'Khách hàng :notes merged into :client by :user',
|
||||
'activity_152' => 'Người bán :notes merged into :vendor by :user',
|
||||
'activity_153' => 'Khách hàng :notes bị :user thanh lọc',
|
||||
'activity_154' => 'E- Hóa đơn :invoice for :client TXEND gửi đến AEAT Thành công',
|
||||
'activity_155' => 'E- Hóa đơn :invoice cho :client không đến được đến AEAT :notes',
|
||||
'activity_156' => 'Hóa đơn hủy :invoice đã gửi đến AEAT Thành công',
|
||||
'activity_157' => 'Hóa đơn hủy cho :invoice không đến được đến AEAT :notes',
|
||||
'activity_158' => 'báo giá :quote đã bị từ chối bởi :client :notes',
|
||||
'quotes_with_status_sent_can_be_rejected' => 'Chỉ Báo giá có trạng thái "Đã gửi" mới bị từ chối.',
|
||||
'reject' => 'Từ chối',
|
||||
'rejected' => 'Vật bị loại bỏ',
|
||||
'reject_quote' => 'Từ chối báo giá',
|
||||
'reject_quote_confirmation' => 'Bạn có chắc chắn muốn đến chối báo giá này không?',
|
||||
'reason' => 'Lý do',
|
||||
'enter_reason' => 'Nhập một lý do...',
|
||||
'notification_invoice_overdue_summary_subject' => 'Invoice Overdue Summary: :date',
|
||||
'notification_invoice_overdue_summary' => 'The following invoices are overdue:',
|
||||
'purge_user_confirmation' => 'Warning! This action will reassign all entities to the account owner and permanently delete the user across all companies and accounts. Are you sure you want to proceed?',
|
||||
);
|
||||
|
||||
return $lang;
|
||||
|
||||
@@ -30,6 +30,7 @@ parameters:
|
||||
- \Stripe\Collection
|
||||
reportUnmatchedIgnoredErrors: false
|
||||
ignoreErrors:
|
||||
- '#\Saxon\SaxonProcessor#'
|
||||
- '#Array has 2 duplicate keys with value#'
|
||||
- '#Call to an undefined method#'
|
||||
- '#makeHidden#'
|
||||
|
||||
@@ -178,7 +178,7 @@ input:checked ~ .dot {
|
||||
Enterprise Plan
|
||||
</h3>
|
||||
<p class="text-5xl font-bold text-center text-white" id="y_plan_price">
|
||||
$140
|
||||
$180
|
||||
</p>
|
||||
<p class="text-xs text-center uppercase text-white">
|
||||
yearly
|
||||
@@ -275,12 +275,12 @@ document.getElementById('handleProYearlyClick').addEventListener('click', functi
|
||||
});
|
||||
const price_map = new Map();
|
||||
//monthly
|
||||
price_map.set('7LDdwRb1YK', '$16');
|
||||
price_map.set('7LDdwRb1YK', '$18');
|
||||
price_map.set('MVyb8mdvAZ', '$32');
|
||||
price_map.set('WpmbkR5azJ', '$54');
|
||||
price_map.set('k8mepY2aMy', '$84');
|
||||
//yearly
|
||||
price_map.set('LYqaQWldnj', '$160');
|
||||
price_map.set('LYqaQWldnj', '$180');
|
||||
price_map.set('kQBeX6mbyK', '$320');
|
||||
price_map.set('GELe32Qd69', '$540');
|
||||
price_map.set('MVyb86oevA', '$840');
|
||||
|
||||
@@ -45,6 +45,44 @@ class CompanyGatewayApiTest extends TestCase
|
||||
Model::reguard();
|
||||
}
|
||||
|
||||
public function testCompanyGatewayIdsUpdateWhenAddingNewGateway()
|
||||
{
|
||||
$settings = $this->company->settings;
|
||||
$settings->company_gateway_ids = "Xe0Vjm5ybx,Xe00Aw9Lex,Xe0RpmK3Gb";
|
||||
$this->company->settings = $settings;
|
||||
$this->company->save();
|
||||
|
||||
$this->assertEquals("Xe0Vjm5ybx,Xe00Aw9Lex,Xe0RpmK3Gb", $this->company->getSetting('company_gateway_ids'));
|
||||
|
||||
$data = [
|
||||
'config' => 'random config',
|
||||
'gateway_key' => '3b6621f970ab18887c4f6dca78d3f8bb',
|
||||
];
|
||||
|
||||
/* POST */
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/company_gateways', $data);
|
||||
|
||||
$cg = $response->json();
|
||||
|
||||
$cg_id = $cg['data']['id'];
|
||||
|
||||
$this->assertNotNull($cg_id);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$company = $this->company->fresh();
|
||||
|
||||
$settings = $company->settings;
|
||||
|
||||
$this->assertCount(4, explode(',', $company->getSetting('company_gateway_ids')));
|
||||
|
||||
$this->assertStringContainsString($cg_id, $company->getSetting('company_gateway_ids'));
|
||||
|
||||
}
|
||||
|
||||
public function testBulkActions()
|
||||
{
|
||||
$cg = CompanyGatewayFactory::create($this->company->id, $this->user->id);
|
||||
|
||||
@@ -12,12 +12,10 @@
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Jobs\Invoice\CheckGatewayFee;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Invoice;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
|
||||
@@ -1134,7 +1134,7 @@ class ReportCsvGenerationTest extends TestCase
|
||||
|
||||
$csv = $response->body();
|
||||
|
||||
$reader = Reader::createFromString($csv);
|
||||
$reader = Reader::fromString($csv);
|
||||
$reader->setHeaderOffset(0);
|
||||
|
||||
$res = $reader->fetchColumnByName('Street');
|
||||
@@ -1983,7 +1983,7 @@ class ReportCsvGenerationTest extends TestCase
|
||||
$csv = $response->body();
|
||||
|
||||
|
||||
$reader = Reader::createFromString($csv);
|
||||
$reader = Reader::fromString($csv);
|
||||
$reader->setHeaderOffset(0);
|
||||
|
||||
$res = $reader->fetchColumnByName('Contact First Name');
|
||||
@@ -2014,7 +2014,7 @@ class ReportCsvGenerationTest extends TestCase
|
||||
|
||||
private function getFirstValueByColumn($csv, $column)
|
||||
{
|
||||
$reader = Reader::createFromString($csv);
|
||||
$reader = Reader::fromString($csv);
|
||||
$reader->setHeaderOffset(0);
|
||||
|
||||
$res = $reader->fetchColumnByName($column);
|
||||
|
||||
Reference in New Issue
Block a user