From 329044dcccf01ac56580973b2203a0512262e29c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 16 Jul 2024 12:47:42 +1000 Subject: [PATCH 1/4] Create and update legal entities --- .../EDocument/Gateway/Storecove/Storecove.php | 114 +++++++++++++++++- 1 file changed, 110 insertions(+), 4 deletions(-) diff --git a/app/Services/EDocument/Gateway/Storecove/Storecove.php b/app/Services/EDocument/Gateway/Storecove/Storecove.php index 7c991aabc621..0dce32741c68 100644 --- a/app/Services/EDocument/Gateway/Storecove/Storecove.php +++ b/app/Services/EDocument/Gateway/Storecove/Storecove.php @@ -11,6 +11,7 @@ namespace App\Services\EDocument\Gateway; +use App\Models\Company; use Illuminate\Support\Facades\Http; @@ -25,6 +26,8 @@ enum HttpVerb: string class Storecove { + private string $base_url = 'https://api.storecove.com/'; + private array $peppol_discovery = [ "documentTypes" => ["invoice"], "network" => "peppol", @@ -62,7 +65,7 @@ class Storecove { default => $network_data = array_merge($this->peppol_discovery, ['scheme' => $scheme, 'identifier' => $identifier]), }; - $uri = "https://api.storecove.com/api/v2/discovery/receives"; + $uri = "api/v2/discovery/receives"; $r = $this->httpClient($uri, (HttpVerb::POST)->value, $network_data, $this->getHeaders()); @@ -105,7 +108,7 @@ class Storecove { 'parseStrategy', 'ubl' ]; - $uri = "https://api.storecove.com/api/v2/document_submissions"; + $uri = "api/v2/document_submissions"; $r = $this->httpClient($uri, (HttpVerb::POST)->value, $payload, $this->getHeaders()); @@ -119,11 +122,114 @@ class Storecove { //document submission sending evidence public function getSendingEvidence(string $guid) { - $uri = "https://api.storecove.com/api/v2/document_submissions/{$guid}"; + $uri = "api/v2/document_submissions/{$guid}"; $r = $this->httpClient($uri, (HttpVerb::GET)->value, [], $this->getHeaders()); } + // { + // "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 + * + * @url https://www.storecove.com/docs/#_openapi_legalentitycreate + * @return mixed + */ + public function createLegalEntity(array $data, Company $company) + { + $uri = 'legal_entities'; + + $company_defaults = [ + 'acts_as_receiver' => true, + 'acts_as_sender' => true, + 'advertisements' => ['invoice'], + 'city' => $company->settings->city, + 'country' => $company->country()->iso_3166_2, + 'county' => $company->settings->state, + 'line1' => $company->settings->address1, + 'line2' => $company->settings->address2, + 'party_name' => $company->settings->name, + 'tax_registered' => true, + 'tenant_id' => $company->company_key, + 'zip' => $company->settings->postal_code, + ]; + + $payload = array_merge($company_defaults, $data); + + $r = $this->httpClient($uri, (HttpVerb::POST)->value, $payload); + + if($r->successful()) + return $r->json(); + + return $r; + + } + + public function updateLegalEntity($id, array $data) + { + + $uri = "legal_entities/{$id}"; + + $r = $this->httpClient($uri, (HttpVerb::PATCH)->value, $data); + + if($r->successful()) { + return $r->json(); + } + + return $r; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// private function getHeaders(array $headers = []) { @@ -139,7 +245,7 @@ class Storecove { $r = Http::withToken(config('ninja.storecove_api_key')) ->withHeaders($this->getHeaders($headers)) - ->{$verb}($uri, $data); + ->{$verb}("{$this->base_url}{$uri}", $data); return $r; } From c46936c9f31e69efe24b23b5b801c4d97694d2ba Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 16 Jul 2024 16:49:18 +1000 Subject: [PATCH 2/4] einvoice --- .../EDocument/Gateway/Storecove/Storecove.php | 53 ++++- app/Services/EDocument/Standards/Peppol.php | 7 + .../Einvoice/Storecove/StorecoveTest.php | 200 ++++++++++++++++++ 3 files changed, 250 insertions(+), 10 deletions(-) create mode 100644 tests/Integration/Einvoice/Storecove/StorecoveTest.php diff --git a/app/Services/EDocument/Gateway/Storecove/Storecove.php b/app/Services/EDocument/Gateway/Storecove/Storecove.php index 0dce32741c68..24d677543c58 100644 --- a/app/Services/EDocument/Gateway/Storecove/Storecove.php +++ b/app/Services/EDocument/Gateway/Storecove/Storecove.php @@ -9,12 +9,11 @@ * @license https://www.elastic.co/licensing/elastic-license */ -namespace App\Services\EDocument\Gateway; +namespace App\Services\EDocument\Gateway\Storecove; use App\Models\Company; use Illuminate\Support\Facades\Http; - enum HttpVerb: string { case POST = 'post'; @@ -26,7 +25,7 @@ enum HttpVerb: string class Storecove { - private string $base_url = 'https://api.storecove.com/'; + private string $base_url = 'https://api.storecove.com/api/v2/'; private array $peppol_discovery = [ "documentTypes" => ["invoice"], @@ -102,16 +101,35 @@ class Storecove { { $payload = [ - 'documentType' => 'invoice', - 'rawDocumentData' => base64_encode($document), - 'parse' => true, - 'parseStrategy', 'ubl' + "legalEntityId"=> 290868, + "idempotencyGuid"=> "61b37456-5f9e-4d56-b63b-3b1a23fa5c73", + "routing"=> [ + "eIdentifiers" => [ + [ + "scheme"=> "DE:LWID", + "id"=> "10101010-STO-10" + ], + ], + "emails"=> [ + "david@invoiceninja.com" + ], + "eIdentifiers"=> [] + ], + "document"=> [ + 'documentType' => 'invoice', + 'rawDocumentData' => ['document' => base64_encode($document)], + 'parse' => true, + 'parseStrategy', 'ubl' + ], ]; - $uri = "api/v2/document_submissions"; + $uri = "document_submissions"; $r = $this->httpClient($uri, (HttpVerb::POST)->value, $payload, $this->getHeaders()); + nlog($r->body()); + nlog($r->json()); + if($r->successful()) return $r->json()['guid']; @@ -122,7 +140,7 @@ class Storecove { //document submission sending evidence public function getSendingEvidence(string $guid) { - $uri = "api/v2/document_submissions/{$guid}"; + $uri = "document_submissions/{$guid}"; $r = $this->httpClient($uri, (HttpVerb::GET)->value, [], $this->getHeaders()); } @@ -214,6 +232,21 @@ class Storecove { } + public function getLegalEntity($id) + { + + $uri = "legal_entities/{$id}"; + + $r = $this->httpClient($uri, (HttpVerb::GET)->value, []); + + if($r->successful()) { + return $r->json(); + } + + return $r; + + } + public function updateLegalEntity($id, array $data) { @@ -242,7 +275,7 @@ class Storecove { private function httpClient(string $uri, string $verb, array $data, ?array $headers = []) { - +nlog("{$this->base_url}{$uri}"); $r = Http::withToken(config('ninja.storecove_api_key')) ->withHeaders($this->getHeaders($headers)) ->{$verb}("{$this->base_url}{$uri}", $data); diff --git a/app/Services/EDocument/Standards/Peppol.php b/app/Services/EDocument/Standards/Peppol.php index fca7ac0ecedb..572f69212838 100644 --- a/app/Services/EDocument/Standards/Peppol.php +++ b/app/Services/EDocument/Standards/Peppol.php @@ -15,6 +15,7 @@ use App\Models\Invoice; use App\Services\AbstractService; use App\Helpers\Invoice\InvoiceSum; use App\Helpers\Invoice\InvoiceSumInclusive; +use InvoiceNinja\EInvoice\EInvoice; use InvoiceNinja\EInvoice\Models\Peppol\ItemType\Item; use InvoiceNinja\EInvoice\Models\Peppol\PartyType\Party; use InvoiceNinja\EInvoice\Models\Peppol\PriceType\Price; @@ -78,6 +79,12 @@ class Peppol extends AbstractService } + public function toXml(): string + { + $e = new EInvoice(); + return $e->encode($this->p_invoice, 'xml'); + } + public function run() { $this->p_invoice->ID = $this->invoice->number; diff --git a/tests/Integration/Einvoice/Storecove/StorecoveTest.php b/tests/Integration/Einvoice/Storecove/StorecoveTest.php new file mode 100644 index 000000000000..9014662aba8e --- /dev/null +++ b/tests/Integration/Einvoice/Storecove/StorecoveTest.php @@ -0,0 +1,200 @@ +makeTestData(); + + if (config('ninja.testvars.travis') !== false || !config('ninja.storecove_api_key')) + $this->markTestSkipped("do not run in CI"); + } + + public function teseateLegalEntity() + { + + $data = [ + 'acts_as_receiver' => true, + 'acts_as_sender' => true, + 'advertisements' => ['invoice'], + 'city' => $this->company->settings->city, + 'country' => 'DE', + 'county' => $this->company->settings->state, + 'line1' => $this->company->settings->address1, + 'line2' => $this->company->settings->address2, + 'party_name' => $this->company->present()->name(), + 'tax_registered' => true, + 'tenant_id' => $this->company->company_key, + 'zip' => $this->company->settings->postal_code, + ]; + + $sc = new \App\Services\EDocument\Gateway\Storecove\Storecove(); + $r = $sc->createLegalEntity($data, $this->company); + + $this->assertIsArray($r); + + } + + + public function testGetLegalEntity() + { + + + $sc = new \App\Services\EDocument\Gateway\Storecove\Storecove(); + $r = $sc->getLegalEntity(290868); + + $this->assertIsArray($r); + + // nlog($r); + + } + + public function testSendDocument() + { + + $x = ' + 0061 + 2024-07-15 + 380 + + + + Eladio Ullrich I + + + Jasper Brook + Kodychester + 73445-5131 + South Dakota + + AT + + + + Jasper Brook + Kodychester + 73445-5131 + South Dakota + + AT + + + + small@example.com + + + + + + + Beispiel GmbH + + + 45 Hauptstraße + Berlin + 10115 + Berlin + + DE + + + + 45 Hauptstraße + Berlin + 10115 + Berlin + + DE + + + + TTKGjKW9Rv00LEr@example.com + + + + + + 215 + 215 + 215.00 + 215.00 + + + 1 + 1 + 10 + + 0.5 + + 10 + 0.5 + + C62 + 20 + + USt + + + + + + The Pro Plan NO MORE + ee + + + 10 + + + + 2 + 1 + 14 + + The Enterprise Plan + eee + + + 14 + + + + 3 + 1 + 191 + + Soluta provident. + k + + + 191 + + '; + + + $sc = new \App\Services\EDocument\Gateway\Storecove\Storecove(); + $sc->sendDocument($x); + + } + +} From 05019e7575d19a3b7c30e79f8165b5c8555a414a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 17 Jul 2024 07:31:56 +1000 Subject: [PATCH 3/4] Adjustments for balance check in auto bill --- .../Invoice/InvoiceItemSumInclusive.php | 2 +- .../Stripe/Jobs/PaymentIntentWebhook.php | 9 +++++++ .../EDocument/Gateway/Storecove/Storecove.php | 14 ++++------- app/Services/Invoice/AutoBillInvoice.php | 2 +- .../Einvoice/Storecove/StorecoveTest.php | 24 +++++++++++++++++-- 5 files changed, 38 insertions(+), 13 deletions(-) diff --git a/app/Helpers/Invoice/InvoiceItemSumInclusive.php b/app/Helpers/Invoice/InvoiceItemSumInclusive.php index eacb13afb9e5..da4c3e1f3aa4 100644 --- a/app/Helpers/Invoice/InvoiceItemSumInclusive.php +++ b/app/Helpers/Invoice/InvoiceItemSumInclusive.php @@ -411,7 +411,7 @@ class InvoiceItemSumInclusive $this->rule = new $class(); - if($this->rule->regionWithNoTaxCoverage($this->client->country->iso_3166_2)) { + if($this->rule->regionWithNoTaxCoverage($this->client->country->iso_3166_2 ?? false)) { return $this; } diff --git a/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php b/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php index 367a99c24206..1cde9a8fffd2 100644 --- a/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php +++ b/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php @@ -313,4 +313,13 @@ class PaymentIntentWebhook implements ShouldQueue $client->company, ); } + + public function failed($exception = null) + { + if ($exception) { + nlog($exception->getMessage()); + } + + config(['queue.failed.driver' => null]); + } } diff --git a/app/Services/EDocument/Gateway/Storecove/Storecove.php b/app/Services/EDocument/Gateway/Storecove/Storecove.php index 24d677543c58..fa84fbd615bf 100644 --- a/app/Services/EDocument/Gateway/Storecove/Storecove.php +++ b/app/Services/EDocument/Gateway/Storecove/Storecove.php @@ -32,7 +32,7 @@ class Storecove { "network" => "peppol", "metaScheme" => "iso6523-actorid-upis", "scheme" => "de:lwid", - "identifier" => "10101010-STO-10" + "identifier" => "DE:VAT" ]; private array $dbn_discovery = [ @@ -102,18 +102,14 @@ class Storecove { $payload = [ "legalEntityId"=> 290868, - "idempotencyGuid"=> "61b37456-5f9e-4d56-b63b-3b1a23fa5c73", + "idempotencyGuid"=> \Illuminate\Support\Str::uuid(), "routing"=> [ "eIdentifiers" => [ [ - "scheme"=> "DE:LWID", - "id"=> "10101010-STO-10" + "scheme" => "DE:VAT", + "id"=> "DE:VAT" ], - ], - "emails"=> [ - "david@invoiceninja.com" - ], - "eIdentifiers"=> [] + ] ], "document"=> [ 'documentType' => 'invoice', diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php index 5e6844fffaad..a9c9f415396d 100644 --- a/app/Services/Invoice/AutoBillInvoice.php +++ b/app/Services/Invoice/AutoBillInvoice.php @@ -62,7 +62,7 @@ class AutoBillInvoice extends AbstractService $this->invoice = $this->invoice->service()->markSent()->save(); /* Mark the invoice as paid if there is no balance */ - if ((int) $this->invoice->balance == 0) { + if (floatval($this->invoice->balance) == 0) { return $this->invoice->service()->markPaid()->save(); } diff --git a/tests/Integration/Einvoice/Storecove/StorecoveTest.php b/tests/Integration/Einvoice/Storecove/StorecoveTest.php index 9014662aba8e..d78c1ed5a4a6 100644 --- a/tests/Integration/Einvoice/Storecove/StorecoveTest.php +++ b/tests/Integration/Einvoice/Storecove/StorecoveTest.php @@ -31,7 +31,7 @@ class StorecoveTest extends TestCase $this->markTestSkipped("do not run in CI"); } - public function teseateLegalEntity() + public function testCreateLegalEntity() { $data = [ @@ -47,6 +47,10 @@ class StorecoveTest extends TestCase 'tax_registered' => true, 'tenant_id' => $this->company->company_key, 'zip' => $this->company->settings->postal_code, + 'peppol_identifiers' => [ + 'scheme' => 'DE:VAT', + 'id' => 'DE:VAT' + ], ]; $sc = new \App\Services\EDocument\Gateway\Storecove\Storecove(); @@ -56,6 +60,22 @@ class StorecoveTest extends TestCase } + // public function testUpdateLegalEntity() + // { + // $data = [ + // 'peppol_identifiers' => [ + // 'scheme' => 'DE:VAT', + // 'id' => 'DE:VAT' + // ], + // ]; + + // $sc = new \App\Services\EDocument\Gateway\Storecove\Storecove(); + // $r = $sc->updateLegalEntity(290868, $data); + + // $this->assertIsArray($r); + // nlog($r); + + // } public function testGetLegalEntity() { @@ -66,7 +86,7 @@ class StorecoveTest extends TestCase $this->assertIsArray($r); - // nlog($r); + nlog($r); } From 79ed0abf6bafa9a8ca11cb200d47b0d1bb50df2b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 17 Jul 2024 09:18:08 +1000 Subject: [PATCH 4/4] Add dubai timezone --- app/Models/Activity.php | 31 ++++++++++++++++ ...6_231556_2024_07_17_add_dubai_timezone.php | 37 +++++++++++++++++++ database/seeders/ConstantsSeeder.php | 1 + 3 files changed, 69 insertions(+) create mode 100644 database/migrations/2024_07_16_231556_2024_07_17_add_dubai_timezone.php diff --git a/app/Models/Activity.php b/app/Models/Activity.php index 13e52f5619ac..23804b0812f4 100644 --- a/app/Models/Activity.php +++ b/app/Models/Activity.php @@ -449,10 +449,41 @@ class Activity extends StaticModel $replacements['created_at'] = $this->created_at ?? ''; $replacements['ip'] = $this->ip ?? ''; + if($this->activity_type_id == 141) + $replacements = $this->harvestNoteEntities($replacements); + return $replacements; } + private function harvestNoteEntities(array $replacements): array + { + $entities = [ + ':invoice', + ':quote', + ':credit', + ':payment', + ':task', + ':expense', + ':purchase_order', + ':recurring_invoice', + ':recurring_expense', + ':client', + + ]; + + foreach($entities as $entity) + { + $entity_key = substr($entity, 1); + + if($this?->{$entity_key}) + $replacements = array_merge($replacements, $this->matchVar($entity)); + + } + + return $replacements; + } + private function matchVar(string $variable) { $system = ctrans('texts.system'); diff --git a/database/migrations/2024_07_16_231556_2024_07_17_add_dubai_timezone.php b/database/migrations/2024_07_16_231556_2024_07_17_add_dubai_timezone.php new file mode 100644 index 000000000000..6ee5ff904ed0 --- /dev/null +++ b/database/migrations/2024_07_16_231556_2024_07_17_add_dubai_timezone.php @@ -0,0 +1,37 @@ +id = 115; + $t->name = 'Asia/Dubai'; + $t->location = '(GMT+04:00) Dubai'; + $t->utc_offset = 14400; + $t->save(); + + } + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // + } +}; diff --git a/database/seeders/ConstantsSeeder.php b/database/seeders/ConstantsSeeder.php index a1e4410367f0..eb5e99cc573b 100644 --- a/database/seeders/ConstantsSeeder.php +++ b/database/seeders/ConstantsSeeder.php @@ -151,6 +151,7 @@ class ConstantsSeeder extends Seeder $timezones[] = ['name'=>'Asia/Magadan', 'location' => '(GMT+12:00) Magadan', 'utc_offset' => 43200]; $timezones[] = ['name'=>'Pacific/Auckland', 'location' => '(GMT+12:00) Auckland', 'utc_offset' => 43200]; $timezones[] = ['name'=>'Pacific/Fiji', 'location' => '(GMT+12:00) Fiji', 'utc_offset' => 43200]; + $timezones[] = ['name' => 'Asia/Dubai', 'location' => '(GMT+04:00) Dubai', 'utc_offset' => 14400]; $x = 1; foreach ($timezones as $timezone) {