1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-16 16:13:20 +01:00
invoiceninja/app/Services/Invoice/InvoiceService.php

652 lines
20 KiB
PHP
Raw Normal View History

<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
2024-04-12 06:15:41 +02:00
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
2021-06-16 08:58:16 +02:00
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\Invoice;
use App\Events\Invoice\InvoiceWasArchived;
use App\Jobs\EDocument\CreateEDocument;
use App\Jobs\Entity\CreateRawPdf;
use App\Jobs\Inventory\AdjustProductInventory;
use App\Libraries\Currency\Conversion\CurrencyApi;
use App\Models\CompanyGateway;
use App\Models\Expense;
use App\Models\Invoice;
use App\Models\Payment;
2024-01-13 08:04:03 +01:00
use App\Models\Subscription;
use App\Models\Task;
use App\Utils\Ninja;
2024-01-13 08:04:03 +01:00
use App\Utils\Traits\MakesHash;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Storage;
class InvoiceService
{
use MakesHash;
2023-02-16 02:36:09 +01:00
public function __construct(public Invoice $invoice)
{
}
/**
* Marks as invoice as paid
* and executes child sub functions.
* @return $this InvoiceService object
*/
public function markPaid(?string $reference = null)
{
$this->removeUnpaidGatewayFees();
$this->invoice = (new MarkPaid($this->invoice, $reference))->run();
return $this;
}
2023-05-14 01:25:08 +02:00
/**
* applyPaymentAmount
*
* @param float $amount
* @param ?string $reference
* @return self
*/
public function applyPaymentAmount($amount, ?string $reference = null): self
2021-08-13 03:30:48 +02:00
{
$this->invoice = (new ApplyPaymentAmount($this->invoice, $amount, $reference))->run();
2021-08-13 03:30:48 +02:00
return $this;
}
/**
* Applies the invoice number.
* @return $this InvoiceService object
*/
public function applyNumber()
{
$this->invoice = (new ApplyNumber($this->invoice->client, $this->invoice))->run();
return $this;
}
2021-03-29 04:14:55 +02:00
/**
* Sets the exchange rate on the invoice if the client currency
* is different to the company currency.
*/
2023-07-24 07:22:12 +02:00
public function setExchangeRate($force = false)
2021-03-27 04:51:34 +01:00
{
2023-07-24 07:22:12 +02:00
if ($this->invoice->exchange_rate != 1 || $force) {
return $this;
}
2021-03-29 04:14:55 +02:00
$client_currency = $this->invoice->client->getSetting('currency_id');
$company_currency = $this->invoice->company->settings->currency_id;
if ($company_currency != $client_currency) {
$exchange_rate = new CurrencyApi();
2024-06-14 09:09:44 +02:00
$this->invoice->exchange_rate = 1 / $exchange_rate->exchangeRate($client_currency, $company_currency, now());
2021-03-29 04:14:55 +02:00
}
2020-09-08 12:34:14 +02:00
2021-03-27 04:51:34 +01:00
return $this;
}
2020-09-08 12:34:14 +02:00
/**
* Applies the recurring invoice number.
* @return $this InvoiceService object
*/
public function applyRecurringNumber()
{
$this->invoice = (new ApplyRecurringNumber($this->invoice->client, $this->invoice))->run();
return $this;
}
/**
* Apply a payment amount to an invoice.
* @param Payment $payment The Payment
* @param float $payment_amount The Payment amount
* @return InvoiceService Parent class object
*/
public function applyPayment(Payment $payment, float $payment_amount)
{
2022-11-24 07:23:36 +01:00
$this->invoice = $this->markSent()->save();
$this->invoice = (new ApplyPayment($this->invoice, $payment, $payment_amount))->run();
return $this;
}
2020-10-12 11:38:55 +02:00
public function addGatewayFee(CompanyGateway $company_gateway, $gateway_type_id, float $amount)
2020-08-25 15:06:38 +02:00
{
2020-10-12 11:38:55 +02:00
$this->invoice = (new AddGatewayFee($company_gateway, $gateway_type_id, $this->invoice, $amount))->run();
2020-08-25 15:06:38 +02:00
return $this;
}
/**
* Update an invoice balance.
*
* @param float $balance_adjustment The amount to adjust the invoice by
* a negative amount will REDUCE the invoice balance, a positive amount will INCREASE
* the invoice balance
*
* @return InvoiceService Parent class object
*/
public function updateBalance($balance_adjustment, bool $is_draft = false)
{
if ((bool) $this->invoice->is_deleted !== false) {
nlog($this->invoice->number.' is deleted returning');
2021-11-19 07:13:57 +01:00
return $this;
}
2022-03-26 12:21:35 +01:00
$this->invoice->balance += $balance_adjustment;
if (round($this->invoice->balance, 2) == 0 && ! $is_draft) {
2021-11-19 07:13:57 +01:00
$this->invoice->status_id = Invoice::STATUS_PAID;
}
if ((int) $this->invoice->balance == 0) {
2020-11-25 15:19:52 +01:00
$this->invoice->next_send_date = null;
}
return $this;
}
public function updatePaidToDate($adjustment)
{
2022-03-26 12:21:35 +01:00
$this->invoice->paid_to_date += $adjustment;
return $this;
}
public function createInvitations()
{
$this->invoice = (new CreateInvitations($this->invoice))->run();
return $this;
}
2022-12-08 01:17:18 +01:00
public function markSent($fire_event = false)
{
2024-06-15 08:51:34 +02:00
$this->invoice->loadMissing(['client' => function ($q) {
$q->without('documents', 'contacts.company', 'contacts'); // Exclude 'grandchildren' relation of 'client'
}]);
2022-12-08 01:17:18 +01:00
$this->invoice = (new MarkSent($this->invoice->client, $this->invoice))->run($fire_event);
2021-03-29 04:14:55 +02:00
$this->setExchangeRate();
return $this;
}
public function getInvoicePdf($contact = null)
{
return (new GetInvoicePdf($this->invoice, $contact))->run();
}
2023-08-16 11:55:35 +02:00
public function getRawInvoicePdf($contact = null)
{
$invitation = $contact ? $this->invoice->invitations()->where('contact_id', $contact->id)->first() : $this->invoice->invitations()->first();
2023-10-26 03:25:56 +02:00
return (new CreateRawPdf($invitation))->handle();
2023-08-16 11:55:35 +02:00
}
public function getInvoiceDeliveryNote(Invoice $invoice, \App\Models\ClientContact $contact = null)
2020-11-04 02:27:07 +01:00
{
return (new GenerateDeliveryNote($invoice, $contact))->run();
2020-11-04 02:27:07 +01:00
}
2023-04-17 09:24:16 +02:00
public function getEInvoice($contact = null)
{
return (new CreateEDocument($this->invoice))->handle();
}
2024-04-04 23:42:31 +02:00
public function getEDocument($contact = null)
{
return $this->getEInvoice($contact);
}
2024-06-14 09:09:44 +02:00
public function sendEmail($contact = null)
{
$send_email = new SendEmail($this->invoice, null, $contact);
return $send_email->run();
}
public function handleReversal()
{
$this->invoice = (new HandleReversal($this->invoice))->run();
return $this;
}
public function handleCancellation()
{
$this->removeUnpaidGatewayFees();
$this->invoice = (new HandleCancellation($this->invoice))->run();
return $this;
}
public function markDeleted()
{
$this->removeUnpaidGatewayFees();
$this->invoice = (new MarkInvoiceDeleted($this->invoice))->run();
return $this;
}
public function handleRestore()
2020-12-03 05:20:39 +01:00
{
$this->invoice = (new HandleRestore($this->invoice))->run();
2020-12-03 05:20:39 +01:00
return $this;
}
public function reverseCancellation()
{
$this->removeUnpaidGatewayFees();
$this->invoice = (new HandleCancellation($this->invoice))->reverse();
return $this;
}
2020-07-06 01:34:25 +02:00
public function triggeredActions($request)
{
2022-04-20 03:55:33 +02:00
$this->invoice = (new TriggeredActions($this->invoice->load('invitations'), $request))->run();
2020-07-06 01:34:25 +02:00
return $this;
}
2020-07-07 14:33:11 +02:00
public function autoBill()
{
(new AutoBillInvoice($this->invoice, $this->invoice->company->db))->run();
2020-07-07 14:33:11 +02:00
return $this;
}
public function markViewed()
{
$this->invoice->last_viewed = Carbon::now()->format('Y-m-d H:i');
return $this;
}
/* One liners */
public function setDueDate()
{
if ($this->invoice->due_date != '' || $this->invoice->client->getSetting('payment_terms') == '') {
return $this;
}
2022-10-12 03:55:11 +02:00
//12-10-2022
2023-02-16 02:36:09 +01:00
if ($this->invoice->partial > 0 && !$this->invoice->partial_due_date) {
2022-10-12 03:55:11 +02:00
$this->invoice->partial_due_date = Carbon::parse($this->invoice->date)->addDays($this->invoice->client->getSetting('payment_terms'));
2023-02-16 02:36:09 +01:00
} else {
2022-10-12 03:55:11 +02:00
$this->invoice->due_date = Carbon::parse($this->invoice->date)->addDays($this->invoice->client->getSetting('payment_terms'));
2023-02-16 02:36:09 +01:00
}
return $this;
}
2024-01-14 05:05:00 +01:00
/**
* Reset the reminders if only the
* partial has been paid.
2023-10-26 04:57:44 +02:00
*
* We can _ONLY_ call this _IF_ a partial
* amount has been paid, otherwise we end up wiping
* all reminders regardless
*
* @return self
*/
public function checkReminderStatus(): self
{
2024-01-14 05:05:00 +01:00
2023-10-26 04:57:44 +02:00
if($this->invoice->partial == 0) {
$this->invoice->partial_due_date = null;
2023-10-26 04:57:44 +02:00
}
2023-10-26 04:57:44 +02:00
if($this->invoice->partial == 0 && $this->invoice->balance > 0) {
$this->invoice->reminder1_sent = null;
$this->invoice->reminder2_sent = null;
$this->invoice->reminder3_sent = null;
$this->setReminder();
}
return $this;
}
2021-05-26 04:37:16 +02:00
public function setReminder($settings = null)
{
$this->invoice = (new UpdateReminder($this->invoice, $settings))->run();
return $this;
}
public function setStatus($status)
{
$this->invoice->status_id = $status;
return $this;
}
2020-10-23 06:18:16 +02:00
public function setCalculatedStatus()
{
if (round($this->invoice->balance, 2) == 0) {
2020-10-23 06:18:16 +02:00
$this->setStatus(Invoice::STATUS_PAID);
2020-11-25 15:19:52 +01:00
} elseif ($this->invoice->balance > 0 && $this->invoice->balance < $this->invoice->amount) {
2020-10-23 06:18:16 +02:00
$this->setStatus(Invoice::STATUS_PARTIAL);
2023-02-16 02:36:09 +01:00
} elseif ($this->invoice->balance < 0 || $this->invoice->balance > 0) {
2022-12-01 05:33:40 +01:00
$this->invoice->status_id = Invoice::STATUS_SENT;
2022-10-21 06:00:33 +02:00
}
2020-10-23 06:18:16 +02:00
return $this;
}
2020-09-11 02:10:53 +02:00
public function updateStatus()
{
if ($this->invoice->status_id == Invoice::STATUS_DRAFT) {
return $this;
}
if (round($this->invoice->balance, 2) == 0) {
2021-12-28 10:57:48 +01:00
$this->invoice->status_id = Invoice::STATUS_PAID;
} elseif ($this->invoice->balance > 0 && $this->invoice->balance < $this->invoice->amount) {
2021-12-28 10:57:48 +01:00
$this->invoice->status_id = Invoice::STATUS_PARTIAL;
2023-02-16 02:36:09 +01:00
} elseif ($this->invoice->balance < 0 || $this->invoice->balance > 0) {
$this->invoice->status_id = Invoice::STATUS_SENT;
}
2020-10-28 11:10:49 +01:00
2020-09-11 02:10:53 +02:00
return $this;
}
2020-07-15 07:05:02 +02:00
public function toggleFeesPaid()
{
$this->invoice->line_items = collect($this->invoice->line_items)
2020-07-15 08:08:57 +02:00
->map(function ($item) {
if ($item->type_id == '3') {
$item->type_id = '4';
}
2020-07-15 07:05:02 +02:00
return $item;
})->toArray();
2020-07-15 07:05:02 +02:00
2023-09-05 03:54:05 +02:00
// $this->deletePdf();
2023-08-07 21:18:50 +02:00
$this->deleteEInvoice();
return $this;
}
public function deletePdf()
{
2021-10-08 07:23:00 +02:00
$this->invoice->load('invitations');
2023-06-30 01:14:00 +02:00
//30-06-2023
$this->invoice->invitations->each(function ($invitation) {
try {
2023-06-30 01:14:00 +02:00
// if (Storage::disk(config('filesystems.default'))->exists($this->invoice->client->invoice_filepath($invitation).$this->invoice->numberFormatter().'.pdf')) {
2023-10-26 04:57:44 +02:00
Storage::disk(config('filesystems.default'))->delete($this->invoice->client->invoice_filepath($invitation).$this->invoice->numberFormatter().'.pdf');
2023-06-30 01:14:00 +02:00
// }
2023-06-30 01:14:00 +02:00
// if (Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->invoice_filepath($invitation).$this->invoice->numberFormatter().'.pdf')) {
if (Ninja::isHosted()) {
Storage::disk('public')->delete($this->invoice->client->invoice_filepath($invitation).$this->invoice->numberFormatter().'.pdf');
}
} catch (\Exception $e) {
nlog($e->getMessage());
2021-06-12 13:50:01 +02:00
}
});
2021-05-24 12:58:37 +02:00
2020-07-15 07:05:02 +02:00
return $this;
}
2023-04-17 09:24:16 +02:00
public function deleteEInvoice()
{
$this->invoice->load('invitations');
$this->invoice->invitations->each(function ($invitation) {
try {
2023-06-30 01:14:00 +02:00
// if (Storage::disk(config('filesystems.default'))->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) {
Storage::disk(config('filesystems.default'))->delete($this->invoice->client->e_document_filepath($invitation).$this->invoice->getFileName("xml"));
2023-06-30 01:14:00 +02:00
// }
2023-06-30 01:14:00 +02:00
// if (Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) {
if (Ninja::isHosted()) {
Storage::disk('public')->delete($this->invoice->client->e_document_filepath($invitation).$this->invoice->getFileName("xml"));
}
} catch (\Exception $e) {
nlog($e->getMessage());
}
});
return $this;
}
2020-07-15 07:05:02 +02:00
public function removeUnpaidGatewayFees()
{
$balance = $this->invoice->balance;
//return early if type three does not exist.
if ($this->invoice->status_id == Invoice::STATUS_PAID || ! collect($this->invoice->line_items)->contains('type_id', 3)) {
return $this;
}
2022-03-29 05:13:11 +02:00
$pre_count = count($this->invoice->line_items);
$items = collect($this->invoice->line_items)
2020-07-15 07:05:02 +02:00
->reject(function ($item) {
return $item->type_id == '3';
})->toArray();
2020-07-15 07:05:02 +02:00
$this->invoice->line_items = array_values($items);
2023-10-11 06:38:18 +02:00
$this->invoice = $this->invoice->calc()->getInvoice();
/* 24-03-2022 */
$new_balance = $this->invoice->balance;
2022-04-01 04:46:55 +02:00
$post_count = count($this->invoice->line_items);
nlog("pre count = {$pre_count} post count = {$post_count}");
if ((int) $pre_count != (int) $post_count) {
$adjustment = $balance - $new_balance;
2023-11-07 11:58:52 +01:00
$this->invoice
->ledger()
->updateInvoiceBalance($adjustment * -1, 'Adjustment for removing gateway fee');
2023-11-07 04:54:44 +01:00
$this->invoice->client->service()->calculateBalance();
}
2020-07-15 07:05:02 +02:00
return $this;
}
2020-09-02 03:11:01 +02:00
/*Set partial value and due date to null*/
public function clearPartial()
{
$this->invoice->partial = null;
$this->invoice->partial_due_date = null;
return $this;
}
2020-09-02 03:11:01 +02:00
/*Update the partial amount of a invoice*/
public function updatePartial($amount)
{
2022-03-27 08:04:13 +02:00
$this->invoice->partial += $amount;
return $this;
}
2020-09-02 03:11:01 +02:00
/*When a reminder is sent we want to touch the dates they were sent*/
public function touchReminder(string $reminder_template)
2024-06-14 09:09:44 +02:00
{
nrlog(now()->format('Y-m-d h:i:s') . " INV #{$this->invoice->number} : Touching Reminder => {$reminder_template}");
2020-09-02 03:11:01 +02:00
switch ($reminder_template) {
case 'reminder1':
2021-06-23 06:55:12 +02:00
$this->invoice->reminder1_sent = now();
$this->invoice->reminder_last_sent = now();
$this->invoice->last_sent_date = now();
2020-09-02 03:11:01 +02:00
break;
case 'reminder2':
2021-06-23 06:55:12 +02:00
$this->invoice->reminder2_sent = now();
$this->invoice->reminder_last_sent = now();
$this->invoice->last_sent_date = now();
2020-09-02 03:11:01 +02:00
break;
case 'reminder3':
2021-06-23 06:55:12 +02:00
$this->invoice->reminder3_sent = now();
$this->invoice->reminder_last_sent = now();
$this->invoice->last_sent_date = now();
2020-09-02 03:11:01 +02:00
break;
2021-06-10 03:15:21 +02:00
case 'endless_reminder':
2021-06-23 06:55:12 +02:00
$this->invoice->reminder_last_sent = now();
$this->invoice->last_sent_date = now();
2021-06-10 03:15:21 +02:00
break;
2020-09-02 03:11:01 +02:00
default:
2021-06-23 06:55:12 +02:00
$this->invoice->reminder1_sent = now();
$this->invoice->reminder_last_sent = now();
$this->invoice->last_sent_date = now();
2020-09-02 03:11:01 +02:00
break;
}
2020-09-02 03:11:01 +02:00
return $this;
}
public function linkEntities()
{
//set all task.invoice_ids = 0
$this->invoice->tasks()->update(['invoice_id' => null]);
//set all tasks.invoice_ids = x with the current line_items
2020-11-25 15:19:52 +01:00
$tasks = collect($this->invoice->line_items)->map(function ($item) {
if (isset($item->task_id)) {
$item->task_id = $this->decodePrimaryKey($item->task_id);
2020-11-25 15:19:52 +01:00
}
2020-11-25 15:19:52 +01:00
if (isset($item->expense_id)) {
$item->expense_id = $this->decodePrimaryKey($item->expense_id);
2020-11-25 15:19:52 +01:00
}
return $item;
});
2023-08-06 09:03:12 +02:00
Task::query()->whereIn('id', $tasks->pluck('task_id'))->update(['invoice_id' => $this->invoice->id]);
Expense::query()->whereIn('id', $tasks->pluck('expense_id'))->update(['invoice_id' => $this->invoice->id]);
return $this;
}
2024-03-17 23:03:34 +01:00
public function fillDefaults(bool $is_recurring = false)
{
2021-10-12 11:45:15 +02:00
$this->invoice->load('client.company');
2020-11-04 09:43:20 +01:00
$settings = $this->invoice->client->getMergedSettings();
if (! $this->invoice->design_id) {
2023-04-26 14:17:40 +02:00
$this->invoice->design_id = intval($this->decodePrimaryKey($settings->invoice_design_id));
}
if (! isset($this->invoice->footer) || empty($this->invoice->footer)) {
2020-11-04 09:43:20 +01:00
$this->invoice->footer = $settings->invoice_footer;
}
2020-11-04 09:43:20 +01:00
if (! isset($this->invoice->terms) || empty($this->invoice->terms)) {
2020-11-04 09:43:20 +01:00
$this->invoice->terms = $settings->invoice_terms;
}
2021-01-18 12:08:18 +01:00
if (! isset($this->invoice->public_notes) || empty($this->invoice->public_notes)) {
2021-01-18 12:08:18 +01:00
$this->invoice->public_notes = $this->invoice->client->public_notes;
}
2021-01-18 21:02:32 +01:00
/* If client currency differs from the company default currency, then insert the client exchange rate on the model.*/
if (! isset($this->invoice->exchange_rate) && $this->invoice->client->currency()->id != (int) $this->invoice->company->settings->currency_id) {
2024-01-09 11:49:29 +01:00
$this->invoice->exchange_rate = $this->invoice->client->setExchangeRate();
}
2021-01-18 21:02:32 +01:00
if (!$is_recurring && $this->invoice->client->getSetting('auto_bill_standard_invoices')) {
$this->invoice->auto_bill_enabled = true;
}
if ($settings->counter_number_applied == 'when_saved') {
$this->invoice->service()->applyNumber()->save();
}
2020-11-25 15:19:52 +01:00
return $this;
}
2021-05-26 02:35:39 +02:00
2021-08-08 00:40:04 +02:00
public function workFlow()
{
if ($this->invoice->status_id == Invoice::STATUS_PAID && $this->invoice->client->getSetting('auto_archive_invoice')) {
/* Throws: Payment amount xxx does not match invoice totals. */
2021-10-07 10:04:33 +02:00
if ($this->invoice->trashed()) {
2022-03-28 07:36:00 +02:00
return $this;
}
$this->invoice->delete();
event(new InvoiceWasArchived($this->invoice, $this->invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
}
if ($this->invoice->status_id == Invoice::STATUS_CANCELLED && $this->invoice->client->getSetting('auto_archive_invoice_cancelled')) {
/* Throws: Payment amount xxx does not match invoice totals. */
if ($this->invoice->trashed()) {
return $this;
}
$this->invoice->delete();
event(new InvoiceWasArchived($this->invoice, $this->invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
}
2021-08-08 00:40:04 +02:00
return $this;
}
2022-06-08 12:40:26 +02:00
public function adjustInventory($old_invoice = [])
2022-05-31 13:17:18 +02:00
{
if ($this->invoice->company->track_inventory) {
2022-07-31 11:11:32 +02:00
(new AdjustProductInventory($this->invoice->company, $this->invoice, $old_invoice))->handle();
}
2022-05-31 13:17:18 +02:00
return $this;
}
2024-01-13 08:04:03 +01:00
public function setPaymentLink(string $subscription_id): self
{
$sub_id = $this->decodePrimaryKey($subscription_id);
2024-01-13 08:27:30 +01:00
if(Subscription::withTrashed()->where('id', $sub_id)->where('company_id', $this->invoice->company_id)->exists()) {
2024-01-13 08:04:03 +01:00
$this->invoice->subscription_id = $sub_id;
}
return $this;
}
/**
* Saves the invoice.
* @return Invoice object
*/
2024-01-14 05:05:00 +01:00
public function save(): ?Invoice
{
$this->invoice->saveQuietly();
return $this->invoice->fresh();
}
}