Update for ivnoice backup casting
This commit is contained in:
parent
47f33c8691
commit
67df175525
|
|
@ -0,0 +1,60 @@
|
|||
<?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\Casts;
|
||||
|
||||
use App\DataMapper\InvoiceBackup;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
|
||||
class InvoiceBackupCast implements CastsAttributes
|
||||
{
|
||||
public function get($model, string $key, $value, array $attributes)
|
||||
{
|
||||
if (is_null($value)) {
|
||||
return new InvoiceBackup();
|
||||
}
|
||||
|
||||
$data = json_decode($value, true) ?? [];
|
||||
|
||||
return InvoiceBackup::fromArray($data);
|
||||
}
|
||||
|
||||
public function set($model, string $key, $value, array $attributes)
|
||||
{
|
||||
if (is_null($value)) {
|
||||
return [$key => null];
|
||||
}
|
||||
|
||||
// Ensure we're dealing with our object type
|
||||
if (! $value instanceof InvoiceBackup) {
|
||||
throw new \InvalidArgumentException('Value must be an InvoiceBackup instance.');
|
||||
}
|
||||
|
||||
return [
|
||||
$key => json_encode([
|
||||
'guid' => $value->guid,
|
||||
'cancellation' => $value->cancellation ? [
|
||||
'adjustment' => $value->cancellation->adjustment,
|
||||
'status_id' => $value->cancellation->status_id,
|
||||
] : [],
|
||||
'cancelled_invoice_id' => $value->cancelled_invoice_id,
|
||||
'cancelled_invoice_number' => $value->cancelled_invoice_number,
|
||||
'cancellation_reason' => $value->cancellation_reason,
|
||||
'credit_invoice_id' => $value->credit_invoice_id,
|
||||
'credit_invoice_number' => $value->credit_invoice_number,
|
||||
'redirect' => $value->redirect,
|
||||
'modified_invoice_id' => $value->modified_invoice_id,
|
||||
'replaced_invoice_id' => $value->replaced_invoice_id,
|
||||
])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?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\DataMapper;
|
||||
|
||||
/**
|
||||
* Cancellation value object for invoice backup data.
|
||||
*/
|
||||
class Cancellation
|
||||
{
|
||||
public function __construct(
|
||||
public float $adjustment = 0, // The cancellation adjustment amount
|
||||
public int $status_id = 0 //The status id of the invoice when it was cancelled
|
||||
) {}
|
||||
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
return new self(
|
||||
adjustment: $data['adjustment'] ?? 0,
|
||||
status_id: $data['status_id'] ?? 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?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\DataMapper;
|
||||
|
||||
use App\Casts\InvoiceBackupCast;
|
||||
use App\DataMapper\Cancellation;
|
||||
use Illuminate\Contracts\Database\Eloquent\Castable;
|
||||
|
||||
/**
|
||||
* InvoiceBackup.
|
||||
*/
|
||||
class InvoiceBackup implements Castable
|
||||
{
|
||||
public function __construct(
|
||||
public string $guid = '', // The E-INVOICE SENT GUID reference
|
||||
public Cancellation $cancellation = new Cancellation(0,0),
|
||||
public ?string $cancelled_invoice_id = null, // The id of the invoice that was cancelled
|
||||
public ?string $cancelled_invoice_number = null, // The number of the invoice that was cancelled
|
||||
public ?string $cancellation_reason = null, // The reason for the cancellation
|
||||
public ?string $credit_invoice_id = null, // The id of the credit invoice that was created
|
||||
public ?string $credit_invoice_number = null, // The number of the credit invoice that was created
|
||||
public ?string $redirect = null, // The redirect url for the invoice
|
||||
public ?string $modified_invoice_id = null, // The id of the modified invoice (replaces the invoice with replaced_invoice_id)
|
||||
public ?string $replaced_invoice_id = null // The id of the replaced invoice (The previous invoice that was replaced by the modified invoice)
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the name of the caster class to use when casting from / to this cast target.
|
||||
*
|
||||
* @param array<string, mixed> $arguments
|
||||
*/
|
||||
public static function castUsing(array $arguments): string
|
||||
{
|
||||
return InvoiceBackupCast::class;
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
return new self(
|
||||
guid: $data['guid'] ?? '',
|
||||
cancellation: Cancellation::fromArray($data['cancellation'] ?? []),
|
||||
cancelled_invoice_id: $data['cancelled_invoice_id'] ?? null,
|
||||
cancelled_invoice_number: $data['cancelled_invoice_number'] ?? null,
|
||||
cancellation_reason: $data['cancellation_reason'] ?? null,
|
||||
credit_invoice_id: $data['credit_invoice_id'] ?? null,
|
||||
credit_invoice_number: $data['credit_invoice_number'] ?? null,
|
||||
redirect: $data['redirect'] ?? null,
|
||||
modified_invoice_id: $data['modified_invoice_id'] ?? null,
|
||||
replaced_invoice_id: $data['replaced_invoice_id'] ?? null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,6 +57,8 @@ class CanGenerateModificationInvoice implements ValidationRule
|
|||
$fail("Cannot create a modification invoice for a reversed invoice.");
|
||||
} elseif ($invoice->status_id !== Invoice::STATUS_SENT) {
|
||||
$fail("Cannot create a modification invoice.");
|
||||
} elseif($invoice->amount <= 0){
|
||||
$fail("Cannot create a modification invoice for an invoice with an amount less than 0.");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ use App\Helpers\Invoice\InvoiceSumInclusive;
|
|||
use App\Utils\Traits\Invoice\ActionsInvoice;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use App\Events\Invoice\InvoiceReminderWasEmailed;
|
||||
use App\DataMapper\InvoiceBackup;
|
||||
use App\Utils\Number;
|
||||
|
||||
/**
|
||||
|
|
@ -55,7 +56,7 @@ use App\Utils\Number;
|
|||
* @property string|null $due_date
|
||||
* @property bool $is_deleted
|
||||
* @property object|array|string $line_items
|
||||
* @property object|null $backup
|
||||
* @property InvoiceBackup $backup
|
||||
* @property object|null $sync
|
||||
* @property string|null $footer
|
||||
* @property string|null $public_notes
|
||||
|
|
@ -207,7 +208,7 @@ class Invoice extends BaseModel
|
|||
|
||||
protected $casts = [
|
||||
'line_items' => 'object',
|
||||
'backup' => 'object',
|
||||
'backup' => InvoiceBackup::class,
|
||||
'updated_at' => 'timestamp',
|
||||
'created_at' => 'timestamp',
|
||||
'deleted_at' => 'timestamp',
|
||||
|
|
|
|||
|
|
@ -328,6 +328,11 @@ class BaseRepository
|
|||
nlog($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** Verifactu modified invoice check */
|
||||
if(isset($data['verifactu_modified']) && $data['verifactu_modified']) {
|
||||
$model->service()->modifyVerifactuWorkflow($data['modified_invoice_id'])->save();
|
||||
}
|
||||
}
|
||||
|
||||
if ($model instanceof Credit) {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class SendEDocument implements ShouldQueue
|
|||
|
||||
$model = $this->entity::withTrashed()->find($this->id);
|
||||
|
||||
if(isset($model->backup->guid) && is_string($model->backup->guid)){
|
||||
if(isset($model->backup->guid) && is_string($model->backup->guid) && strlen($model->backup->guid) > 3){
|
||||
nlog("already sent!");
|
||||
return;
|
||||
}
|
||||
|
|
@ -217,9 +217,9 @@ class SendEDocument implements ShouldQueue
|
|||
|
||||
if($activity_id == Activity::EINVOICE_DELIVERY_SUCCESS){
|
||||
|
||||
$backup = ($model->backup && is_object($model->backup)) ? $model->backup : new \stdClass();
|
||||
$backup->guid = str_replace('"', '', $notes);
|
||||
$model->backup = $backup;
|
||||
// $backup = ($model->backup && is_object($model->backup)) ? $model->backup : new \stdClass();
|
||||
// $backup->guid = str_replace('"', '', $notes);
|
||||
$model->backup->guid = str_replace('"', '', $notes);
|
||||
$model->saveQuietly();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,10 +87,11 @@ class HandleCancellation extends AbstractService
|
|||
|
||||
$replicated_invoice->line_items = $items;
|
||||
|
||||
$backup = new \stdClass();
|
||||
$backup->cancelled_invoice_id = $this->invoice->hashed_id;
|
||||
$backup->cancelled_invoice_number = $this->invoice->number;
|
||||
$backup->cancellation_reason = $this->reason ?? 'R3';
|
||||
$backup = new \App\DataMapper\InvoiceBackup(
|
||||
cancelled_invoice_id: $this->invoice->hashed_id,
|
||||
cancelled_invoice_number: $this->invoice->number,
|
||||
cancellation_reason: $this->reason ?? 'R3'
|
||||
);
|
||||
|
||||
$replicated_invoice->backup = $backup;
|
||||
|
||||
|
|
@ -98,10 +99,11 @@ class HandleCancellation extends AbstractService
|
|||
$replicated_invoice = $invoice_repository->save([], $replicated_invoice);
|
||||
$replicated_invoice->service()->markSent()->sendVerifactu()->save();
|
||||
|
||||
$old_backup = new \stdClass();
|
||||
$old_backup->credit_invoice_id = $replicated_invoice->hashed_id;
|
||||
$old_backup->credit_invoice_number = $replicated_invoice->number;
|
||||
$old_backup->cancellation_reason = $this->reason ?? 'R3';
|
||||
$old_backup = new \App\DataMapper\InvoiceBackup(
|
||||
credit_invoice_id: $replicated_invoice->hashed_id,
|
||||
credit_invoice_number: $replicated_invoice->number,
|
||||
cancellation_reason: $this->reason ?? 'R3'
|
||||
);
|
||||
|
||||
$this->invoice->backup = $old_backup;
|
||||
$this->invoice->saveQuietly();
|
||||
|
|
@ -115,10 +117,9 @@ class HandleCancellation extends AbstractService
|
|||
|
||||
public function reverse()
|
||||
{
|
||||
/* The stored cancelled object - contains the adjustment and status*/
|
||||
$cancellation = $this->invoice->backup->cancellation;
|
||||
|
||||
/* Will turn the negative cancellation amount to a positive adjustment*/
|
||||
|
||||
$cancellation = $this->invoice->backup->cancellation;
|
||||
$adjustment = $cancellation->adjustment * -1;
|
||||
|
||||
$this->invoice->ledger()->updateInvoiceBalance($adjustment, "Invoice {$this->invoice->number} reversal");
|
||||
|
|
@ -133,11 +134,9 @@ class HandleCancellation extends AbstractService
|
|||
|
||||
$this->invoice->client->service()->calculateBalance();
|
||||
|
||||
|
||||
/* Pop the cancellation out of the backup*/
|
||||
$backup = $this->invoice->backup;
|
||||
unset($backup->cancellation);
|
||||
$this->invoice->backup = $backup;
|
||||
/* Clear the cancellation data */
|
||||
$this->invoice->backup->cancellation->adjustment = 0;
|
||||
$this->invoice->backup->cancellation->status_id = 0;
|
||||
$this->invoice->saveQuietly();
|
||||
$this->invoice->fresh();
|
||||
|
||||
|
|
@ -152,19 +151,11 @@ class HandleCancellation extends AbstractService
|
|||
*/
|
||||
private function backupCancellation($adjustment)
|
||||
{
|
||||
if (! is_object($this->invoice->backup)) {
|
||||
$backup = new stdClass();
|
||||
$this->invoice->backup = $backup;
|
||||
}
|
||||
|
||||
$cancellation = new stdClass();
|
||||
$cancellation->adjustment = $adjustment;
|
||||
$cancellation->status_id = $this->invoice->status_id;
|
||||
// Direct assignment to properties
|
||||
$this->invoice->backup->cancellation->adjustment = $adjustment;
|
||||
$this->invoice->backup->cancellation->status_id = $this->invoice->status_id;
|
||||
|
||||
$invoice_backup = $this->invoice->backup;
|
||||
$invoice_backup->cancellation = $cancellation;
|
||||
|
||||
$this->invoice->backup = $invoice_backup;
|
||||
$this->invoice->saveQuietly();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -708,6 +708,33 @@ class InvoiceService
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* modifyVerifactuWorkflow
|
||||
* @todo - handle invoice modifications - ensure when we
|
||||
* sent this to AEAT we reference the invoice that was replaced.
|
||||
*
|
||||
* @param string $modified_invoice_hashed_id
|
||||
* @return self
|
||||
*/
|
||||
public function modifyVerifactuWorkflow(string $modified_invoice_hashed_id): self
|
||||
{
|
||||
$modified_invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($modified_invoice_hashed_id));
|
||||
$modified_invoice->status_id = Invoice::STATUS_REPLACED;
|
||||
$modified_invoice->backup->modified_invoice_id = $this->invoice->hashed_id;
|
||||
$modified_invoice->save();
|
||||
|
||||
$this->markSent();
|
||||
//Update the client balance by the delta amount from the previous invoice to this one.
|
||||
$this->invoice->backup->replaced_invoice_id = $modified_invoice->hashed_id;
|
||||
$this->invoice->saveQuietly();
|
||||
|
||||
$this->invoice->client->service()->updateBalance(round(($modified_invoice->amount - $this->invoice->amount), 2));
|
||||
$this->sendVerifactu();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the invoice.
|
||||
* @return Invoice object
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class TriggeredActions extends AbstractService
|
|||
$company->save();
|
||||
}
|
||||
|
||||
if($this->request->has('retry_e_send') && $this->request->input('retry_e_send') == 'true' && !isset($this->invoice->backup->guid) && $this->invoice->client->peppolSendingEnabled()) {
|
||||
if($this->request->has('retry_e_send') && $this->request->input('retry_e_send') == 'true' && strlen($this->invoice->backup->guid ?? '') < 2 && $this->invoice->client->peppolSendingEnabled()) {
|
||||
\App\Services\EDocument\Jobs\SendEDocument::dispatch(get_class($this->invoice), $this->invoice->id, $this->invoice->company->db);
|
||||
}
|
||||
|
||||
|
|
@ -90,9 +90,9 @@ class TriggeredActions extends AbstractService
|
|||
$redirectUrl = urldecode($this->request->input('redirect'));
|
||||
|
||||
if (filter_var($redirectUrl, FILTER_VALIDATE_URL)) {
|
||||
$backup = ($this->invoice->backup && is_object($this->invoice->backup)) ? $this->invoice->backup : new \stdClass();
|
||||
$backup->redirect = $redirectUrl;
|
||||
$this->invoice->backup = $backup;
|
||||
// $backup = ($this->invoice->backup && is_object($this->invoice->backup)) ? $this->invoice->backup : new \stdClass();
|
||||
// $backup->redirect = $redirectUrl;
|
||||
$this->invoice->backup->redirect = $redirectUrl;
|
||||
$this->invoice->saveQuietly();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ class InvoiceTransformer extends EntityTransformer
|
|||
'auto_bill_enabled' => (bool) $invoice->auto_bill_enabled,
|
||||
'tax_info' => $invoice->tax_data ?: new \stdClass(),
|
||||
'e_invoice' => $invoice->e_invoice ?: new \stdClass(),
|
||||
'backup' => $invoice->backup ?: new \stdClass(),
|
||||
'backup' => $invoice->backup,
|
||||
'location_id' => $this->encodePrimaryKey($invoice->location_id),
|
||||
];
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue