diff --git a/app/DataMapper/QuickbooksSettings.php b/app/DataMapper/QuickbooksSettings.php index 553215f02d..43013d2b41 100644 --- a/app/DataMapper/QuickbooksSettings.php +++ b/app/DataMapper/QuickbooksSettings.php @@ -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 diff --git a/app/Models/Company.php b/app/Models/Company.php index b996e47d83..e977bdce0f 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -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 $activities * @property-read int|null $activities_count diff --git a/app/Services/EDocument/Gateway/Storecove/Storecove.php b/app/Services/EDocument/Gateway/Storecove/Storecove.php index cff9875076..7bf3427fea 100644 --- a/app/Services/EDocument/Gateway/Storecove/Storecove.php +++ b/app/Services/EDocument/Gateway/Storecove/Storecove.php @@ -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": "", - // "line1": "", - // "city": "", - // "zip": "", - // "country": "EH", - // "line2": "", - // "county": "", - // "tenant_id": "", - // "public": true, - // "advertisements": [ - // "invoice" - // ], - // "third_party_username": "", - // "third_party_password": "", - // "rea": { - // "province": "AR", - // "identifier": "", - // "capital": "", - // "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; } diff --git a/app/Services/EDocument/Standards/Peppol.php b/app/Services/EDocument/Standards/Peppol.php index f2d3722b16..cced78cc91 100644 --- a/app/Services/EDocument/Standards/Peppol.php +++ b/app/Services/EDocument/Standards/Peppol.php @@ -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; diff --git a/app/Services/Quickbooks/SdkWrapper.php b/app/Services/Quickbooks/SdkWrapper.php index eda0006b33..946353abdf 100644 --- a/app/Services/Quickbooks/SdkWrapper.php +++ b/app/Services/Quickbooks/SdkWrapper.php @@ -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); diff --git a/database/migrations/2024_08_26_055523_add_qb_product_hash.php b/database/migrations/2024_08_26_055523_add_qb_product_hash.php index 89177311e7..20dc98df6a 100644 --- a/database/migrations/2024_08_26_055523_add_qb_product_hash.php +++ b/database/migrations/2024_08_26_055523_add_qb_product_hash.php @@ -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(); + }) } /** diff --git a/phpstan.neon b/phpstan.neon index 1811c3080b..aeb4c03e79 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -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#' \ No newline at end of file diff --git a/tests/Integration/Einvoice/Storecove/StorecoveTest.php b/tests/Integration/Einvoice/Storecove/StorecoveTest.php index 155bcb3a1a..fb29790042 100644 --- a/tests/Integration/Einvoice/Storecove/StorecoveTest.php +++ b/tests/Integration/Einvoice/Storecove/StorecoveTest.php @@ -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;