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/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/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 7c991aabc621..fa84fbd615bf 100644 --- a/app/Services/EDocument/Gateway/Storecove/Storecove.php +++ b/app/Services/EDocument/Gateway/Storecove/Storecove.php @@ -9,11 +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'; @@ -25,12 +25,14 @@ enum HttpVerb: string class Storecove { + private string $base_url = 'https://api.storecove.com/api/v2/'; + private array $peppol_discovery = [ "documentTypes" => ["invoice"], "network" => "peppol", "metaScheme" => "iso6523-actorid-upis", "scheme" => "de:lwid", - "identifier" => "10101010-STO-10" + "identifier" => "DE:VAT" ]; private array $dbn_discovery = [ @@ -62,7 +64,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()); @@ -99,16 +101,31 @@ class Storecove { { $payload = [ - 'documentType' => 'invoice', - 'rawDocumentData' => base64_encode($document), - 'parse' => true, - 'parseStrategy', 'ubl' + "legalEntityId"=> 290868, + "idempotencyGuid"=> \Illuminate\Support\Str::uuid(), + "routing"=> [ + "eIdentifiers" => [ + [ + "scheme" => "DE:VAT", + "id"=> "DE:VAT" + ], + ] + ], + "document"=> [ + 'documentType' => 'invoice', + 'rawDocumentData' => ['document' => base64_encode($document)], + 'parse' => true, + 'parseStrategy', 'ubl' + ], ]; - $uri = "https://api.storecove.com/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']; @@ -119,11 +136,129 @@ class Storecove { //document submission sending evidence public function getSendingEvidence(string $guid) { - $uri = "https://api.storecove.com/api/v2/document_submissions/{$guid}"; + $uri = "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 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) + { + + $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 = []) { @@ -136,10 +271,10 @@ 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}($uri, $data); + ->{$verb}("{$this->base_url}{$uri}", $data); return $r; } 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/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/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) { diff --git a/tests/Integration/Einvoice/Storecove/StorecoveTest.php b/tests/Integration/Einvoice/Storecove/StorecoveTest.php new file mode 100644 index 000000000000..d78c1ed5a4a6 --- /dev/null +++ b/tests/Integration/Einvoice/Storecove/StorecoveTest.php @@ -0,0 +1,220 @@ +makeTestData(); + + if (config('ninja.testvars.travis') !== false || !config('ninja.storecove_api_key')) + $this->markTestSkipped("do not run in CI"); + } + + public function testCreateLegalEntity() + { + + $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, + 'peppol_identifiers' => [ + 'scheme' => 'DE:VAT', + 'id' => 'DE:VAT' + ], + ]; + + $sc = new \App\Services\EDocument\Gateway\Storecove\Storecove(); + $r = $sc->createLegalEntity($data, $this->company); + + $this->assertIsArray($r); + + } + + // 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() + { + + + $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); + + } + +}