File "Invoice.php"
Full Path: /home/pulsehostuk9/public_html/invoicer.pulsehost.co.uk/app/Http/Controllers/Invoice.php
File size: 22.28 KB
MIME-type: text/x-php
Charset: utf-8
<?php
namespace App\Models;
use App;
use App\Mail\SendInvoiceMail;
use App\Services\SerialNumberFormatter;
use App\Traits\GeneratesPdfTrait;
use App\Traits\HasCustomFieldsTrait;
use Barryvdh\DomPDF\Facade\Pdf as PDF;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Vite;
use Illuminate\Support\Str;
use Nwidart\Modules\Facades\Module;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Vinkla\Hashids\Facades\Hashids;
class Invoice extends Model implements HasMedia
{
use GeneratesPdfTrait;
use HasCustomFieldsTrait;
use HasFactory;
use InteractsWithMedia;
public const STATUS_DRAFT = 'DRAFT';
public const STATUS_SENT = 'SENT';
public const STATUS_VIEWED = 'VIEWED';
public const STATUS_COMPLETED = 'COMPLETED';
public const STATUS_UNPAID = 'UNPAID';
public const STATUS_PARTIALLY_PAID = 'PARTIALLY_PAID';
public const STATUS_PAID = 'PAID';
protected $dates = [
'created_at',
'updated_at',
'deleted_at',
'invoice_date',
'due_date',
];
protected $guarded = [
'id',
];
protected $appends = [
'formattedCreatedAt',
'formattedInvoiceDate',
'formattedDueDate',
'invoicePdfUrl',
];
protected function casts(): array
{
return [
'total' => 'integer',
'tax' => 'integer',
'sub_total' => 'integer',
'discount' => 'float',
'discount_val' => 'integer',
'exchange_rate' => 'float',
];
}
public function transactions(): HasMany
{
return $this->hasMany(Transaction::class);
}
public function emailLogs(): MorphMany
{
return $this->morphMany('App\Models\EmailLog', 'mailable');
}
public function items(): HasMany
{
return $this->hasMany(\App\Models\InvoiceItem::class);
}
public function taxes(): HasMany
{
return $this->hasMany(Tax::class);
}
public function payments(): HasMany
{
return $this->hasMany(Payment::class);
}
public function currency(): BelongsTo
{
return $this->belongsTo(Currency::class);
}
public function company(): BelongsTo
{
return $this->belongsTo(Company::class);
}
public function customer(): BelongsTo
{
return $this->belongsTo(Customer::class, 'customer_id');
}
public function recurringInvoice(): BelongsTo
{
return $this->belongsTo(RecurringInvoice::class);
}
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'creator_id');
}
public function getInvoicePdfUrlAttribute()
{
return url('/invoices/pdf/'.$this->unique_hash);
}
public function getPaymentModuleEnabledAttribute()
{
if (Module::has('Payments')) {
return Module::isEnabled('Payments');
}
return false;
}
public function getAllowEditAttribute()
{
$retrospective_edit = CompanySetting::getSetting('retrospective_edits', $this->company_id);
$allowed = true;
$status = [
self::STATUS_DRAFT,
self::STATUS_SENT,
self::STATUS_VIEWED,
self::STATUS_COMPLETED,
];
if ($retrospective_edit == 'disable_on_invoice_sent' && (in_array($this->status, $status)) && ($this->paid_status === Invoice::STATUS_PARTIALLY_PAID || $this->paid_status === Invoice::STATUS_PAID)) {
$allowed = false;
} elseif ($retrospective_edit == 'disable_on_invoice_partial_paid' && ($this->paid_status === Invoice::STATUS_PARTIALLY_PAID || $this->paid_status === Invoice::STATUS_PAID)) {
$allowed = false;
} elseif ($retrospective_edit == 'disable_on_invoice_paid' && $this->paid_status === Invoice::STATUS_PAID) {
$allowed = false;
}
return $allowed;
}
public function getPreviousStatus()
{
if ($this->viewed) {
return self::STATUS_VIEWED;
} elseif ($this->sent) {
return self::STATUS_SENT;
} else {
return self::STATUS_DRAFT;
}
}
public function getFormattedNotesAttribute($value)
{
return $this->getNotes();
}
public function getFormattedCreatedAtAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->created_at)->format($dateFormat);
}
public function getFormattedDueDateAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->due_date)->translatedFormat($dateFormat);
}
public function getFormattedInvoiceDateAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
return Carbon::parse($this->invoice_date)->translatedFormat($dateFormat);
}
public function scopeWhereStatus($query, $status)
{
return $query->where('invoices.status', $status);
}
public function scopeWherePaidStatus($query, $status)
{
return $query->where('invoices.paid_status', $status);
}
public function scopeWhereDueStatus($query, $status)
{
return $query->whereIn('invoices.paid_status', [
self::STATUS_UNPAID,
self::STATUS_PARTIALLY_PAID,
]);
}
public function scopeWhereInvoiceNumber($query, $invoiceNumber)
{
return $query->where('invoices.invoice_number', 'LIKE', '%'.$invoiceNumber.'%');
}
public function scopeInvoicesBetween($query, $start, $end)
{
return $query->whereBetween(
'invoices.invoice_date',
[$start->format('Y-m-d'), $end->format('Y-m-d')]
);
}
public function scopeWhereSearch($query, $search)
{
foreach (explode(' ', $search) as $term) {
$query->whereHas('customer', function ($query) use ($term) {
$query->where('name', 'LIKE', '%'.$term.'%')
->orWhere('contact_name', 'LIKE', '%'.$term.'%')
->orWhere('company_name', 'LIKE', '%'.$term.'%');
});
}
}
public function scopeWhereOrder($query, $orderByField, $orderBy)
{
$query->orderBy($orderByField, $orderBy);
}
public function scopeApplyFilters($query, array $filters)
{
$filters = collect($filters);
if ($filters->get('search')) {
$query->whereSearch($filters->get('search'));
}
if ($filters->get('status')) {
if (
$filters->get('status') == self::STATUS_UNPAID ||
$filters->get('status') == self::STATUS_PARTIALLY_PAID ||
$filters->get('status') == self::STATUS_PAID
) {
$query->wherePaidStatus($filters->get('status'));
} elseif ($filters->get('status') == 'DUE') {
$query->whereDueStatus($filters->get('status'));
} else {
$query->whereStatus($filters->get('status'));
}
}
if ($filters->get('paid_status')) {
$query->wherePaidStatus($filters->get('status'));
}
if ($filters->get('invoice_id')) {
$query->whereInvoice($filters->get('invoice_id'));
}
if ($filters->get('invoice_number')) {
$query->whereInvoiceNumber($filters->get('invoice_number'));
}
if ($filters->get('from_date') && $filters->get('to_date')) {
$start = Carbon::createFromFormat('Y-m-d', $filters->get('from_date'));
$end = Carbon::createFromFormat('Y-m-d', $filters->get('to_date'));
$query->invoicesBetween($start, $end);
}
if ($filters->get('customer_id')) {
$query->whereCustomer($filters->get('customer_id'));
}
if ($filters->get('orderByField') || $filters->get('orderBy')) {
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'sequence_number';
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'desc';
$query->whereOrder($field, $orderBy);
}
}
public function scopeWhereInvoice($query, $invoice_id)
{
$query->orWhere('id', $invoice_id);
}
public function scopeWhereCompany($query)
{
$query->where('invoices.company_id', request()->header('company'));
}
public function scopeWhereCompanyId($query, $company)
{
$query->where('invoices.company_id', $company);
}
public function scopeWhereCustomer($query, $customer_id)
{
$query->where('invoices.customer_id', $customer_id);
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return $query->get();
}
return $query->paginate($limit);
}
public static function createInvoice($request)
{
$data = $request->getInvoicePayload();
if ($request->has('invoiceSend')) {
$data['status'] = Invoice::STATUS_SENT;
}
$invoice = Invoice::create($data);
$serial = (new SerialNumberFormatter())
->setModel($invoice)
->setCompany($invoice->company_id)
->setCustomer($invoice->customer_id)
->setNextNumbers();
$invoice->sequence_number = $serial->nextSequenceNumber;
$invoice->customer_sequence_number = $serial->nextCustomerSequenceNumber;
$invoice->unique_hash = Hashids::connection(Invoice::class)->encode($invoice->id);
$invoice->save();
self::createItems($invoice, $request->items);
$company_currency = CompanySetting::getSetting('currency', $request->header('company'));
if ((string) $data['currency_id'] !== $company_currency) {
ExchangeRateLog::addExchangeRateLog($invoice);
}
if ($request->has('taxes') && (! empty($request->taxes))) {
self::createTaxes($invoice, $request->taxes);
}
if ($request->customFields) {
$invoice->addCustomFields($request->customFields);
}
$invoice = Invoice::with([
'items',
'items.fields',
'items.fields.customField',
'customer',
'taxes',
])
->find($invoice->id);
return $invoice;
}
public function updateInvoice($request)
{
$serial = (new SerialNumberFormatter())
->setModel($this)
->setCompany($this->company_id)
->setCustomer($request->customer_id)
->setModelObject($this->id)
->setNextNumbers();
$data = $request->getInvoicePayload();
$oldTotal = $this->total;
$total_paid_amount = $this->total - $this->due_amount;
if ($total_paid_amount > 0 && $this->customer_id !== $request->customer_id) {
return 'customer_cannot_be_changed_after_payment_is_added';
}
if ($request->total < $total_paid_amount) {
return 'total_invoice_amount_must_be_more_than_paid_amount';
}
if ($oldTotal != $request->total) {
$oldTotal = (int) round($request->total) - (int) $oldTotal;
} else {
$oldTotal = 0;
}
$data['due_amount'] = ($this->due_amount + $oldTotal);
$data['base_due_amount'] = $data['due_amount'] * $data['exchange_rate'];
$data['customer_sequence_number'] = $serial->nextCustomerSequenceNumber;
$this->update($data);
$statusData = $this->getInvoiceStatusByAmount($data['due_amount']);
if (! empty($statusData)) {
$this->update($statusData);
}
$company_currency = CompanySetting::getSetting('currency', $request->header('company'));
if ((string) $data['currency_id'] !== $company_currency) {
ExchangeRateLog::addExchangeRateLog($this);
}
$this->items->map(function ($item) {
$fields = $item->fields()->get();
$fields->map(function ($field) {
$field->delete();
});
});
$this->items()->delete();
$this->taxes()->delete();
self::createItems($this, $request->items);
if ($request->has('taxes') && (! empty($request->taxes))) {
self::createTaxes($this, $request->taxes);
}
if ($request->customFields) {
$this->updateCustomFields($request->customFields);
}
$invoice = Invoice::with([
'items',
'items.fields',
'items.fields.customField',
'customer',
'taxes',
])
->find($this->id);
return $invoice;
}
public function sendInvoiceData($data)
{
$data['invoice'] = $this->toArray();
$data['customer'] = $this->customer->toArray();
$data['company'] = Company::find($this->company_id);
$data['subject'] = $this->getEmailString($data['subject']);
$data['body'] = $this->getEmailString($data['body']);
$data['attach']['data'] = ($this->getEmailAttachmentSetting()) ? $this->getPDFData() : null;
return $data;
}
public function preview($data)
{
$data = $this->sendInvoiceData($data);
return [
'type' => 'preview',
'view' => new SendInvoiceMail($data),
];
}
public function send($data)
{
$data = $this->sendInvoiceData($data);
\Mail::to($data['to'])->send(new SendInvoiceMail($data));
if ($this->status == Invoice::STATUS_DRAFT) {
$this->status = Invoice::STATUS_SENT;
$this->sent = true;
$this->save();
}
return [
'success' => true,
'type' => 'send',
];
}
public static function createItems($invoice, $invoiceItems)
{
$exchange_rate = $invoice->exchange_rate;
foreach ($invoiceItems as $invoiceItem) {
$invoiceItem['company_id'] = $invoice->company_id;
$invoiceItem['exchange_rate'] = $exchange_rate;
$invoiceItem['base_price'] = $invoiceItem['price'] * $exchange_rate;
$invoiceItem['base_discount_val'] = $invoiceItem['discount_val'] * $exchange_rate;
$invoiceItem['base_tax'] = $invoiceItem['tax'] * $exchange_rate;
$invoiceItem['base_total'] = $invoiceItem['total'] * $exchange_rate;
if (array_key_exists('recurring_invoice_id', $invoiceItem)) {
unset($invoiceItem['recurring_invoice_id']);
}
$item = $invoice->items()->create($invoiceItem);
if (array_key_exists('taxes', $invoiceItem) && $invoiceItem['taxes']) {
foreach ($invoiceItem['taxes'] as $tax) {
$tax['company_id'] = $invoice->company_id;
$tax['exchange_rate'] = $invoice->exchange_rate;
$tax['base_amount'] = $tax['amount'] * $exchange_rate;
$tax['currency_id'] = $invoice->currency_id;
if (gettype($tax['amount']) !== 'NULL') {
if (array_key_exists('recurring_invoice_id', $invoiceItem)) {
unset($invoiceItem['recurring_invoice_id']);
}
$item->taxes()->create($tax);
}
}
}
if (array_key_exists('custom_fields', $invoiceItem) && $invoiceItem['custom_fields']) {
$item->addCustomFields($invoiceItem['custom_fields']);
}
}
}
public static function createTaxes($invoice, $taxes)
{
$exchange_rate = $invoice->exchange_rate;
foreach ($taxes as $tax) {
$tax['company_id'] = $invoice->company_id;
$tax['exchange_rate'] = $invoice->exchange_rate;
$tax['base_amount'] = $tax['amount'] * $exchange_rate;
$tax['currency_id'] = $invoice->currency_id;
if (gettype($tax['amount']) !== 'NULL') {
if (array_key_exists('recurring_invoice_id', $tax)) {
unset($tax['recurring_invoice_id']);
}
$invoice->taxes()->create($tax);
}
}
}
public function getPDFData()
{
$taxes = collect();
if ($this->tax_per_item === 'YES') {
foreach ($this->items as $item) {
foreach ($item->taxes as $tax) {
$found = $taxes->filter(function ($item) use ($tax) {
return $item->tax_type_id == $tax->tax_type_id;
})->first();
if ($found) {
$found->amount += $tax->amount;
} else {
$taxes->push($tax);
}
}
}
}
$invoiceTemplate = self::find($this->id)->template_name;
$company = Company::find($this->company_id);
$locale = CompanySetting::getSetting('language', $company->id);
$customFields = CustomField::where('model_type', 'Item')->get();
App::setLocale($locale);
$logo = $company->logo_path;
view()->share([
'invoice' => $this,
'customFields' => $customFields,
'company_address' => $this->getCompanyAddress(),
'shipping_address' => $this->getCustomerShippingAddress(),
'billing_address' => $this->getCustomerBillingAddress(),
'notes' => $this->getNotes(),
'logo' => $logo ?? null,
'taxes' => $taxes,
]);
if (request()->has('preview')) {
return view('app.pdf.invoice.'.$invoiceTemplate);
}
return PDF::loadView('app.pdf.invoice.'.$invoiceTemplate);
}
public function getEmailAttachmentSetting()
{
$invoiceAsAttachment = CompanySetting::getSetting('invoice_email_attachment', $this->company_id);
if ($invoiceAsAttachment == 'NO') {
return false;
}
return true;
}
public function getCompanyAddress()
{
if ($this->company && (! $this->company->address()->exists())) {
return false;
}
$format = CompanySetting::getSetting('invoice_company_address_format', $this->company_id);
return $this->getFormattedString($format);
}
public function getCustomerShippingAddress()
{
if ($this->customer && (! $this->customer->shippingAddress()->exists())) {
return false;
}
$format = CompanySetting::getSetting('invoice_shipping_address_format', $this->company_id);
return $this->getFormattedString($format);
}
public function getCustomerBillingAddress()
{
if ($this->customer && (! $this->customer->billingAddress()->exists())) {
return false;
}
$format = CompanySetting::getSetting('invoice_billing_address_format', $this->company_id);
return $this->getFormattedString($format);
}
public function getNotes()
{
return $this->getFormattedString($this->notes);
}
public function getEmailString($body)
{
$values = array_merge($this->getFieldsArray(), $this->getExtraFields());
$body = strtr($body, $values);
return preg_replace('/{(.*?)}/', '', $body);
}
public function getExtraFields()
{
return [
'{INVOICE_DATE}' => $this->formattedInvoiceDate,
'{INVOICE_DUE_DATE}' => $this->formattedDueDate,
'{INVOICE_NUMBER}' => $this->invoice_number,
'{INVOICE_REF_NUMBER}' => $this->reference_number,
];
}
public static function invoiceTemplates()
{
$templates = Storage::disk('views')->files('/app/pdf/invoice');
$invoiceTemplates = [];
foreach ($templates as $key => $template) {
$templateName = Str::before(basename($template), '.blade.php');
$invoiceTemplates[$key]['name'] = $templateName;
$invoiceTemplates[$key]['path'] = Vite::asset('resources/static/img/PDF/'.$templateName.'.png');
}
return $invoiceTemplates;
}
public function addInvoicePayment($amount)
{
$this->due_amount += $amount;
$this->base_due_amount = $this->due_amount * $this->exchange_rate;
$this->changeInvoiceStatus($this->due_amount);
}
public function subtractInvoicePayment($amount)
{
$this->due_amount -= $amount;
$this->base_due_amount = $this->due_amount * $this->exchange_rate;
$this->changeInvoiceStatus($this->due_amount);
}
/**
* Set the invoice status from amount.
*
* @return array
*/
public function getInvoiceStatusByAmount($amount)
{
if ($amount < 0) {
return [];
}
if ($amount == 0) {
$data = [
'status' => Invoice::STATUS_COMPLETED,
'paid_status' => Invoice::STATUS_PAID,
'overdue' => false,
];
} elseif ($amount == $this->total) {
$data = [
'status' => $this->getPreviousStatus(),
'paid_status' => Invoice::STATUS_UNPAID,
];
} else {
$data = [
'status' => $this->getPreviousStatus(),
'paid_status' => Invoice::STATUS_PARTIALLY_PAID,
];
}
return $data;
}
/**
* Changes the invoice status right away
*
* @return string[]|void
*/
public function changeInvoiceStatus($amount)
{
$status = $this->getInvoiceStatusByAmount($amount);
if (! empty($status)) {
foreach ($status as $key => $value) {
$this->setAttribute($key, $value);
}
$this->save();
}
}
public static function deleteInvoices($ids)
{
foreach ($ids as $id) {
$invoice = self::find($id);
if ($invoice->transactions()->exists()) {
$invoice->transactions()->delete();
}
$invoice->delete();
}
return true;
}
}