1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-13 22:54:25 +01:00
invoiceninja/app/Models/Invoice.php

1314 lines
34 KiB
PHP
Raw Normal View History

2015-03-18 00:39:03 +01:00
<?php namespace App\Models;
2015-03-16 22:45:25 +01:00
2015-09-29 12:21:57 +02:00
use Utils;
2015-10-20 10:23:38 +02:00
use DateTime;
2015-03-31 11:38:24 +02:00
use Illuminate\Database\Eloquent\SoftDeletes;
2015-11-08 09:43:49 +01:00
use Laracasts\Presenter\PresentableTrait;
2015-10-28 20:22:07 +01:00
use App\Events\QuoteWasCreated;
use App\Events\QuoteWasUpdated;
use App\Events\InvoiceWasCreated;
use App\Events\InvoiceWasUpdated;
2015-10-29 15:42:05 +01:00
use App\Events\InvoiceInvitationWasEmailed;
use App\Events\QuoteInvitationWasEmailed;
2015-10-28 20:22:07 +01:00
/**
* Class Invoice
*/
2015-10-28 20:22:07 +01:00
class Invoice extends EntityModel implements BalanceAffecting
2015-03-16 22:45:25 +01:00
{
2015-11-08 09:43:49 +01:00
use PresentableTrait;
2015-11-16 20:21:48 +01:00
use OwnedByClientTrait;
2015-10-25 08:13:06 +01:00
use SoftDeletes {
SoftDeletes::trashed as parentTrashed;
}
/**
* @var string
*/
2015-11-08 09:43:49 +01:00
protected $presenter = 'App\Ninja\Presenters\InvoicePresenter';
/**
* @var array
*/
2015-03-31 11:38:24 +02:00
protected $dates = ['deleted_at'];
/**
* @var array
*/
2016-03-31 11:29:01 +02:00
protected $fillable = [
'tax_name1',
'tax_rate1',
'tax_name2',
2016-05-29 14:34:44 +02:00
'tax_rate2',
2016-03-31 11:29:01 +02:00
];
2016-05-29 14:34:44 +02:00
/**
* @var array
*/
2015-04-21 22:09:45 +02:00
protected $casts = [
'is_recurring' => 'boolean',
2015-06-10 10:34:20 +02:00
'has_tasks' => 'boolean',
2016-05-09 22:29:02 +02:00
'client_enable_auto_bill' => 'boolean',
2016-01-10 11:25:05 +01:00
'has_expenses' => 'boolean',
2015-04-21 22:09:45 +02:00
];
2015-11-24 20:45:38 +01:00
// used for custom invoice numbers
/**
* @var array
*/
2015-10-22 20:48:12 +02:00
public static $patternFields = [
'counter',
'custom1',
'custom2',
'userId',
'year',
'date:',
];
2015-10-28 20:22:07 +01:00
/**
* @var string
*/
2015-11-24 20:45:38 +01:00
public static $fieldInvoiceNumber = 'invoice_number';
/**
* @var string
*/
2015-11-24 20:45:38 +01:00
public static $fieldInvoiceDate = 'invoice_date';
/**
* @var string
*/
2015-11-24 20:45:38 +01:00
public static $fieldDueDate = 'due_date';
/**
* @var string
*/
2015-11-24 20:45:38 +01:00
public static $fieldAmount = 'amount';
/**
* @var string
*/
2015-11-24 20:45:38 +01:00
public static $fieldPaid = 'paid';
/**
* @var string
*/
2015-11-24 20:45:38 +01:00
public static $fieldNotes = 'notes';
/**
* @var string
*/
2015-11-24 20:45:38 +01:00
public static $fieldTerms = 'terms';
/**
* @return array
*/
2015-11-24 20:45:38 +01:00
public static function getImportColumns()
{
return [
Client::$fieldName,
Invoice::$fieldInvoiceNumber,
Invoice::$fieldInvoiceDate,
Invoice::$fieldDueDate,
Invoice::$fieldAmount,
Invoice::$fieldPaid,
Invoice::$fieldNotes,
Invoice::$fieldTerms,
];
}
/**
* @return array
*/
2015-11-24 20:45:38 +01:00
public static function getImportMap()
{
return [
2015-11-25 10:35:24 +01:00
'number^po' => 'invoice_number',
'amount' => 'amount',
2015-11-24 20:45:38 +01:00
'organization' => 'name',
2015-11-25 10:35:24 +01:00
'paid^date' => 'paid',
'invoice_date|create_date' => 'invoice_date',
2015-11-24 20:45:38 +01:00
'terms' => 'terms',
'notes' => 'notes',
];
}
/**
* @return string
*/
2015-10-28 20:22:07 +01:00
public function getRoute()
{
2016-08-23 22:20:03 +02:00
if ($this->is_recurring) {
$entityType = 'recurring_invoice';
} else {
$entityType = $this->getEntityType();
}
2015-10-28 20:22:07 +01:00
return "/{$entityType}s/{$this->public_id}/edit";
}
/**
* @return mixed
*/
2015-10-28 20:22:07 +01:00
public function getDisplayName()
{
2016-07-21 14:35:23 +02:00
return $this->is_recurring ? trans('texts.recurring') : $this->invoice_number;
2015-10-28 20:22:07 +01:00
}
/**
* @return bool
*/
2015-11-02 07:51:57 +01:00
public function affectsBalance()
{
2016-05-26 16:56:54 +02:00
return $this->isType(INVOICE_TYPE_STANDARD) && !$this->is_recurring;
2015-11-02 07:51:57 +01:00
}
/**
* @return float|int
*/
2015-10-28 20:22:07 +01:00
public function getAdjustment()
{
2015-11-02 07:51:57 +01:00
if (!$this->affectsBalance()) {
2015-10-28 20:22:07 +01:00
return 0;
}
return $this->getRawAdjustment();
}
/**
* @return float
*/
2015-10-28 20:22:07 +01:00
private function getRawAdjustment()
{
return floatval($this->amount) - floatval($this->getOriginal('amount'));
}
/**
* @return bool
*/
2015-10-28 20:22:07 +01:00
public function isChanged()
{
if ($this->getRawAdjustment() != 0) {
return true;
}
foreach ([
2015-12-13 21:12:54 +01:00
'invoice_number',
'po_number',
'invoice_date',
'due_date',
'terms',
'public_notes',
'invoice_footer',
'partial',
2015-10-28 20:22:07 +01:00
] as $field) {
if ($this->$field != $this->getOriginal($field)) {
return true;
}
}
return false;
}
/**
* @param bool $calculate
* @return int|mixed
*/
2016-02-24 21:58:42 +01:00
public function getAmountPaid($calculate = false)
2015-10-28 20:22:07 +01:00
{
2016-05-26 16:56:54 +02:00
if ($this->isType(INVOICE_TYPE_QUOTE) || $this->is_recurring) {
2015-10-28 20:22:07 +01:00
return 0;
}
2016-02-24 21:58:42 +01:00
if ($calculate) {
$amount = 0;
foreach ($this->payments as $payment) {
if ($payment->payment_status_id == PAYMENT_STATUS_VOIDED || $payment->payment_status_id == PAYMENT_STATUS_FAILED) {
continue;
}
$amount += $payment->getCompletedAmount();
2016-02-24 21:58:42 +01:00
}
return $amount;
} else {
return ($this->amount - $this->balance);
}
2015-10-28 20:22:07 +01:00
}
2016-01-10 11:25:05 +01:00
/**
* @return bool
*/
2015-10-25 08:13:06 +01:00
public function trashed()
2015-10-23 13:55:18 +02:00
{
2015-10-25 08:13:06 +01:00
if ($this->client && $this->client->trashed()) {
2015-10-23 13:55:18 +02:00
return true;
}
2015-10-25 08:13:06 +01:00
return self::parentTrashed();
2015-10-23 13:55:18 +02:00
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
2015-03-16 22:45:25 +01:00
public function account()
{
2015-03-31 11:38:24 +02:00
return $this->belongsTo('App\Models\Account');
2015-03-16 22:45:25 +01:00
}
/**
* @return mixed
*/
2015-03-16 22:45:25 +01:00
public function user()
{
2015-09-25 11:57:40 +02:00
return $this->belongsTo('App\Models\User')->withTrashed();
2015-03-16 22:45:25 +01:00
}
/**
* @return mixed
*/
2015-03-16 22:45:25 +01:00
public function client()
{
2015-03-31 11:38:24 +02:00
return $this->belongsTo('App\Models\Client')->withTrashed();
2015-03-16 22:45:25 +01:00
}
/**
* @return mixed
*/
2015-03-16 22:45:25 +01:00
public function invoice_items()
{
2015-03-31 11:38:24 +02:00
return $this->hasMany('App\Models\InvoiceItem')->orderBy('id');
2015-03-16 22:45:25 +01:00
}
/**
* @return mixed
*/
2016-03-23 03:23:45 +01:00
public function documents()
{
return $this->hasMany('App\Models\Document')->orderBy('id');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
2015-03-16 22:45:25 +01:00
public function invoice_status()
{
2015-03-31 11:38:24 +02:00
return $this->belongsTo('App\Models\InvoiceStatus');
2015-03-16 22:45:25 +01:00
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
2015-03-16 22:45:25 +01:00
public function invoice_design()
{
2015-03-31 11:38:24 +02:00
return $this->belongsTo('App\Models\InvoiceDesign');
2015-03-16 22:45:25 +01:00
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
2016-01-05 13:51:27 +01:00
public function payments()
{
2016-02-07 12:01:39 +01:00
return $this->hasMany('App\Models\Payment', 'invoice_id', 'id');
2016-01-05 13:51:27 +01:00
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
2015-07-07 22:08:16 +02:00
public function recurring_invoice()
{
return $this->belongsTo('App\Models\Invoice');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function recurring_invoices()
{
return $this->hasMany('App\Models\Invoice', 'recurring_invoice_id');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
2016-02-25 10:25:07 +01:00
public function frequency()
{
return $this->belongsTo('App\Models\Frequency');
}
/**
* @return mixed
*/
2015-03-16 22:45:25 +01:00
public function invitations()
{
2015-03-31 11:38:24 +02:00
return $this->hasMany('App\Models\Invitation')->orderBy('invitations.contact_id');
2015-03-16 22:45:25 +01:00
}
/**
* @return mixed
*/
2016-02-17 09:10:33 +01:00
public function expenses()
{
return $this->hasMany('App\Models\Expense','invoice_id','id')->withTrashed();
}
/**
* @param $query
* @return mixed
*/
2016-05-05 18:25:26 +02:00
public function scopeInvoices($query)
{
2016-05-26 16:56:54 +02:00
return $query->where('invoice_type_id', '=', INVOICE_TYPE_STANDARD)
2016-05-05 18:25:26 +02:00
->where('is_recurring', '=', false);
}
2016-11-27 10:46:32 +01:00
/**
* @param $query
* @return mixed
*/
public function scopeRecurring($query)
{
return $query->where('invoice_type_id', '=', INVOICE_TYPE_STANDARD)
->where('is_recurring', '=', true);
}
/**
* @param $query
* @return mixed
*/
2016-06-06 21:17:16 +02:00
public function scopeQuotes($query)
{
return $query->where('invoice_type_id', '=', INVOICE_TYPE_QUOTE)
->where('is_recurring', '=', false);
}
/**
* @param $query
* @param $typeId
* @return mixed
*/
2016-05-26 16:56:54 +02:00
public function scopeInvoiceType($query, $typeId)
{
return $query->where('invoice_type_id', '=', $typeId);
}
/**
* @param $typeId
* @return bool
*/
2016-05-26 16:56:54 +02:00
public function isType($typeId) {
return $this->invoice_type_id == $typeId;
}
/**
* @return bool
*/
2016-05-29 14:34:44 +02:00
public function isQuote() {
return $this->isType(INVOICE_TYPE_QUOTE);
}
/**
* @return bool
*/
public function isInvoice() {
return $this->isType(INVOICE_TYPE_STANDARD) && ! $this->is_recurring;
}
/**
* @param bool $notify
*/
2015-10-29 15:42:05 +01:00
public function markInvitationsSent($notify = false)
{
foreach ($this->invitations as $invitation) {
$this->markInvitationSent($invitation, false, $notify);
}
}
/**
* @param $invitation
* @param bool $messageId
* @param bool $notify
*/
2015-10-29 15:42:05 +01:00
public function markInvitationSent($invitation, $messageId = false, $notify = true)
2015-10-28 20:22:07 +01:00
{
if (!$this->isSent()) {
$this->invoice_status_id = INVOICE_STATUS_SENT;
$this->save();
}
2015-10-29 15:42:05 +01:00
$invitation->markSent($messageId);
2016-01-10 11:25:05 +01:00
// if the user marks it as sent rather than acually sending it
2015-10-29 15:42:05 +01:00
// then we won't track it in the activity log
if (!$notify) {
return;
}
2016-05-26 16:56:54 +02:00
if ($this->isType(INVOICE_TYPE_QUOTE)) {
2015-10-29 15:42:05 +01:00
event(new QuoteInvitationWasEmailed($invitation));
} else {
event(new InvoiceInvitationWasEmailed($invitation));
}
2015-10-28 20:22:07 +01:00
}
public function markViewed()
{
if (!$this->isViewed()) {
$this->invoice_status_id = INVOICE_STATUS_VIEWED;
$this->save();
}
}
/**
* @param bool $save
*/
2015-11-18 15:40:50 +01:00
public function updatePaidStatus($save = true)
2015-10-28 20:22:07 +01:00
{
2015-11-18 23:02:01 +01:00
$statusId = false;
if ($this->amount > 0 && $this->balance == 0) {
$statusId = INVOICE_STATUS_PAID;
} elseif ($this->balance > 0 && $this->balance < $this->amount) {
$statusId = INVOICE_STATUS_PARTIAL;
} elseif ($this->isPartial() && $this->balance > 0) {
$statusId = ($this->balance == $this->amount ? INVOICE_STATUS_SENT : INVOICE_STATUS_PARTIAL);
}
if ($statusId && $statusId != $this->invoice_status_id) {
$this->invoice_status_id = $statusId;
2015-11-18 15:40:50 +01:00
if ($save) {
$this->save();
}
2015-10-28 20:22:07 +01:00
}
}
public function markApproved()
{
2016-05-26 16:56:54 +02:00
if ($this->isType(INVOICE_TYPE_QUOTE)) {
$this->invoice_status_id = INVOICE_STATUS_APPROVED;
$this->save();
}
}
/**
* @param $balanceAdjustment
* @param int $partial
*/
2015-10-28 20:22:07 +01:00
public function updateBalances($balanceAdjustment, $partial = 0)
{
if ($this->is_deleted) {
return;
}
2015-10-28 20:22:07 +01:00
$this->balance = $this->balance + $balanceAdjustment;
if ($this->partial > 0) {
$this->partial = $partial;
}
$this->save();
}
/**
* @return mixed
*/
2015-03-16 22:45:25 +01:00
public function getName()
{
return $this->is_recurring ? trans('texts.recurring') : $this->invoice_number;
2015-03-16 22:45:25 +01:00
}
/**
* @return string
*/
2015-04-22 23:40:21 +02:00
public function getFileName()
{
$entityType = $this->getEntityType();
return trans("texts.$entityType") . '_' . $this->invoice_number . '.pdf';
}
/**
* @return string
*/
2015-05-09 20:25:16 +02:00
public function getPDFPath()
{
return storage_path() . '/pdfcache/cache-' . $this->id . '.pdf';
}
public function canBePaid()
{
return floatval($this->balance) > 0 && ! $this->is_deleted;
}
/**
* @param $invoice
* @return string
*/
2015-07-29 21:55:12 +02:00
public static function calcLink($invoice)
{
return link_to('invoices/' . $invoice->public_id, $invoice->invoice_number);
}
/**
* @return string
*/
2015-03-16 22:45:25 +01:00
public function getLink()
{
2015-07-29 21:55:12 +02:00
return self::calcLink($this);
2015-03-16 22:45:25 +01:00
}
2016-08-13 21:19:37 +02:00
public function getInvitationLink($type = 'view', $forceOnsite = false)
{
if ( ! $this->relationLoaded('invitations')) {
$this->load('invitations');
}
return $this->invitations[0]->getLink($type, $forceOnsite);
}
/**
* @return mixed
*/
2015-03-16 22:45:25 +01:00
public function getEntityType()
{
2016-05-26 16:56:54 +02:00
return $this->isType(INVOICE_TYPE_QUOTE) ? ENTITY_QUOTE : ENTITY_INVOICE;
2015-03-16 22:45:25 +01:00
}
2016-08-31 21:10:41 +02:00
public function subEntityType()
{
if ($this->is_recurring) {
return ENTITY_RECURRING_INVOICE;
} else {
return $this->getEntityType();
}
}
/**
* @return bool
*/
2015-03-16 22:45:25 +01:00
public function isSent()
{
return $this->invoice_status_id >= INVOICE_STATUS_SENT;
}
/**
* @return bool
*/
2015-03-16 22:45:25 +01:00
public function isViewed()
{
return $this->invoice_status_id >= INVOICE_STATUS_VIEWED;
}
/**
* @return bool
*/
2015-11-18 23:02:01 +01:00
public function isPartial()
{
return $this->invoice_status_id >= INVOICE_STATUS_PARTIAL;
}
/**
* @return bool
*/
2015-03-16 22:45:25 +01:00
public function isPaid()
{
return $this->invoice_status_id >= INVOICE_STATUS_PAID;
}
/**
* @return bool
*/
2015-12-16 12:49:26 +01:00
public function isOverdue()
{
if ( ! $this->due_date) {
return false;
}
return time() > strtotime($this->due_date);
}
/**
* @return mixed
*/
2015-04-16 21:57:12 +02:00
public function getRequestedAmount()
{
return $this->partial > 0 ? $this->partial : $this->balance;
}
/**
* @return string
*/
2016-01-14 22:28:17 +01:00
public function getCurrencyCode()
{
if ($this->client->currency) {
return $this->client->currency->code;
} elseif ($this->account->currency) {
return $this->account->currency->code;
} else {
return 'USD';
}
}
/**
* @return $this
*/
2015-03-16 22:45:25 +01:00
public function hidePrivateFields()
{
$this->setVisible([
'invoice_number',
'discount',
'is_amount_discount',
'po_number',
'invoice_date',
'due_date',
'terms',
'invoice_footer',
'public_notes',
'amount',
'balance',
'invoice_items',
2016-03-23 03:23:45 +01:00
'documents',
'expenses',
2015-03-16 22:45:25 +01:00
'client',
2016-03-31 11:29:01 +02:00
'tax_name1',
'tax_rate1',
'tax_name2',
'tax_rate2',
2015-03-16 22:45:25 +01:00
'account',
'invoice_design',
'invoice_design_id',
2016-01-07 08:08:30 +01:00
'invoice_fonts',
'features',
2016-05-26 16:56:54 +02:00
'invoice_type_id',
2015-03-16 22:45:25 +01:00
'custom_value1',
'custom_value2',
'custom_taxes1',
'custom_taxes2',
2015-04-16 19:12:56 +02:00
'partial',
2015-06-10 10:34:20 +02:00
'has_tasks',
2015-10-11 16:41:09 +02:00
'custom_text_value1',
'custom_text_value2',
2016-01-10 11:25:05 +01:00
'has_expenses',
]);
2015-03-16 22:45:25 +01:00
$this->client->setVisible([
'name',
'id_number',
'vat_number',
'address1',
'address2',
'city',
'state',
'postal_code',
'work_phone',
'payment_terms',
'contacts',
'country',
'currency_id',
2016-05-01 07:55:59 +02:00
'country_id',
2015-03-16 22:45:25 +01:00
'custom_value1',
'custom_value2',
]);
2015-03-16 22:45:25 +01:00
$this->account->setVisible([
'name',
2015-12-31 12:31:50 +01:00
'website',
2015-03-16 22:45:25 +01:00
'id_number',
'vat_number',
'address1',
'address2',
'city',
'state',
'postal_code',
'work_phone',
'work_email',
'country',
'currency_id',
'custom_label1',
'custom_value1',
'custom_label2',
'custom_value2',
'custom_client_label1',
'custom_client_label2',
'primary_color',
'secondary_color',
'hide_quantity',
'hide_paid_to_date',
'all_pages_header',
'all_pages_footer',
2015-03-16 22:45:25 +01:00
'custom_invoice_label1',
'custom_invoice_label2',
'pdf_email_attachment',
2015-09-07 11:07:55 +02:00
'show_item_taxes',
2015-10-11 16:41:09 +02:00
'custom_invoice_text_label1',
'custom_invoice_text_label2',
2016-02-28 12:59:52 +01:00
'custom_invoice_item_label1',
'custom_invoice_item_label2',
'invoice_embed_documents',
'page_size',
'include_item_taxes_inline',
2016-09-05 14:28:59 +02:00
'invoice_fields',
]);
2015-03-16 22:45:25 +01:00
foreach ($this->invoice_items as $invoiceItem) {
$invoiceItem->setVisible([
'product_key',
'notes',
2016-02-28 12:59:52 +01:00
'custom_value1',
'custom_value2',
2015-03-16 22:45:25 +01:00
'cost',
'qty',
2016-03-31 11:29:01 +02:00
'tax_name1',
'tax_rate1',
'tax_name2',
'tax_rate2',
]);
2015-03-16 22:45:25 +01:00
}
foreach ($this->client->contacts as $contact) {
$contact->setVisible([
'first_name',
'last_name',
'email',
'phone',
]);
2015-03-16 22:45:25 +01:00
}
foreach ($this->documents as $document) {
$document->setVisible([
'public_id',
'name',
]);
}
2016-05-29 14:34:44 +02:00
foreach ($this->expenses as $expense) {
$expense->setVisible([
'documents',
]);
2016-05-29 14:34:44 +02:00
foreach ($expense->documents as $document) {
$document->setVisible([
'public_id',
'name',
]);
}
}
2015-03-16 22:45:25 +01:00
return $this;
}
/**
* @return bool|\Recurr\RecurrenceCollection
* @throws \Recurr\Exception\MissingData
*/
2015-10-15 16:14:13 +02:00
public function getSchedule()
{
if (!$this->start_date || !$this->is_recurring || !$this->frequency_id) {
return false;
}
2015-10-20 10:23:38 +02:00
$startDate = $this->getOriginal('last_sent_date') ?: $this->getOriginal('start_date');
2015-10-22 20:48:12 +02:00
$startDate .= ' ' . $this->account->recurring_hour . ':00:00';
2015-10-15 21:37:01 +02:00
$startDate = $this->account->getDateTime($startDate);
2015-10-18 12:37:04 +02:00
$endDate = $this->end_date ? $this->account->getDateTime($this->getOriginal('end_date')) : null;
2015-10-15 21:37:01 +02:00
$timezone = $this->account->getTimezone();
2015-10-15 16:14:13 +02:00
$rule = $this->getRecurrenceRule();
$rule = new \Recurr\Rule("{$rule}", $startDate, $endDate, $timezone);
// Fix for months with less than 31 days
$transformerConfig = new \Recurr\Transformer\ArrayTransformerConfig();
$transformerConfig->enableLastDayOfMonthFix();
2016-01-10 11:25:05 +01:00
2015-10-15 16:14:13 +02:00
$transformer = new \Recurr\Transformer\ArrayTransformer();
$transformer->setConfig($transformerConfig);
$dates = $transformer->transform($rule);
if (count($dates) < 2) {
return false;
}
return $dates;
}
/**
* @return null
*/
2015-10-15 16:14:13 +02:00
public function getNextSendDate()
{
if ($this->start_date && !$this->last_sent_date) {
2015-10-22 20:48:12 +02:00
$startDate = $this->getOriginal('start_date') . ' ' . $this->account->recurring_hour . ':00:00';
2015-10-15 21:37:01 +02:00
return $this->account->getDateTime($startDate);
2015-10-15 16:14:13 +02:00
}
if (!$schedule = $this->getSchedule()) {
return null;
}
if (count($schedule) < 2) {
return null;
}
2016-01-10 11:25:05 +01:00
2015-10-15 16:14:13 +02:00
return $schedule[1]->getStart();
}
2016-05-29 14:34:44 +02:00
/**
* @param null $invoice_date
* @return mixed|null
*/
public function getDueDate($invoice_date = null){
if(!$this->is_recurring) {
return $this->due_date ? $this->due_date : null;
}
2016-05-29 14:34:44 +02:00
else{
$now = time();
if($invoice_date) {
// If $invoice_date is specified, all calculations are based on that date
if(is_numeric($invoice_date)) {
$now = $invoice_date;
}
else if(is_string($invoice_date)) {
$now = strtotime($invoice_date);
}
elseif ($invoice_date instanceof \DateTime) {
$now = $invoice_date->getTimestamp();
}
}
2016-05-29 14:34:44 +02:00
2016-01-14 22:28:17 +01:00
if($this->due_date && $this->due_date != '0000-00-00'){
// This is a recurring invoice; we're using a custom format here.
// The year is always 1998; January is 1st, 2nd, last day of the month.
// February is 1st Sunday after, 1st Monday after, ..., through 4th Saturday after.
$dueDateVal = strtotime($this->due_date);
$monthVal = (int)date('n', $dueDateVal);
$dayVal = (int)date('j', $dueDateVal);
2016-01-13 10:09:00 +01:00
$dueDate = false;
2016-05-29 14:34:44 +02:00
if($monthVal == 1) {// January; day of month
$currentDay = (int)date('j', $now);
$lastDayOfMonth = (int)date('t', $now);
$dueYear = (int)date('Y', $now);// This year
$dueMonth = (int)date('n', $now);// This month
$dueDay = $dayVal;// The day specified for the invoice
if($dueDay > $lastDayOfMonth) {
// No later than the end of the month
$dueDay = $lastDayOfMonth;
}
if($currentDay >= $dueDay) {
// Wait until next month
// We don't need to handle the December->January wraparaound, since PHP handles month 13 as January of next year
$dueMonth++;
// Reset the due day
$dueDay = $dayVal;
$lastDayOfMonth = (int)date('t', mktime(0, 0, 0, $dueMonth, 1, $dueYear));// The number of days in next month
// Check against the last day again
if($dueDay > $lastDayOfMonth){
// No later than the end of the month
$dueDay = $lastDayOfMonth;
2016-05-29 14:34:44 +02:00
}
}
$dueDate = mktime(0, 0, 0, $dueMonth, $dueDay, $dueYear);
}
else if($monthVal == 2) {// February; day of week
$ordinals = ['first', 'second', 'third', 'fourth'];
$daysOfWeek = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
$ordinalIndex = ceil($dayVal / 7) - 1;// 1-7 are "first"; 8-14 are "second", etc.
$dayOfWeekIndex = ($dayVal - 1) % 7;// 1,8,15,22 are Sunday, 2,9,16,23 are Monday, etc.
$dayStr = $ordinals[$ordinalIndex] . ' ' . $daysOfWeek[$dayOfWeekIndex];// "first sunday", "first monday", etc.
$dueDate = strtotime($dayStr, $now);
}
if($dueDate) {
return date('Y-m-d', $dueDate);// SQL format
}
}
else if ($this->client->payment_terms != 0) {
// No custom due date set for this invoice; use the client's payment terms
$days = $this->client->payment_terms;
if ($days == -1) {
$days = 0;
}
return date('Y-m-d', strtotime('+'.$days.' day', $now));
}
}
2016-05-29 14:34:44 +02:00
// Couldn't calculate one
return null;
}
2015-10-15 16:14:13 +02:00
/**
* @param int $min
* @param int $max
* @return null
*/
2015-10-15 16:14:13 +02:00
public function getPrettySchedule($min = 1, $max = 10)
{
if (!$schedule = $this->getSchedule($max)) {
return null;
}
$dates = [];
for ($i=$min; $i<min($max, count($schedule)); $i++) {
$date = $schedule[$i];
$dateStart = $date->getStart();
$date = $this->account->formatDate($dateStart);
$dueDate = $this->getDueDate($dateStart);
2016-05-29 14:34:44 +02:00
if($dueDate) {
$date .= ' <small>(' . trans('texts.due') . ' ' . $this->account->formatDate($dueDate) . ')</small>';
}
2016-05-29 14:34:44 +02:00
2015-10-15 16:14:13 +02:00
$dates[] = $date;
}
return implode('<br/>', $dates);
}
/**
* @return string
*/
2015-10-15 16:14:13 +02:00
private function getRecurrenceRule()
{
$rule = '';
switch ($this->frequency_id) {
case FREQUENCY_WEEKLY:
$rule = 'FREQ=WEEKLY;';
break;
case FREQUENCY_TWO_WEEKS:
$rule = 'FREQ=WEEKLY;INTERVAL=2;';
break;
case FREQUENCY_FOUR_WEEKS:
$rule = 'FREQ=WEEKLY;INTERVAL=4;';
break;
case FREQUENCY_MONTHLY:
$rule = 'FREQ=MONTHLY;';
break;
case FREQUENCY_THREE_MONTHS:
$rule = 'FREQ=MONTHLY;INTERVAL=3;';
break;
case FREQUENCY_SIX_MONTHS:
$rule = 'FREQ=MONTHLY;INTERVAL=6;';
break;
case FREQUENCY_ANNUALLY:
$rule = 'FREQ=YEARLY;';
break;
}
if ($this->end_date) {
2015-12-24 12:17:11 +01:00
$rule .= 'UNTIL=' . $this->getOriginal('end_date');
2015-10-15 16:14:13 +02:00
}
return $rule;
}
/*
2015-10-20 10:23:38 +02:00
public function shouldSendToday()
{
if (!$nextSendDate = $this->getNextSendDate()) {
return false;
}
2016-01-10 11:25:05 +01:00
2015-10-20 10:23:38 +02:00
return $this->account->getDateTime() >= $nextSendDate;
}
*/
/**
* @return bool
*/
2015-03-16 22:45:25 +01:00
public function shouldSendToday()
{
if ( ! $this->user->confirmed) {
return false;
}
if ( ! $this->start_date || strtotime($this->start_date) > strtotime('now')) {
2015-03-16 22:45:25 +01:00
return false;
}
if ($this->end_date && strtotime($this->end_date) < strtotime('now')) {
return false;
}
$dayOfWeekToday = date('w');
$dayOfWeekStart = date('w', strtotime($this->start_date));
$dayOfMonthToday = date('j');
$dayOfMonthStart = date('j', strtotime($this->start_date));
if (!$this->last_sent_date) {
return true;
} else {
$date1 = new DateTime($this->last_sent_date);
$date2 = new DateTime();
$diff = $date2->diff($date1);
$daysSinceLastSent = $diff->format('%a');
2015-03-16 22:45:25 +01:00
$monthsSinceLastSent = ($diff->format('%y') * 12) + $diff->format('%m');
if ($daysSinceLastSent == 0) {
return false;
}
}
switch ($this->frequency_id) {
case FREQUENCY_WEEKLY:
return $daysSinceLastSent >= 7;
case FREQUENCY_TWO_WEEKS:
return $daysSinceLastSent >= 14;
case FREQUENCY_FOUR_WEEKS:
return $daysSinceLastSent >= 28;
case FREQUENCY_MONTHLY:
return $monthsSinceLastSent >= 1;
case FREQUENCY_THREE_MONTHS:
return $monthsSinceLastSent >= 3;
case FREQUENCY_SIX_MONTHS:
return $monthsSinceLastSent >= 6;
case FREQUENCY_ANNUALLY:
return $monthsSinceLastSent >= 12;
default:
return false;
}
return false;
}
2015-09-17 21:01:06 +02:00
/**
* @return bool|string
*/
2015-10-13 09:11:44 +02:00
public function getPDFString()
2015-09-17 21:01:06 +02:00
{
2015-10-13 09:11:44 +02:00
if (!env('PHANTOMJS_CLOUD_KEY')) {
return false;
2015-09-29 12:21:57 +02:00
}
2015-10-13 09:11:44 +02:00
$invitation = $this->invitations[0];
$link = $invitation->getLink('view', true);
2016-02-17 19:59:24 +01:00
$key = env('PHANTOMJS_CLOUD_KEY');
2016-05-29 14:34:44 +02:00
2016-02-17 16:50:01 +01:00
if (Utils::isNinjaDev()) {
$link = env('TEST_LINK');
}
2016-02-17 19:59:24 +01:00
$url = "http://api.phantomjscloud.com/api/browser/v2/{$key}/?request=%7Burl:%22{$link}?phantomjs=true%22,renderType:%22html%22%7D";
2016-05-29 14:34:44 +02:00
2016-02-17 19:59:24 +01:00
$pdfString = file_get_contents($url);
$pdfString = strip_tags($pdfString);
2016-05-29 14:34:44 +02:00
2016-01-14 22:28:17 +01:00
if ( ! $pdfString || strlen($pdfString) < 200) {
2016-02-17 16:50:01 +01:00
Utils::logError("PhantomJSCloud - failed to create pdf: {$pdfString}");
return false;
2016-01-14 22:28:17 +01:00
}
2016-02-17 20:13:55 +01:00
return Utils::decodePDF($pdfString);
2015-09-17 21:01:06 +02:00
}
2016-02-23 22:32:39 +01:00
/**
* @param $invoiceItem
* @param $invoiceTotal
* @return float|int
*/
2016-02-23 22:32:39 +01:00
public function getItemTaxable($invoiceItem, $invoiceTotal)
{
$total = $invoiceItem->qty * $invoiceItem->cost;
if ($this->discount > 0) {
if ($this->is_amount_discount) {
$total -= $invoiceTotal ? ($total / $invoiceTotal * $this->discount) : 0;
} else {
$total *= (100 - $this->discount) / 100;
$total = round($total, 2);
}
}
return $total;
}
/**
* @return float|int|mixed
*/
2016-02-23 22:32:39 +01:00
public function getTaxable()
{
$total = 0;
foreach ($this->invoice_items as $invoiceItem) {
$total += $invoiceItem->qty * $invoiceItem->cost;
}
if ($this->discount > 0) {
if ($this->is_amount_discount) {
$total -= $this->discount;
} else {
$total *= (100 - $this->discount) / 100;
$total = round($total, 2);
}
}
if ($this->custom_value1 && $this->custom_taxes1) {
$total += $this->custom_value1;
}
if ($this->custom_value2 && $this->custom_taxes2) {
$total += $this->custom_value2;
}
return $total;
}
2016-05-29 14:34:44 +02:00
// if $calculatePaid is true we'll loop through each payment to
2016-03-31 11:29:01 +02:00
// determine the sum, otherwise we'll use the cached paid_to_date amount
/**
* @param bool $calculatePaid
* @return array
*/
2016-02-24 21:58:42 +01:00
public function getTaxes($calculatePaid = false)
2016-02-23 22:32:39 +01:00
{
$taxes = [];
$taxable = $this->getTaxable();
2016-03-31 11:29:01 +02:00
$paidAmount = $this->getAmountPaid($calculatePaid);
2016-05-29 14:34:44 +02:00
2016-03-31 11:29:01 +02:00
if ($this->tax_name1) {
$invoiceTaxAmount = round($taxable * ($this->tax_rate1 / 100), 2);
2016-06-20 16:14:43 +02:00
$invoicePaidAmount = floatVal($this->amount) && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0;
2016-03-31 11:29:01 +02:00
$this->calculateTax($taxes, $this->tax_name1, $this->tax_rate1, $invoiceTaxAmount, $invoicePaidAmount);
}
if ($this->tax_name2) {
$invoiceTaxAmount = round($taxable * ($this->tax_rate2 / 100), 2);
2016-06-20 16:14:43 +02:00
$invoicePaidAmount = floatVal($this->amount) && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0;
2016-03-31 11:29:01 +02:00
$this->calculateTax($taxes, $this->tax_name2, $this->tax_rate2, $invoiceTaxAmount, $invoicePaidAmount);
2016-02-23 22:32:39 +01:00
}
foreach ($this->invoice_items as $invoiceItem) {
2016-03-31 11:29:01 +02:00
$itemTaxAmount = $this->getItemTaxable($invoiceItem, $taxable);
2016-05-29 14:34:44 +02:00
2016-03-31 11:29:01 +02:00
if ($invoiceItem->tax_name1) {
$itemTaxAmount = round($taxable * ($invoiceItem->tax_rate1 / 100), 2);
2016-06-20 16:14:43 +02:00
$itemPaidAmount = floatVal($this->amount) && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0;
2016-03-31 11:29:01 +02:00
$this->calculateTax($taxes, $invoiceItem->tax_name1, $invoiceItem->tax_rate1, $itemTaxAmount, $itemPaidAmount);
2016-02-23 22:32:39 +01:00
}
2016-03-31 11:29:01 +02:00
if ($invoiceItem->tax_name2) {
$itemTaxAmount = round($taxable * ($invoiceItem->tax_rate2 / 100), 2);
2016-06-20 16:14:43 +02:00
$itemPaidAmount = floatVal($this->amount) && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0;
2016-03-31 11:29:01 +02:00
$this->calculateTax($taxes, $invoiceItem->tax_name2, $invoiceItem->tax_rate2, $itemTaxAmount, $itemPaidAmount);
2016-02-23 22:32:39 +01:00
}
}
2016-05-29 14:34:44 +02:00
2016-02-23 22:32:39 +01:00
return $taxes;
}
2016-05-29 14:34:44 +02:00
/**
* @param $taxes
* @param $name
* @param $rate
* @param $amount
* @param $paid
*/
2016-05-29 14:34:44 +02:00
private function calculateTax(&$taxes, $name, $rate, $amount, $paid)
{
2016-03-31 11:29:01 +02:00
if ( ! $amount) {
return;
2016-05-29 14:34:44 +02:00
}
2016-03-31 11:29:01 +02:00
$amount = round($amount, 2);
$paid = round($paid, 2);
$key = $rate . ' ' . $name;
2016-05-29 14:34:44 +02:00
2016-03-31 11:29:01 +02:00
if ( ! isset($taxes[$key])) {
$taxes[$key] = [
'name' => $name,
'rate' => $rate+0,
'amount' => 0,
'paid' => 0
];
}
$taxes[$key]['amount'] += $amount;
2016-05-29 14:34:44 +02:00
$taxes[$key]['paid'] += $paid;
2016-03-31 11:29:01 +02:00
}
2016-05-29 14:34:44 +02:00
/**
* @return bool
*/
public function hasDocuments(){
if(count($this->documents))return true;
return $this->hasExpenseDocuments();
}
2016-05-29 14:34:44 +02:00
/**
* @return bool
*/
public function hasExpenseDocuments(){
foreach($this->expenses as $expense){
if(count($expense->documents))return true;
}
return false;
}
2016-05-25 04:49:06 +02:00
/**
* @return bool
*/
2016-05-25 04:49:06 +02:00
public function getAutoBillEnabled() {
if (!$this->is_recurring) {
$recurInvoice = $this->recurring_invoice;
} else {
$recurInvoice = $this;
}
if (!$recurInvoice) {
return false;
}
return $recurInvoice->auto_bill == AUTO_BILL_ALWAYS || ($recurInvoice->auto_bill != AUTO_BILL_OFF && $recurInvoice->client_enable_auto_bill);
}
2016-11-18 14:31:43 +01:00
public static function getStatuses($entityType = false)
{
2016-11-20 15:08:36 +01:00
$statuses = [];
2016-11-18 14:31:43 +01:00
if ($entityType == ENTITY_RECURRING_INVOICE) {
return $statuses;
}
foreach (\Cache::get('invoiceStatus') as $status) {
if ($entityType == ENTITY_QUOTE) {
if (in_array($status->id, [INVOICE_STATUS_PAID, INVOICE_STATUS_PARTIAL])) {
continue;
}
2016-11-20 15:08:36 +01:00
} elseif ($entityType == ENTITY_INVOICE) {
if (in_array($status->id, [INVOICE_STATUS_APPROVED])) {
continue;
}
2016-11-18 14:31:43 +01:00
}
$statuses[$status->id] = trans('texts.status_' . strtolower($status->name));
}
if ($entityType == ENTITY_INVOICE) {
$statuses[INVOICE_STATUS_OVERDUE] = trans('texts.overdue');
}
return $statuses;
}
2015-03-16 22:45:25 +01:00
}
2015-07-12 21:43:45 +02:00
Invoice::creating(function ($invoice) {
if (!$invoice->is_recurring) {
2015-10-22 20:48:12 +02:00
$invoice->account->incrementCounter($invoice);
}
2015-07-12 21:43:45 +02:00
});
Invoice::created(function ($invoice) {
2016-05-26 16:56:54 +02:00
if ($invoice->isType(INVOICE_TYPE_QUOTE)) {
2015-10-28 20:22:07 +01:00
event(new QuoteWasCreated($invoice));
} else {
event(new InvoiceWasCreated($invoice));
}
2015-03-16 22:45:25 +01:00
});
Invoice::updating(function ($invoice) {
2016-05-26 16:56:54 +02:00
if ($invoice->isType(INVOICE_TYPE_QUOTE)) {
2015-10-28 20:22:07 +01:00
event(new QuoteWasUpdated($invoice));
} else {
event(new InvoiceWasUpdated($invoice));
}
2015-12-13 21:12:54 +01:00
});