1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-08 20:22:42 +01:00

Add legal entity id to model

This commit is contained in:
David Bomba 2024-08-27 14:47:18 +10:00
parent 620bdc61dd
commit 02cbcbd5ed
8 changed files with 316 additions and 147 deletions

View File

@ -29,6 +29,7 @@ class QuickbooksSettings implements Castable
public int $refreshTokenExpiresAt;
public string $baseURL;
/**
* entity client,invoice,quote,purchase_order,vendor,payment
* sync true/false

View File

@ -121,6 +121,7 @@ use Laracasts\Presenter\PresentableTrait;
* @property string|null $smtp_local_domain
* @property \App\DataMapper\QuickbooksSettings|null $quickbooks
* @property boolean $smtp_verify_peer
* @property int|null $legal_entity_id
* @property-read \App\Models\Account $account
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read int|null $activities_count

View File

@ -13,6 +13,9 @@ namespace App\Services\EDocument\Gateway\Storecove;
use App\Models\Company;
use Illuminate\Support\Facades\Http;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
use Illuminate\Http\Client\RequestException;
enum HttpVerb: string
{
@ -24,9 +27,11 @@ enum HttpVerb: string
}
class Storecove
{
{
/** @var mixed $base_url */
private string $base_url = 'https://api.storecove.com/api/v2/';
/** @var mixed $peppol_discovery */
private array $peppol_discovery = [
"documentTypes" => ["invoice"],
"network" => "peppol",
@ -34,7 +39,8 @@ class Storecove
"scheme" => "de:lwid",
"identifier" => "DE:VAT"
];
/** @var mixed $dbn_discovery */
private array $dbn_discovery = [
"documentTypes" => ["invoice"],
"network" => "dbnalliance",
@ -43,18 +49,19 @@ class Storecove
"identifier" => "1200109963131"
];
public function __construct()
{
}
//config('ninja.storecove_api_key');
//https://app.storecove.com/en/docs#_test_identifiers
//check if identifier is able to send on the network.
//response = { "code": "OK", "email": false}
public function discovery($identifier, $scheme, $network = 'peppol')
/**
* Discovery
*
* @param string $identifier
* @param string $scheme
* @param string $network
* @return bool
*/
public function discovery(string $identifier, string $scheme, string $network = 'peppol'): bool
{
$network_data = [];
@ -71,50 +78,23 @@ class Storecove
return ($r->successful() && $r->json()['code'] == 'OK') ? true : false;
}
//response = "guid" : "xx",
/**
* If the receiver cannot be found, then an
* email is sent to that user if a appropriate
* email is included in the document payload
* Unused as yet
*
* {
"routing": {
"emails": [
"test@example.com"
],
"eIdentifiers": []
}
}
*
*
*
// documentType : invoice/invoice_response/order
// rawDocumentData : {
// document: base64_encode($ubl)
// parse: true
// parseStrategy: ubl
// }
* @param mixed $document
* @return void
*/
public function sendJsonDocument($document)
{
$payload = [
"legalEntityId" => 290868,
// "legalEntityId" => 290868,
"idempotencyGuid" => \Illuminate\Support\Str::uuid(),
"routing" => [
"eIdentifiers" => [],
"emails" => ["david@invoiceninja.com"]
],
// "document" => [
// 'documentType' => 'invoice',
// "rawDocumentData" => [
// "document" => base64_encode($document),
// "parse" => true,
// "parseStrategy" => "ubl",
// ],
// ],
"document" => [
"documentType" => "invoice",
"invoice" => $document,
@ -123,13 +103,8 @@ class Storecove
$uri = "document_submissions";
nlog($payload);
$r = $this->httpClient($uri, (HttpVerb::POST)->value, $payload, $this->getHeaders());
nlog($r->body());
nlog($r->json());
if($r->successful()) {
return $r->json()['guid'];
}
@ -137,8 +112,17 @@ class Storecove
return false;
}
public function sendDocument(string $document, int $routing_id, array $override_payload = [])
/**
* Send Document via StoreCove
*
* @param string $document
* @param int $routing_id
* @param array $override_payload
*
* @return string|null
*/
public function sendDocument(string $document, int $routing_id, array $override_payload = []): ?string
{
$payload = [
@ -155,7 +139,6 @@ class Storecove
$payload = array_merge($payload, $override_payload);
$payload['document']['documentType'] = 'invoice';
$payload['document']["rawDocumentData"] = [
"document" => base64_encode($document),
@ -165,84 +148,45 @@ class Storecove
$uri = "document_submissions";
nlog($payload);
$r = $this->httpClient($uri, (HttpVerb::POST)->value, $payload, $this->getHeaders());
nlog($r->body());
nlog($r->json());
// nlog($r->json());
if($r->successful()) {
return $r->json()['guid'];
}
return false;
return null;
}
//document submission sending evidence
/**
* Get Sending Evidence
*
* @param string $guid
* @return array
*/
public function getSendingEvidence(string $guid)
{
$uri = "document_submissions/{$guid}";
$r = $this->httpClient($uri, (HttpVerb::GET)->value, [], $this->getHeaders());
if($r->successful())
return $r->json();
}
// {
// "party_name": "<string>",
// "line1": "<string>",
// "city": "<string>",
// "zip": "<string>",
// "country": "EH",
// "line2": "<string>",
// "county": "<string>",
// "tenant_id": "<string>",
// "public": true,
// "advertisements": [
// "invoice"
// ],
// "third_party_username": "<string>",
// "third_party_password": "<string>",
// "rea": {
// "province": "AR",
// "identifier": "<string>",
// "capital": "<number>",
// "partners": "SM",
// "liquidation_status": "LN"
// },
// "acts_as_sender": true,
// "acts_as_receiver": true,
// "tax_registered": true
// }
// acts_as_receiver - optional - Default : true
// acts_as_sender - optional - Default : true
// advertisements - optional < enum (invoice, invoice_response, order, ordering, order_response, selfbilling) > array
// city - required - Length : 2 - 64
// country - required - ISO 3166-1 alpha-2
// county - optional - Maximal length : 64
// line1 - required - The first address line - Length : 2 - 192
// line2 - optional - The second address line, if applicable Maximal length : 192
// party_name - required - The name of the company. Length : 2 - 64
// public - optional - Whether or not this LegalEntity is public. Public means it will be entered into the PEPPOL directory at https://directory.peppol.eu/ Default : true
// rea - optional - The REA details for the LegalEntity. Only applies to IT (Italian) LegalEntities. - https://www.storecove.com/docs/#_openapi_rea (schema)
// capital - optional - The captial for the company. - number
// identifier - optional - The identifier. Length : 2 - 20
// liquidation_status - optional - The liquidation status of the company. enum (LN, LS)
// partners - optional - The number of partners. enum (SU, SM)
// province - optional - The provincia of the ufficio that issued the identifier.enum (AG, AL, AN, AO, AQ, AR, AP, AT, AV, BA, BT, BL, BN, BG, BI, BO, BZ, BS, BR, CA, CL, CB, CI, CE, CT, CZ, CH, CO, CS, CR, KR, CN, EN, FM, FE, FI, FG, FC, FR, GE, GO, GR, IM, IS, SP, LT, LE, LC, LI, LO, LU, MC, MN, MS, MT, VS, ME, MI, MO, MB, NA, NO, NU, OG, OT, OR, PD, PA, PR, PV, PG, PU, PE, PC, PI, PT, PN, PZ, PO, RG, RA, RC, RE, RI, RN, RO, SA, SS, SV, SI, SR, SO, TA, TE, TR, TO, TP, TN, TV, TS, UD, VA, VE, VB, VC, VR, VV, VI, VT)
// tax_registered - optional - Whether or not this LegalEntity is tax registered. This influences the validation of the data presented when sending documents. Default : true
// tenant_id - optional - The id of the tenant, to be used in case of single-tenant solutions that share webhook URLs. This property will included in webhook events. Maximal length : 64
// third_party_password - optional - The password to use to authenticate to a system through which to send the document, or to obtain tax authority approval to send it. This field is currently relevant only for India and mandatory when creating an IN LegalEntity. Length : 2 - 64
// third_party_username - optional - The username to use to authenticate to a system through which to send the document, or to obtain tax authority approval to send it. This field is currently relevant only for India and mandatory when creating an IN LegalEntity. Length : 2 - 64
// zip - required - The zipcode. Length : 2 - 32
/**
* CreateLegalEntity
*
* Creates a base entity.
*
* Following creation, you will also need to create a Peppol Identifier
*
* @url https://www.storecove.com/docs/#_openapi_legalentitycreate
*
* @return mixed
*/
public function createLegalEntity(array $data, Company $company)
@ -275,7 +219,13 @@ class Storecove
return $r;
}
/**
* GetLegalEntity
*
* @param int $id
* @return mixed
*/
public function getLegalEntity($id)
{
@ -290,8 +240,15 @@ class Storecove
return $r;
}
public function updateLegalEntity($id, array $data)
/**
* UpdateLegalEntity
*
* @param int $id
* @param array $data
* @return void
*/
public function updateLegalEntity(int $id, array $data)
{
$uri = "legal_entities/{$id}";
@ -305,7 +262,17 @@ class Storecove
return $r;
}
/**
* AddIdentifier
*
* Add a Peppol identifier to the legal entity
*
* @param int $legal_entity_id
* @param string $identifier
* @param string $scheme
* @return mixed
*/
public function addIdentifier(int $legal_entity_id, string $identifier, string $scheme)
{
$uri = "legal_entities/{$legal_entity_id}/peppol_identifiers";
@ -325,6 +292,21 @@ class Storecove
return $r;
}
/**
* deleteIdentifier
*
* @param int $legal_entity_id
* @return bool
*/
public function deleteIdentifier(int $legal_entity_id): bool
{
$uri = "/legal_entities/{$legal_entity_id}";
$r = $this->httpClient($uri, (HttpVerb::DELETE)->value, []);
return $r->successful();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private function getHeaders(array $headers = [])
@ -340,9 +322,25 @@ class Storecove
private function httpClient(string $uri, string $verb, array $data, ?array $headers = [])
{
$r = Http::withToken(config('ninja.storecove_api_key'))
->withHeaders($this->getHeaders($headers))
->{$verb}("{$this->base_url}{$uri}", $data);
try {
$r = Http::withToken(config('ninja.storecove_api_key'))
->withHeaders($this->getHeaders($headers))
->{$verb}("{$this->base_url}{$uri}", $data)->throw();
}
catch (ClientException $e) {
// 4xx errors
nlog("Client error: " . $e->getMessage());
nlog("\nResponse body: " . $e->getResponse()->getBody()->getContents());
} catch (ServerException $e) {
// 5xx errors
nlog("Server error: " . $e->getMessage());
nlog("\nResponse body: " . $e->getResponse()->getBody()->getContents());
} catch (RequestException $e) {
nlog("Request error: " . $e->getMessage());
if ($e->hasResponse()) {
nlog("\nResponse body: " . $e->getResponse()->getBody()->getContents());
}
}
return $r;
}

View File

@ -42,7 +42,6 @@ use InvoiceNinja\EInvoice\Models\Peppol\TaxTotal as PeppolTaxTotal;
use InvoiceNinja\EInvoice\Models\Peppol\InvoiceLineType\InvoiceLine;
use InvoiceNinja\EInvoice\Models\Peppol\TaxCategoryType\TaxCategory;
use InvoiceNinja\EInvoice\Models\Peppol\TaxSubtotalType\TaxSubtotal;
use InvoiceNinja\EInvoice\Models\Peppol\TaxScheme as PeppolTaxScheme;
use InvoiceNinja\EInvoice\Models\Peppol\AmountType\TaxExclusiveAmount;
use InvoiceNinja\EInvoice\Models\Peppol\AmountType\TaxInclusiveAmount;
use InvoiceNinja\EInvoice\Models\Peppol\AmountType\LineExtensionAmount;
@ -421,10 +420,14 @@ class Peppol extends AbstractService
$tax_subtotal->TaxableAmount = $taxable_amount;
$tc = new TaxCategory();
$tc->ID = $type_id == '2' ? 'HUR' : 'C62';
$id = new ID();
$id->value = $type_id == '2' ? 'HUR' : 'C62';
$tc->ID = $id;
$tc->Percent = $this->invoice->tax_rate1;
$ts = new PeppolTaxScheme();
$ts->ID = strlen($this->invoice->tax_name1 ?? '') > 1 ? $this->invoice->tax_name1 : '0';
$ts = new TaxScheme();
$id = new ID();
$id->value = strlen($this->invoice->tax_name1 ?? '') > 1 ? $this->invoice->tax_name1 : '0';
$ts->ID = $id;
$tc->TaxScheme = $ts;
$tax_subtotal->TaxCategory = $tc;
@ -453,10 +456,14 @@ class Peppol extends AbstractService
$tc = new TaxCategory();
$tc->ID = $type_id == '2' ? 'HUR' : 'C62';
$id = new ID();
$id->value = $type_id == '2' ? 'HUR' : 'C62';
$tc->ID = $id;
$tc->Percent = $this->invoice->tax_rate2;
$ts = new PeppolTaxScheme();
$ts->ID = $this->invoice->tax_name2;
$ts = new TaxScheme();
$id = new ID();
$id->value = $this->invoice->tax_name2;
$ts->ID = $id;
$tc->TaxScheme = $ts;
$tax_subtotal->TaxCategory = $tc;
@ -483,16 +490,21 @@ class Peppol extends AbstractService
$taxable_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->invoice->amount - $this->invoice->total_taxes : $this->invoice->amount;
$tax_subtotal->TaxableAmount = $taxable_amount;
$tc = new TaxCategory();
$tc->ID = $type_id == '2' ? 'HUR' : 'C62';
$id = new ID();
$id->value = $type_id == '2' ? 'HUR' : 'C62';
$tc->ID = $id;
$tc->Percent = $this->invoice->tax_rate3;
$ts = new PeppolTaxScheme();
$ts->ID = $this->invoice->tax_name3;
$ts = new TaxScheme();
$id = new ID();
$id->value = $this->invoice->tax_name3;
$ts->ID = $id;
$tc->TaxScheme = $ts;
$tax_subtotal->TaxCategory = $tc;
$tax_total = new TaxTotal();
$tax_total->TaxAmount = $tax_amount;
$tax_total->TaxSubtotal[] = $tax_subtotal;
@ -501,7 +513,6 @@ class Peppol extends AbstractService
}
return $taxes;
}
@ -516,7 +527,10 @@ class Peppol extends AbstractService
$_item->Description = $item->notes;
$line = new InvoiceLine();
$line->ID = $key + 1;
$id = new ID();
$id->value = (string) ($key+1);
$line->ID = $id;
$line->InvoicedQuantity = $item->quantity;
$lea = new LineExtensionAmount();
@ -538,7 +552,7 @@ class Peppol extends AbstractService
$price = new Price();
$pa = new PriceAmount();
$pa->currencyID = $this->invoice->client->currency()->code;
$pa->amount = $this->costWithDiscount($item) - ($this->invoice->uses_inclusive_taxes ? ($this->calcInclusiveLineTax($item->tax_rate1, $item->line_total) / $item->quantity) : 0);
$pa->amount = (string) ($this->costWithDiscount($item) - ($this->invoice->uses_inclusive_taxes ? ($this->calcInclusiveLineTax($item->tax_rate1, $item->line_total) / $item->quantity) : 0));
$price->PriceAmount = $pa;
$line->Price = $price;
@ -579,10 +593,15 @@ class Peppol extends AbstractService
$taxable_amount->amount = '0';
$tax_subtotal->TaxableAmount = $taxable_amount;
$tc = new TaxCategory();
$tc->ID = 'Z';
$tc->Percent = 0;
$ts = new PeppolTaxScheme();
$ts->ID = '0';
$id = new ID();
$id->value = 'Z';
$tc->ID = $id;
$tc->Percent = '0';
$ts = new TaxScheme();
$id = new ID();
$id->value = '0';
$ts->ID = $id;
$tc->TaxScheme = $ts;
$tax_subtotal->TaxCategory = $tc;
@ -612,10 +631,18 @@ class Peppol extends AbstractService
$taxable_amount->amount = $this->invoice->uses_inclusive_taxes ? $item->line_total - $tax_amount->amount : $item->line_total;
$tax_subtotal->TaxableAmount = $taxable_amount;
$tc = new TaxCategory();
$tc->ID = $item->type_id == '2' ? 'HUR' : 'C62';
$id = new ID();
$id->value = $item->type_id == '2' ? 'HUR' : 'C62';
$tc->ID = $id;
$tc->Percent = $item->tax_rate1;
$ts = new PeppolTaxScheme();
$ts->ID = $item->tax_name1;
$ts = new TaxScheme();
$id = new ID();
$id->value = $item->tax_name1;
$ts->ID = $id;
$tc->TaxScheme = $ts;
$tax_subtotal->TaxCategory = $tc;
@ -645,10 +672,18 @@ class Peppol extends AbstractService
$tc = new TaxCategory();
$tc->ID = $item->type_id == '2' ? 'HUR' : 'C62';
$id = new ID();
$id->value = $item->type_id == '2' ? 'HUR' : 'C62';
$tc->ID = $id;
$tc->Percent = $item->tax_rate2;
$ts = new PeppolTaxScheme();
$ts->ID = $item->tax_name2;
$ts = new TaxScheme();
$id = new ID();
$id->value = $item->tax_name2;
$ts->ID = $id;
$tc->TaxScheme = $ts;
$tax_subtotal->TaxCategory = $tc;
@ -658,7 +693,6 @@ class Peppol extends AbstractService
$tax_total->TaxSubtotal[] = $tax_subtotal;
$item_taxes[] = $tax_total;
}
@ -679,10 +713,18 @@ class Peppol extends AbstractService
$tc = new TaxCategory();
$tc->ID = $item->type_id == '2' ? 'HUR' : 'C62';
$id = new ID();
$id->value = $item->type_id == '2' ? 'HUR' : 'C62';
$tc->ID = $id;
$tc->Percent = $item->tax_rate3;
$ts = new PeppolTaxScheme();
$ts->ID = $item->tax_name3;
$ts = new TaxScheme();
$id = new ID();
$id->value = $item->tax_name3;
$ts->ID = $id;
$tc->TaxScheme = $ts;
$tax_subtotal->TaxCategory = $tc;

View File

@ -97,8 +97,8 @@ class SdkWrapper
8726400
);
$token->setAccessTokenExpiresAt($token_object->accessTokenExpiresAt);
$token->setRefreshTokenExpiresAt($token_object->refreshTokenExpiresAt);
$token->setAccessTokenExpiresAt($token_object->accessTokenExpiresAt); //@phpstan-ignore-line
$token->setRefreshTokenExpiresAt($token_object->refreshTokenExpiresAt); //@phpstan-ignore-line
$token->setAccessTokenValidationPeriodInSeconds(3600);
$token->setRefreshTokenValidationPeriodInSeconds(8726400);

View File

@ -14,6 +14,10 @@ return new class extends Migration
Schema::table('products', function (Blueprint $table){
$table->string('hash')->nullable();
});
Schema::table('companies', function (Blueprint $table){
$table->bigInteger('legal_entity_id')->nullable();
})
}
/**

View File

@ -37,4 +37,6 @@ parameters:
- '#Expression on left side of ?? is not nullable.#'
- '#Left side of && is always true.#'
- '#Right side of && is always true.#'
- '#is never read, only written.#'
- '#is never written#'

View File

@ -358,7 +358,19 @@ class StorecoveTest extends TestCase
}
*/
public function XXestCreateCHClient()
public function testCreateTestData()
{
$this->createESData();
$this->createATData();
$this->createDEData();
$this->createFRData();
$this->createITData();
$this->createROData();
$this->assertTrue(true);
}
public function testCreateCHClient()
{
Client::unguard();
@ -564,6 +576,115 @@ class StorecoveTest extends TestCase
}
private function createDEData()
{
// $this->routing_id = 293098;
$settings = CompanySettings::defaults();
$settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png';
$settings->website = 'www.invoiceninja.de';
$settings->address1 = 'Musterstraße 12';
$settings->address2 = 'Gebäude B';
$settings->city = 'Berlin';
$settings->state = 'Berlin';
$settings->postal_code = '10115';
$settings->phone = '030 1234567';
$settings->email = $this->faker->unique()->safeEmail();
$settings->country_id = '276'; // Germany's ISO country code
$settings->vat_number = 'DE123456789';
$settings->id_number = 'HRB 98765';
$settings->use_credits_payment = 'always';
$settings->timezone_id = '1'; // CET (Central European Time)
$settings->entity_send_time = 0;
$settings->e_invoice_type = 'PEPPOL';
$settings->currency_id = '3'; // Euro
$settings->classification = 'business';
$company = Company::factory()->create([
'account_id' => $this->account->id,
'settings' => $settings,
]);
$this->user->companies()->attach($company->id, [
'account_id' => $this->account->id,
'is_owner' => true,
'is_admin' => 1,
'is_locked' => 0,
'permissions' => '',
'notifications' => CompanySettings::notificationAdminDefaults(),
'settings' => null,
]);
Client::unguard();
$c =
Client::create([
'company_id' => $company->id,
'user_id' => $this->user->id,
'name' => 'Beispiel GmbH',
'website' => 'https://www.beispiel.de',
'private_notes' => 'Dies sind private Notizen für den Testkunden.',
'balance' => 0,
'paid_to_date' => 0,
'vat_number' => 'DE123456789', // German VAT number with DE prefix
'id_number' => 'HRB 12345', // Typical format for German company registration numbers
'custom_value1' => '2024-07-22 10:00:00',
'custom_value2' => 'blau', // German for blue
'custom_value3' => 'beispielwort', // German for sample word
'custom_value4' => 'test@beispiel.de',
'address1' => 'Beispielstraße 123',
'address2' => '2. Stock, Büro 45',
'city' => 'Berlin',
'state' => 'Berlin',
'postal_code' => '10115',
'country_id' => '276', // Germany
'shipping_address1' => 'Beispielstraße 123',
'shipping_address2' => '2. Stock, Büro 45',
'shipping_city' => 'Berlin',
'shipping_state' => 'Berlin',
'shipping_postal_code' => '10115',
'shipping_country_id' => '276', // Germany
'settings' => ClientSettings::Defaults(),
'client_hash' => \Illuminate\Support\Str::random(32),
'routing_id' => 'DEDEDE',
]);
$item = new InvoiceItem();
$item->product_key = "Product Key";
$item->notes = "Product Description";
$item->cost = 10;
$item->quantity = 10;
$item->tax_rate1 = 19;
$item->tax_name1 = 'mwst';
$invoice = Invoice::factory()->create([
'company_id' => $company->id,
'user_id' => $this->user->id,
'client_id' => $c->id,
'discount' => 0,
'uses_inclusive_taxes' => false,
'status_id' => 1,
'tax_rate1' => 0,
'tax_name1' => '',
'tax_rate2' => 0,
'tax_rate3' => 0,
'tax_name2' => '',
'tax_name3' => '',
'line_items' => [$item],
'number' => 'DE-'.rand(1000, 100000),
'date' => now()->format('Y-m-d'),
'due_date' => now()->addDays(14)->format('Y-m-d'),
]);
$invoice = $invoice->calc()->getInvoice();
$invoice->service()->markSent()->save();
return $invoice;
}
private function createESData()
{
$this->routing_id = 293098;