mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-08 20:22:42 +01:00
commit
960a1d858a
@ -21,7 +21,13 @@ class TaxModel
|
||||
|
||||
/** @var object $regions */
|
||||
public object $regions;
|
||||
|
||||
|
||||
/** @var bool $act_as_sender */
|
||||
public bool $act_as_sender = false;
|
||||
|
||||
/** @var bool $act_as_receiver */
|
||||
public bool $act_as_receiver = false;
|
||||
|
||||
/**
|
||||
* __construct
|
||||
*
|
||||
@ -34,16 +40,17 @@ class TaxModel
|
||||
if(!$model) {
|
||||
$this->regions = $this->init();
|
||||
} else {
|
||||
|
||||
if(is_null($model->seller_subregion)) {//@phpstan-ignore-line
|
||||
$this->seller_subregion = '';
|
||||
}
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
foreach($model as $key => $value) {
|
||||
|
||||
$this->seller_subregion = $model->seller_subregion ?? '';
|
||||
$this->act_as_sender = $model->act_as_sender ?? false;
|
||||
$this->act_as_receiver = $model->act_as_receiver ?? false;
|
||||
|
||||
$modelArray = get_object_vars($model);
|
||||
foreach ($modelArray as $key => $value) {
|
||||
$this->{$key} = $value;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
$this->migrate();
|
||||
|
@ -11,31 +11,101 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\EInvoice\Peppol\CreateRequest;
|
||||
use App\Http\Requests\EInvoice\Peppol\DisconnectRequest;
|
||||
use App\Services\EDocument\Gateway\Storecove\Storecove;
|
||||
use Illuminate\Http\Response;
|
||||
use App\Http\Requests\EInvoice\Peppol\StoreEntityRequest;
|
||||
use App\Services\EDocument\Gateway\Storecove\Storecove;
|
||||
use App\Http\Requests\EInvoice\Peppol\DisconnectRequest;
|
||||
use App\Http\Requests\EInvoice\Peppol\AddTaxIdentifierRequest;
|
||||
use App\Http\Requests\EInvoice\Peppol\ShowEntityRequest;
|
||||
use App\Http\Requests\EInvoice\Peppol\UpdateEntityRequest;
|
||||
|
||||
class EInvoicePeppolController extends BaseController
|
||||
{
|
||||
public function setup(CreateRequest $request, Storecove $storecove): Response
|
||||
{
|
||||
/**
|
||||
* Returns the legal entity ID
|
||||
*
|
||||
*
|
||||
* [
|
||||
* "id" => 290868,
|
||||
* "party_name" => "Untitled Company",
|
||||
* "line1" => "Address 1",
|
||||
* "line2" => "Address 2",
|
||||
* "zip" => "Postal Code",
|
||||
* "city" => "City",
|
||||
* "county" => "State",
|
||||
* "country" => "DE",
|
||||
* "tenant_id" => "EbRYYRWO7oUJE3G3jVa4Xddf6gHGI6kD",
|
||||
* "public" => true,
|
||||
* "acts_as_sender" => true,
|
||||
* "acts_as_receiver" => true,
|
||||
* "tax_registered" => true,
|
||||
* "peppol_identifiers" => [
|
||||
* [
|
||||
* "superscheme" => "iso6523-actorid-upis",
|
||||
* "scheme" => "DE:VAT",
|
||||
* "identifier" => "DE923356489",
|
||||
* "networks" => [
|
||||
* "peppol",
|
||||
* ],
|
||||
* "corppass_enabled" => false,
|
||||
* ],
|
||||
* ],
|
||||
* "administrations" => [],
|
||||
* "advertisements" => [
|
||||
* "invoice",
|
||||
* ],
|
||||
* "smart_inbox" => "a4p2q0@receive.storecove.com",
|
||||
* "api_keys" => [],
|
||||
* "additional_tax_identifiers" => [
|
||||
* [
|
||||
* "id" => 264566,
|
||||
* "legal_entity_id" => 290868,
|
||||
* "country" => null,
|
||||
* "county" => null,
|
||||
* "identifier" => "ATU73769157",
|
||||
* "superscheme" => "iso6523-actorid-upis",
|
||||
* "scheme" => "AT:VAT",
|
||||
* ],
|
||||
* ],
|
||||
* ]
|
||||
*
|
||||
*
|
||||
* @param ShowEntityRequest $request
|
||||
* @param Storecove $storecove
|
||||
* @return mixed
|
||||
*/
|
||||
public function show(ShowEntityRequest $request, Storecove $storecove)
|
||||
{
|
||||
$company = auth()->user()->company();
|
||||
|
||||
$response = $storecove->getLegalEntity($company->legal_entity_id);
|
||||
|
||||
return response()->json($response, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a legal entity id, response will be
|
||||
* the same as show()
|
||||
*
|
||||
* @param StoreEntityRequest $request
|
||||
* @param Storecove $storecove
|
||||
* @return Response
|
||||
*/
|
||||
public function setup(StoreEntityRequest $request, Storecove $storecove): Response
|
||||
{
|
||||
/**
|
||||
* @var \App\Models\Company
|
||||
*/
|
||||
$company = auth()->user()->company();
|
||||
|
||||
$data = [
|
||||
...$request->validated(),
|
||||
'country' => $request->country()->iso_3166_2,
|
||||
];
|
||||
$legal_entity_response = $storecove->createLegalEntity($request->validated(), $company);
|
||||
|
||||
$legal_entity_response = $storecove->createLegalEntity($data, $company);
|
||||
$scheme = $storecove->router->resolveRouting($request->country, $company->settings->classification);
|
||||
|
||||
$add_identifier_response = $storecove->addIdentifier(
|
||||
legal_entity_id: $legal_entity_response['id'],
|
||||
identifier: $company->settings->vat_number,
|
||||
scheme: $request->receiverIdentifier(),
|
||||
scheme: $scheme,
|
||||
);
|
||||
|
||||
if ($add_identifier_response) {
|
||||
@ -49,11 +119,62 @@ class EInvoicePeppolController extends BaseController
|
||||
|
||||
return response()->noContent(status: 422);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an additional tax identifier to
|
||||
* an existing legal entity id
|
||||
*
|
||||
* Response will be the same as show()
|
||||
*
|
||||
* @param AddTaxIdentifierRequest $request
|
||||
* @param Storecove $storecove
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function addAdditionalTaxIdentifier(AddTaxIdentifierRequest $request, Storecove $storecove): \Illuminate\Http\JsonResponse
|
||||
{
|
||||
|
||||
$company = auth()->user()->company();
|
||||
$tax_data = $company->tax_data;
|
||||
|
||||
public function disconnect(DisconnectRequest $request, Storecove $storecove): Response
|
||||
$additional_vat = $tax_data->regions->EU->subregions->{$request->country}->vat_number ?? null;
|
||||
|
||||
if (!is_null($additional_vat) && !empty($additional_vat)) {
|
||||
return response()->json(['message' => 'Identifier already exists for this region.'], 400);
|
||||
}
|
||||
|
||||
$scheme = $storecove->router->resolveRouting($request->country, $company->settings->classification);
|
||||
|
||||
$storecove->addAdditionalTaxIdentifier($company->legal_entity_id, $request->identifier, $scheme);
|
||||
|
||||
$tax_data->regions->EU->subregions->{$request->country}->vat_number = $request->identifier;
|
||||
$company->tax_data = $tax_data;
|
||||
$company->save();
|
||||
|
||||
return response()->json(['message' => 'ok'], 200);
|
||||
|
||||
}
|
||||
|
||||
public function updateLegalEntity(UpdateEntityRequest $request, Storecove $storecove)
|
||||
{
|
||||
|
||||
$company = auth()->user()->company();
|
||||
|
||||
$r = $storecove->updateLegalEntity($company->legal_entity_id, $request->validated());
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Removed the legal identity from the Peppol network
|
||||
*
|
||||
* @param DisconnectRequest $request
|
||||
* @param Storecove $storecove
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function disconnect(DisconnectRequest $request, Storecove $storecove): \Illuminate\Http\Response
|
||||
{
|
||||
/**
|
||||
* @var \App\Models\Company
|
||||
* @var \App\Models\Company $company
|
||||
*/
|
||||
$company = auth()->user()->company();
|
||||
|
||||
@ -63,6 +184,7 @@ class EInvoicePeppolController extends BaseController
|
||||
|
||||
if ($response) {
|
||||
$company->legal_entity_id = null;
|
||||
$company->tax_data = $this->unsetVatNumbers($company->tax_data);
|
||||
$company->save();
|
||||
|
||||
return response()->noContent();
|
||||
@ -73,4 +195,26 @@ class EInvoicePeppolController extends BaseController
|
||||
|
||||
return response()->noContent(status: 422);
|
||||
}
|
||||
|
||||
private function unsetVatNumbers(mixed $taxData): mixed
|
||||
{
|
||||
if (isset($taxData->regions->EU->subregions)) {
|
||||
foreach ($taxData->regions->EU->subregions as $country => $data) {
|
||||
if (isset($data->vat_number)) {
|
||||
$newData = new \stdClass();
|
||||
if (is_object($data)) {
|
||||
$dataArray = get_object_vars($data);
|
||||
foreach ($dataArray as $key => $value) {
|
||||
if ($key !== 'vat_number') {
|
||||
$newData->$key = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
$taxData->regions->EU->subregions->$country = $newData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $taxData;
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,36 @@ class UpdateCompanyRequest extends Request
|
||||
'portal_custom_css',
|
||||
'portal_custom_head'
|
||||
];
|
||||
|
||||
private array $vat_regex_patterns = [
|
||||
'DE' => '/^DE\d{9}$/',
|
||||
'AT' => '/^ATU\d{8}$/',
|
||||
'BE' => '/^BE0\d{9}$/',
|
||||
'BG' => '/^BG\d{9,10}$/',
|
||||
'CY' => '/^CY\d{8}L$/',
|
||||
'HR' => '/^HR\d{11}$/',
|
||||
'DK' => '/^DK\d{8}$/',
|
||||
'ES' => '/^ES[A-Z0-9]\d{7}[A-Z0-9]$/',
|
||||
'EE' => '/^EE\d{9}$/',
|
||||
'FI' => '/^FI\d{8}$/',
|
||||
'FR' => '/^FR\d{2}\d{9}$/',
|
||||
'EL' => '/^EL\d{9}$/',
|
||||
'HU' => '/^HU\d{8}$/',
|
||||
'IE' => '/^IE\d{7}[A-Z]{1,2}$/',
|
||||
'IT' => '/^IT\d{11}$/',
|
||||
'LV' => '/^LV\d{11}$/',
|
||||
'LT' => '/^LT(\d{9}|\d{12})$/',
|
||||
'LU' => '/^LU\d{8}$/',
|
||||
'MT' => '/^MT\d{8}$/',
|
||||
'NL' => '/^NL\d{9}B\d{2}$/',
|
||||
'PL' => '/^PL\d{10}$/',
|
||||
'PT' => '/^PT\d{9}$/',
|
||||
'CZ' => '/^CZ\d{8,10}$/',
|
||||
'RO' => '/^RO\d{2,10}$/',
|
||||
'SK' => '/^SK\d{10}$/',
|
||||
'SI' => '/^SI\d{8}$/',
|
||||
'SE' => '/^SE\d{12}$/',
|
||||
];
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
@ -86,6 +116,24 @@ class UpdateCompanyRequest extends Request
|
||||
$rules['inbound_mailbox_whitelist'] = ['sometimes', 'string', 'nullable', 'regex:/^[\w\-\.\+]+@([\w-]+\.)+[\w-]{2,4}(,[\w\-\.\+]+@([\w-]+\.)+[\w-]{2,4})*$/'];
|
||||
$rules['inbound_mailbox_blacklist'] = ['sometimes', 'string', 'nullable', 'regex:/^[\w\-\.\+]+@([\w-]+\.)+[\w-]{2,4}(,[\w\-\.\+]+@([\w-]+\.)+[\w-]{2,4})*$/'];
|
||||
|
||||
$rules['settings.vat_number'] = [
|
||||
'nullable',
|
||||
'string',
|
||||
'bail',
|
||||
'sometimes',
|
||||
Rule::requiredIf(function () {
|
||||
return $this->input('settings.e_invoice_type') === 'PEPPOL';
|
||||
}),
|
||||
function ($attribute, $value, $fail) {
|
||||
$country_code = $this->getCountryCode();
|
||||
if ($country_code && isset($this->vat_regex_patterns[$country_code]) && $this->input('settings.e_invoice_type') === 'PEPPOL') {
|
||||
if (!preg_match($this->vat_regex_patterns[$country_code], $value)) {
|
||||
$fail(ctrans('texts.invalid_vat_number'));
|
||||
}
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
@ -139,6 +187,12 @@ class UpdateCompanyRequest extends Request
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
|
||||
private function getCountryCode()
|
||||
{
|
||||
return auth()->user()->company()->country()->iso_3166_2;
|
||||
}
|
||||
|
||||
/**
|
||||
* For the hosted platform, we restrict the feature settings.
|
||||
*
|
||||
|
115
app/Http/Requests/EInvoice/Peppol/AddTaxIdentifierRequest.php
Normal file
115
app/Http/Requests/EInvoice/Peppol/AddTaxIdentifierRequest.php
Normal file
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\EInvoice\Peppol;
|
||||
|
||||
use App\Models\Country;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use App\Rules\EInvoice\Peppol\SupportsReceiverIdentifier;
|
||||
use App\Services\EDocument\Standards\Peppol\ReceiverIdentifier;
|
||||
|
||||
class AddTaxIdentifierRequest extends FormRequest
|
||||
{
|
||||
private array $vat_regex_patterns = [
|
||||
'DE' => '/^DE\d{9}$/',
|
||||
'AT' => '/^ATU\d{8}$/',
|
||||
'BE' => '/^BE0\d{9}$/',
|
||||
'BG' => '/^BG\d{9,10}$/',
|
||||
'CY' => '/^CY\d{8}L$/',
|
||||
'HR' => '/^HR\d{11}$/',
|
||||
'DK' => '/^DK\d{8}$/',
|
||||
'ES' => '/^ES[A-Z0-9]\d{7}[A-Z0-9]$/',
|
||||
'EE' => '/^EE\d{9}$/',
|
||||
'FI' => '/^FI\d{8}$/',
|
||||
'FR' => '/^FR\d{2}\d{9}$/',
|
||||
'EL' => '/^EL\d{9}$/',
|
||||
'HU' => '/^HU\d{8}$/',
|
||||
'IE' => '/^IE\d{7}[A-Z]{1,2}$/',
|
||||
'IT' => '/^IT\d{11}$/',
|
||||
'LV' => '/^LV\d{11}$/',
|
||||
'LT' => '/^LT(\d{9}|\d{12})$/',
|
||||
'LU' => '/^LU\d{8}$/',
|
||||
'MT' => '/^MT\d{8}$/',
|
||||
'NL' => '/^NL\d{9}B\d{2}$/',
|
||||
'PL' => '/^PL\d{10}$/',
|
||||
'PT' => '/^PT\d{9}$/',
|
||||
'CZ' => '/^CZ\d{8,10}$/',
|
||||
'RO' => '/^RO\d{2,10}$/',
|
||||
'SK' => '/^SK\d{10}$/',
|
||||
'SI' => '/^SI\d{8}$/',
|
||||
'SE' => '/^SE\d{12}$/',
|
||||
];
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
/**
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
$user = auth()->user();
|
||||
|
||||
if (app()->isLocal()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $user->account->isPaid() && $user->isAdmin() && $user->company()->legal_entity_id != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'country' => ['required', 'bail', Rule::in(array_keys($this->vat_regex_patterns))],
|
||||
'vat_number' => [
|
||||
'required',
|
||||
'string',
|
||||
'bail',
|
||||
function ($attribute, $value, $fail) {
|
||||
if ($this->country && isset($this->vat_regex_patterns[$this->country])) {
|
||||
if (!preg_match($this->vat_regex_patterns[$this->country], $value)) {
|
||||
$fail(ctrans('texts.invalid_vat_number'));
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if(isset($input['country'])) {
|
||||
$country = $this->country();
|
||||
$input['country'] = $country->iso_3166_2;
|
||||
}
|
||||
|
||||
$this->replace($input);
|
||||
|
||||
}
|
||||
|
||||
public function country(): Country
|
||||
{
|
||||
|
||||
/** @var \Illuminate\Support\Collection<\App\Models\Country> */
|
||||
$countries = app('countries');
|
||||
|
||||
return $countries->first(function ($c){
|
||||
return $this->country == $c->id;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\EInvoice\Peppol;
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Rules\EInvoice\Peppol\SupportsReceiverIdentifier;
|
||||
use App\Services\EDocument\Standards\Peppol\ReceiverIdentifier;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CreateRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
/**
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
$user = auth()->user();
|
||||
|
||||
if (app()->isLocal()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $user->account->isPaid() &&
|
||||
$user->company()->legal_entity_id === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'party_name' => ['required', 'string'],
|
||||
'line1' => ['required', 'string'],
|
||||
'line2' => ['nullable', 'string'],
|
||||
'city' => ['required', 'string'],
|
||||
'country' => ['required', 'integer', 'exists:countries,id', new SupportsReceiverIdentifier()],
|
||||
'zip' => ['required', 'string'],
|
||||
'county' => ['required', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
protected function failedAuthorization(): void
|
||||
{
|
||||
throw new AuthorizationException(
|
||||
message: ctrans('texts.peppol_not_paid_message'),
|
||||
);
|
||||
}
|
||||
|
||||
public function country(): Country
|
||||
{
|
||||
return Country::find($this->country);
|
||||
}
|
||||
|
||||
public function receiverIdentifier(): string
|
||||
{
|
||||
$identifier = new ReceiverIdentifier($this->country()->iso_3166_2);
|
||||
|
||||
return $identifier->get();
|
||||
}
|
||||
}
|
42
app/Http/Requests/EInvoice/Peppol/ShowEntityRequest.php
Normal file
42
app/Http/Requests/EInvoice/Peppol/ShowEntityRequest.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\EInvoice\Peppol;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ShowEntityRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
/**
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
$user = auth()->user();
|
||||
|
||||
if (app()->isLocal()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $user->isAdmin() && $user->company()->legal_entity_id != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
}
|
121
app/Http/Requests/EInvoice/Peppol/StoreEntityRequest.php
Normal file
121
app/Http/Requests/EInvoice/Peppol/StoreEntityRequest.php
Normal file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\EInvoice\Peppol;
|
||||
|
||||
use App\Models\Country;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
|
||||
class StoreEntityRequest extends FormRequest
|
||||
{
|
||||
|
||||
private array $vat_regex_patterns = [
|
||||
'DE' => '/^DE\d{9}$/',
|
||||
'AT' => '/^ATU\d{8}$/',
|
||||
'BE' => '/^BE0\d{9}$/',
|
||||
'BG' => '/^BG\d{9,10}$/',
|
||||
'CY' => '/^CY\d{8}L$/',
|
||||
'HR' => '/^HR\d{11}$/',
|
||||
'DK' => '/^DK\d{8}$/',
|
||||
'ES' => '/^ES[A-Z0-9]\d{7}[A-Z0-9]$/',
|
||||
'EE' => '/^EE\d{9}$/',
|
||||
'FI' => '/^FI\d{8}$/',
|
||||
'FR' => '/^FR\d{2}\d{9}$/',
|
||||
'EL' => '/^EL\d{9}$/',
|
||||
'HU' => '/^HU\d{8}$/',
|
||||
'IE' => '/^IE\d{7}[A-Z]{1,2}$/',
|
||||
'IT' => '/^IT\d{11}$/',
|
||||
'LV' => '/^LV\d{11}$/',
|
||||
'LT' => '/^LT(\d{9}|\d{12})$/',
|
||||
'LU' => '/^LU\d{8}$/',
|
||||
'MT' => '/^MT\d{8}$/',
|
||||
'NL' => '/^NL\d{9}B\d{2}$/',
|
||||
'PL' => '/^PL\d{10}$/',
|
||||
'PT' => '/^PT\d{9}$/',
|
||||
'CZ' => '/^CZ\d{8,10}$/',
|
||||
'RO' => '/^RO\d{2,10}$/',
|
||||
'SK' => '/^SK\d{10}$/',
|
||||
'SI' => '/^SI\d{8}$/',
|
||||
'SE' => '/^SE\d{12}$/',
|
||||
];
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
/**
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
$user = auth()->user();
|
||||
|
||||
if (app()->isLocal()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $user->account->isPaid() && $user->isAdmin() &&
|
||||
$user->company()->legal_entity_id === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'party_name' => ['required', 'string'],
|
||||
'line1' => ['required', 'string'],
|
||||
'line2' => ['nullable', 'string'],
|
||||
'city' => ['required', 'string'],
|
||||
'country' => ['required', 'bail', Rule::in(array_keys($this->vat_regex_patterns))],
|
||||
'zip' => ['required', 'string'],
|
||||
'county' => ['required', 'string'],
|
||||
'acts_as_receiver' => ['required', 'bool'],
|
||||
'acts_as_sender' => ['required', 'bool'],
|
||||
];
|
||||
}
|
||||
|
||||
protected function failedAuthorization(): void
|
||||
{
|
||||
throw new AuthorizationException(
|
||||
message: ctrans('texts.peppol_not_paid_message'),
|
||||
);
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if(isset($input['country'])) {
|
||||
$country = $this->country();
|
||||
$input['country'] = $country->iso_3166_2;
|
||||
}
|
||||
|
||||
$input['acts_as_receiver'] = $input['acts_as_receiver'] ?? true;
|
||||
$input['acts_as_sender'] = $input['acts_as_sender'] ?? true;
|
||||
|
||||
$this->replace($input);
|
||||
|
||||
}
|
||||
|
||||
public function country(): Country
|
||||
{
|
||||
|
||||
/** @var \Illuminate\Support\Collection<\App\Models\Country> */
|
||||
$countries = app('countries');
|
||||
|
||||
return $countries->first(function ($c){
|
||||
return $this->country == $c->id;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
55
app/Http/Requests/EInvoice/Peppol/UpdateEntityRequest.php
Normal file
55
app/Http/Requests/EInvoice/Peppol/UpdateEntityRequest.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\EInvoice\Peppol;
|
||||
|
||||
use App\Models\Country;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateEntityRequest extends FormRequest
|
||||
{
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
/**
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
$user = auth()->user();
|
||||
|
||||
if (app()->isLocal()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $user->account->isPaid() && $user->isAdmin() &&
|
||||
$user->company()->legal_entity_id != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'acts_as_receiver' => ['required', 'bool'],
|
||||
'acts_as_sender' => ['required', 'bool'],
|
||||
];
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
@ -417,7 +417,7 @@ class NinjaMailerJob implements ShouldQueue
|
||||
$company = $this->company;
|
||||
|
||||
$smtp_host = $company->smtp_host ?? '';
|
||||
$smtp_port = (int)$company->smtp_port;
|
||||
$smtp_port = $company->smtp_port ?? 0;
|
||||
$smtp_username = $company->smtp_username ?? '';
|
||||
$smtp_password = $company->smtp_password ?? '';
|
||||
$smtp_encryption = $company->smtp_encryption ?? 'tls';
|
||||
@ -437,7 +437,7 @@ class NinjaMailerJob implements ShouldQueue
|
||||
'mail.mailers.smtp' => [
|
||||
'transport' => 'smtp',
|
||||
'host' => $smtp_host,
|
||||
'port' => $smtp_port,
|
||||
'port' => (int)$smtp_port,
|
||||
'username' => $smtp_username,
|
||||
'password' => $smtp_password,
|
||||
'encryption' => $smtp_encryption,
|
||||
|
@ -240,30 +240,6 @@ class Company extends BaseModel
|
||||
use AppSetup;
|
||||
use \Awobaz\Compoships\Compoships;
|
||||
|
||||
// const ENTITY_RECURRING_INVOICE = 'recurring_invoice';
|
||||
|
||||
// const ENTITY_CREDIT = 'credit';
|
||||
|
||||
// const ENTITY_QUOTE = 'quote';
|
||||
|
||||
// const ENTITY_TASK = 'task';
|
||||
|
||||
// const ENTITY_EXPENSE = 'expense';
|
||||
|
||||
// const ENTITY_PROJECT = 'project';
|
||||
|
||||
// const ENTITY_VENDOR = 'vendor';
|
||||
|
||||
// const ENTITY_TICKET = 'ticket';
|
||||
|
||||
// const ENTITY_PROPOSAL = 'proposal';
|
||||
|
||||
// const ENTITY_RECURRING_EXPENSE = 'recurring_expense';
|
||||
|
||||
// const ENTITY_RECURRING_TASK = 'task';
|
||||
|
||||
// const ENTITY_RECURRING_QUOTE = 'recurring_quote';
|
||||
|
||||
/** @var CompanyPresenter */
|
||||
protected $presenter = CompanyPresenter::class;
|
||||
|
||||
|
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Rules\EInvoice\Peppol;
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Services\EDocument\Standards\Peppol\ReceiverIdentifier;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
|
||||
class SupportsReceiverIdentifier implements ValidationRule
|
||||
{
|
||||
/**
|
||||
* Run the validation rule.
|
||||
*
|
||||
* @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
$country = Country::find($value);
|
||||
|
||||
if ($country === null) {
|
||||
$fail(ctrans('texts.peppol_country_not_supported'));
|
||||
}
|
||||
|
||||
$checker = new ReceiverIdentifier($country->iso_3166_2);
|
||||
|
||||
if ($checker->get() === null) {
|
||||
$fail(ctrans('texts.peppol_country_not_supported'));
|
||||
}
|
||||
}
|
||||
}
|
@ -11,13 +11,13 @@
|
||||
|
||||
namespace App\Services\EDocument\Gateway\Storecove;
|
||||
|
||||
use App\DataMapper\Analytics\LegalEntityCreated;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Turbo124\Beacon\Facades\LightLogs;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use GuzzleHttp\Exception\ServerException;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
use Turbo124\Beacon\Facades\LightLogs;
|
||||
use App\DataMapper\Analytics\LegalEntityCreated;
|
||||
|
||||
enum HttpVerb: string
|
||||
{
|
||||
@ -82,13 +82,47 @@ class Storecove
|
||||
};
|
||||
|
||||
$uri = "api/v2/discovery/receives";
|
||||
|
||||
$r = $this->httpClient($uri, (HttpVerb::POST)->value, $network_data, $this->getHeaders());
|
||||
|
||||
nlog($network_data);
|
||||
nlog($r->json());
|
||||
nlog($r->body());
|
||||
return ($r->successful() && $r->json()['code'] == 'OK') ? true : false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Discovery
|
||||
*
|
||||
* @param string $identifier
|
||||
* @param string $scheme
|
||||
* @param string $network
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(string $identifier, string $scheme, string $network = 'peppol'): bool
|
||||
{
|
||||
$network_data = [];
|
||||
|
||||
match ($network) {
|
||||
'peppol' => $network_data = array_merge($this->peppol_discovery, ['scheme' => $scheme, 'identifier' => $identifier]),
|
||||
'dbn' => $network_data = array_merge($this->dbn_discovery, ['scheme' => $scheme, 'identifier' => $identifier]),
|
||||
default => $network_data = array_merge($this->peppol_discovery, ['scheme' => $scheme, 'identifier' => $identifier]),
|
||||
};
|
||||
|
||||
$uri = "api/v2/discovery/exists";
|
||||
|
||||
$r = $this->httpClient($uri, (HttpVerb::POST)->value, $network_data, $this->getHeaders());
|
||||
|
||||
|
||||
nlog($network_data);
|
||||
nlog($r->json());
|
||||
nlog($r->body());
|
||||
|
||||
return ($r->successful() && $r->json()['code'] == 'OK') ? true : false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unused as yet
|
||||
*
|
||||
@ -222,7 +256,7 @@ class Storecove
|
||||
}
|
||||
|
||||
$company_defaults = [
|
||||
'acts_as_receiver' => false,
|
||||
'acts_as_receiver' => true,
|
||||
'acts_as_sender' => true,
|
||||
'advertisements' => ['invoice'],
|
||||
];
|
||||
@ -232,6 +266,8 @@ class Storecove
|
||||
$r = $this->httpClient($uri, (HttpVerb::POST)->value, $payload);
|
||||
|
||||
if($r->successful()) {
|
||||
$data = $r->object();
|
||||
LightLogs::create(new LegalEntityCreated($data->id, $data->tenant_id))->batch();
|
||||
return $r->json();
|
||||
}
|
||||
|
||||
@ -248,8 +284,6 @@ class Storecove
|
||||
public function getLegalEntity($id)
|
||||
{
|
||||
|
||||
// $uri = "legal_entities";
|
||||
|
||||
$uri = "legal_entities/{$id}";
|
||||
|
||||
$r = $this->httpClient($uri, (HttpVerb::GET)->value, []);
|
||||
@ -316,6 +350,86 @@ class Storecove
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* addAdditionalTaxIdentifier
|
||||
*
|
||||
* Adds an additional TAX identifier to the legal entity, where they are selling cross border
|
||||
* and are required to be registered in the destination country.
|
||||
*
|
||||
* @param int $legal_entity_id
|
||||
* @param string $identifier
|
||||
* @param string $scheme
|
||||
* @return mixed
|
||||
*/
|
||||
|
||||
public function addAdditionalTaxIdentifier(int $legal_entity_id, string $identifier, string $scheme)
|
||||
{
|
||||
|
||||
$uri = "legal_entities/{$legal_entity_id}/additional_tax_identifiers";
|
||||
|
||||
$data = [
|
||||
"identifier" => $identifier,
|
||||
"scheme" => $scheme,
|
||||
"superscheme" => "iso6523-actorid-upis",
|
||||
];
|
||||
|
||||
$r = $this->httpClient($uri, (HttpVerb::POST)->value, $data);
|
||||
|
||||
if ($r->successful()) {
|
||||
$data = $r->json();
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
return $r;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* removeAdditionalTaxIdentifier
|
||||
*
|
||||
* Adds an additional TAX identifier to the legal entity, where they are selling cross border
|
||||
* and are required to be registered in the destination country.
|
||||
*
|
||||
* @param int $legal_entity_id
|
||||
* @param string $tax_identifier
|
||||
* @return mixed
|
||||
*/
|
||||
|
||||
public function removeAdditionalTaxIdentifier(int $legal_entity_id, string $tax_identifier)
|
||||
{
|
||||
$legal_entity = $this->getLegalEntity($legal_entity_id);
|
||||
|
||||
if(isset($legal_entity['additional_tax_identifiers']) && is_array($legal_entity['additional_tax_identifiers']))
|
||||
{
|
||||
|
||||
foreach($legal_entity['additional_tax_identifiers'] as $ati)
|
||||
{
|
||||
|
||||
if($ati['identifier'] == $tax_identifier)
|
||||
{
|
||||
|
||||
$uri = "legal_entities/{$legal_entity_id}/additional_tax_identifiers/{$ati['id']}";
|
||||
|
||||
$r = $this->httpClient($uri, (HttpVerb::DELETE)->value, []);
|
||||
|
||||
if ($r->successful()) {
|
||||
$data = $r->json();
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
return $r;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* deleteIdentifier
|
||||
*
|
||||
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\EDocument\Standards\Peppol;
|
||||
|
||||
// https://www.storecove.com/docs/#_receiver_identifiers_list
|
||||
|
||||
class ReceiverIdentifier
|
||||
{
|
||||
public array $mappings = [
|
||||
'DE' => 'DE:VAT',
|
||||
|
||||
// @todo: Check with Dave what other countries we support.
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
public string $country,
|
||||
) {
|
||||
}
|
||||
|
||||
public function get(): ?string
|
||||
{
|
||||
return $this->mappings[$this->country] ?? null;
|
||||
}
|
||||
}
|
@ -622,7 +622,7 @@ class Email implements ShouldQueue
|
||||
$company = $this->company;
|
||||
|
||||
$smtp_host = $company->smtp_host ?? '';
|
||||
$smtp_port = (int)$company->smtp_port;
|
||||
$smtp_port = $company->smtp_port ?? 0;
|
||||
$smtp_username = $company->smtp_username ?? '';
|
||||
$smtp_password = $company->smtp_password ?? '';
|
||||
$smtp_encryption = $company->smtp_encryption ?? 'tls';
|
||||
@ -641,7 +641,7 @@ class Email implements ShouldQueue
|
||||
'mail.mailers.smtp' => [
|
||||
'transport' => 'smtp',
|
||||
'host' => $smtp_host,
|
||||
'port' => $smtp_port,
|
||||
'port' => (int)$smtp_port,
|
||||
'username' => $smtp_username,
|
||||
'password' => $smtp_password,
|
||||
'encryption' => $smtp_encryption,
|
||||
|
@ -5402,6 +5402,7 @@ $lang = array(
|
||||
'credit_updated' => 'Credit Updated',
|
||||
'payment_updated' => 'Payment Updated',
|
||||
'search_placeholder' => 'Find invoices, clients, and more',
|
||||
'invalid_vat_number' => "The VAT number is not valid for the selected country. Format should be Country Code followed by number only ie, DE123456789",
|
||||
);
|
||||
|
||||
return $lang;
|
||||
|
@ -233,6 +233,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
|
||||
Route::post('einvoice/validateEntity', [EInvoiceController::class, 'validateEntity'])->name('einvoice.validateEntity');
|
||||
Route::post('einvoice/configurations', [EInvoiceController::class, 'configurations'])->name('einvoice.configurations');
|
||||
|
||||
Route::post('einvoice/peppol/legal_entity', [EInvoicePeppolController::class, 'show'])->name('einvoice.peppol.legal_entity');
|
||||
Route::post('einvoice/peppol/setup', [EInvoicePeppolController::class, 'setup'])->name('einvoice.peppol.setup');
|
||||
Route::post('einvoice/peppol/disconnect', [EInvoicePeppolController::class, 'disconnect'])->name('einvoice.peppol.disconnect');
|
||||
|
||||
|
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Requests\EInvoice\Peppol;
|
||||
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use App\Http\Requests\EInvoice\Peppol\AddTaxIdentifierRequest;
|
||||
|
||||
class AddTaxIdentifierRequestTest extends TestCase
|
||||
{
|
||||
protected AddTaxIdentifierRequest $request;
|
||||
|
||||
private array $vat_regex_patterns = [
|
||||
'DE' => '/^DE\d{9}$/',
|
||||
'AT' => '/^ATU\d{8}$/',
|
||||
'BE' => '/^BE0\d{9}$/',
|
||||
'BG' => '/^BG\d{9,10}$/',
|
||||
'CY' => '/^CY\d{8}L$/',
|
||||
'HR' => '/^HR\d{11}$/',
|
||||
'DK' => '/^DK\d{8}$/',
|
||||
'ES' => '/^ES[A-Z0-9]\d{7}[A-Z0-9]$/',
|
||||
'EE' => '/^EE\d{9}$/',
|
||||
'FI' => '/^FI\d{8}$/',
|
||||
'FR' => '/^FR\d{2}\d{9}$/',
|
||||
'EL' => '/^EL\d{9}$/',
|
||||
'HU' => '/^HU\d{8}$/',
|
||||
'IE' => '/^IE\d{7}[A-Z]{1,2}$/',
|
||||
'IT' => '/^IT\d{11}$/',
|
||||
'LV' => '/^LV\d{11}$/',
|
||||
'LT' => '/^LT(\d{9}|\d{12})$/',
|
||||
'LU' => '/^LU\d{8}$/',
|
||||
'MT' => '/^MT\d{8}$/',
|
||||
'NL' => '/^NL\d{9}B\d{2}$/',
|
||||
'PL' => '/^PL\d{10}$/',
|
||||
'PT' => '/^PT\d{9}$/',
|
||||
'CZ' => '/^CZ\d{8,10}$/',
|
||||
'RO' => '/^RO\d{2,10}$/',
|
||||
'SK' => '/^SK\d{10}$/',
|
||||
'SI' => '/^SI\d{8}$/',
|
||||
'SE' => '/^SE\d{12}$/',
|
||||
];
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->request = new AddTaxIdentifierRequest();
|
||||
}
|
||||
|
||||
public function testValidInput()
|
||||
{
|
||||
$validator = Validator::make([
|
||||
'country' => 'DE',
|
||||
'vat_number' => 'DE123456789',
|
||||
], $this->request->rules());
|
||||
|
||||
$this->assertTrue($validator->passes());
|
||||
}
|
||||
|
||||
public function testInvalidCountry()
|
||||
{
|
||||
$validator = Validator::make([
|
||||
'country' => 'US',
|
||||
'vat_number' => 'DE123456789',
|
||||
], $this->request->rules());
|
||||
|
||||
$this->assertFalse($validator->passes());
|
||||
$this->assertArrayHasKey('country', $validator->errors()->toArray());
|
||||
}
|
||||
|
||||
public function testInvalidVatNumber()
|
||||
{
|
||||
$data = [
|
||||
'country' => 'DE',
|
||||
'vat_number' => 'DE12345', // Too short
|
||||
];
|
||||
|
||||
$rules = [
|
||||
'country' => ['required', 'bail'],
|
||||
'vat_number' => [
|
||||
'required',
|
||||
'string',
|
||||
'bail',
|
||||
function ($attribute, $value, $fail) use ($data){
|
||||
if ( isset($this->vat_regex_patterns[$data['country']])) {
|
||||
if (!preg_match($this->vat_regex_patterns[$data['country']], $value)) {
|
||||
$fail(ctrans('texts.invalid_vat_number'));
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
$validator = Validator::make($data, $rules);
|
||||
|
||||
$this->assertFalse($validator->passes());
|
||||
$this->assertArrayHasKey('vat_number', $validator->errors()->toArray());
|
||||
}
|
||||
|
||||
public function testMissingCountry()
|
||||
{
|
||||
$validator = Validator::make([
|
||||
'vat_number' => 'DE123456789',
|
||||
], $this->request->rules());
|
||||
|
||||
$this->assertFalse($validator->passes());
|
||||
$this->assertArrayHasKey('country', $validator->errors()->toArray());
|
||||
}
|
||||
|
||||
public function testMissingVatNumber()
|
||||
{
|
||||
$validator = Validator::make([
|
||||
'country' => 'DE',
|
||||
], $this->request->rules());
|
||||
|
||||
$this->assertFalse($validator->passes());
|
||||
$this->assertArrayHasKey('vat_number', $validator->errors()->toArray());
|
||||
}
|
||||
}
|
99
tests/Feature/EInvoice/Validation/CreateRequestTest.php
Normal file
99
tests/Feature/EInvoice/Validation/CreateRequestTest.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\EInvoice\Validation;
|
||||
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use App\Http\Requests\EInvoice\Peppol\StoreEntityRequest;
|
||||
use App\Models\Country;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class CreateRequestTest extends TestCase
|
||||
{
|
||||
protected StoreEntityRequest $request;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->request = new StoreEntityRequest();
|
||||
}
|
||||
|
||||
public function testValidInput()
|
||||
{
|
||||
$validator = Validator::make([
|
||||
'party_name' => 'Test Company',
|
||||
'line1' => '123 Test St',
|
||||
'city' => 'Test City',
|
||||
'country' => 'DE', // Assuming 1 is the ID for Germany
|
||||
'zip' => '12345',
|
||||
'county' => 'Test County',
|
||||
'acts_as_sender' => true,
|
||||
'acts_as_receiver' => true,
|
||||
], $this->request->rules());
|
||||
|
||||
|
||||
$this->assertTrue($validator->passes());
|
||||
}
|
||||
|
||||
public function testInvalidCountry()
|
||||
{
|
||||
$validator = Validator::make([
|
||||
'party_name' => 'Test Company',
|
||||
'line1' => '123 Test St',
|
||||
'city' => 'Test City',
|
||||
'country' => 999, // Invalid country ID
|
||||
'zip' => '12345',
|
||||
'county' => 'Test County',
|
||||
'acts_as_sender' => true,
|
||||
'acts_as_receiver' => true,
|
||||
], $this->request->rules());
|
||||
|
||||
$this->assertFalse($validator->passes());
|
||||
$this->assertArrayHasKey('country', $validator->errors()->toArray());
|
||||
}
|
||||
|
||||
public function testMissingRequiredFields()
|
||||
{
|
||||
$validator = Validator::make([
|
||||
'line2' => 'Optional line',
|
||||
], $this->request->rules());
|
||||
|
||||
$this->assertFalse($validator->passes());
|
||||
$errors = $validator->errors()->toArray();
|
||||
$this->assertArrayHasKey('party_name', $errors);
|
||||
$this->assertArrayHasKey('line1', $errors);
|
||||
$this->assertArrayHasKey('city', $errors);
|
||||
$this->assertArrayHasKey('country', $errors);
|
||||
$this->assertArrayHasKey('zip', $errors);
|
||||
$this->assertArrayHasKey('county', $errors);
|
||||
}
|
||||
|
||||
public function testOptionalLine2()
|
||||
{
|
||||
$validator = Validator::make([
|
||||
'party_name' => 'Test Company',
|
||||
'line1' => '123 Test St',
|
||||
'line2' => 'Optional line',
|
||||
'city' => 'Test City',
|
||||
'country' => 'AT',
|
||||
'zip' => '12345',
|
||||
'county' => 'Test County',
|
||||
|
||||
'acts_as_sender' => true,
|
||||
'acts_as_receiver' => true,
|
||||
], $this->request->rules());
|
||||
|
||||
$this->assertTrue($validator->passes());
|
||||
}
|
||||
|
||||
public function testCountryPreparation()
|
||||
{
|
||||
$request = new StoreEntityRequest([
|
||||
'country' => '276', // Assuming 1 is the ID for Germany
|
||||
]);
|
||||
|
||||
$request->prepareForValidation();
|
||||
|
||||
$this->assertEquals('DE', $request->input('country'));
|
||||
}
|
||||
}
|
@ -16,13 +16,14 @@ use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use Tests\MockAccountData;
|
||||
use App\Models\ClientContact;
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\DataMapper\Tax\TaxModel;
|
||||
use App\DataMapper\ClientSettings;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\Models\ClientContact;
|
||||
use App\Services\EDocument\Standards\Peppol;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\PaymentMeans;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
|
||||
class StorecoveTest extends TestCase
|
||||
{
|
||||
@ -42,6 +43,69 @@ class StorecoveTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function testUnsetOfVatNumers()
|
||||
{
|
||||
|
||||
$settings = CompanySettings::defaults();
|
||||
$settings->country_id = '276'; // germany
|
||||
|
||||
$tax_data = new TaxModel();
|
||||
$tax_data->seller_subregion = 'DE';
|
||||
$tax_data->regions->EU->has_sales_above_threshold = false;
|
||||
$tax_data->regions->EU->tax_all_subregions = true;
|
||||
$tax_data->regions->US->tax_all_subregions = true;
|
||||
$tax_data->regions->US->has_sales_above_threshold = true;
|
||||
|
||||
$tax_data->regions->EU->subregions->DE->vat_number = 'DE12345';
|
||||
$tax_data->regions->EU->subregions->RO->vat_number = 'RO12345';
|
||||
|
||||
$company = Company::factory()->create([
|
||||
'account_id' => $this->account->id,
|
||||
'settings' => $settings,
|
||||
'tax_data' => $tax_data,
|
||||
'calculate_taxes' => true,
|
||||
]);
|
||||
|
||||
|
||||
$this->assertEquals('DE12345', $company->tax_data->regions->EU->subregions->DE->vat_number);
|
||||
$this->assertEquals('RO12345', $company->tax_data->regions->EU->subregions->RO->vat_number);
|
||||
|
||||
$this->assertEquals('DE12345', $company->tax_data->regions->EU->subregions->DE->vat_number);
|
||||
$this->assertEquals('RO12345', $company->tax_data->regions->EU->subregions->RO->vat_number);
|
||||
|
||||
$company->tax_data = $this->unsetVatNumbers($company->tax_data);
|
||||
|
||||
$company->save();
|
||||
|
||||
$company = $company->fresh();
|
||||
|
||||
$this->assertFalse(property_exists($company->tax_data->regions->EU->subregions->DE, 'vat_number'), "DE subregion should not have vat_number property");
|
||||
$this->assertFalse(property_exists($company->tax_data->regions->EU->subregions->RO, 'vat_number'), "RO subregion should not have vat_number property");
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function unsetVatNumbers(mixed $taxData): mixed
|
||||
{
|
||||
if (isset($taxData->regions->EU->subregions)) {
|
||||
foreach ($taxData->regions->EU->subregions as $country => $data) {
|
||||
if (isset($data->vat_number)) {
|
||||
$newData = new \stdClass();
|
||||
if (is_object($data)) {
|
||||
$dataArray = get_object_vars($data);
|
||||
foreach ($dataArray as $key => $value) {
|
||||
if ($key !== 'vat_number') {
|
||||
$newData->$key = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
$taxData->regions->EU->subregions->$country = $newData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $taxData;
|
||||
}
|
||||
// public function testCreateLegalEntity()
|
||||
// {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user