commit
90e825662e
|
|
@ -1 +1 @@
|
|||
5.12.21
|
||||
5.12.22
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?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\Events\Client;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class ClientWasMerged.
|
||||
*/
|
||||
class ClientWasMerged
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param string $mergeable_client
|
||||
* @param Client $client
|
||||
* @param Company $company
|
||||
* @param array $event_vars
|
||||
*/
|
||||
public function __construct(public string $mergeable_client, public Client $client, public Company $company, public array $event_vars)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?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\Events\Client;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class ClientWasMerged.
|
||||
*/
|
||||
class ClientWasPurged
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param string $purged_client
|
||||
* @param User $user
|
||||
* @param Company $company
|
||||
* @param array $event_vars
|
||||
*/
|
||||
public function __construct(public string $purged_client, public User $user, public Company $company, public array $event_vars)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?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\Events\Vendor;
|
||||
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class ClientWasMerged.
|
||||
*/
|
||||
class VendorWasMerged
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param string $mergeable_vendor
|
||||
* @param Vendor $vendor
|
||||
* @param Company $company
|
||||
* @param array $event_vars
|
||||
*/
|
||||
public function __construct(public string $mergeable_vendor, public Vendor $vendor, public Company $company, public array $event_vars)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -139,7 +139,7 @@ class LoginController extends BaseController
|
|||
->header('X-App-Version', config('ninja.app_version'))
|
||||
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||
}
|
||||
} elseif ($user->google_2fa_secret && !$request->has('one_time_password')) {
|
||||
} elseif (strlen($user->google_2fa_secret ?? '') > 2 && !$request->has('one_time_password')) {
|
||||
return response()
|
||||
->json(['message' => ctrans('texts.invalid_one_time_password')], 401)
|
||||
->header('X-App-Version', config('ninja.app_version'))
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ class TwilioController extends BaseController
|
|||
'+23',
|
||||
'+21',
|
||||
'+17152567760',
|
||||
'+93',
|
||||
'+85',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
|
|
@ -231,7 +233,7 @@ class TwilioController extends BaseController
|
|||
return response()->json(['message' => 'SMS verified'], 200);
|
||||
}
|
||||
|
||||
$user->google_2fa_secret = '';
|
||||
$user->google_2fa_secret = null;
|
||||
$user->sms_verification_code = '';
|
||||
$user->save();
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class TwoFactorController extends BaseController
|
|||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
if ($user->google_2fa_secret) {
|
||||
if (strlen($user->google_2fa_secret ?? '') > 2) {
|
||||
return response()->json(['message' => '2FA already enabled'], 400);
|
||||
} elseif (Ninja::isSelfHost()) {
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ class BlackListRule implements ValidationRule
|
|||
{
|
||||
/** Bad domains +/- disposable email domains */
|
||||
private array $blacklist = [
|
||||
"mailshan.com",
|
||||
"tabletship.com",
|
||||
"tiktook.lol",
|
||||
"0-mail.com",
|
||||
|
|
|
|||
|
|
@ -346,18 +346,13 @@ class NinjaMailerJob implements ShouldQueue
|
|||
$t = app('translator');
|
||||
$t->replace(Ninja::transformTranslations($this->nmo->settings));
|
||||
|
||||
if(Ninja::isHosted() && $this->nmo?->transport == 'default') {
|
||||
$this->mailer = config('mail.default');
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** Force free/trials onto specific mail driver */
|
||||
if ($this->nmo->settings->email_sending_method == 'default' && $this->company->account->isNewHostedAccount()) {
|
||||
if(Ninja::isHosted() && $this->nmo?->transport == 'default' && ($this->company->account->isNewHostedAccount() || !$this->company->account->isPaid())) {
|
||||
$this->mailer = 'mailgun';
|
||||
$this->setHostedMailgunMailer();
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
if (Ninja::isHosted() && $this->company->account->isPaid() && $this->nmo->settings->email_sending_method == 'default') {
|
||||
//check if outlook.
|
||||
|
||||
|
|
@ -399,10 +394,6 @@ class NinjaMailerJob implements ShouldQueue
|
|||
$this->mailer = 'mailgun';
|
||||
$this->setHostedMailgunMailer();
|
||||
return $this;
|
||||
case 'ses':
|
||||
$this->mailer = 'ses';
|
||||
$this->setHostedSesMailer();
|
||||
return $this;
|
||||
case 'gmail':
|
||||
$this->mailer = 'gmail';
|
||||
$this->setGmailMailer();
|
||||
|
|
@ -584,20 +575,6 @@ class NinjaMailerJob implements ShouldQueue
|
|||
|
||||
}
|
||||
|
||||
private function setHostedSesMailer()
|
||||
{
|
||||
|
||||
if (property_exists($this->nmo->settings, 'email_from_name') && strlen($this->nmo->settings->email_from_name) > 1) {
|
||||
$email_from_name = $this->nmo->settings->email_from_name;
|
||||
} else {
|
||||
$email_from_name = $this->company->present()->name();
|
||||
}
|
||||
|
||||
$this->nmo
|
||||
->mailable
|
||||
->from(config('services.ses.from.address'), $email_from_name);
|
||||
|
||||
}
|
||||
/**
|
||||
* Configures Mailgun using client supplied secret
|
||||
* as the Mailer
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
<?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\Listeners\Activity;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Activity;
|
||||
use App\Repositories\ActivityRepository;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use stdClass;
|
||||
|
||||
class ClientMergedActivity implements ShouldQueue
|
||||
{
|
||||
protected $activity_repo;
|
||||
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @param ActivityRepository $activity_repo
|
||||
*/
|
||||
public function __construct(ActivityRepository $activity_repo)
|
||||
{
|
||||
$this->activity_repo = $activity_repo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle($event)
|
||||
{
|
||||
MultiDB::setDb($event->company->db);
|
||||
|
||||
$client = $event->client;
|
||||
|
||||
$fields = new stdClass();
|
||||
|
||||
$user_id = array_key_exists('user_id', $event->event_vars)
|
||||
? $event->event_vars['user_id']
|
||||
: $event->client->user_id;
|
||||
|
||||
$fields->client_id = $client->id;
|
||||
$fields->user_id = $user_id;
|
||||
$fields->company_id = $client->company_id;
|
||||
$fields->activity_type_id = Activity::MERGE_CLIENT;
|
||||
$fields->notes = $event->mergeable_client;
|
||||
|
||||
$this->activity_repo->save($fields, $client, $event->event_vars);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<?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\Listeners\Activity;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Activity;
|
||||
use App\Repositories\ActivityRepository;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use stdClass;
|
||||
|
||||
class ClientPurgedActivity implements ShouldQueue
|
||||
{
|
||||
protected $activity_repo;
|
||||
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @param ActivityRepository $activity_repo
|
||||
*/
|
||||
public function __construct(ActivityRepository $activity_repo)
|
||||
{
|
||||
$this->activity_repo = $activity_repo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle($event)
|
||||
{
|
||||
MultiDB::setDb($event->company->db);
|
||||
|
||||
$fields = new stdClass();
|
||||
|
||||
$user_id = array_key_exists('user_id', $event->event_vars)
|
||||
? $event->event_vars['user_id']
|
||||
: $event->user->id;
|
||||
|
||||
$fields->user_id = $user_id;
|
||||
$fields->company_id = $event->company->id;
|
||||
$fields->activity_type_id = Activity::PURGE_CLIENT;
|
||||
$fields->notes = $event->purged_client;
|
||||
|
||||
$this->activity_repo->save($fields, $event->user, $event->event_vars);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<?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\Listeners\Activity;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Activity;
|
||||
use App\Repositories\ActivityRepository;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use stdClass;
|
||||
|
||||
class VendorMergedActivity implements ShouldQueue
|
||||
{
|
||||
protected $activity_repo;
|
||||
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @param ActivityRepository $activity_repo
|
||||
*/
|
||||
public function __construct(ActivityRepository $activity_repo)
|
||||
{
|
||||
$this->activity_repo = $activity_repo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle($event)
|
||||
{
|
||||
MultiDB::setDb($event->company->db);
|
||||
|
||||
$vendor = $event->vendor;
|
||||
|
||||
$fields = new stdClass();
|
||||
|
||||
$user_id = array_key_exists('user_id', $event->event_vars)
|
||||
? $event->event_vars['user_id']
|
||||
: $event->vendor->user_id;
|
||||
|
||||
$fields->vendor_id = $vendor->id;
|
||||
$fields->user_id = $user_id;
|
||||
$fields->company_id = $vendor->company_id;
|
||||
$fields->activity_type_id = Activity::MERGE_VENDOR;
|
||||
$fields->notes = $event->mergeable_vendor;
|
||||
|
||||
$this->activity_repo->save($fields, $vendor, $event->event_vars);
|
||||
}
|
||||
}
|
||||
|
|
@ -225,7 +225,7 @@ class RequiredClientInfo extends Component
|
|||
$hash = Cache::get(request()->input('hash'));
|
||||
|
||||
/** @var \App\Models\Invoice $invoice */
|
||||
$invoice = Invoice::find($this->decodePrimaryKey($hash['invoice_id']));
|
||||
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($hash['invoice_id']));
|
||||
|
||||
$this->invoice_terms = $invoice->terms;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -282,6 +282,13 @@ class Activity extends StaticModel
|
|||
|
||||
public const ACCOUNT_DELETED = 150;
|
||||
|
||||
public const MERGE_CLIENT = 151;
|
||||
|
||||
public const MERGE_VENDOR = 152;
|
||||
|
||||
public const PURGE_CLIENT = 153;
|
||||
|
||||
|
||||
protected $casts = [
|
||||
'is_system' => 'boolean',
|
||||
'updated_at' => 'timestamp',
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ class ACH implements MethodInterface, LivewireMethodInterface
|
|||
|
||||
private function processUnsuccessfulPayment($response)
|
||||
{
|
||||
$this->braintree->sendFailureMail($response->transaction->additionalProcessorResponse);
|
||||
$this->braintree->sendFailureMail($response->transaction->additionalProcessorResponse ?? 'Unknown error occurred');
|
||||
|
||||
$message = [
|
||||
'server_response' => $response,
|
||||
|
|
|
|||
|
|
@ -61,17 +61,22 @@ use App\Events\Task\TaskWasRestored;
|
|||
use App\Events\User\UserLoginFailed;
|
||||
use App\Events\User\UserWasArchived;
|
||||
use App\Events\User\UserWasRestored;
|
||||
use App\Listeners\LogRequestSending;
|
||||
use App\Events\Quote\QuoteWasCreated;
|
||||
use App\Events\Quote\QuoteWasDeleted;
|
||||
use App\Events\Quote\QuoteWasEmailed;
|
||||
use App\Events\Quote\QuoteWasUpdated;
|
||||
use App\Events\Account\AccountCreated;
|
||||
use App\Events\Account\AccountDeleted;
|
||||
use App\Events\Client\ClientWasMerged;
|
||||
use App\Events\Client\ClientWasPurged;
|
||||
use App\Events\Credit\CreditWasViewed;
|
||||
use App\Events\Invoice\InvoiceWasPaid;
|
||||
use App\Events\Quote\QuoteWasApproved;
|
||||
use App\Events\Quote\QuoteWasArchived;
|
||||
use App\Events\Quote\QuoteWasRestored;
|
||||
use App\Events\Vendor\VendorWasMerged;
|
||||
use App\Listeners\LogResponseReceived;
|
||||
use Illuminate\Queue\Events\JobFailed;
|
||||
use App\Events\Client\ClientWasCreated;
|
||||
use App\Events\Client\ClientWasDeleted;
|
||||
|
|
@ -166,12 +171,15 @@ use App\Listeners\Activity\TaskUpdatedActivity;
|
|||
use App\Listeners\Invoice\InvoiceEmailActivity;
|
||||
use App\Listeners\SendVerificationNotification;
|
||||
use App\Events\Credit\CreditWasEmailedAndFailed;
|
||||
use App\Listeners\Activity\ClientMergedActivity;
|
||||
use App\Listeners\Activity\ClientPurgedActivity;
|
||||
use App\Listeners\Activity\CreatedQuoteActivity;
|
||||
use App\Listeners\Activity\DeleteClientActivity;
|
||||
use App\Listeners\Activity\DeleteCreditActivity;
|
||||
use App\Listeners\Activity\QuoteUpdatedActivity;
|
||||
use App\Listeners\Activity\TaskArchivedActivity;
|
||||
use App\Listeners\Activity\TaskRestoredActivity;
|
||||
use App\Listeners\Activity\VendorMergedActivity;
|
||||
use App\Listeners\Credit\CreditRestoredActivity;
|
||||
use App\Listeners\Invoice\CreateInvoiceActivity;
|
||||
use App\Listeners\Invoice\InvoiceViewedActivity;
|
||||
|
|
@ -194,6 +202,7 @@ use App\Listeners\Payment\PaymentBalanceActivity;
|
|||
use App\Listeners\Payment\PaymentEmailedActivity;
|
||||
use App\Listeners\Quote\QuoteCreatedNotification;
|
||||
use App\Listeners\Quote\QuoteEmailedNotification;
|
||||
use Illuminate\Http\Client\Events\RequestSending;
|
||||
use App\Events\Invoice\InvoiceWasEmailedAndFailed;
|
||||
use App\Events\Payment\PaymentWasEmailedAndFailed;
|
||||
use App\Listeners\Activity\ArchivedClientActivity;
|
||||
|
|
@ -212,6 +221,8 @@ use App\Listeners\Invoice\InvoiceRestoredActivity;
|
|||
use App\Listeners\Invoice\InvoiceReversedActivity;
|
||||
use App\Listeners\Payment\PaymentRestoredActivity;
|
||||
use App\Listeners\Quote\QuoteApprovedNotification;
|
||||
use SocialiteProviders\Apple\AppleExtendSocialite;
|
||||
use SocialiteProviders\Manager\SocialiteWasCalled;
|
||||
use App\Events\Subscription\SubscriptionWasCreated;
|
||||
use App\Events\Subscription\SubscriptionWasDeleted;
|
||||
use App\Events\Subscription\SubscriptionWasUpdated;
|
||||
|
|
@ -223,6 +234,7 @@ use App\Listeners\Credit\CreditCreatedNotification;
|
|||
use App\Listeners\Credit\CreditEmailedNotification;
|
||||
use App\Listeners\Invoice\InvoiceCancelledActivity;
|
||||
use App\Listeners\Quote\QuoteReminderEmailActivity;
|
||||
use Illuminate\Http\Client\Events\ResponseReceived;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasViewed;
|
||||
use App\Events\Subscription\SubscriptionWasArchived;
|
||||
use App\Events\Subscription\SubscriptionWasRestored;
|
||||
|
|
@ -238,6 +250,7 @@ use App\Listeners\Statement\StatementEmailedActivity;
|
|||
use App\Events\PurchaseOrder\PurchaseOrderWasAccepted;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasArchived;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasRestored;
|
||||
use App\Listeners\Payment\PaymentEmailFailureActivity;
|
||||
use App\Listeners\Vendor\UpdateVendorContactLastLogin;
|
||||
use App\Events\RecurringQuote\RecurringQuoteWasCreated;
|
||||
use App\Events\RecurringQuote\RecurringQuoteWasDeleted;
|
||||
|
|
@ -254,6 +267,7 @@ use App\Listeners\Activity\SubscriptionRestoredActivity;
|
|||
use App\Listeners\Invoice\InvoiceAutoBillFailedActivity;
|
||||
use App\Listeners\Invoice\InvoiceAutoBillSuccessActivity;
|
||||
use App\Listeners\Invoice\InvoiceFailedEmailNotification;
|
||||
use SocialiteProviders\Microsoft\MicrosoftExtendSocialite;
|
||||
use App\Events\RecurringExpense\RecurringExpenseWasCreated;
|
||||
use App\Events\RecurringExpense\RecurringExpenseWasDeleted;
|
||||
use App\Events\RecurringExpense\RecurringExpenseWasUpdated;
|
||||
|
|
@ -385,6 +399,12 @@ class EventServiceProvider extends ServiceProvider
|
|||
ClientWasRestored::class => [
|
||||
RestoreClientActivity::class,
|
||||
],
|
||||
ClientWasMerged::class => [
|
||||
ClientMergedActivity::class,
|
||||
],
|
||||
ClientWasPurged::class => [
|
||||
ClientPurgedActivity::class,
|
||||
],
|
||||
// Documents
|
||||
DocumentWasCreated::class => [
|
||||
],
|
||||
|
|
@ -671,6 +691,9 @@ class EventServiceProvider extends ServiceProvider
|
|||
VendorContactLoggedIn::class => [
|
||||
UpdateVendorContactLastLogin::class,
|
||||
],
|
||||
VendorWasMerged::class => [
|
||||
VendorMergedActivity::class,
|
||||
],
|
||||
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
|
||||
// ... Manager won't register drivers that are not added to this listener.
|
||||
\SocialiteProviders\Apple\AppleExtendSocialite::class.'@handle',
|
||||
|
|
|
|||
|
|
@ -147,6 +147,17 @@ class ClientRepository extends BaseRepository
|
|||
public function purge($client)
|
||||
{
|
||||
|
||||
$purged_client = $client->present()->name();
|
||||
$purged_client_hash = $client->client_hash;
|
||||
|
||||
$user = auth()->user() ?? $client->user;
|
||||
$company = $client->company;
|
||||
|
||||
$event_vars = \App\Utils\Ninja::eventVars(auth()->user() ? auth()->user()->id : null);
|
||||
$event_vars['client_hash'] = $purged_client_hash;
|
||||
|
||||
event(new \App\Events\Client\ClientWasPurged($purged_client, $user, $company, $event_vars));
|
||||
|
||||
nlog("Purging client id => {$client->id} => {$client->number}");
|
||||
|
||||
$client->contacts()->forceDelete();
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ class Merge extends AbstractService
|
|||
nlog("balance pre {$this->client->balance}");
|
||||
nlog("paid_to_date pre {$this->client->paid_to_date}");
|
||||
|
||||
$mergeable_client = $this->mergable_client->present()->name();
|
||||
|
||||
$this->client->balance += $this->mergable_client->balance;
|
||||
$this->client->paid_to_date += $this->mergable_client->paid_to_date;
|
||||
$this->client->save();
|
||||
|
|
@ -43,6 +45,9 @@ class Merge extends AbstractService
|
|||
nlog("balance post {$this->client->balance}");
|
||||
nlog("paid_to_date post {$this->client->paid_to_date}");
|
||||
|
||||
$event_vars = \App\Utils\Ninja::eventVars(auth()->user() ? auth()->user()->id : null);
|
||||
$event_vars['client_hash'] = $this->mergable_client->client_hash;
|
||||
|
||||
$this->updateLedger($this->mergable_client->balance);
|
||||
|
||||
$this->mergable_client->activities()->update(['client_id' => $this->client->id]);
|
||||
|
|
@ -71,11 +76,14 @@ class Merge extends AbstractService
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
$this->mergable_client->forceDelete();
|
||||
|
||||
$this->client->credit_balance = $this->client->service()->getCreditBalance();
|
||||
$this->client->saveQuietly();
|
||||
|
||||
event(new \App\Events\Client\ClientWasMerged($mergeable_client, $this->client, $this->client->company, $event_vars));
|
||||
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ class OrderXDocument extends AbstractService
|
|||
} elseif (in_array($this->document->client->country->iso_3166_2, ["ES-CE", "ES-ML"])) {
|
||||
$tax_type = OrderDutyTaxFeeCategories::TAX_FOR_PRODUCTION_SERVICES_AND_IMPORTATION_IN_CEUTA_AND_MELILLA;
|
||||
} else {
|
||||
nlog("Unkown tax case for xinvoice");
|
||||
// nlog("Unkown tax case for xinvoice");
|
||||
$tax_type = OrderDutyTaxFeeCategories::STANDARD_RATE;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -104,22 +104,22 @@ class ZugferdEDocument extends AbstractService
|
|||
|
||||
if ($this->document->custom_surcharge1 > 0) {
|
||||
$surcharge = $this->document->uses_inclusive_taxes ? ($this->document->custom_surcharge1 / (1 + ($item["tax_rate"] / 100))) : $this->document->custom_surcharge1;
|
||||
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"]);
|
||||
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"],null,null,null,null,null,null, ctrans('texts.surcharge'));
|
||||
}
|
||||
|
||||
if ($this->document->custom_surcharge2 > 0) {
|
||||
$surcharge = $this->document->uses_inclusive_taxes ? ($this->document->custom_surcharge2 / (1 + ($item["tax_rate"] / 100))) : $this->document->custom_surcharge2;
|
||||
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"]);
|
||||
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"],null,null,null,null,null,null, ctrans('texts.surcharge'));
|
||||
}
|
||||
|
||||
if ($this->document->custom_surcharge3 > 0) {
|
||||
$surcharge = $this->document->uses_inclusive_taxes ? ($this->document->custom_surcharge3 / (1 + ($item["tax_rate"] / 100))) : $this->document->custom_surcharge3;
|
||||
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"]);
|
||||
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"],null,null,null,null,null,null, ctrans('texts.surcharge'));
|
||||
}
|
||||
|
||||
if ($this->document->custom_surcharge4 > 0) {
|
||||
$surcharge = $this->document->uses_inclusive_taxes ? ($this->document->custom_surcharge4 / (1 + ($item["tax_rate"] / 100))) : $this->document->custom_surcharge4;
|
||||
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"]);
|
||||
$this->xdocument->addDocumentAllowanceCharge($surcharge, true, $tax_code, "VAT", $item["tax_rate"],null,null,null,null,null,null, ctrans('texts.surcharge'));
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
|
@ -147,7 +147,8 @@ class ZugferdEDocument extends AbstractService
|
|||
*/
|
||||
private function setDocumentTaxes(): self
|
||||
{
|
||||
if ($this->document->total_taxes == 0) {
|
||||
|
||||
if ((string) $this->document->total_taxes == '0') {
|
||||
|
||||
$base_amount = 0;
|
||||
$tax_amount = 0;
|
||||
|
|
@ -175,7 +176,8 @@ class ZugferdEDocument extends AbstractService
|
|||
false,
|
||||
$this->tax_code,
|
||||
"VAT",
|
||||
0
|
||||
0,
|
||||
null,null,null,null,null,null, ctrans('texts.discount')
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -215,7 +217,8 @@ class ZugferdEDocument extends AbstractService
|
|||
false,
|
||||
$this->getTaxType($item["tax_id"] ?? '2'),
|
||||
"VAT",
|
||||
$item["tax_rate"]
|
||||
$item["tax_rate"],
|
||||
null,null,null,null,null,null,ctrans('texts.discount')
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -260,6 +263,7 @@ class ZugferdEDocument extends AbstractService
|
|||
$item = $this->document->line_items[0] ?? null;
|
||||
|
||||
if (is_null($item)) {
|
||||
$this->tax_code = ZugferdDutyTaxFeeCategories::EXEMPT_FROM_TAX;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -270,7 +274,7 @@ class ZugferdEDocument extends AbstractService
|
|||
$this->tax_code = ZugferdDutyTaxFeeCategories::EXEMPT_FROM_TAX;
|
||||
// $this->exemption_reason_code = "VATEX-EU-NOT-TAX";
|
||||
$this->exemption_reason_code = "VATEX-EU-O";
|
||||
nlog("exemption_reason_code: {$this->exemption_reason_code}");
|
||||
// nlog("exemption_reason_code: {$this->exemption_reason_code}");
|
||||
} elseif ($item->tax_id == '9') { //reverse charge
|
||||
$this->tax_code = ZugferdDutyTaxFeeCategories::VAT_REVERSE_CHARGE;
|
||||
$this->exemption_reason_code = "VATEX-EU-AE";
|
||||
|
|
@ -587,7 +591,7 @@ class ZugferdEDocument extends AbstractService
|
|||
} elseif (in_array($this->document->client->country->iso_3166_2, ["ES-CE", "ES-ML"])) {
|
||||
$tax_type = ZugferdDutyTaxFeeCategories::TAX_FOR_PRODUCTION_SERVICES_AND_IMPORTATION_IN_CEUTA_AND_MELILLA;
|
||||
} else {
|
||||
nlog("Unkown tax case for xinvoice");
|
||||
// nlog("Unkown tax case for xinvoice");
|
||||
$tax_type = ZugferdDutyTaxFeeCategories::STANDARD_RATE;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -350,7 +350,7 @@ class ZugferdEDokument extends AbstractService
|
|||
} elseif (in_array($this->document->client->country->iso_3166_2, ["ES-CE", "ES-ML"])) {
|
||||
$tax_type = ZugferdDutyTaxFeeCategories::TAX_FOR_PRODUCTION_SERVICES_AND_IMPORTATION_IN_CEUTA_AND_MELILLA;
|
||||
} else {
|
||||
nlog("Unkown tax case for xinvoice");
|
||||
// nlog("Unkown tax case for xinvoice");
|
||||
$tax_type = ZugferdDutyTaxFeeCategories::STANDARD_RATE;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -546,19 +546,6 @@ class Email implements ShouldQueue
|
|||
|
||||
}
|
||||
|
||||
private function setHostedSesMailer()
|
||||
{
|
||||
|
||||
if (property_exists($this->email_object->settings, 'email_from_name') && strlen($this->email_object->settings->email_from_name) > 1) {
|
||||
$email_from_name = $this->email_object->settings->email_from_name;
|
||||
} else {
|
||||
$email_from_name = $this->company->present()->name();
|
||||
}
|
||||
|
||||
$this->mailable
|
||||
->from(config('services.ses.from.address'), $email_from_name);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mail driver to use and applies any specific configuration
|
||||
|
|
@ -568,7 +555,7 @@ class Email implements ShouldQueue
|
|||
{
|
||||
|
||||
/** Force free/trials onto specific mail driver */
|
||||
if ($this->email_object->settings->email_sending_method == 'default' && $this->company->account->isNewHostedAccount()) {
|
||||
if ($this->email_object->settings->email_sending_method == 'default' && (!$this->company->account->isPaid() || $this->company->account->isNewHostedAccount())) {
|
||||
$this->mailer = 'mailgun';
|
||||
$this->setHostedMailgunMailer();
|
||||
return $this;
|
||||
|
|
@ -614,10 +601,6 @@ class Email implements ShouldQueue
|
|||
$this->mailer = 'mailgun';
|
||||
$this->setHostedMailgunMailer();
|
||||
return $this;
|
||||
case 'ses':
|
||||
$this->mailer = 'ses';
|
||||
$this->setHostedSesMailer();
|
||||
return $this;
|
||||
case 'gmail':
|
||||
$this->mailer = 'gmail';
|
||||
$this->setGmailMailer();
|
||||
|
|
|
|||
|
|
@ -82,6 +82,11 @@ class PdfDesigner
|
|||
$html .= $partials['body'];
|
||||
$html .= $partials['footer'];
|
||||
|
||||
// Valid HTML is always required.
|
||||
if(strlen($html) == 0){
|
||||
return '<p></p>';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,10 +95,11 @@ class ProjectReport extends BaseExport
|
|||
|
||||
$ts = new TemplateService();
|
||||
|
||||
/** @var Project $_project */
|
||||
/** @var ?Project $_project */
|
||||
$_project = $query->first();
|
||||
|
||||
$currency_code = $_project ? $_project->company->currency()->code : $this->company->currency()->code;
|
||||
$currency_code = $_project?->client ? $_project->client->currency()->code : $this->company->currency()->code;
|
||||
|
||||
$ts_instance = $ts->setCompany($this->company)
|
||||
// ->setData($data)
|
||||
->processData($data)
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -33,6 +33,10 @@ class Merge extends AbstractService
|
|||
public function run()
|
||||
{
|
||||
|
||||
$_mergeable_vendor = $this->mergable_vendor->present()->name();
|
||||
$event_vars = \App\Utils\Ninja::eventVars(auth()->user() ? auth()->user()->id : null);
|
||||
$event_vars['vendor_hash'] = $this->mergable_vendor->vendor_hash;
|
||||
|
||||
$this->mergable_vendor->activities()->update(['vendor_id' => $this->vendor->id]);
|
||||
$this->mergable_vendor->contacts()->update(['vendor_id' => $this->vendor->id]);
|
||||
$this->mergable_vendor->credits()->update(['vendor_id' => $this->vendor->id]);
|
||||
|
|
@ -56,6 +60,8 @@ class Merge extends AbstractService
|
|||
|
||||
$this->mergable_vendor->forceDelete();
|
||||
|
||||
event(new \App\Events\Vendor\VendorWasMerged($_mergeable_vendor, $this->vendor, $this->vendor->company, $event_vars));
|
||||
|
||||
return $this->vendor;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,23 +36,21 @@ class PDF extends FPDI
|
|||
|
||||
// Calculate X position with offset
|
||||
$base_x = config('ninja.pdf_page_numbering_x_alignment');
|
||||
$x_position = $base_x + $this->x_offset;
|
||||
|
||||
// Set X position based on alignment
|
||||
if ($this->text_alignment == 'L') {
|
||||
$this->SetX($x_position);
|
||||
$this->SetX($this->GetX() + $base_x);
|
||||
// Adjust cell width to account for X offset
|
||||
$cell_width = $this->GetPageWidth() - $x_position - 10;
|
||||
$cell_width = $this->GetPageWidth();
|
||||
$this->Cell($cell_width, 5, $trans, 0, 0, 'L');
|
||||
} elseif ($this->text_alignment == 'R') {
|
||||
$this->SetX($x_position);
|
||||
$this->SetX($this->GetX() + $base_x);
|
||||
// For right alignment, calculate width from X position to right edge
|
||||
$cell_width = $this->GetPageWidth() - $x_position;
|
||||
$cell_width = $this->GetPageWidth();
|
||||
$this->Cell($cell_width, 5, $trans, 0, 0, 'R');
|
||||
} else {
|
||||
$this->SetX($x_position);
|
||||
// For center alignment, calculate appropriate width
|
||||
$cell_width = $this->GetPageWidth() - ($x_position * 2);
|
||||
$cell_width = $this->GetPageWidth();
|
||||
$this->Cell($cell_width, 5, $trans, 0, 0, 'C');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11410,16 +11410,16 @@
|
|||
},
|
||||
{
|
||||
"name": "sentry/sentry",
|
||||
"version": "4.15.0",
|
||||
"version": "4.15.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/getsentry/sentry-php.git",
|
||||
"reference": "b2d84de69f3eda8ca22b0b00e9f923be3b837355"
|
||||
"reference": "0d09baf3700869ec4b723c95eb466de56c3d74b6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/getsentry/sentry-php/zipball/b2d84de69f3eda8ca22b0b00e9f923be3b837355",
|
||||
"reference": "b2d84de69f3eda8ca22b0b00e9f923be3b837355",
|
||||
"url": "https://api.github.com/repos/getsentry/sentry-php/zipball/0d09baf3700869ec4b723c95eb466de56c3d74b6",
|
||||
"reference": "0d09baf3700869ec4b723c95eb466de56c3d74b6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -11483,7 +11483,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/getsentry/sentry-php/issues",
|
||||
"source": "https://github.com/getsentry/sentry-php/tree/4.15.0"
|
||||
"source": "https://github.com/getsentry/sentry-php/tree/4.15.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -11495,7 +11495,7 @@
|
|||
"type": "custom"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-20T14:26:37+00:00"
|
||||
"time": "2025-08-28T15:45:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sentry/sentry-laravel",
|
||||
|
|
@ -19338,16 +19338,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "11.5.34",
|
||||
"version": "11.5.35",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "3e4c6ef395f7cb61a6206c23e0e04b31724174f2"
|
||||
"reference": "d341ee94ee5007b286fc7907b383aae6b5b3cc91"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e4c6ef395f7cb61a6206c23e0e04b31724174f2",
|
||||
"reference": "3e4c6ef395f7cb61a6206c23e0e04b31724174f2",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d341ee94ee5007b286fc7907b383aae6b5b3cc91",
|
||||
"reference": "d341ee94ee5007b286fc7907b383aae6b5b3cc91",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -19361,7 +19361,7 @@
|
|||
"phar-io/manifest": "^2.0.4",
|
||||
"phar-io/version": "^3.2.1",
|
||||
"php": ">=8.2",
|
||||
"phpunit/php-code-coverage": "^11.0.10",
|
||||
"phpunit/php-code-coverage": "^11.0.11",
|
||||
"phpunit/php-file-iterator": "^5.1.0",
|
||||
"phpunit/php-invoker": "^5.0.1",
|
||||
"phpunit/php-text-template": "^4.0.1",
|
||||
|
|
@ -19419,7 +19419,7 @@
|
|||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.34"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.35"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -19443,7 +19443,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-20T14:41:45+00:00"
|
||||
"time": "2025-08-28T05:13:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "react/cache",
|
||||
|
|
@ -20947,16 +20947,16 @@
|
|||
},
|
||||
{
|
||||
"name": "spatie/backtrace",
|
||||
"version": "1.8.0",
|
||||
"version": "1.8.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/spatie/backtrace.git",
|
||||
"reference": "1607d8870bf597fc4ad79a6945cf0b2e584c2669"
|
||||
"reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/spatie/backtrace/zipball/1607d8870bf597fc4ad79a6945cf0b2e584c2669",
|
||||
"reference": "1607d8870bf597fc4ad79a6945cf0b2e584c2669",
|
||||
"url": "https://api.github.com/repos/spatie/backtrace/zipball/8c0f16a59ae35ec8c62d85c3c17585158f430110",
|
||||
"reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -20995,7 +20995,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/spatie/backtrace/issues",
|
||||
"source": "https://github.com/spatie/backtrace/tree/1.8.0"
|
||||
"source": "https://github.com/spatie/backtrace/tree/1.8.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -21007,7 +21007,7 @@
|
|||
"type": "other"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-25T16:16:45+00:00"
|
||||
"time": "2025-08-26T08:22:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/error-solutions",
|
||||
|
|
|
|||
|
|
@ -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.21'),
|
||||
'app_tag' => env('APP_TAG', '5.12.21'),
|
||||
'app_version' => env('APP_VERSION', '5.12.22'),
|
||||
'app_tag' => env('APP_TAG', '5.12.22'),
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', false),
|
||||
|
|
@ -257,7 +257,7 @@ return [
|
|||
'storecove_email_catchall' => env('STORECOVE_CATCHALL_EMAIL',false),
|
||||
'qvalia_api_key' => env('QVALIA_API_KEY', false),
|
||||
'qvalia_partner_number' => env('QVALIA_PARTNER_NUMBER', false),
|
||||
'pdf_page_numbering_x_alignment' => env('PDF_PAGE_NUMBER_X', 0),
|
||||
'pdf_page_numbering_x_alignment' => env('PDF_PAGE_NUMBER_X', -10),
|
||||
'pdf_page_numbering_y_alignment' => env('PDF_PAGE_NUMBER_Y', -6),
|
||||
'hosted_einvoice_secret' => env('HOSTED_EINVOICE_SECRET', null),
|
||||
'e_invoice_quota_warning' => env('E_INVOICE_QUOTA_WARNING', 15),
|
||||
|
|
|
|||
|
|
@ -5618,7 +5618,10 @@ $lang = array(
|
|||
'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'
|
||||
'ses_access_key' => 'SES Access Key ID',
|
||||
'activity_151' => 'Client :notes merged into :client by :user',
|
||||
'activity_152' => 'Vendor :notes merged into :vendor by :user',
|
||||
'activity_153' => 'Client :notes purged by :user',
|
||||
);
|
||||
|
||||
return $lang;
|
||||
|
|
|
|||
|
|
@ -415,7 +415,7 @@ Route::group(['middleware' => ['throttle:api', 'token_auth', 'valid_json','local
|
|||
Route::post('settings/disable_two_factor', [TwoFactorController::class, 'disableTwoFactor']);
|
||||
|
||||
Route::post('verify', [TwilioController::class, 'generate'])->name('verify.generate')->middleware('throttle:1,1');
|
||||
Route::post('verify/confirm', [TwilioController::class, 'confirm'])->name('verify.confirm');
|
||||
Route::post('verify/confirm', [TwilioController::class, 'confirm'])->name('verify.confirm')->middleware('throttle:2,1');
|
||||
|
||||
Route::resource('vendors', VendorController::class); // name = (vendors. index / create / show / update / destroy / edit
|
||||
Route::post('vendors/bulk', [VendorController::class, 'bulk'])->name('vendors.bulk');
|
||||
|
|
|
|||
|
|
@ -68,6 +68,10 @@ class ZugferdTest extends TestCase
|
|||
|
||||
private string $zug_16931 = 'Services/EDocument/Standards/Validation/Zugferd/zugferd_16931.xslt';
|
||||
|
||||
private string $zf_extended_wl = 'Services/EDocument/Standards/Validation/Zugferd/FACTUR-X_EXTENDED.xslt';
|
||||
|
||||
private string $extended_profile = 'XInvoice-Extended';
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
|
@ -96,7 +100,7 @@ class ZugferdTest extends TestCase
|
|||
$settings->classification = $params['company_classification'] ?? 'business';
|
||||
$settings->country_id = Country::where('iso_3166_2', 'DE')->first()->id;
|
||||
$settings->email = $this->faker->safeEmail();
|
||||
$settings->e_invoice_type = 'XInvoice_3_0';
|
||||
$settings->e_invoice_type = $params['e_invoice_type'] ?? 'XInvoice_3_0';
|
||||
$settings->currency_id = '3';
|
||||
$settings->name = 'Test Company';
|
||||
$settings->address1 = 'Line 1 of address of the seller';
|
||||
|
|
@ -255,6 +259,66 @@ class ZugferdTest extends TestCase
|
|||
|
||||
$this->assertCount(0, $validator->getErrors());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function testDeTodeTaxExemptExtendedProfile()
|
||||
{
|
||||
|
||||
$scenario = [
|
||||
'company_vat' => 'DE923356489',
|
||||
'company_country' => 'DE',
|
||||
'client_country' => 'DE',
|
||||
'client_vat' => 'DE923356488',
|
||||
'classification' => 'business',
|
||||
'has_valid_vat' => true,
|
||||
'over_threshold' => true,
|
||||
'legal_entity_id' => 290868,
|
||||
'e_invoice_type' => $this->extended_profile,
|
||||
];
|
||||
|
||||
$data = $this->setupTestData($scenario);
|
||||
|
||||
$invoice = $data['invoice'];
|
||||
|
||||
$repo = new InvoiceRepository();
|
||||
|
||||
foreach($this->inclusive_scenarios as $scenario){
|
||||
|
||||
$invoice_data = json_decode($scenario, true);
|
||||
|
||||
$line_items = $invoice_data['line_items'];
|
||||
|
||||
foreach ($line_items as &$item) {
|
||||
$item['tax_rate1'] = 0;
|
||||
$item['tax_name1'] = 'VAT';
|
||||
$item['tax_id'] = '5';
|
||||
}
|
||||
unset($item);
|
||||
|
||||
$invoice_data['line_items'] = array_values($line_items);
|
||||
|
||||
$invoice_data['uses_inclusive_taxes'] = false;
|
||||
|
||||
$invoice = $repo->save($invoice_data, $invoice);
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
|
||||
$xml = $invoice->service()->getEInvoice();
|
||||
|
||||
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
|
||||
$validator->setStyleSheets([$this->zf_extended_wl]);
|
||||
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
|
||||
$validator->validate();
|
||||
|
||||
if (count($validator->getErrors()) > 0) {
|
||||
|
||||
nlog($invoice->withoutRelations()->toArray());
|
||||
nlog($xml);
|
||||
nlog($validator->getErrors());
|
||||
}
|
||||
|
||||
$this->assertCount(0, $validator->getErrors());
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -585,6 +649,7 @@ class ZugferdTest extends TestCase
|
|||
{
|
||||
|
||||
$zug_16931 = 'Services/EDocument/Standards/Validation/Zugferd/zugferd_16931.xslt';
|
||||
// $xr_cii = 'Services/EDocument/Standards/Validation/Zugferd/xrechnung_cii.xslt';
|
||||
|
||||
// $zug_16931 = 'Services/EDocument/Standards/Validation/Zugferd/FACTUR-X_MINIMUM.xslt';
|
||||
|
||||
|
|
@ -621,6 +686,8 @@ class ZugferdTest extends TestCase
|
|||
|
||||
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
|
||||
$validator->setStyleSheets([$this->zug_16931]);
|
||||
|
||||
|
||||
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
|
||||
$validator->validate();
|
||||
|
||||
|
|
@ -684,5 +751,399 @@ class ZugferdTest extends TestCase
|
|||
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// EXTENDED PROFILE TEST METHODS - Duplicates using extended profile and XSLT
|
||||
// ============================================================================
|
||||
|
||||
public function testDeToNlReverseTaxExtendedProfile()
|
||||
{
|
||||
|
||||
$scenario = [
|
||||
'company_vat' => 'DE923356489',
|
||||
'company_country' => 'DE',
|
||||
'client_country' => 'NL',
|
||||
'client_vat' => 'NL808436332B01',
|
||||
'classification' => 'business',
|
||||
'has_valid_vat' => true,
|
||||
'over_threshold' => true,
|
||||
'legal_entity_id' => 290868,
|
||||
'e_invoice_type' => $this->extended_profile,
|
||||
];
|
||||
|
||||
$data = $this->setupTestData($scenario);
|
||||
|
||||
$invoice = $data['invoice'];
|
||||
|
||||
$repo = new InvoiceRepository();
|
||||
|
||||
foreach($this->inclusive_scenarios as $scenario){
|
||||
|
||||
$invoice_data = json_decode($scenario, true);
|
||||
|
||||
$line_items = $invoice_data['line_items'];
|
||||
|
||||
foreach ($line_items as &$item) {
|
||||
$item['tax_rate1'] = 0;
|
||||
$item['tax_name1'] = '';
|
||||
$item['tax_id'] = '9';
|
||||
}
|
||||
unset($item);
|
||||
|
||||
$invoice_data['line_items'] = array_values($line_items);
|
||||
|
||||
$invoice_data['uses_inclusive_taxes'] = false;
|
||||
|
||||
$invoice = $repo->save($invoice_data, $invoice);
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
|
||||
$xml = $invoice->service()->getEInvoice();
|
||||
|
||||
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
|
||||
$validator->setStyleSheets([$this->zf_extended_wl]);
|
||||
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
|
||||
$validator->validate();
|
||||
|
||||
if (count($validator->getErrors()) > 0) {
|
||||
|
||||
nlog($invoice->withoutRelations()->toArray());
|
||||
nlog($xml);
|
||||
nlog($validator->getErrors());
|
||||
}
|
||||
|
||||
$this->assertCount(0, $validator->getErrors());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function testInclusiveScenariosExtendedProfile()
|
||||
{
|
||||
$scenario = [
|
||||
'company_vat' => 'DE923356489',
|
||||
'company_country' => 'DE',
|
||||
'client_country' => 'DE',
|
||||
'client_vat' => 'DE923356488',
|
||||
'classification' => 'business',
|
||||
'has_valid_vat' => true,
|
||||
'over_threshold' => true,
|
||||
'legal_entity_id' => 290868,
|
||||
'e_invoice_type' => $this->extended_profile,
|
||||
];
|
||||
|
||||
$data = $this->setupTestData($scenario);
|
||||
|
||||
$invoice = $data['invoice'];
|
||||
|
||||
$repo = new InvoiceRepository();
|
||||
|
||||
foreach($this->inclusive_scenarios as $scenario){
|
||||
|
||||
$invoice_data = json_decode($scenario, true);
|
||||
|
||||
$invoice = $repo->save($invoice_data, $invoice);
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
|
||||
$xml = $invoice->service()->getEInvoice();
|
||||
|
||||
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
|
||||
$validator->setStyleSheets([$this->zf_extended_wl]);
|
||||
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
|
||||
$validator->validate();
|
||||
|
||||
if (count($validator->getErrors()) > 0) {
|
||||
|
||||
nlog($invoice->withoutRelations()->toArray());
|
||||
nlog($xml);
|
||||
nlog($validator->getErrors());
|
||||
}
|
||||
|
||||
$this->assertCount(0, $validator->getErrors());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function testExclusiveScenariosExtendedProfile()
|
||||
{
|
||||
$scenario = [
|
||||
'company_vat' => 'DE923356489',
|
||||
'company_country' => 'DE',
|
||||
'client_country' => 'DE',
|
||||
'client_vat' => 'DE923356488',
|
||||
'classification' => 'business',
|
||||
'has_valid_vat' => true,
|
||||
'over_threshold' => true,
|
||||
'legal_entity_id' => 290868,
|
||||
'e_invoice_type' => $this->extended_profile,
|
||||
];
|
||||
|
||||
$data = $this->setupTestData($scenario);
|
||||
|
||||
$invoice = $data['invoice'];
|
||||
|
||||
$repo = new InvoiceRepository();
|
||||
|
||||
foreach($this->inclusive_scenarios as $scenario){
|
||||
|
||||
$invoice_data = json_decode($scenario, true);
|
||||
|
||||
$invoice_data['uses_inclusive_taxes'] = false;
|
||||
|
||||
$invoice = $repo->save($invoice_data, $invoice);
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
|
||||
$xml = $invoice->service()->getEInvoice();
|
||||
|
||||
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
|
||||
$validator->setStyleSheets([$this->zf_extended_wl]);
|
||||
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
|
||||
$validator->validate();
|
||||
|
||||
if (count($validator->getErrors()) > 0) {
|
||||
|
||||
nlog($invoice->withoutRelations()->toArray());
|
||||
nlog($xml);
|
||||
nlog($validator->getErrors());
|
||||
}
|
||||
|
||||
$this->assertCount(0, $validator->getErrors());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function testZugFerdValidationExtendedProfile()
|
||||
{
|
||||
$scenario = [
|
||||
'company_vat' => 'DE923356489',
|
||||
'company_country' => 'DE',
|
||||
'client_country' => 'DE',
|
||||
'client_vat' => 'DE923356488',
|
||||
'classification' => 'business',
|
||||
'has_valid_vat' => true,
|
||||
'over_threshold' => true,
|
||||
'legal_entity_id' => 290868,
|
||||
'e_invoice_type' => $this->extended_profile,
|
||||
];
|
||||
|
||||
$data = $this->setupTestData($scenario);
|
||||
|
||||
$invoice = $data['invoice'];
|
||||
|
||||
$xml = $invoice->service()->getEInvoice();
|
||||
|
||||
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
|
||||
$validator->setStyleSheets([$this->zf_extended_wl]);
|
||||
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
|
||||
$validator->validate();
|
||||
|
||||
if (count($validator->getErrors()) > 0) {
|
||||
nlog($xml);
|
||||
nlog($validator->getErrors());
|
||||
}
|
||||
|
||||
$this->assertCount(0, $validator->getErrors());
|
||||
}
|
||||
|
||||
public function testZugFerdValidationWithInclusiveTaxesExtendedProfile()
|
||||
{
|
||||
$scenario = [
|
||||
'company_vat' => 'DE923356489',
|
||||
'company_country' => 'DE',
|
||||
'client_country' => 'DE',
|
||||
'client_vat' => 'DE923356488',
|
||||
'classification' => 'business',
|
||||
'has_valid_vat' => true,
|
||||
'over_threshold' => true,
|
||||
'legal_entity_id' => 290868,
|
||||
'e_invoice_type' => $this->extended_profile,
|
||||
];
|
||||
|
||||
$data = $this->setupTestData($scenario);
|
||||
|
||||
$invoice = $data['invoice'];
|
||||
$invoice->uses_inclusive_taxes = true;
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
|
||||
$xml = $invoice->service()->getEInvoice();
|
||||
|
||||
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
|
||||
$validator->setStyleSheets([$this->zf_extended_wl]);
|
||||
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
|
||||
$validator->validate();
|
||||
|
||||
if (count($validator->getErrors()) > 0) {
|
||||
nlog($xml);
|
||||
nlog($validator->getErrors());
|
||||
}
|
||||
|
||||
$this->assertCount(0, $validator->getErrors());
|
||||
}
|
||||
|
||||
public function testZugFerdValidationWithInclusiveTaxesAndTotalAmountDiscountExtendedProfile()
|
||||
{
|
||||
$scenario = [
|
||||
'company_vat' => 'DE923356489',
|
||||
'company_country' => 'DE',
|
||||
'client_country' => 'DE',
|
||||
'client_vat' => 'DE923356488',
|
||||
'classification' => 'business',
|
||||
'has_valid_vat' => true,
|
||||
'over_threshold' => true,
|
||||
'legal_entity_id' => 290868,
|
||||
'e_invoice_type' => $this->extended_profile,
|
||||
];
|
||||
|
||||
$data = $this->setupTestData($scenario);
|
||||
|
||||
$invoice = $data['invoice'];
|
||||
$invoice->uses_inclusive_taxes = true;
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
$invoice->discount=20;
|
||||
$invoice->is_amount_discount = true;
|
||||
|
||||
$xml = $invoice->service()->getEInvoice();
|
||||
|
||||
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
|
||||
$validator->setStyleSheets([$this->zf_extended_wl]);
|
||||
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
|
||||
$validator->validate();
|
||||
|
||||
if (count($validator->getErrors()) > 0) {
|
||||
nlog($xml);
|
||||
nlog($validator->getErrors());
|
||||
}
|
||||
|
||||
$this->assertCount(0, $validator->getErrors());
|
||||
}
|
||||
|
||||
public function testZugFerdValidationWithInclusiveTaxesAndTotalPercentDiscountExtendedProfile()
|
||||
{
|
||||
$scenario = [
|
||||
'company_vat' => 'DE923356489',
|
||||
'company_country' => 'DE',
|
||||
'client_country' => 'DE',
|
||||
'client_vat' => 'DE923356488',
|
||||
'classification' => 'business',
|
||||
'has_valid_vat' => true,
|
||||
'over_threshold' => true,
|
||||
'legal_entity_id' => 290868,
|
||||
'e_invoice_type' => $this->extended_profile,
|
||||
];
|
||||
|
||||
$data = $this->setupTestData($scenario);
|
||||
|
||||
$invoice = $data['invoice'];
|
||||
$invoice->uses_inclusive_taxes = true;
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
$invoice->discount=20;
|
||||
$invoice->is_amount_discount = false;
|
||||
|
||||
$xml = $invoice->service()->getEInvoice();
|
||||
|
||||
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
|
||||
$validator->setStyleSheets([$this->zf_extended_wl]);
|
||||
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
|
||||
$validator->validate();
|
||||
|
||||
if (count($validator->getErrors()) > 0) {
|
||||
nlog($xml);
|
||||
nlog($validator->getErrors());
|
||||
}
|
||||
|
||||
$this->assertCount(0, $validator->getErrors());
|
||||
}
|
||||
|
||||
public function testZugFerdValidationWithInclusiveTaxesAndTotalPercentDiscountOnLineItemsAlsoExtendedProfile()
|
||||
{
|
||||
$scenario = [
|
||||
'company_vat' => 'DE923356489',
|
||||
'company_country' => 'DE',
|
||||
'client_country' => 'DE',
|
||||
'client_vat' => 'DE923356488',
|
||||
'classification' => 'business',
|
||||
'has_valid_vat' => true,
|
||||
'over_threshold' => true,
|
||||
'legal_entity_id' => 290868,
|
||||
'e_invoice_type' => $this->extended_profile,
|
||||
];
|
||||
|
||||
$data = $this->setupTestData($scenario);
|
||||
|
||||
$invoice = $data['invoice'];
|
||||
$invoice->uses_inclusive_taxes = true;
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
$invoice->discount=20;
|
||||
$invoice->is_amount_discount = false;
|
||||
|
||||
$items = $invoice->line_items;
|
||||
|
||||
foreach($items as &$item){
|
||||
$item->discount=10;
|
||||
$item->is_amount_discount = false;
|
||||
}
|
||||
unset($item);
|
||||
|
||||
$invoice->line_items = $items;
|
||||
|
||||
$xml = $invoice->service()->getEInvoice();
|
||||
|
||||
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
|
||||
$validator->setStyleSheets([$this->zf_extended_wl]);
|
||||
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
|
||||
$validator->validate();
|
||||
|
||||
if (count($validator->getErrors()) > 0) {
|
||||
nlog($xml);
|
||||
nlog($validator->getErrors());
|
||||
}
|
||||
|
||||
$this->assertCount(0, $validator->getErrors());
|
||||
}
|
||||
|
||||
public function testZugFerdValidationWithInclusiveTaxesAndTotalAmountDiscountOnLineItemsAlsoExtendedProfile()
|
||||
{
|
||||
$scenario = [
|
||||
'company_vat' => 'DE923356489',
|
||||
'company_country' => 'DE',
|
||||
'client_country' => 'DE',
|
||||
'client_vat' => 'DE923356488',
|
||||
'classification' => 'business',
|
||||
'has_valid_vat' => true,
|
||||
'over_threshold' => true,
|
||||
'legal_entity_id' => 290868,
|
||||
'e_invoice_type' => $this->extended_profile,
|
||||
];
|
||||
|
||||
$data = $this->setupTestData($scenario);
|
||||
|
||||
$invoice = $data['invoice'];
|
||||
$invoice->uses_inclusive_taxes = true;
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
$invoice->discount=20;
|
||||
$invoice->is_amount_discount = true;
|
||||
|
||||
$items = $invoice->line_items;
|
||||
|
||||
foreach($items as &$item){
|
||||
$item->discount=5;
|
||||
$item->is_amount_discount = true;
|
||||
}
|
||||
unset($item);
|
||||
|
||||
$invoice->line_items = $items;
|
||||
|
||||
$xml = $invoice->service()->getEInvoice();
|
||||
|
||||
$validator = new \App\Services\EDocument\Standards\Validation\XsltDocumentValidator($xml);
|
||||
$validator->setStyleSheets([$this->zf_extended_wl]);
|
||||
$validator->setXsd('/Services/EDocument/Standards/Validation/Zugferd/Schema/XSD/CrossIndustryInvoice_100pD22B.xsd');
|
||||
$validator->validate();
|
||||
|
||||
if (count($validator->getErrors()) > 0) {
|
||||
nlog($xml);
|
||||
nlog($validator->getErrors());
|
||||
}
|
||||
|
||||
$this->assertCount(0, $validator->getErrors());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue