commit
fc0ece1bf0
|
|
@ -1 +1 @@
|
|||
5.10.46
|
||||
5.10.47
|
||||
|
|
@ -316,7 +316,8 @@ class BaseRule implements RuleInterface
|
|||
|
||||
return $this;
|
||||
|
||||
} elseif($this->client_region == 'AU') { //these are defaults and are only stubbed out for now, for AU we can actually remove these
|
||||
}
|
||||
elseif($this->client_region == 'AU') { //these are defaults and are only stubbed out for now, for AU we can actually remove these
|
||||
|
||||
$this->tax_rate1 = $this->client->company->tax_data->regions->AU->subregions->AU->tax_rate;
|
||||
$this->tax_name1 = $this->client->company->tax_data->regions->AU->subregions->AU->tax_name;
|
||||
|
|
@ -346,6 +347,10 @@ class BaseRule implements RuleInterface
|
|||
$this->tax_rate1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$company_country_code}->tax_rate;
|
||||
$this->tax_name1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$company_country_code}->tax_name;
|
||||
}
|
||||
elseif($is_over_threshold){
|
||||
$this->tax_rate1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_rate;
|
||||
$this->tax_name1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_name;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->tax_rate1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_rate;
|
||||
|
|
|
|||
|
|
@ -218,6 +218,9 @@ class NordigenController extends BaseController
|
|||
|
||||
$nordigen_account = $nordigen->getAccount($nordigenAccountId);
|
||||
|
||||
if(!$nordigen_account)
|
||||
continue;
|
||||
|
||||
$existing_bank_integration = BankIntegration::withTrashed()->where('nordigen_account_id', $nordigen_account['id'])->where('company_id', $company->id)->where('is_deleted', 0)->first();
|
||||
|
||||
if (!$existing_bank_integration) {
|
||||
|
|
|
|||
|
|
@ -132,10 +132,10 @@ class EInvoiceController extends BaseController
|
|||
->withHeaders([
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
'X-EInvoice-Token' => $company->account->e_invoicing_token,
|
||||
])
|
||||
->post('/api/einvoice/quota', data: [
|
||||
'license_key' => config('ninja.license_key'),
|
||||
'e_invoicing_token' => $company->account->e_invoicing_token,
|
||||
'account_key' => $company->account->key,
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use Http;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
|
|
@ -79,14 +80,23 @@ class EInvoicePeppolController extends BaseController
|
|||
{
|
||||
$company = auth()->user()->company();
|
||||
|
||||
$headers = [
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
];
|
||||
|
||||
if (Ninja::isSelfHost()) {
|
||||
$headers['X-EInvoice-Token'] = $company->account->e_invoicing_token;
|
||||
}
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
$headers['X-EInvoice-Secret'] = config('ninja.hosted_einvoice_secret');
|
||||
}
|
||||
|
||||
$response = Http::baseUrl(config('ninja.hosted_ninja_url'))
|
||||
->withHeaders([
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
])
|
||||
->withHeaders($headers)
|
||||
->post('/api/einvoice/peppol/legal_entity', data: [
|
||||
'legal_entity_id' => $company->legal_entity_id,
|
||||
'e_invoicing_token' => $company->account->e_invoicing_token,
|
||||
]);
|
||||
|
||||
return response()->json($response->json(), 200);
|
||||
|
|
@ -107,17 +117,26 @@ class EInvoicePeppolController extends BaseController
|
|||
*/
|
||||
$company = auth()->user()->company();
|
||||
|
||||
$headers = [
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
];
|
||||
|
||||
if (Ninja::isSelfHost()) {
|
||||
$headers['X-EInvoice-Token'] = $company->account->e_invoicing_token;
|
||||
}
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
$headers['X-EInvoice-Secret'] = config('ninja.hosted_einvoice_secret');
|
||||
}
|
||||
|
||||
$response = Http::baseUrl(config('ninja.hosted_ninja_url'))
|
||||
->withHeaders([
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
])
|
||||
->withHeaders($headers)
|
||||
->post('/api/einvoice/peppol/setup', data: [
|
||||
...$request->validated(),
|
||||
'classification' => $request->classification ?? $company->settings->classification,
|
||||
'vat_number' => $request->vat_number ?? $company->settings->vat_number,
|
||||
'id_number' => $request->id_number ?? $company->settings->id_number,
|
||||
'e_invoicing_token' => $company->account->e_invoicing_token,
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
|
|
@ -169,15 +188,24 @@ class EInvoicePeppolController extends BaseController
|
|||
{
|
||||
$company = auth()->user()->company();
|
||||
|
||||
$headers = [
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
];
|
||||
|
||||
if (Ninja::isSelfHost()) {
|
||||
$headers['X-EInvoice-Token'] = $company->account->e_invoicing_token;
|
||||
}
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
$headers['X-EInvoice-Secret'] = config('ninja.hosted_einvoice_secret');
|
||||
}
|
||||
|
||||
$response = Http::baseUrl(config('ninja.hosted_ninja_url'))
|
||||
->withHeaders([
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
])
|
||||
->withHeaders($headers)
|
||||
->put('/api/einvoice/peppol/update', data: [
|
||||
...$request->validated(),
|
||||
'legal_entity_id' => $company->legal_entity_id,
|
||||
'e_invoicing_token' => $company->account->e_invoicing_token,
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
|
|
@ -209,15 +237,26 @@ class EInvoicePeppolController extends BaseController
|
|||
*/
|
||||
$company = auth()->user()->company();
|
||||
|
||||
$headers = [
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
];
|
||||
|
||||
if (Ninja::isSelfHost()) {
|
||||
$headers['X-EInvoice-Token'] = $company->account->e_invoicing_token;
|
||||
}
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
$headers['X-EInvoice-Secret'] = config('ninja.hosted_einvoice_secret');
|
||||
}
|
||||
|
||||
nlog($headers);
|
||||
|
||||
$response = Http::baseUrl(config('ninja.hosted_ninja_url'))
|
||||
->withHeaders([
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
])
|
||||
->withHeaders($headers)
|
||||
->post('/api/einvoice/peppol/disconnect', data: [
|
||||
'company_key' => $company->company_key,
|
||||
'legal_entity_id' => $company->legal_entity_id,
|
||||
'e_invoicing_token' => $company->account->e_invoicing_token,
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
|
|
@ -234,6 +273,8 @@ class EInvoicePeppolController extends BaseController
|
|||
return response()->noContent();
|
||||
}
|
||||
|
||||
nlog($response->status());
|
||||
|
||||
return response()->noContent(status: 500);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class TasksTable extends Component
|
|||
|
||||
return render('components.livewire.tasks-table', [
|
||||
'tasks' => $query,
|
||||
'show_item_description' => auth()->guard('contact')->user()->company->invoice_task_item_description ?? false,
|
||||
'show_item_description' => auth()->guard('contact')->user()->client->getSetting("show_task_item_description"),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,16 +118,13 @@ class StorecoveAdapter
|
|||
public function transform($invoice): self
|
||||
{
|
||||
$this->ninja_invoice = $invoice;
|
||||
|
||||
$serializer = $this->getSerializer();
|
||||
|
||||
|
||||
/** Currently - due to class structures, the serialization process goes like this:
|
||||
*
|
||||
* e-invoice => Peppol -> XML -> Peppol Decoded -> encode to Peppol -> deserialize to Storecove
|
||||
*/
|
||||
$p = (new Peppol($invoice))->run()->toXml();
|
||||
|
||||
$context = [
|
||||
DateTimeNormalizer::FORMAT_KEY => 'Y-m-d',
|
||||
AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
|
||||
|
|
@ -135,7 +132,6 @@ class StorecoveAdapter
|
|||
|
||||
$e = new \InvoiceNinja\EInvoice\EInvoice();
|
||||
$peppolInvoice = $e->decode('Peppol', $p, 'xml');
|
||||
|
||||
$parent = \App\Services\EDocument\Gateway\Storecove\Models\Invoice::class;
|
||||
$peppolInvoice = $e->encode($peppolInvoice, 'json');
|
||||
$this->storecove_invoice = $serializer->deserialize($peppolInvoice, $parent, 'json', $context);
|
||||
|
|
@ -395,6 +391,8 @@ class StorecoveAdapter
|
|||
// B2C under threshold - origin country VAT
|
||||
$this->nexus = $company_country_code;
|
||||
}
|
||||
} elseif ($is_over_threshold && !in_array($company_country_code, $eu_countries)){
|
||||
$this->nexus = $client_country_code;
|
||||
} else {
|
||||
nlog("B2B with valid vat");
|
||||
// B2B with valid VAT - origin country
|
||||
|
|
@ -422,7 +420,7 @@ class StorecoveAdapter
|
|||
|
||||
private function setupDestinationVAT($client_country_code):self
|
||||
{
|
||||
nlog("configuring destination tax");
|
||||
|
||||
$this->storecove_invoice->setConsumerTaxMode(true);
|
||||
$id = $this->ninja_invoice->company->tax_data->regions->EU->subregions->{$client_country_code}->vat_number;
|
||||
$scheme = $this->storecove->router->setInvoice($this->ninja_invoice)->resolveTaxScheme($client_country_code, $this->ninja_invoice->client->classification ?? 'individual');
|
||||
|
|
|
|||
|
|
@ -1390,10 +1390,11 @@ class Peppol extends AbstractService
|
|||
$country_code = $this->company->country()->iso_3166_2;
|
||||
}
|
||||
elseif(in_array($country_code, $eu_countries) && !in_array($this->invoice->client->country->iso_3166_2, $eu_countries)){
|
||||
//NON-EU sale
|
||||
//EU => FOREIGN sale
|
||||
}
|
||||
elseif(in_array($country_code, $eu_countries) && in_array($this->invoice->client->country->iso_3166_2, $eu_countries)){
|
||||
//EU Sale
|
||||
elseif(in_array($this->invoice->client->country->iso_3166_2, $eu_countries)){
|
||||
// elseif(in_array($country_code, $eu_countries) && in_array($this->invoice->client->country->iso_3166_2, $eu_countries)){
|
||||
// EU Sale
|
||||
if((isset($this->company->tax_data->regions->EU->has_sales_above_threshold) && $this->company->tax_data->regions->EU->has_sales_above_threshold) || !$this->invoice->client->has_valid_vat_number){ //over threshold - tax in buyer country
|
||||
$country_code = $this->invoice->client->country->iso_3166_2;
|
||||
|
||||
|
|
@ -1424,12 +1425,12 @@ class Peppol extends AbstractService
|
|||
$eu_countries = $br->eu_country_codes;
|
||||
|
||||
// If company is in EU, standardize to VAT
|
||||
if (in_array($this->company->country()->iso_3166_2, $eu_countries)) {
|
||||
// if (in_array($this->company->country()->iso_3166_2, $eu_countries)) {
|
||||
return "VAT";
|
||||
}
|
||||
// }
|
||||
|
||||
// For non-EU countries, return original or handle specifically
|
||||
return $this->standardizeTaxSchemeId($tax_name);
|
||||
// return $this->standardizeTaxSchemeId($tax_name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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.10.46'),
|
||||
'app_tag' => env('APP_TAG', '5.10.46'),
|
||||
'app_version' => env('APP_VERSION', '5.10.47'),
|
||||
'app_tag' => env('APP_TAG', '5.10.47'),
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', false),
|
||||
|
|
@ -258,5 +258,5 @@ return [
|
|||
'qvalia_partner_number' => env('QVALIA_PARTNER_NUMBER', false),
|
||||
'pdf_page_numbering_x_alignment' => env('PDF_PAGE_NUMBER_X', 0),
|
||||
'pdf_page_numbering_y_alignment' => env('PDF_PAGE_NUMBER_Y', -6),
|
||||
|
||||
'hosted_einvoice_secret' => env('HOSTED_EINVOICE_SECRET', null),
|
||||
];
|
||||
|
|
|
|||
|
|
@ -51,10 +51,11 @@ class TaxRuleConsistencyTest extends TestCase
|
|||
|
||||
private function setupTestData(array $params = []): array
|
||||
{
|
||||
$company_iso = isset($params['company_country']) ? $params['company_country'] : 'DE';
|
||||
|
||||
$settings = CompanySettings::defaults();
|
||||
$settings->vat_number = $params['company_vat'] ?? 'DE123456789';
|
||||
$settings->country_id = Country::where('iso_3166_2', 'DE')->first()->id;
|
||||
$settings->country_id = (string)Country::where('iso_3166_2', $company_iso)->first()->id;
|
||||
$settings->email = $this->faker->safeEmail();
|
||||
|
||||
$tax_data = new TaxModel();
|
||||
|
|
@ -137,7 +138,7 @@ class TaxRuleConsistencyTest extends TestCase
|
|||
'expected_rate' => 19, // Should use German VAT
|
||||
'expected_nexus' => 'DE',
|
||||
],
|
||||
'B2B Transaction' => [
|
||||
'B2B Transaction DE FR' => [
|
||||
'params' => [
|
||||
'company_country' => 'DE',
|
||||
'client_country' => 'FR',
|
||||
|
|
@ -150,6 +151,19 @@ class TaxRuleConsistencyTest extends TestCase
|
|||
'expected_rate' => 19, // Should use German VAT
|
||||
'expected_nexus' => 'DE',
|
||||
],
|
||||
'B2B Transaction US DK' => [
|
||||
'params' => [
|
||||
'company_country' => 'US',
|
||||
'client_country' => 'DK',
|
||||
'company_vat' => 'US123456789',
|
||||
'client_vat' => 'DK123456789',
|
||||
'classification' => 'business',
|
||||
'has_valid_vat' => true,
|
||||
'over_threshold' => true,
|
||||
],
|
||||
'expected_rate' => 25, // Should use DK VAT
|
||||
'expected_nexus' => 'DK',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($scenarios as $name => $scenario) {
|
||||
|
|
@ -166,7 +180,7 @@ class TaxRuleConsistencyTest extends TestCase
|
|||
|
||||
$this->assertEquals(
|
||||
$scenario['expected_rate'],
|
||||
$baseRule->tax_rate1
|
||||
$baseRule->tax_rate1, "{$name} {$scenario['expected_nexus']}"
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
|
|
|
|||
Loading…
Reference in New Issue