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.");
|
$fail("Cannot create a modification invoice for a reversed invoice.");
|
||||||
} elseif ($invoice->status_id !== Invoice::STATUS_SENT) {
|
} elseif ($invoice->status_id !== Invoice::STATUS_SENT) {
|
||||||
$fail("Cannot create a modification invoice.");
|
$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 App\Utils\Traits\Invoice\ActionsInvoice;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use App\Events\Invoice\InvoiceReminderWasEmailed;
|
use App\Events\Invoice\InvoiceReminderWasEmailed;
|
||||||
|
use App\DataMapper\InvoiceBackup;
|
||||||
use App\Utils\Number;
|
use App\Utils\Number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -55,7 +56,7 @@ use App\Utils\Number;
|
||||||
* @property string|null $due_date
|
* @property string|null $due_date
|
||||||
* @property bool $is_deleted
|
* @property bool $is_deleted
|
||||||
* @property object|array|string $line_items
|
* @property object|array|string $line_items
|
||||||
* @property object|null $backup
|
* @property InvoiceBackup $backup
|
||||||
* @property object|null $sync
|
* @property object|null $sync
|
||||||
* @property string|null $footer
|
* @property string|null $footer
|
||||||
* @property string|null $public_notes
|
* @property string|null $public_notes
|
||||||
|
|
@ -207,7 +208,7 @@ class Invoice extends BaseModel
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'line_items' => 'object',
|
'line_items' => 'object',
|
||||||
'backup' => 'object',
|
'backup' => InvoiceBackup::class,
|
||||||
'updated_at' => 'timestamp',
|
'updated_at' => 'timestamp',
|
||||||
'created_at' => 'timestamp',
|
'created_at' => 'timestamp',
|
||||||
'deleted_at' => 'timestamp',
|
'deleted_at' => 'timestamp',
|
||||||
|
|
|
||||||
|
|
@ -328,6 +328,11 @@ class BaseRepository
|
||||||
nlog($e->getMessage());
|
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) {
|
if ($model instanceof Credit) {
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ class SendEDocument implements ShouldQueue
|
||||||
|
|
||||||
$model = $this->entity::withTrashed()->find($this->id);
|
$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!");
|
nlog("already sent!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -217,9 +217,9 @@ class SendEDocument implements ShouldQueue
|
||||||
|
|
||||||
if($activity_id == Activity::EINVOICE_DELIVERY_SUCCESS){
|
if($activity_id == Activity::EINVOICE_DELIVERY_SUCCESS){
|
||||||
|
|
||||||
$backup = ($model->backup && is_object($model->backup)) ? $model->backup : new \stdClass();
|
// $backup = ($model->backup && is_object($model->backup)) ? $model->backup : new \stdClass();
|
||||||
$backup->guid = str_replace('"', '', $notes);
|
// $backup->guid = str_replace('"', '', $notes);
|
||||||
$model->backup = $backup;
|
$model->backup->guid = str_replace('"', '', $notes);
|
||||||
$model->saveQuietly();
|
$model->saveQuietly();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,10 +87,11 @@ class HandleCancellation extends AbstractService
|
||||||
|
|
||||||
$replicated_invoice->line_items = $items;
|
$replicated_invoice->line_items = $items;
|
||||||
|
|
||||||
$backup = new \stdClass();
|
$backup = new \App\DataMapper\InvoiceBackup(
|
||||||
$backup->cancelled_invoice_id = $this->invoice->hashed_id;
|
cancelled_invoice_id: $this->invoice->hashed_id,
|
||||||
$backup->cancelled_invoice_number = $this->invoice->number;
|
cancelled_invoice_number: $this->invoice->number,
|
||||||
$backup->cancellation_reason = $this->reason ?? 'R3';
|
cancellation_reason: $this->reason ?? 'R3'
|
||||||
|
);
|
||||||
|
|
||||||
$replicated_invoice->backup = $backup;
|
$replicated_invoice->backup = $backup;
|
||||||
|
|
||||||
|
|
@ -98,10 +99,11 @@ class HandleCancellation extends AbstractService
|
||||||
$replicated_invoice = $invoice_repository->save([], $replicated_invoice);
|
$replicated_invoice = $invoice_repository->save([], $replicated_invoice);
|
||||||
$replicated_invoice->service()->markSent()->sendVerifactu()->save();
|
$replicated_invoice->service()->markSent()->sendVerifactu()->save();
|
||||||
|
|
||||||
$old_backup = new \stdClass();
|
$old_backup = new \App\DataMapper\InvoiceBackup(
|
||||||
$old_backup->credit_invoice_id = $replicated_invoice->hashed_id;
|
credit_invoice_id: $replicated_invoice->hashed_id,
|
||||||
$old_backup->credit_invoice_number = $replicated_invoice->number;
|
credit_invoice_number: $replicated_invoice->number,
|
||||||
$old_backup->cancellation_reason = $this->reason ?? 'R3';
|
cancellation_reason: $this->reason ?? 'R3'
|
||||||
|
);
|
||||||
|
|
||||||
$this->invoice->backup = $old_backup;
|
$this->invoice->backup = $old_backup;
|
||||||
$this->invoice->saveQuietly();
|
$this->invoice->saveQuietly();
|
||||||
|
|
@ -115,10 +117,9 @@ class HandleCancellation extends AbstractService
|
||||||
|
|
||||||
public function reverse()
|
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*/
|
/* Will turn the negative cancellation amount to a positive adjustment*/
|
||||||
|
|
||||||
|
$cancellation = $this->invoice->backup->cancellation;
|
||||||
$adjustment = $cancellation->adjustment * -1;
|
$adjustment = $cancellation->adjustment * -1;
|
||||||
|
|
||||||
$this->invoice->ledger()->updateInvoiceBalance($adjustment, "Invoice {$this->invoice->number} reversal");
|
$this->invoice->ledger()->updateInvoiceBalance($adjustment, "Invoice {$this->invoice->number} reversal");
|
||||||
|
|
@ -133,11 +134,9 @@ class HandleCancellation extends AbstractService
|
||||||
|
|
||||||
$this->invoice->client->service()->calculateBalance();
|
$this->invoice->client->service()->calculateBalance();
|
||||||
|
|
||||||
|
/* Clear the cancellation data */
|
||||||
/* Pop the cancellation out of the backup*/
|
$this->invoice->backup->cancellation->adjustment = 0;
|
||||||
$backup = $this->invoice->backup;
|
$this->invoice->backup->cancellation->status_id = 0;
|
||||||
unset($backup->cancellation);
|
|
||||||
$this->invoice->backup = $backup;
|
|
||||||
$this->invoice->saveQuietly();
|
$this->invoice->saveQuietly();
|
||||||
$this->invoice->fresh();
|
$this->invoice->fresh();
|
||||||
|
|
||||||
|
|
@ -152,19 +151,11 @@ class HandleCancellation extends AbstractService
|
||||||
*/
|
*/
|
||||||
private function backupCancellation($adjustment)
|
private function backupCancellation($adjustment)
|
||||||
{
|
{
|
||||||
if (! is_object($this->invoice->backup)) {
|
|
||||||
$backup = new stdClass();
|
|
||||||
$this->invoice->backup = $backup;
|
|
||||||
}
|
|
||||||
|
|
||||||
$cancellation = new stdClass();
|
// Direct assignment to properties
|
||||||
$cancellation->adjustment = $adjustment;
|
$this->invoice->backup->cancellation->adjustment = $adjustment;
|
||||||
$cancellation->status_id = $this->invoice->status_id;
|
$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();
|
$this->invoice->saveQuietly();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -708,6 +708,33 @@ class InvoiceService
|
||||||
|
|
||||||
return $this;
|
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.
|
* Saves the invoice.
|
||||||
* @return Invoice object
|
* @return Invoice object
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ class TriggeredActions extends AbstractService
|
||||||
$company->save();
|
$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);
|
\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'));
|
$redirectUrl = urldecode($this->request->input('redirect'));
|
||||||
|
|
||||||
if (filter_var($redirectUrl, FILTER_VALIDATE_URL)) {
|
if (filter_var($redirectUrl, FILTER_VALIDATE_URL)) {
|
||||||
$backup = ($this->invoice->backup && is_object($this->invoice->backup)) ? $this->invoice->backup : new \stdClass();
|
// $backup = ($this->invoice->backup && is_object($this->invoice->backup)) ? $this->invoice->backup : new \stdClass();
|
||||||
$backup->redirect = $redirectUrl;
|
// $backup->redirect = $redirectUrl;
|
||||||
$this->invoice->backup = $backup;
|
$this->invoice->backup->redirect = $redirectUrl;
|
||||||
$this->invoice->saveQuietly();
|
$this->invoice->saveQuietly();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,7 @@ class InvoiceTransformer extends EntityTransformer
|
||||||
'auto_bill_enabled' => (bool) $invoice->auto_bill_enabled,
|
'auto_bill_enabled' => (bool) $invoice->auto_bill_enabled,
|
||||||
'tax_info' => $invoice->tax_data ?: new \stdClass(),
|
'tax_info' => $invoice->tax_data ?: new \stdClass(),
|
||||||
'e_invoice' => $invoice->e_invoice ?: new \stdClass(),
|
'e_invoice' => $invoice->e_invoice ?: new \stdClass(),
|
||||||
'backup' => $invoice->backup ?: new \stdClass(),
|
'backup' => $invoice->backup,
|
||||||
'location_id' => $this->encodePrimaryKey($invoice->location_id),
|
'location_id' => $this->encodePrimaryKey($invoice->location_id),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue