From c5c9c4325ea797a311f51714f6e5ba6633e366f8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 13 Aug 2025 14:26:03 +1000 Subject: [PATCH] Wiring up verifactu sending --- .../Invoice/VerifactuAmountCheck.php | 6 +- app/Models/Activity.php | 8 +++ app/Models/SystemLog.php | 12 ++++ .../Standards/Verifactu/SendToAeat.php | 66 +++++++++++------- app/Services/Invoice/HandleCancellation.php | 69 ++++++++++++------- app/Services/Invoice/InvoiceService.php | 22 +++--- lang/en/texts.php | 4 ++ 7 files changed, 118 insertions(+), 69 deletions(-) diff --git a/app/Http/ValidationRules/Invoice/VerifactuAmountCheck.php b/app/Http/ValidationRules/Invoice/VerifactuAmountCheck.php index 2edf7cc547..7e40828a18 100644 --- a/app/Http/ValidationRules/Invoice/VerifactuAmountCheck.php +++ b/app/Http/ValidationRules/Invoice/VerifactuAmountCheck.php @@ -39,13 +39,13 @@ class VerifactuAmountCheck implements ValidationRule $company = $user->company(); - if ($company->verifactuEnabled()) { + if ($company->verifactuEnabled()) { // Company level check if Verifactu is enabled $client = Client::withTrashed()->find($this->input['client_id']); - if($client->country->iso_3166_2 !== 'ES') { + if($client->country->iso_3166_2 !== 'ES') { // Client level check if client is in Spain return; - } + } $invoice = false; $child_invoices = false; diff --git a/app/Models/Activity.php b/app/Models/Activity.php index 65afdd9ddf..ddbb494b59 100644 --- a/app/Models/Activity.php +++ b/app/Models/Activity.php @@ -278,6 +278,14 @@ class Activity extends StaticModel public const E_EXPENSE_CREATED = 148; public const EMAIL_CREDIT = 149; + + public const VERIFACTU_INVOICE_SENT = 150; + + public const VERIFACTU_INVOICE_SENT_FAILURE = 151; + + public const VERIFACTU_CANCELLATION_SENT = 152; + + public const VERIFACTU_CANCELLATION_SENT_FAILURE = 153; protected $casts = [ 'is_system' => 'boolean', diff --git a/app/Models/SystemLog.php b/app/Models/SystemLog.php index 4befc0753b..f2b5de801d 100644 --- a/app/Models/SystemLog.php +++ b/app/Models/SystemLog.php @@ -78,6 +78,8 @@ class SystemLog extends Model public const CATEGORY_LOG = 6; + public const CATEGORY_VERIFACTU = 7; + /* Event IDs*/ public const EVENT_PAYMENT_RECONCILIATION_FAILURE = 10; @@ -115,6 +117,10 @@ class SystemLog extends Model public const EVENT_INBOUND_MAIL_BLOCKED = 62; + public const EVENT_VERIFACTU_FAILURE = 70; + + public const EVENT_VERIFACTU_SUCCESS = 71; + /*Type IDs*/ public const TYPE_PAYPAL = 300; @@ -180,6 +186,12 @@ class SystemLog extends Model public const TYPE_GENERIC = 900; + public const TYPE_VERIFACTU_CANCELLATION = 1000; + + public const TYPE_VERIFACTU_INVOICE = 1001; + + public const TYPE_VERIFACTU_RECTIFICATION = 1002; + protected $fillable = [ 'client_id', 'company_id', diff --git a/app/Services/EDocument/Standards/Verifactu/SendToAeat.php b/app/Services/EDocument/Standards/Verifactu/SendToAeat.php index 7bbf08f477..4aa1e5fbfa 100644 --- a/app/Services/EDocument/Standards/Verifactu/SendToAeat.php +++ b/app/Services/EDocument/Standards/Verifactu/SendToAeat.php @@ -15,12 +15,16 @@ use Mail; use App\Utils\Ninja; use App\Models\Company; use App\Models\Invoice; +use App\Models\Activity; +use App\Models\SystemLog; use App\Libraries\MultiDB; use Illuminate\Bus\Queueable; +use App\Jobs\Util\SystemLogger; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Cache; use Illuminate\Mail\Mailables\Address; use Illuminate\Queue\SerializesModels; +use App\Repositories\ActivityRepository; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -63,16 +67,13 @@ class SendToAeat implements ShouldQueue return [5, 30, 240, 3600, 7200]; } - public function handle() + public function handle(ActivityRepository $activity_repository) { MultiDB::setDB($this->company->db); $invoice = Invoice::withTrashed()->find($this->invoice_id); switch($this->action) { - case 'modify': - $this->modifyInvoice($invoice); - break; case 'create': $this->createInvoice($invoice); break; @@ -92,27 +93,7 @@ class SendToAeat implements ShouldQueue * @param Invoice $invoice * @return void */ - public function modifyInvoice(Invoice $invoice) - { - - $verifactu = new Verifactu($invoice); - $verifactu->run(); - - $envelope = $verifactu->getEnvelope(); - - $response = $verifactu->send($envelope); - - nlog($response); - - // if($invoice->amount >= 0) { - // $document = (new RegistroAlta($invoice))->run()->getInvoice(); - // } - // else { - // $document = (new RegistroRectificacion($invoice))->run()->getInvoice(); - // } - - } - + public function createInvoice(Invoice $invoice) { $verifactu = new Verifactu($invoice); @@ -124,6 +105,9 @@ class SendToAeat implements ShouldQueue nlog($response); + $this->writeActivity($invoice, $response['success'] ? Activity::VERIFACTU_INVOICE_SENT : Activity::VERIFACTU_INVOICE_SENT_FAILURE, $response['message']); + $this->systemLog($invoice, $response, $response['success'] ? SystemLog::EVENT_VERIFACTU_SUCCESS : SystemLog::EVENT_VERIFACTU_FAILURE, SystemLog::TYPE_VERIFACTU_INVOICE); + } public function cancelInvoice(Invoice $invoice) @@ -153,12 +137,15 @@ class SendToAeat implements ShouldQueue $parent->saveQuietly(); } } + //@todo - verifactu logging + $this->writeActivity($invoice, $response['success'] ? Activity::VERIFACTU_CANCELLATION_SENT : Activity::VERIFACTU_CANCELLATION_SENT_FAILURE, $response['message']); + $this->systemLog($invoice, $response, $response['success'] ? SystemLog::EVENT_VERIFACTU_SUCCESS : SystemLog::EVENT_VERIFACTU_FAILURE, SystemLog::TYPE_VERIFACTU_CANCELLATION); } public function middleware() { - return [new WithoutOverlapping("send_to_aeat_{$this->company->company_key}")]; + return [(new WithoutOverlapping("send_to_aeat_{$this->company->company_key}"))->releaseAfter(30)->expireAfter(30)]; } public function failed($exception = null) @@ -166,7 +153,34 @@ class SendToAeat implements ShouldQueue nlog($exception); } + private function writeActivity(Invoice $invoice, int $activity_id, string $notes = ''): void + { + $activity = new Activity(); + $activity->user_id = $invoice->user_id; + $activity->client_id = $invoice->client_id; + $activity->company_id = $invoice->company_id; + $activity->account_id = $invoice->company->account_id; + $activity->activity_type_id = $activity_id; + $activity->invoice_id = $invoice->id; + $activity->notes = str_replace('"', '', $notes); + $activity->is_system = true; + $activity->save(); + + } + + private function systemLog(Invoice $invoice, array $data, int $event_id, int $type_id): void + { + (new SystemLogger( + $data, + SystemLog::CATEGORY_VERIFACTU, + $event_id, + $type_id, + $invoice->client, + $invoice->company + ) + )->handle(); + } /** * cancellationHash diff --git a/app/Services/Invoice/HandleCancellation.php b/app/Services/Invoice/HandleCancellation.php index c6b5765a2e..02cff4bc60 100644 --- a/app/Services/Invoice/HandleCancellation.php +++ b/app/Services/Invoice/HandleCancellation.php @@ -76,39 +76,56 @@ class HandleCancellation extends AbstractService private function verifactuCancellation(): Invoice { - $replicated_invoice = $this->invoice->replicate(); - $this->invoice = $this->invoice->service()->setStatus(Invoice::STATUS_CANCELLED)->save(); $this->invoice->service()->workFlow()->save(); - $replicated_invoice->status_id = Invoice::STATUS_DRAFT; - $replicated_invoice->date = now()->format('Y-m-d'); - $replicated_invoice->due_date = null; - $replicated_invoice->partial = 0; - $replicated_invoice->partial_due_date = null; - $replicated_invoice->number = null; - $replicated_invoice->amount = 0; - $replicated_invoice->balance = 0; - $replicated_invoice->paid_to_date = 0; + // R2 Cancellation - do not create a separate document + if($this->invoice->backup->document_type === 'R2'){ - $items = $replicated_invoice->line_items; + $parent = Invoice::withTrashed()->find($this->decodePrimaryKey($this->invoice->backup->parent_invoice_id)); - foreach($items as &$item) { - $item->quantity = $item->quantity * -1; + if(!$parent) { + return $this->invoice; + } + + $parent->backup->adjustable_amount -= $this->invoice->amount; + $parent->backup->child_invoice_ids->reject(fn($id) => $id === $this->invoice->hashed_id); + $parent->save(); + + $this->invoice->service()->cancelVerifactu(); + } + else { + $replicated_invoice = $this->invoice->replicate(); + $replicated_invoice->status_id = Invoice::STATUS_DRAFT; + $replicated_invoice->date = now()->format('Y-m-d'); + $replicated_invoice->due_date = null; + $replicated_invoice->partial = 0; + $replicated_invoice->partial_due_date = null; + $replicated_invoice->number = null; + $replicated_invoice->amount = 0; + $replicated_invoice->balance = 0; + $replicated_invoice->paid_to_date = 0; + + $items = $replicated_invoice->line_items; + + foreach($items as &$item) { + $item->quantity = $item->quantity * -1; + } + + $replicated_invoice->line_items = $items; + $replicated_invoice->backup->parent_invoice_id = $this->invoice->hashed_id; + $replicated_invoice->backup->parent_invoice_number = $this->invoice->number; + $replicated_invoice->backup->document_type = 'R2'; // Full Credit Note Generated for the invoice + + $invoice_repository = new InvoiceRepository(); + $replicated_invoice = $invoice_repository->save([], $replicated_invoice); + $replicated_invoice->service()->markSent()->sendVerifactu()->save(); + + $this->invoice->backup->child_invoice_ids->push($replicated_invoice->hashed_id); + + $this->invoice->saveQuietly(); } - $replicated_invoice->line_items = $items; - $replicated_invoice->backup->parent_invoice_id = $this->invoice->hashed_id; - $replicated_invoice->backup->parent_invoice_number = $this->invoice->number; - $replicated_invoice->backup->document_type = 'R2'; // Full Credit Note Generated for the invoice - - $invoice_repository = new InvoiceRepository(); - $replicated_invoice = $invoice_repository->save([], $replicated_invoice); - $replicated_invoice->service()->markSent()->sendVerifactu()->save(); - - $this->invoice->backup->child_invoice_ids->push($replicated_invoice->hashed_id); - - $this->invoice->saveQuietly(); $this->invoice->fresh(); event(new InvoiceWasCancelled($this->invoice, $this->invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); diff --git a/app/Services/Invoice/InvoiceService.php b/app/Services/Invoice/InvoiceService.php index 0c22ff1c33..55723af146 100644 --- a/app/Services/Invoice/InvoiceService.php +++ b/app/Services/Invoice/InvoiceService.php @@ -27,6 +27,7 @@ use Illuminate\Support\Facades\Storage; use App\Events\Invoice\InvoiceWasArchived; use App\Jobs\Inventory\AdjustProductInventory; use App\Libraries\Currency\Conversion\CurrencyApi; +use App\Services\EDocument\Standards\Verifactu\SendToAeat; class InvoiceService { @@ -248,7 +249,7 @@ class InvoiceService $this->invoice = (new MarkInvoiceDeleted($this->invoice))->run(); if($this->invoice->company->verifactuEnabled()) { - $this->deleteVerifactu(); + $this->cancelVerifactu(); } return $this; @@ -680,31 +681,24 @@ class InvoiceService /** * sendVerifactu - * @todo - send the invoice to AEAT - * ONLY send when the transaction is ES => ES - * Ensure we run all sending syncronously to ensure chronology * * @return self */ public function sendVerifactu(): self { - // if($this->invoice->company->verifactuEnabled()) { - // (new SendVerifactu($this->invoice))->handle(); - // } - + SendToAeat::dispatch($this->invoice->id, $this->invoice->company, 'create'); + return $this; } /** - * deleteVerifactu - * @todo - handle "cancelling" the invoice in AEAT + * cancelVerifactu + * * @return self */ - public function deleteVerifactu(): self + public function cancelVerifactu(): self { - // if($this->invoice->company->verifactuEnabled()) { - // (new DeleteVerifactu($this->invoice))->handle(); - // } + SendToAeat::dispatch($this->invoice->id, $this->invoice->company, 'cancel'); return $this; } diff --git a/lang/en/texts.php b/lang/en/texts.php index 980f0427f0..7a761bfb45 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5576,6 +5576,10 @@ $lang = array( 'restore_disabled_verifactu' => 'You cannot restore an invoice once it has been deleted', 'delete_disabled_verifactu' => 'You cannot delete an invoice once it has been cancelled or modified', 'rectify' => 'Rectificar', + 'verifactu_invoice_send_success' => 'Invoice :invoice for :client sent to AEAT successfully', + 'verifactu_invoice_sent_failure' => 'Invoice :invoice for :client failed to send to AEAT :notes', + 'verifactu_cancellation_send_success' => 'Invoice cancellation for :invoice sent to AEAT successfully', + 'verifactu_cancellation_send_failure' => 'Invoice cancellation for :invoice failed to send to AEAT :notes', ); return $lang;