Wiring up verifactu sending

This commit is contained in:
David Bomba 2025-08-13 14:26:03 +10:00
parent 3d3b5f6938
commit c5c9c4325e
7 changed files with 118 additions and 69 deletions

View File

@ -39,11 +39,11 @@ 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;
}

View File

@ -279,6 +279,14 @@ class Activity extends StaticModel
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',
'updated_at' => 'timestamp',

View File

@ -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',

View File

@ -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,26 +93,6 @@ 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)
{
@ -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

View File

@ -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)));

View File

@ -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;
}

View File

@ -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;