Integration of Verifactu with UI

This commit is contained in:
David Bomba 2025-08-14 08:36:55 +10:00
parent af926a394c
commit 1252cdf7ae
10 changed files with 96 additions and 44 deletions

View File

@ -330,8 +330,13 @@ class BaseModel extends Model
} }
// special catch here for einvoicing eventing // special catch here for einvoicing eventing
if ($event_id == Webhook::EVENT_SENT_INVOICE && ($this instanceof Invoice) && $this->backup->guid == "" && $this->client->peppolSendingEnabled()) { if ($event_id == Webhook::EVENT_SENT_INVOICE && ($this instanceof Invoice) && $this->backup->guid == "") {
\App\Services\EDocument\Jobs\SendEDocument::dispatch(get_class($this), $this->id, $this->company->db); if($this->client->peppolSendingEnabled()) {
\App\Services\EDocument\Jobs\SendEDocument::dispatch(get_class($this), $this->id, $this->company->db);
}
elseif($this->company->verifactuEnabled()) {
$this->service()->sendVerifactu();
}
} }
} }

View File

@ -1,4 +1,13 @@
<?php <?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Models; namespace App\Models;
@ -57,7 +66,6 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Quote> $quotes * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Quote> $quotes
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Expense> $expenses * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Expense> $expenses
*
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class Project extends BaseModel class Project extends BaseModel
@ -134,17 +142,17 @@ class Project extends BaseModel
return $this->hasMany(Task::class); return $this->hasMany(Task::class);
} }
public function expenses(): HasMany public function expenses(): \Illuminate\Database\Eloquent\Relations\HasMany
{ {
return $this->hasMany(Expense::class); return $this->hasMany(Expense::class);
} }
public function invoices(): HasMany public function invoices(): \Illuminate\Database\Eloquent\Relations\HasMany
{ {
return $this->hasMany(Invoice::class)->withTrashed(); return $this->hasMany(Invoice::class)->withTrashed();
} }
public function quotes(): HasMany public function quotes(): \Illuminate\Database\Eloquent\Relations\HasMany
{ {
return $this->hasMany(Quote::class); return $this->hasMany(Quote::class);
} }

View File

@ -192,7 +192,7 @@ class Verifactu extends AbstractService
$response = $this->aeat_client->send($soapXml); $response = $this->aeat_client->send($soapXml);
if($response['success']){ if($response['success'] || $response['status'] == 'ParcialmenteCorrecto'){
$this->writeLog($response); $this->writeLog($response);
} }

View File

@ -35,6 +35,10 @@ class AeatClient
$this->certificate = $this->certificate ?? config('services.verifactu.certificate'); $this->certificate = $this->certificate ?? config('services.verifactu.certificate');
$this->ssl_key = $this->ssl_key ?? config('services.verifactu.ssl_key'); $this->ssl_key = $this->ssl_key ?? config('services.verifactu.ssl_key');
if(config('services.verifactu.test_mode')) {
$this->setTestMode();
}
return $this; return $this;
} }

View File

@ -44,7 +44,6 @@ class SendToAeat implements ShouldQueue
/** /**
* Modification Invoices - (modify) * Modification Invoices - (modify)
* - If Amount > 0 - We generates a F3 document which replaces the original invoice. And becomes the new invoice.
* - If Amount < 0 - We generate a R2 document which is a negative modification on the original invoice. * - If Amount < 0 - We generate a R2 document which is a negative modification on the original invoice.
* Create Invoices - (create) Generates a F1 document. * Create Invoices - (create) Generates a F1 document.
* Cancellation Invoices - (cancel) Generates a R3 document with full negative values of the original invoice. * Cancellation Invoices - (cancel) Generates a R3 document with full negative values of the original invoice.
@ -111,7 +110,12 @@ class SendToAeat implements ShouldQueue
nlog($response); nlog($response);
$this->writeActivity($invoice, $response['success'] ? Activity::VERIFACTU_INVOICE_SENT : Activity::VERIFACTU_INVOICE_SENT_FAILURE, $response['message']); $message = '';
if (isset($response['errors'][0]['message'])) {
$message = $response['errors'][0]['message'];
}
$this->writeActivity($invoice, $response['success'] ? Activity::VERIFACTU_INVOICE_SENT : Activity::VERIFACTU_INVOICE_SENT_FAILURE, $message);
$this->systemLog($invoice, $response, $response['success'] ? SystemLog::EVENT_VERIFACTU_SUCCESS : SystemLog::EVENT_VERIFACTU_FAILURE, SystemLog::TYPE_VERIFACTU_INVOICE); $this->systemLog($invoice, $response, $response['success'] ? SystemLog::EVENT_VERIFACTU_SUCCESS : SystemLog::EVENT_VERIFACTU_FAILURE, SystemLog::TYPE_VERIFACTU_INVOICE);
} }
@ -120,6 +124,7 @@ class SendToAeat implements ShouldQueue
{ {
$verifactu = new Verifactu($invoice); $verifactu = new Verifactu($invoice);
$document = (new RegistroAlta($invoice))->run()->getInvoice(); $document = (new RegistroAlta($invoice))->run()->getInvoice();
$last_hash = $invoice->company->verifactu_logs()->first(); $last_hash = $invoice->company->verifactu_logs()->first();
@ -127,25 +132,36 @@ class SendToAeat implements ShouldQueue
$huella = $this->cancellationHash($document, $last_hash->hash); $huella = $this->cancellationHash($document, $last_hash->hash);
$cancellation = $document->createCancellation(); $cancellation = $document->createCancellation();
$cancellation->setHuella($huella); $cancellation->setHuella($huella);
$soapXml = $cancellation->toSoapEnvelope(); $soapXml = $cancellation->toSoapEnvelope();
$response = $verifactu->send($soapXml); $response = $verifactu->setInvoice($document)
->setHuella($huella)
->setPreviousHash($last_hash->hash)
->send($soapXml);
nlog($response); nlog($response);
$message = '';
if($response['success']) { if($response['success']) {
//if successful, we need to pop this invoice from the child array of the parent invoice! //if successful, we need to pop this invoice from the child array of the parent invoice!
$parent = Invoice::withTrashed()->find($invoice->backup->parent_invoice_id); $parent = Invoice::withTrashed()->find($invoice->backup->parent_invoice_id);
if($parent) {
$parent->backup->child_invoice_ids = $parent->backup->child_invoice_ids->reject(fn($id) => $id === $invoice->hashed_id); if($parent) {
$parent->saveQuietly(); $parent->backup->child_invoice_ids = $parent->backup->child_invoice_ids->reject(fn($id) => $id === $invoice->hashed_id);
} $parent->saveQuietly();
}
}
if(isset($response['errors'][0]['message'])){
$message = $response['errors'][0]['message'];
} }
//@todo - verifactu logging //@todo - verifactu logging
$this->writeActivity($invoice, $response['success'] ? Activity::VERIFACTU_CANCELLATION_SENT : Activity::VERIFACTU_CANCELLATION_SENT_FAILURE, $response['message']); $this->writeActivity($invoice, $response['success'] ? Activity::VERIFACTU_CANCELLATION_SENT : Activity::VERIFACTU_CANCELLATION_SENT_FAILURE, $message);
$this->systemLog($invoice, $response, $response['success'] ? SystemLog::EVENT_VERIFACTU_SUCCESS : SystemLog::EVENT_VERIFACTU_FAILURE, SystemLog::TYPE_VERIFACTU_CANCELLATION); $this->systemLog($invoice, $response, $response['success'] ? SystemLog::EVENT_VERIFACTU_SUCCESS : SystemLog::EVENT_VERIFACTU_FAILURE, SystemLog::TYPE_VERIFACTU_CANCELLATION);
} }

View File

@ -17,11 +17,13 @@ use App\Repositories\InvoiceRepository;
use App\Services\AbstractService; use App\Services\AbstractService;
use App\Utils\Ninja; use App\Utils\Ninja;
use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\GeneratesCounter;
use App\Utils\Traits\MakesHash;
use stdClass; use stdClass;
class HandleCancellation extends AbstractService class HandleCancellation extends AbstractService
{ {
use GeneratesCounter; use GeneratesCounter;
use MakesHash;
public function __construct(private Invoice $invoice, private ?string $reason = null) public function __construct(private Invoice $invoice, private ?string $reason = null)
{ {

View File

@ -678,7 +678,6 @@ class InvoiceService
} }
/** /**
* sendVerifactu * sendVerifactu
* *

View File

@ -81,8 +81,13 @@ class TriggeredActions extends AbstractService
$company->save(); $company->save();
} }
if($this->request->has('retry_e_send') && $this->request->input('retry_e_send') == 'true' && strlen($this->invoice->backup->guid ?? '') == 0 && $this->invoice->client->peppolSendingEnabled()) { if($this->request->has('retry_e_send') && $this->request->input('retry_e_send') == 'true' && strlen($this->invoice->backup->guid ?? '') == 0) {
\App\Services\EDocument\Jobs\SendEDocument::dispatch(get_class($this->invoice), $this->invoice->id, $this->invoice->company->db); if($this->invoice->client->peppolSendingEnabled()) {
\App\Services\EDocument\Jobs\SendEDocument::dispatch(get_class($this->invoice), $this->invoice->id, $this->invoice->company->db);
}
elseif($this->invoice->company->verifactuEnabled()) {
$this->invoice->service()->sendVerifactu();
}
} }
if($this->request->has('redirect')) { if($this->request->has('redirect')) {

62
composer.lock generated
View File

@ -5168,16 +5168,16 @@
}, },
{ {
"name": "laravel/framework", "name": "laravel/framework",
"version": "v11.45.1", "version": "v11.45.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/framework.git", "url": "https://github.com/laravel/framework.git",
"reference": "b09ba32795b8e71df10856a2694706663984a239" "reference": "d134bf11e2208c0c5bd488cf19e612ca176b820a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/b09ba32795b8e71df10856a2694706663984a239", "url": "https://api.github.com/repos/laravel/framework/zipball/d134bf11e2208c0c5bd488cf19e612ca176b820a",
"reference": "b09ba32795b8e71df10856a2694706663984a239", "reference": "d134bf11e2208c0c5bd488cf19e612ca176b820a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -5285,7 +5285,7 @@
"league/flysystem-read-only": "^3.25.1", "league/flysystem-read-only": "^3.25.1",
"league/flysystem-sftp-v3": "^3.25.1", "league/flysystem-sftp-v3": "^3.25.1",
"mockery/mockery": "^1.6.10", "mockery/mockery": "^1.6.10",
"orchestra/testbench-core": "^9.13.2", "orchestra/testbench-core": "^9.16.0",
"pda/pheanstalk": "^5.0.6", "pda/pheanstalk": "^5.0.6",
"php-http/discovery": "^1.15", "php-http/discovery": "^1.15",
"phpstan/phpstan": "^2.0", "phpstan/phpstan": "^2.0",
@ -5379,7 +5379,7 @@
"issues": "https://github.com/laravel/framework/issues", "issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework" "source": "https://github.com/laravel/framework"
}, },
"time": "2025-06-03T14:01:40+00:00" "time": "2025-08-13T20:28:00+00:00"
}, },
{ {
"name": "laravel/octane", "name": "laravel/octane",
@ -8238,16 +8238,16 @@
}, },
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
"version": "v5.6.0", "version": "v5.6.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nikic/PHP-Parser.git", "url": "https://github.com/nikic/PHP-Parser.git",
"reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56" "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56", "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2",
"reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56", "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -8266,7 +8266,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "5.0-dev" "dev-master": "5.x-dev"
} }
}, },
"autoload": { "autoload": {
@ -8290,9 +8290,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/nikic/PHP-Parser/issues", "issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0" "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1"
}, },
"time": "2025-07-27T20:03:57+00:00" "time": "2025-08-13T20:13:15+00:00"
}, },
{ {
"name": "nordigen/nordigen-php", "name": "nordigen/nordigen-php",
@ -8675,16 +8675,16 @@
}, },
{ {
"name": "open-telemetry/context", "name": "open-telemetry/context",
"version": "1.3.0", "version": "1.3.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/opentelemetry-php/context.git", "url": "https://github.com/opentelemetry-php/context.git",
"reference": "4d5d98f1d4311a55b8d07e3d4c06d2430b4e6efc" "reference": "438f71812242db3f196fb4c717c6f92cbc819be6"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/opentelemetry-php/context/zipball/4d5d98f1d4311a55b8d07e3d4c06d2430b4e6efc", "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/438f71812242db3f196fb4c717c6f92cbc819be6",
"reference": "4d5d98f1d4311a55b8d07e3d4c06d2430b4e6efc", "reference": "438f71812242db3f196fb4c717c6f92cbc819be6",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -8730,7 +8730,7 @@
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php" "source": "https://github.com/open-telemetry/opentelemetry-php"
}, },
"time": "2025-08-04T03:25:06+00:00" "time": "2025-08-13T01:12:00+00:00"
}, },
{ {
"name": "paragonie/constant_time_encoding", "name": "paragonie/constant_time_encoding",
@ -20559,23 +20559,23 @@
}, },
{ {
"name": "sebastian/recursion-context", "name": "sebastian/recursion-context",
"version": "6.0.2", "version": "6.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git", "url": "https://github.com/sebastianbergmann/recursion-context.git",
"reference": "694d156164372abbd149a4b85ccda2e4670c0e16" "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc",
"reference": "694d156164372abbd149a4b85ccda2e4670c0e16", "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=8.2" "php": ">=8.2"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^11.0" "phpunit/phpunit": "^11.3"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -20611,15 +20611,27 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/recursion-context/issues", "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
"security": "https://github.com/sebastianbergmann/recursion-context/security/policy", "security": "https://github.com/sebastianbergmann/recursion-context/security/policy",
"source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3"
}, },
"funding": [ "funding": [
{ {
"url": "https://github.com/sebastianbergmann", "url": "https://github.com/sebastianbergmann",
"type": "github" "type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context",
"type": "tidelift"
} }
], ],
"time": "2024-07-03T05:10:34+00:00" "time": "2025-08-13T04:42:22+00:00"
}, },
{ {
"name": "sebastian/type", "name": "sebastian/type",

View File

@ -151,5 +151,6 @@ return [
'certificate' => env('VERIFACTU_CERTIFICATE', ''), 'certificate' => env('VERIFACTU_CERTIFICATE', ''),
'ssl_key' => env('VERIFACTU_SSL_KEY', ''), 'ssl_key' => env('VERIFACTU_SSL_KEY', ''),
'sender_name' => env('VERIFACTU_SENDER_NAME', 'CERTIFICADO FISICA PRUEBAS'), 'sender_name' => env('VERIFACTU_SENDER_NAME', 'CERTIFICADO FISICA PRUEBAS'),
'test_mode' => env('VERIFACTU_TEST_MODE', false),
], ],
]; ];