Merge pull request #10245 from turbo124/v5-develop

v5.10.47
This commit is contained in:
David Bomba 2024-11-12 08:27:46 +11:00 committed by GitHub
commit fc0ece1bf0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 109 additions and 47 deletions

View File

@ -1 +1 @@
5.10.46
5.10.47

View File

@ -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;

View File

@ -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) {

View File

@ -127,15 +127,15 @@ class EInvoiceController extends BaseController
* @var \App\Models\Company
*/
$company = auth()->user()->company();
$response = \Illuminate\Support\Facades\Http::baseUrl(config('ninja.hosted_ninja_url'))
->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,
]);

View File

@ -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);
}

View File

@ -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"),
]);
}
}

View File

@ -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,13 +132,12 @@ 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);
$this->buildNexus();
return $this;
}
@ -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');

View File

@ -944,7 +944,7 @@ class Peppol extends AbstractService
$this->globalTaxCategories[] = $taxCategory;
});
return $this;
}
@ -1379,7 +1379,7 @@ class Peppol extends AbstractService
public function getJurisdiction()
{
//calculate nexus
$country_code = $this->company->country()->iso_3166_2;
$br = new \App\DataMapper\Tax\BaseRule();
@ -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);
}
/**

View File

@ -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),
];

View File

@ -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) {
@ -163,10 +177,10 @@ class TaxRuleConsistencyTest extends TestCase
// Test StorecoveAdapter
$storecove = new Storecove();
$storecove->build($data['invoice']);
$this->assertEquals(
$scenario['expected_rate'],
$baseRule->tax_rate1
$baseRule->tax_rate1, "{$name} {$scenario['expected_nexus']}"
);
$this->assertEquals(